DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
iec60870.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 iec60870.cpp
6 * @brief IEC 60870-5-101 / -104 telecontrol codec (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_IEC60870
12
13#include <string.h>
14
15// --- -104 APCI ---
16
17size_t iec104_build_i(uint8_t *buf, size_t cap, uint16_t ns, uint16_t nr, const uint8_t *asdu, size_t asdu_len)
18{
19 if (!buf || (asdu_len && !asdu) || asdu_len > 249) // APDU length octet maxes at 253 (= 4 + 249)
20 return 0;
21 size_t total = IEC104_APCI_LEN + asdu_len;
22 if (cap < total)
23 return 0;
24 buf[0] = IEC_START_104;
25 buf[1] = (uint8_t)(4 + asdu_len); // APDU length: 4 control octets + ASDU
26 buf[2] = (uint8_t)((ns << 1) & 0xFEu); // I-format: bit0 of octet 1 is 0
27 buf[3] = (uint8_t)((ns >> 7) & 0xFFu);
28 buf[4] = (uint8_t)((nr << 1) & 0xFEu);
29 buf[5] = (uint8_t)((nr >> 7) & 0xFFu);
30 if (asdu_len)
31 memcpy(buf + 6, asdu, asdu_len);
32 return total;
33}
34
35size_t iec104_build_s(uint8_t *buf, size_t cap, uint16_t nr)
36{
37 if (!buf || cap < IEC104_APCI_LEN)
38 return 0;
39 buf[0] = IEC_START_104;
40 buf[1] = 4;
41 buf[2] = 0x01; // S-format
42 buf[3] = 0x00;
43 buf[4] = (uint8_t)((nr << 1) & 0xFEu);
44 buf[5] = (uint8_t)((nr >> 7) & 0xFFu);
45 return IEC104_APCI_LEN;
46}
47
48size_t iec104_build_u(uint8_t *buf, size_t cap, uint8_t u_cmd)
49{
50 if (!buf || cap < IEC104_APCI_LEN)
51 return 0;
52 buf[0] = IEC_START_104;
53 buf[1] = 4;
54 buf[2] = u_cmd; // U-format: bits 0-1 are 11 in every defined command
55 buf[3] = 0x00;
56 buf[4] = 0x00;
57 buf[5] = 0x00;
58 return IEC104_APCI_LEN;
59}
60
61bool iec104_parse(const uint8_t *buf, size_t len, Iec104Apci *out, size_t *consumed)
62{
63 if (!buf || !out || len < 2 || buf[0] != IEC_START_104)
64 return false;
65 uint8_t L = buf[1];
66 if (L < 4)
67 return false;
68 size_t total = (size_t)2 + L;
69 if (len < total)
70 return false;
71 uint8_t c0 = buf[2];
72 out->ns = out->nr = 0;
73 out->u_cmd = 0;
74 out->asdu = nullptr;
75 out->asdu_len = 0;
76 if ((c0 & 0x01u) == 0) // I-format
77 {
78 out->format = Iec104Format::IEC104_I;
79 out->ns = (uint16_t)((buf[2] >> 1) | ((uint16_t)buf[3] << 7));
80 out->nr = (uint16_t)((buf[4] >> 1) | ((uint16_t)buf[5] << 7));
81 out->asdu_len = (size_t)(L - 4);
82 out->asdu = out->asdu_len ? buf + 6 : nullptr;
83 }
84 else if ((c0 & 0x03u) == 0x01u) // S-format
85 {
86 out->format = Iec104Format::IEC104_S;
87 out->nr = (uint16_t)((buf[4] >> 1) | ((uint16_t)buf[5] << 7));
88 }
89 else // U-format (bits 0-1 == 11)
90 {
91 out->format = Iec104Format::IEC104_U;
92 out->u_cmd = c0;
93 }
94 if (consumed)
95 *consumed = total;
96 return true;
97}
98
99// --- ASDU header + IOA ---
100
101size_t iec_asdu_build_header(uint8_t *buf, size_t cap, const IecAsduHeader *h)
102{
103 if (!buf || !h || cap < 6)
104 return 0;
105 buf[0] = h->type_id;
106 buf[1] = (uint8_t)((h->sq ? 0x80u : 0u) | (h->count & 0x7Fu)); // variable structure qualifier
107 buf[2] = (uint8_t)((h->test ? 0x80u : 0u) | (h->negative ? 0x40u : 0u) | (h->cot & 0x3Fu));
108 buf[3] = h->orig_addr;
109 buf[4] = (uint8_t)(h->common_addr & 0xFFu); // common address, little-endian
110 buf[5] = (uint8_t)((h->common_addr >> 8) & 0xFFu);
111 return 6;
112}
113
114bool iec_asdu_parse_header(const uint8_t *buf, size_t len, IecAsduHeader *out, size_t *consumed)
115{
116 if (!buf || !out || len < 6)
117 return false;
118 out->type_id = buf[0];
119 out->sq = (buf[1] & 0x80u) != 0;
120 out->count = (uint8_t)(buf[1] & 0x7Fu);
121 out->test = (buf[2] & 0x80u) != 0;
122 out->negative = (buf[2] & 0x40u) != 0;
123 out->cot = (uint8_t)(buf[2] & 0x3Fu);
124 out->orig_addr = buf[3];
125 out->common_addr = (uint16_t)(buf[4] | ((uint16_t)buf[5] << 8));
126 if (consumed)
127 *consumed = 6;
128 return true;
129}
130
131size_t iec_put_ioa(uint8_t *buf, size_t cap, uint32_t ioa)
132{
133 if (!buf || cap < 3)
134 return 0;
135 buf[0] = (uint8_t)(ioa & 0xFFu);
136 buf[1] = (uint8_t)((ioa >> 8) & 0xFFu);
137 buf[2] = (uint8_t)((ioa >> 16) & 0xFFu);
138 return 3;
139}
140
141uint32_t iec_get_ioa(const uint8_t *p)
142{
143 return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16);
144}
145
146// --- -101 FT1.2 link frames ---
147
148static uint8_t sum8(const uint8_t *p, size_t n)
149{
150 uint8_t s = 0;
151 for (size_t i = 0; i < n; i++)
152 s = (uint8_t)(s + p[i]);
153 return s;
154}
155
156size_t iec101_build_fixed(uint8_t *buf, size_t cap, uint8_t control, uint8_t addr)
157{
158 if (!buf || cap < 5)
159 return 0;
160 buf[0] = IEC_START_FIXED;
161 buf[1] = control;
162 buf[2] = addr;
163 buf[3] = (uint8_t)(control + addr); // checksum over control + address
164 buf[4] = IEC_STOP;
165 return 5;
166}
167
168size_t iec101_build_variable(uint8_t *buf, size_t cap, uint8_t control, uint8_t addr, const uint8_t *asdu,
169 uint8_t asdu_len)
170{
171 if (!buf || (asdu_len && !asdu) || asdu_len > 253)
172 return 0;
173 uint8_t L = (uint8_t)(2 + asdu_len); // L counts control + address + ASDU
174 size_t total = (size_t)6 + L;
175 if (cap < total)
176 return 0;
177 buf[0] = IEC_START_104; // 0x68
178 buf[1] = L;
179 buf[2] = L;
180 buf[3] = IEC_START_104;
181 buf[4] = control;
182 buf[5] = addr;
183 if (asdu_len)
184 memcpy(buf + 6, asdu, asdu_len);
185 buf[4 + L] = sum8(buf + 4, L); // checksum over control..end of ASDU
186 buf[5 + L] = IEC_STOP;
187 return total;
188}
189
190bool iec101_parse(const uint8_t *buf, size_t len, Iec101Frame *out, size_t *consumed)
191{
192 if (!buf || !out || len < 1)
193 return false;
194 out->fixed = false;
195 out->control = out->addr = 0;
196 out->asdu = nullptr;
197 out->asdu_len = 0;
198
199 if (buf[0] == IEC_START_FIXED)
200 {
201 if (len < 5 || buf[4] != IEC_STOP || buf[3] != (uint8_t)(buf[1] + buf[2]))
202 return false;
203 out->fixed = true;
204 out->control = buf[1];
205 out->addr = buf[2];
206 if (consumed)
207 *consumed = 5;
208 return true;
209 }
210 if (buf[0] == IEC_START_104)
211 {
212 if (len < 4)
213 return false;
214 uint8_t L = buf[1];
215 if (L < 2 || buf[2] != L || buf[3] != IEC_START_104)
216 return false;
217 size_t total = (size_t)6 + L;
218 if (len < total || buf[5 + L] != IEC_STOP || sum8(buf + 4, L) != buf[4 + L])
219 return false;
220 out->control = buf[4];
221 out->addr = buf[5];
222 out->asdu_len = (uint8_t)(L - 2);
223 out->asdu = out->asdu_len ? buf + 6 : nullptr;
224 if (consumed)
225 *consumed = total;
226 return true;
227 }
228 return false;
229}
230
231#endif // DETWS_ENABLE_IEC60870
IEC 60870-5-101 / -104 telecontrol (SCADA) codec (DETWS_ENABLE_IEC60870).