arduino-emulator
Loading...
Searching...
No Matches
Ethernet.h
1
2/*
3 Ethernet.h
4 Copyright (c) 2025 Phil Schatzmann. All right reserved.
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19*/
20#pragma once
21
22#include "DesktopSocket.h"
23#include <chrono>
24#include <cstring>
25#include <future>
26#include <memory> // This is the include you need
27#include <string>
28#include <thread>
29
30#include "ArduinoLogger.h"
31#include "RingBufferExt.h"
32#include "SocketImpl.h"
33#include "SignalHandler.h"
34#include "api/Client.h"
35#include "api/Common.h"
36#include "api/IPAddress.h"
37
38namespace arduino {
39
40#define ETHERNET_DEFAULT_READ_TIMEOUT 200
41
42typedef enum {
43 WL_NO_SHIELD = 255,
44 WL_IDLE_STATUS = 0,
45 WL_NO_SSID_AVAIL,
46 WL_SCAN_COMPLETED,
47 WL_CONNECTED,
48 WL_CONNECT_FAILED,
49 WL_CONNECTION_LOST,
50 WL_DISCONNECTED
51} wl_status_t;
52
54public:
55 EthernetImpl() {
56 // Set some defaults
57 _macAddress[0] = 0xDE; _macAddress[1] = 0xAD; _macAddress[2] = 0xBE; _macAddress[3] = 0xEF; _macAddress[4] = 0xFE; _macAddress[5] = 0xED;
58 _localIP = IPAddress(192,168,1,177);
59 _subnetMask = IPAddress(255,255,255,0);
60 _gatewayIP = IPAddress(192,168,1,1);
61 _dnsServerIP = IPAddress(8,8,8,8);
62 _hardwareStatus = 1; // Assume present
63 _linkStatus = 2; // Assume linkON
64 _retransmissionCount = 8;
65 _retransmissionTimeout = 2000;
66 }
67
68 // Begin with MAC only
69 bool begin(uint8_t macAddress[6]) {
70 setMACAddress(macAddress);
71 return true;
72 }
73 // Begin with full config
74 bool begin(uint8_t macAddress[6], IPAddress localIP, IPAddress dnsServerIP, IPAddress gateway, IPAddress subnet) {
75 setMACAddress(macAddress);
76 setLocalIP(localIP);
77 setDnsServerIP(dnsServerIP);
78 setGatewayIP(gateway);
79 setSubnetMask(subnet);
80 return true;
81 }
82
83 // Returns the local IP address
84 IPAddress localIP() { return _localIP; }
85 // Returns the subnet mask
86 IPAddress subnetMask() { return _subnetMask; }
87 // Returns the gateway IP
88 IPAddress gatewayIP() { return _gatewayIP; }
89 // Returns the DNS server IP
90 IPAddress dnsServerIP() { return _dnsServerIP; }
91 // Returns the MAC address
92 void MACAddress(uint8_t* mac) { for (int i=0; i<6; ++i) mac[i] = _macAddress[i]; }
93 // Set the MAC address
94 void setMACAddress(const uint8_t* mac) { for (int i=0; i<6; ++i) _macAddress[i] = mac[i]; }
95 // Set the local IP
96 void setLocalIP(const IPAddress& ip) { _localIP = ip; }
97 // Set the subnet mask
98 void setSubnetMask(const IPAddress& mask) { _subnetMask = mask; }
99 // Set the gateway IP
100 void setGatewayIP(const IPAddress& ip) { _gatewayIP = ip; }
101 // Set the DNS server IP
102 void setDnsServerIP(const IPAddress& ip) { _dnsServerIP = ip; }
103 // Set retransmission count
104 void setRetransmissionCount(uint8_t count) { _retransmissionCount = count; }
105 // Set retransmission timeout
106 void setRetransmissionTimeout(uint16_t timeout) { _retransmissionTimeout = timeout; }
107
108 // Returns hardware status (1 = present, 0 = absent)
109 int hardwareStatus() { return _hardwareStatus; }
110 // Returns link status (2 = linkON, 1 = linkOFF, 0 = unknown)
111 int linkStatus() { return _linkStatus; }
112 // Init (returns true for compatibility)
113 bool init(uint8_t socketCount = 4) { _hardwareStatus = 1; return true; }
114 // Maintain (returns 0 for compatibility)
115 int maintain() { return 0; }
116
117protected:
118 uint8_t _macAddress[6];
119 IPAddress _localIP;
120 IPAddress _subnetMask;
121 IPAddress _gatewayIP;
122 IPAddress _dnsServerIP;
123 int _hardwareStatus = 1; // 1 = present, 0 = absent
124 int _linkStatus = 2; // 2 = linkON, 1 = linkOFF, 0 = unknown
125 uint8_t _retransmissionCount = 8;
126 uint16_t _retransmissionTimeout = 2000;
127};
128
129inline EthernetImpl Ethernet;
130
131class EthernetClient : public Client {
132 private:
133 static std::vector<EthernetClient*>& active_clients() {
134 static std::vector<EthernetClient*> clients;
135 return clients;
136 }
137 static void cleanupAll(int sig) {
138 for (auto* client : active_clients()) {
139 if (client) {
140 client->stop();
141 }
142 }
143 }
144
145 public:
148 p_sock = std::make_shared<SocketImpl>();
149 readBuffer = RingBufferExt(bufferSize);
150 writeBuffer = RingBufferExt(bufferSize);
151 registerCleanup();
152 active_clients().push_back(this);
153 }
154 EthernetClient(std::shared_ptr<SocketImpl> sock, int bufferSize = 256, long timeout = ETHERNET_DEFAULT_READ_TIMEOUT) {
155 if (sock) {
156 setTimeout(timeout);
157 this->bufferSize = bufferSize;
158 readBuffer = RingBufferExt(bufferSize);
159 writeBuffer = RingBufferExt(bufferSize);
160 p_sock = sock;
161 is_connected = p_sock->connected();
162 registerCleanup();
163 active_clients().push_back(this);
164 }
165 }
168 readBuffer = RingBufferExt(bufferSize);
169 writeBuffer = RingBufferExt(bufferSize);
170 p_sock = std::make_shared<SocketImpl>(socket);
171 is_connected = p_sock->connected();
172 }
173
174 // checks if we are connected - using a timeout
175 virtual uint8_t connected() override {
176 if (!is_connected || !p_sock) return false;
177
178 // A disconnected socket should be reported immediately. Retrying here
179 // turns normal peer shutdown into a multi-second stall for callers like
180 // TelnetClient::closeOnDisconnect().
181 is_connected = p_sock->connected();
182 return is_connected;
183 }
184
185 // support conversion to bool
186 operator bool() override { return connected(); }
187
188 // opens a conection
189 virtual int connect(IPAddress ipAddress, uint16_t port) override {
190 return connect(ipAddress, port, getConnectionTimeout());
191 }
192
193 int connect(IPAddress ipAddress, uint16_t port, int32_t timeout_ms) {
194 String str = String(ipAddress[0]) + String(".") + String(ipAddress[1]) +
195 String(".") + String(ipAddress[2]) + String(".") +
196 String(ipAddress[3]);
197 this->address = ipAddress;
198 this->port = port;
199 return connect(str.c_str(), port, timeout_ms);
200 }
201
202 // opens a connection
203 virtual int connect(const char* address, uint16_t port) override {
204 return connect(address, port, getConnectionTimeout());
205 }
206
207 int connect(const char* address, uint16_t port, int32_t timeout_ms) {
208 Logger.info(WIFICLIENT, "connect");
209 this->port = port;
210 if (connectedFast()) {
211 p_sock->close();
212 }
213 uint32_t start_ms = millis();
214 IPAddress adr = resolveAddress(address, timeout_ms);
215 if (adr == IPAddress(0, 0, 0, 0)) {
216 is_connected = false;
217 return 0;
218 }
219
220 // DNS resolution above already consumed part of the caller's timeout
221 // budget; give the TCP connect only what's left so that the overall
222 // connect(..., timeout_ms) call stays bounded by timeout_ms.
223 int32_t remaining_timeout = timeout_ms;
224 if (timeout_ms >= 0) {
225 uint32_t elapsed_ms = millis() - start_ms;
226 remaining_timeout = elapsed_ms >= static_cast<uint32_t>(timeout_ms)
227 ? 0
228 : timeout_ms - static_cast<int32_t>(elapsed_ms);
229 }
230
231 // performs the actual connection
232 String str = adr.toString();
233 Logger.info("Connecting to ", str.c_str());
234 // p_sock->connect()'s result was previously ignored here, so a TLS
235 // handshake failure (SocketImplSecure, see NetworkClientSecure.h)
236 // still left is_connected true - callers went on to write/read over
237 // the now-invalid socket, surfacing as a confusing downstream
238 // failure (e.g. a header read timeout) instead of a clear connect
239 // failure right here.
240 if (p_sock->connect(str.c_str(), port, remaining_timeout) <= 0) {
241 is_connected = false;
242 return 0;
243 }
244 is_connected = true;
245 return 1;
246 }
247
248 virtual size_t write(char c) { return write((uint8_t)c); }
249
250 // writes an individual character into the buffer. We flush the buffer when it
251 // is full
252 virtual size_t write(uint8_t c) override {
253 if (writeBuffer.availableToWrite() == 0) {
254 flush();
255 }
256 return writeBuffer.write(c);
257 }
258
259 virtual size_t write(const char* str, int len) {
260 return write((const uint8_t*)str, len);
261 }
262
263 // direct write - if we have anything in the buffer we write that out first
264 virtual size_t write(const uint8_t* str, size_t len) override {
265 flush();
266 return p_sock->write(str, len);
267 }
268
269 virtual int print(const char* str = "") {
270 int len = strlen(str);
271 return write(str, len);
272 }
273
274 virtual int println(const char* str = "") {
275 int len = strlen(str);
276 int result = write(str, len);
277 char eol[1];
278 eol[0] = '\n';
279 write(eol, 1);
280 return result;
281 }
282
283 // flush write buffer
284 virtual void flush() override {
285 Logger.debug(WIFICLIENT, "flush");
286
287 int flushSize = writeBuffer.available();
288 if (flushSize > 0) {
290 writeBuffer.read(rbuffer, flushSize);
291 p_sock->write(rbuffer, flushSize);
292 }
293 }
294
295 // provides the available bytes from the read buffer or from the socket
296 virtual int available() override {
297 Logger.debug(WIFICLIENT, "available");
298 if (readBuffer.available() > 0) {
299 return readBuffer.available();
300 }
301 long timeout = millis() + getTimeout();
302 int result = p_sock->available();
303 while (result <= 0 && millis() < timeout) {
304 delay(200);
305 result = p_sock->available();
306 }
307 return result;
308 }
309
310 // read via ring buffer
311 virtual int read() override {
312 int result = -1;
313 uint8_t c;
314 if (readBytes(&c, 1) == 1) {
315 result = c;
316 }
317 return result;
318 }
319
320 virtual size_t readBytes(char* buffer, size_t len) {
321 int result = read((uint8_t*)buffer, len);
323 }
324
325 virtual size_t readBytes(uint8_t* buffer, size_t len) {
326 int result = read(buffer, len);
328 }
329
330 // peeks one character
331 virtual int peek() override {
332 return p_sock->peek();
333 return -1;
334 }
335
336 // close the connection
337 virtual void stop() override { p_sock->close(); }
338
339 virtual void setInsecure() {}
340
341 int fd() { return p_sock->fd(); }
342
343 uint16_t remotePort() { return port; }
344
345 IPAddress remoteIP() { return address; }
346
347 virtual void setCACert(const char* cert) {
348 Logger.error(WIFICLIENT, "setCACert not supported");
349 }
350
351 void setConnectionTimeout(int32_t timeout) {
352 connectTimeout = timeout;
353 }
354
355 int32_t getConnectionTimeout() {
356 return connectTimeout;
357 }
358
359 protected:
360 const char* WIFICLIENT = "EthernetClient";
361 int32_t connectTimeout = 5000; // default timeout 5 seconds
362 std::shared_ptr<SocketImpl> p_sock = nullptr;
363 int bufferSize = 256;
364 RingBufferExt readBuffer;
365 RingBufferExt writeBuffer;
366 bool is_connected = false;
367 IPAddress address{0, 0, 0, 0};
368 uint16_t port = 0;
369
370 // Runs on the calling thread (timeout_ms < 0) or on a background thread
371 // (timeout_ms >= 0, see resolveHostname()) - must not touch `this`.
372 static IPAddress resolveHostnameBlocking(const std::string& hostname) {
373 struct addrinfo hints;
374 memset(&hints, 0, sizeof(hints));
375 hints.ai_family = AF_INET;
376 hints.ai_socktype = SOCK_STREAM;
377
378 struct addrinfo* result = nullptr;
379 if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result) != 0 ||
380 result == nullptr) {
381 return IPAddress(0, 0, 0, 0);
382 }
383
384 auto* addr = reinterpret_cast<struct sockaddr_in*>(result->ai_addr);
385 IPAddress resolved(addr->sin_addr.s_addr);
386 freeaddrinfo(result);
387 return resolved;
388 }
389
390 // Resolves hostname with a deadline so that connect(..., timeout_ms) stays
391 // bounded even when the resolver itself is slow or unresponsive. getaddrinfo()
392 // has no portable non-blocking form, so the blocking call runs on a helper
393 // thread; on timeout we detach it rather than blocking the caller further -
394 // it will finish (or leak until it does) in the background and its result is
395 // discarded.
396 IPAddress resolveHostname(const char* hostname, int32_t timeout_ms) {
397 if (timeout_ms < 0) {
398 IPAddress resolved = resolveHostnameBlocking(hostname);
399 if (resolved == IPAddress(0, 0, 0, 0)) {
400 Logger.error(WIFICLIENT, "Hostname resolution failed");
401 }
402 return resolved;
403 }
404
405 auto promise = std::make_shared<std::promise<IPAddress>>();
406 std::future<IPAddress> future = promise->get_future();
407 std::string hostname_copy(hostname);
408 std::thread resolver([promise, hostname_copy]() {
409 promise->set_value(resolveHostnameBlocking(hostname_copy));
410 });
411
412 if (future.wait_for(std::chrono::milliseconds(timeout_ms)) ==
413 std::future_status::ready) {
414 resolver.join();
415 IPAddress resolved = future.get();
416 if (resolved == IPAddress(0, 0, 0, 0)) {
417 Logger.error(WIFICLIENT, "Hostname resolution failed");
418 }
419 return resolved;
420 }
421
422 resolver.detach();
423 Logger.error(WIFICLIENT, "Hostname resolution timeout");
424 return IPAddress(0, 0, 0, 0);
425 }
426
427 // resolves the address and returns sockaddr_in
428 IPAddress resolveAddress(const char* address, int32_t timeout_ms) {
429 struct sockaddr_in serv_addr4;
430 memset(&serv_addr4, 0, sizeof(serv_addr4));
431 serv_addr4.sin_family = AF_INET;
432 if (::inet_pton(AF_INET, address, &serv_addr4.sin_addr) <= 0) {
433 return resolveHostname(address, timeout_ms);
434 }
435 return IPAddress(serv_addr4.sin_addr.s_addr);
436 }
437
438 void registerCleanup() {
439 static bool signal_registered = false;
440 if (!signal_registered) {
441 SignalHandler::registerHandler(SIGINT, cleanupAll);
442 SignalHandler::registerHandler(SIGTERM, cleanupAll);
443 signal_registered = true;
444 }
445 }
446
447 int read(uint8_t* buffer, size_t len) override {
448 Logger.debug(WIFICLIENT, "read");
449 int result = 0;
450 long timeout = millis() + getTimeout();
451 result = p_sock->read(buffer, len);
452 while (result == 0 && millis() < timeout) {
453 delay(200);
454 result = p_sock->read(buffer, len);
455 }
456
457 char lenStr[16];
458 sprintf(lenStr, "%d", result);
459 Logger.debug(WIFICLIENT, "read->", lenStr);
460
461 return result;
462 }
463
464 bool connectedFast() { return is_connected; }
465};
466
467} // namespace arduino
Definition Client.h:27
Definition DMAPool.h:103
Definition Ethernet.h:131
Definition Ethernet.h:53
Definition IPAddress.h:43
Implementation of a Simple Circular Buffer. Instead of comparing the position of the read and write p...
Definition RingBufferExt.h:35
Definition String.h:53
We provide the WiFi class to simulate the Arduino WIFI. In in Linux we can expect that networking is ...
Definition CanMsg.cpp:31