OXYGENHUB
◆ main openmsx / src / network / EmulatedEsp32.hh
9.2 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef EMULATEDESP32_HH
2
#define EMULATEDESP32_HH
3
4
#include "BooleanSetting.hh"
5
#include "DeviceConfig.hh"
6
#include "EmuTime.hh"
7
#include "Socket.hh"
8
#include "circular_buffer.hh"
9
10
#include <array>
11
#include <atomic>
12
#include <chrono>
13
#include <condition_variable>
14
#include <cstdint>
15
#include <functional>
16
#include <memory>
17
#include <mutex>
18
#include <span>
19
#include <string>
20
#include <string_view>
21
#include <thread>
22
#include <vector>
23
24
namespace openmsx {
25
26
// Forward declarations (the full types are only needed in the .cc).
27
class CliComm;
28
namespace OpenSSL {
29
struct ErrorCode; // see OpenSSL.hh
30
}
31
32
// Emulates the ESP32 of the SMXWiFi device: it presents the same variable
33
// serial contract as RS232Raw (recvByte + setBaudRate — the SM-X WiFi UART
34
// only ever changes the baud rate; the format is fixed 8N1) and implements
35
// the ESP32 UNAPI firmware: the command parser, the boot sequence and the
36
// full TCP/IP-UNAPI command set over the host network. Bytes produced by
37
// the emulation are delivered through a callback sink provided by the
38
// owner (which routes them like bytes from a real ESP32 on a serial port).
39
class EmulatedEsp32
40
{
41
public:
42
	EmulatedEsp32(const DeviceConfig& config, std::function<void(std::span<const uint8_t>)> sink);
43
	~EmulatedEsp32();
44
45
	// Serial contract (same variable part as RS232Raw): the owner routes
46
	// MSX-side bytes and baud-rate changes to this endpoint while it is
47
	// the active one.
48
	void recvByte(uint8_t value, EmuTime time);
49
	void setBaudRate(unsigned baud) const;
50
51
	// Closes all network connections and clears the parser, boot
52
	// sequence and pending input: called on MSX reset and whenever the
53
	// owner switches to or from this endpoint, so the emulation always
54
	// starts clean after a migration.
55
	void resetState();
56
	// Clears the pending MSX->ESP input (UART FIFO reset command).
57
	void resetFifo();
58
59
	// Thread lifecycle. The thread is started in the constructor (when
60
	// the extension is inserted into an already-running machine,
61
	// powerUp() is never called on the new device) and stopped on
62
	// destruction, power down and machine reset.
63
	void start();
64
	void stop();
65
66
private:
67
	// ESP emulator thread
68
	void espThreadFunc();
69
	void pushBootText(std::string_view text) const;
70
	void resetParser();
71
	// Dispatch a complete command. Must be called without msxToEspMutex
72
	// held.
73
	void processCommand();
74
	// Wait logic for an empty MSX->ESP FIFO. Returns true when the thread
75
	// should stop; on return 'unlocked' may hold work that must run
76
	// without the mutex held.
77
	[[nodiscard]] bool espWaitForWork(std::unique_lock<std::mutex>& lock,
78
	                                  std::function<void()>& unlocked);
79
	// Parser state machine for one received byte. On return 'unlocked'
80
	// may hold work that must run without the mutex held.
81
	void espParseByte(uint8_t b, std::function<void()>& unlocked);
82
	void tcpReaderThreadFunc(int connIdx);
83
84
	// Connection management
85
	class Connection;
86
	struct ListenSocket;
87
	static constexpr int MAX_CONNECTIONS = 4;
88
89
	// TCP reader-thread helpers (keep the reader loop shallow).
90
	void tcpAcceptClient(Connection* conn) const;
91
	void tcpDriveHandshake(Connection* conn, int connIdx);
92
	void tcpTlsFail(Connection* conn, int connIdx, uint8_t reason);
93
	// Console diagnostics for TLS failures (printed from the ESP/reader
94
	// threads; CliComm is thread-safe). No-op when no error was queued.
95
	void logTlsError(OpenSSL::ErrorCode code) const;
96
	// Reads inbound data; returns true when the reader loop must stop
97
	// (the remote side closed the connection or TLS failed).
98
	[[nodiscard]] bool tcpReadData(Connection* conn, int connIdx);
99
	void tcpDrainSsl(Connection* conn, int connIdx);
100
	void tcpReadClient(Connection* conn) const;
101
	// Polls the connection's sockets (never blocks) and handles whatever
102
	// is ready. Returns false when the reader loop must stop.
103
	[[nodiscard]] bool tcpPollConnection(Connection* conn, int connIdx);
104
105
	Connection* allocateConnection();
106
	void resetConnectionSlot(int i);
107
	void freeConnection(int idx);
108
	Connection* getConnection(int idx) const;
109
	int getFreeConnectionSlot() const;
110
111
	// Command dispatch
112
	void handleCustomCommand(uint8_t cmd, std::span<const uint8_t> data);
113
	void handleUnapiCommand(uint8_t cmd, std::span<const uint8_t> data);
114
115
	// Response helpers
116
	void sendQuickResponse(uint8_t cmdByte, uint8_t errorCode);
117
	void sendResponse(uint8_t cmdByte, uint8_t errorCode,
118
	                  std::span<const uint8_t> data = {});
119
	void sendRawResponse(std::span<const uint8_t> data);
120
121
	// Last response for Retry
122
	std::vector<uint8_t> lastResponse;
123
	void saveLastResponse(std::span<const uint8_t> data);
124
125
	// Custom command handlers
126
	void cmdReset(std::span<const uint8_t> data);
127
	void cmdWarmReset(std::span<const uint8_t> data);
128
	void cmdQuery(std::span<const uint8_t> data);
129
	void cmdGetVersion(std::span<const uint8_t> data);
130
	void cmdRetry(std::span<const uint8_t> data);
131
	void cmdScanAP(std::span<const uint8_t> data);
132
	void cmdScanResults(std::span<const uint8_t> data);
133
	void cmdConnectAP(std::span<const uint8_t> data);
134
	void cmdGetAPStatus(std::span<const uint8_t> data);
135
	void cmdGetBoard(std::span<const uint8_t> data);
136
	void cmdFirmwareUpdate(uint8_t cmd, std::span<const uint8_t> data);
137
	void cmdGetSettings(std::span<const uint8_t> data);
138
	void cmdAutoClock(uint8_t cmd, std::span<const uint8_t> data);
139
	void cmdSetWiFiTimer(std::span<const uint8_t> data);
140
	void cmdGetDateTime(std::span<const uint8_t> data);
141
142
	// UNAPI command handlers
143
	void unimplementedCmd(uint8_t cmd);
144
	void cmdGetInfo(std::span<const uint8_t> data);
145
	void cmdGetCapab(std::span<const uint8_t> data);
146
	void cmdGetIPInfo(std::span<const uint8_t> data);
147
	void cmdNetState(std::span<const uint8_t> data);
148
	void cmdDnsQ(std::span<const uint8_t> data);
149
	void cmdDnsQNew(std::span<const uint8_t> data);
150
	void cmdUdpOpen(std::span<const uint8_t> data);
151
	void cmdUdpClose(std::span<const uint8_t> data);
152
	void cmdUdpState(std::span<const uint8_t> data);
153
	void cmdUdpSend(std::span<const uint8_t> data);
154
	void cmdUdpRcv(std::span<const uint8_t> data);
155
	void cmdTcpOpen(std::span<const uint8_t> data);
156
	void cmdTcpOpenPassive(uint16_t localPort, uint8_t flags);
157
	void cmdTcpOpenActive(uint32_t remoteIP, uint16_t remotePort,
158
	                      uint16_t localPort, uint8_t flags,
159
	                      const std::string& hostname,
160
	                      bool useTls, bool verifyCert);
161
	void cmdTcpClose(std::span<const uint8_t> data);
162
	void cmdTcpAbort(std::span<const uint8_t> data);
163
	void cmdTcpState(std::span<const uint8_t> data);
164
	void cmdTcpSend(std::span<const uint8_t> data);
165
	void cmdTcpRcv(std::span<const uint8_t> data);
166
	void cmdCfgAutoIP(std::span<const uint8_t> data);
167
	void cmdCfgIP(std::span<const uint8_t> data);
168
169
	// Convert IP address string to 4 bytes
170
	[[nodiscard]] bool parseIP(const std::string& str, std::span<uint8_t, 4> ipOut) const;
171
	// Random ephemeral port (spec 4.4.1/4.5.1), never one used by another
172
	// open connection.
173
	void pickEphemeralTcpPort(uint16_t& port);
174
175
	// Settings
176
	BooleanSetting enabledSetting;
177
178
	// Console interface (TLS failure diagnostics).
179
	CliComm& cliComm;
180
181
	// MSX->ESP FIFO
182
	cb_queue<uint8_t> msxToEspFifo;
183
	mutable std::mutex msxToEspMutex;
184
	std::condition_variable msxToEspCond;
185
186
	// ESP thread
187
	std::thread espThread;
188
	std::atomic<bool> espRunning{false};
189
190
	// Set by cmdReset (running in the ESP thread); the ESP thread then
191
	// wipes pending input and emulates the device reboot sequence.
192
	bool bootStartPending = false;
193
194
	// ESP firmware emulation state, driven by espThreadFunc only:
195
	//  - the UART parser (CMD + 2-byte size + data framing)
196
	//  - the boot sequence ("Ready\r\n" retries after a reset)
197
	enum class ParserState : uint8_t { IDLE, WAIT_DATA_SIZE, GET_DATA };
198
	ParserState parserState = ParserState::IDLE;
199
	uint8_t parserCmdByte = 0;
200
	uint8_t parserSizeStep = 0;
201
	uint16_t parserExpectedSize = 0;
202
	std::vector<uint8_t> parserDataBuf;
203
	std::chrono::steady_clock::time_point parserDeadline;
204
205
	enum class BootStage : uint8_t { NONE, READY_LOOP };
206
	BootStage bootStage = BootStage::NONE;
207
	uint8_t bootReadyRetries = 0;
208
	std::chrono::steady_clock::time_point bootEventTime;
209
210
	// Connections
211
	mutable std::mutex connectionsMutex;
212
	std::array<std::unique_ptr<Connection>, MAX_CONNECTIONS> connections;
213
214
	// Close reason of the last connection that was freed (TCP-IP UNAPI
215
	// spec 4.5.4): kept when the connection is closed so that TCP_STATE
216
	// can still report the reason alongside ERR_NO_CONN. Written by the
217
	// reader thread (without holding connectionsMutex, to avoid a
218
	// deadlock with freeConnection joining the reader thread under that
219
	// mutex); cleared when the slot is reused.
220
	std::array<std::atomic<uint8_t>, MAX_CONNECTIONS> closeReason = {};
221
222
	// Passive TCP listeners: several passive connections may share one
223
	// listening socket for the same local port (TCP-IP UNAPI spec 1.1,
224
	// section 4.5.1). The connections own the listeners via shared_ptr;
225
	// this registry is only a weak_ptr lookup (expired entries are
226
	// pruned). Guarded by connectionsMutex.
227
	std::vector<std::weak_ptr<ListenSocket>> listenSockets;
228
229
	// Socket subsystem init
230
	[[no_unique_address]] SocketActivator socketActivator;
231
232
	// Byte delivery into the owner's UART FIFO (thread-safe by the
233
	// owner's FIFO mutex); a whole block is delivered per call.
234
	std::function<void(std::span<const uint8_t>)> sink;
235
};
236
237
} // namespace openmsx
238
239
#endif