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 // provides the local IP address of the connection (0.0.0.0 if not connected)
348 IPAddress localIP() {
349 struct sockaddr_in addr;
350 if (!localAddress(addr)) return IPAddress(0, 0, 0, 0);
351 return IPAddress(addr.sin_addr.s_addr);
352 }
353
354 // provides the local port of the connection (0 if not connected)
355 uint16_t localPort() {
356 struct sockaddr_in addr;
357 if (!localAddress(addr)) return 0;
358 return ntohs(addr.sin_port);
359 }
360
361 virtual void setCACert(const char* cert) {
362 Logger.error(WIFICLIENT, "setCACert not supported");
363 }
364
365 void setConnectionTimeout(int32_t timeout) {
366 connectTimeout = timeout;
367 }
368
369 int32_t getConnectionTimeout() {
370 return connectTimeout;
371 }
372
373 protected:
374 const char* WIFICLIENT = "EthernetClient";
375 int32_t connectTimeout = 5000; // default timeout 5 seconds
376 std::shared_ptr<SocketImpl> p_sock = nullptr;
377 int bufferSize = 256;
378 RingBufferExt readBuffer;
379 RingBufferExt writeBuffer;
380 bool is_connected = false;
381 IPAddress address{0, 0, 0, 0};
382 uint16_t port = 0;
383
384 // Runs on the calling thread (timeout_ms < 0) or on a background thread
385 // (timeout_ms >= 0, see resolveHostname()) - must not touch `this`.
386 static IPAddress resolveHostnameBlocking(const std::string& hostname) {
387 struct addrinfo hints;
388 memset(&hints, 0, sizeof(hints));
389 hints.ai_family = AF_INET;
390 hints.ai_socktype = SOCK_STREAM;
391
392 struct addrinfo* result = nullptr;
393 if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result) != 0 ||
394 result == nullptr) {
395 return IPAddress(0, 0, 0, 0);
396 }
397
398 auto* addr = reinterpret_cast<struct sockaddr_in*>(result->ai_addr);
399 IPAddress resolved(addr->sin_addr.s_addr);
400 freeaddrinfo(result);
401 return resolved;
402 }
403
404 // Resolves hostname with a deadline so that connect(..., timeout_ms) stays
405 // bounded even when the resolver itself is slow or unresponsive. getaddrinfo()
406 // has no portable non-blocking form, so the blocking call runs on a helper
407 // thread; on timeout we detach it rather than blocking the caller further -
408 // it will finish (or leak until it does) in the background and its result is
409 // discarded.
410 IPAddress resolveHostname(const char* hostname, int32_t timeout_ms) {
411 if (timeout_ms < 0) {
412 IPAddress resolved = resolveHostnameBlocking(hostname);
413 if (resolved == IPAddress(0, 0, 0, 0)) {
414 Logger.error(WIFICLIENT, "Hostname resolution failed");
415 }
416 return resolved;
417 }
418
419 auto promise = std::make_shared<std::promise<IPAddress>>();
420 std::future<IPAddress> future = promise->get_future();
421 std::string hostname_copy(hostname);
422 std::thread resolver([promise, hostname_copy]() {
423 promise->set_value(resolveHostnameBlocking(hostname_copy));
424 });
425
426 if (future.wait_for(std::chrono::milliseconds(timeout_ms)) ==
427 std::future_status::ready) {
428 resolver.join();
429 IPAddress resolved = future.get();
430 if (resolved == IPAddress(0, 0, 0, 0)) {
431 Logger.error(WIFICLIENT, "Hostname resolution failed");
432 }
433 return resolved;
434 }
435
436 resolver.detach();
437 Logger.error(WIFICLIENT, "Hostname resolution timeout");
438 return IPAddress(0, 0, 0, 0);
439 }
440
441 // resolves the address and returns sockaddr_in
442 IPAddress resolveAddress(const char* address, int32_t timeout_ms) {
443 struct sockaddr_in serv_addr4;
444 memset(&serv_addr4, 0, sizeof(serv_addr4));
445 serv_addr4.sin_family = AF_INET;
446 if (::inet_pton(AF_INET, address, &serv_addr4.sin_addr) <= 0) {
447 return resolveHostname(address, timeout_ms);
448 }
449 return IPAddress(serv_addr4.sin_addr.s_addr);
450 }
451
452 void registerCleanup() {
453 static bool signal_registered = false;
454 if (!signal_registered) {
455 SignalHandler::registerHandler(SIGINT, cleanupAll);
456 SignalHandler::registerHandler(SIGTERM, cleanupAll);
457 signal_registered = true;
458 }
459 }
460
461 int read(uint8_t* buffer, size_t len) override {
462 Logger.debug(WIFICLIENT, "read");
463 int result = 0;
464 long timeout = millis() + getTimeout();
465 result = p_sock->read(buffer, len);
466 while (result == 0 && millis() < timeout) {
467 delay(200);
468 result = p_sock->read(buffer, len);
469 }
470
471 char lenStr[16];
472 sprintf(lenStr, "%d", result);
473 Logger.debug(WIFICLIENT, "read->", lenStr);
474
475 return result;
476 }
477
478 // determines the local socket address
479 bool localAddress(struct sockaddr_in& addr) {
480 memset(&addr, 0, sizeof(addr));
481 if (!p_sock || p_sock->fd() == INVALID_SOCKET_HANDLE) return false;
482 SocketLength len = sizeof(addr);
483 if (::getsockname(p_sock->fd(), (struct sockaddr*)&addr, &len) != 0)
484 return false;
485 return addr.sin_family == AF_INET;
486 }
487
488 bool connectedFast() { return is_connected; }
489};
490
491} // 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