DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
enip.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file enip.cpp
6 * @brief EtherNet/IP encapsulation builder + parser (pure, host-tested; constants per Wireshark).
7 */
8
10
11#if DETWS_ENABLE_ENIP
12
13#include <string.h>
14
15// EtherNet/IP fields are little-endian.
16static size_t put16(uint8_t *p, uint16_t v)
17{
18 p[0] = (uint8_t)(v & 0xFF);
19 p[1] = (uint8_t)(v >> 8);
20 return 2;
21}
22
23static size_t put32(uint8_t *p, uint32_t v)
24{
25 p[0] = (uint8_t)(v & 0xFF);
26 p[1] = (uint8_t)(v >> 8);
27 p[2] = (uint8_t)(v >> 16);
28 p[3] = (uint8_t)(v >> 24);
29 return 4;
30}
31
32static uint16_t get16(const uint8_t *p)
33{
34 return (uint16_t)(p[0] | ((uint16_t)p[1] << 8));
35}
36
37static uint32_t get32(const uint8_t *p)
38{
39 return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
40}
41
42size_t eip_build(uint8_t *buf, size_t cap, const EipHeader *h, const uint8_t *data, size_t data_len)
43{
44 if (!buf || !h || (data_len && !data) || data_len > 0xFFFF)
45 return 0;
46 size_t total = EIP_HEADER_SIZE + data_len;
47 if (total > cap)
48 return 0;
49 size_t p = 0;
50 p += put16(buf + p, h->command);
51 p += put16(buf + p, (uint16_t)data_len); // length covers the command data only
52 p += put32(buf + p, h->session_handle);
53 p += put32(buf + p, h->status);
54 memcpy(buf + p, h->sender_context, 8);
55 p += 8;
56 p += put32(buf + p, h->options);
57 if (data_len)
58 {
59 memcpy(buf + p, data, data_len);
60 p += data_len;
61 }
62 return p;
63}
64
65bool eip_parse(const uint8_t *buf, size_t len, EipHeader *out, const uint8_t **data, size_t *data_len)
66{
67 if (!buf || !out || len < EIP_HEADER_SIZE)
68 return false;
69 out->command = get16(buf);
70 out->length = get16(buf + 2);
71 out->session_handle = get32(buf + 4);
72 out->status = get32(buf + 8);
73 memcpy(out->sender_context, buf + 12, 8);
74 out->options = get32(buf + 20);
75 if ((size_t)EIP_HEADER_SIZE + out->length > len) // declared data not fully buffered
76 return false;
77 if (data)
78 *data = buf + EIP_HEADER_SIZE;
79 if (data_len)
80 *data_len = out->length;
81 return true;
82}
83
84size_t eip_build_register_session(uint8_t *buf, size_t cap, const uint8_t sender_context[8])
85{
86 EipHeader h;
87 memset(&h, 0, sizeof(h));
88 h.command = EIP_CMD_REGISTER_SESSION;
89 if (sender_context)
90 memcpy(h.sender_context, sender_context, 8);
91 uint8_t data[4];
92 put16(data, 1); // protocol version
93 put16(data + 2, 0); // options flags
94 return eip_build(buf, cap, &h, data, sizeof(data));
95}
96
97size_t eip_build_send_rr_data(uint8_t *buf, size_t cap, uint32_t session_handle, const uint8_t sender_context[8],
98 uint16_t timeout, const uint8_t *cip, size_t cip_len)
99{
100 if (!buf || (cip_len && !cip) || cip_len > 0xFFFF)
101 return 0;
102 // command data: interface handle(4) + timeout(2) + CPF{ count(2) + null item(4) + unconn item(4+cip) }
103 size_t data_len = 4 + 2 + 2 + 4 + 4 + cip_len;
104 size_t total = EIP_HEADER_SIZE + data_len;
105 if (total > cap || data_len > 0xFFFF)
106 return 0;
107
108 // Write the header (length = the command-data length) then the command data straight into
109 // buf - no temp buffer, so a large CIP payload never lands on the stack.
110 EipHeader h;
111 memset(&h, 0, sizeof(h));
112 h.command = EIP_CMD_SEND_RR_DATA;
113 h.session_handle = session_handle;
114 if (sender_context)
115 memcpy(h.sender_context, sender_context, 8);
116 if (eip_build(buf, cap, &h, nullptr, 0) == 0) // writes only the 24-octet header, length 0
117 return 0; // GCOVR_EXCL_LINE unreachable: total>cap above already proved cap>=40>EIP_HEADER_SIZE, so this can't
118 // fail
119 // Patch the length field (offset 2) to the real command-data length.
120 put16(buf + 2, (uint16_t)data_len);
121
122 size_t p = EIP_HEADER_SIZE;
123 p += put32(buf + p, 0); // interface handle (CIP)
124 p += put16(buf + p, timeout); // timeout
125 p += put16(buf + p, 2); // CPF item count
126 p += put16(buf + p, EIP_CPF_NULL);
127 p += put16(buf + p, 0); // null address item length
128 p += put16(buf + p, EIP_CPF_UNCONNECTED_DATA);
129 p += put16(buf + p, (uint16_t)cip_len);
130 if (cip_len)
131 {
132 memcpy(buf + p, cip, cip_len);
133 p += cip_len;
134 }
135 return p;
136}
137
138bool eip_parse_send_rr_data(const uint8_t *data, size_t data_len, const uint8_t **cip, size_t *cip_len)
139{
140 if (!data || data_len < 8) // interface handle(4) + timeout(2) + item count(2)
141 return false;
142 size_t pos = 6; // skip interface handle + timeout
143 uint16_t item_count = get16(data + pos);
144 pos += 2;
145 for (uint16_t i = 0; i < item_count; i++)
146 {
147 if (pos + 4 > data_len)
148 return false;
149 uint16_t type = get16(data + pos);
150 uint16_t ilen = get16(data + pos + 2);
151 pos += 4;
152 if (pos + ilen > data_len)
153 return false;
154 if (type == EIP_CPF_UNCONNECTED_DATA)
155 {
156 if (cip)
157 *cip = data + pos;
158 if (cip_len)
159 *cip_len = ilen;
160 return true;
161 }
162 pos += ilen;
163 }
164 return false; // no unconnected data item
165}
166
167#endif // DETWS_ENABLE_ENIP
EtherNet/IP encapsulation codec (DETWS_ENABLE_ENIP) - zero-heap builder + parser for the ODVA EtherNe...