DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
mms.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 mms.cpp
6 * @brief IEC 61850 MMS PDU codec (see mms.h).
7 */
8
9#include "services/mms/mms.h"
10
11#if DETWS_ENABLE_MMS
12
13#include <string.h>
14
15namespace
16{
17// BER definite-length octet count for a length value < 64 KiB.
18size_t len_octets(size_t len)
19{
20 if (len < 0x80)
21 return 1;
22 if (len < 0x100)
23 return 2;
24 return 3; // GCOVR_EXCL_LINE >=256 unreachable (see write_len): 256B wrap buffers bound every value <256
25}
26
27// Write a BER length; returns octets written.
28size_t write_len(uint8_t *p, size_t len)
29{
30 if (len < 0x80)
31 {
32 p[0] = (uint8_t)len;
33 return 1;
34 }
35 if (len < 0x100)
36 {
37 p[0] = 0x81;
38 p[1] = (uint8_t)len;
39 return 2;
40 }
41 // GCOVR_EXCL_START a TLV value >=256 never occurs: every intermediate wrap buffer is 256 bytes and item
42 // names are capped at 128, so the 3-octet (0x82) length form is unreachable. Kept for encoder correctness.
43 p[0] = 0x82;
44 p[1] = (uint8_t)(len >> 8);
45 p[2] = (uint8_t)len;
46 return 3;
47 // GCOVR_EXCL_STOP
48}
49
50// Write a full TLV (tag + length + value) at out; returns total length, or 0 on overflow.
51size_t tlv(uint8_t tag, const uint8_t *val, size_t val_len, uint8_t *out, size_t cap)
52{
53 // One source of truth for the length-octet count: the value offset (k) and the total (n) both derive
54 // from it, so k + val_len == n is provable (write_len writes exactly this many octets).
55 size_t k = 1 + len_octets(val_len); // value offset: tag + length octets
56 size_t n = k + val_len; // total = offset + value
57 if (n > cap)
58 return 0;
59 out[0] = tag;
60 write_len(out + 1, val_len); // writes exactly len_octets(val_len) octets
61 // Copy length expressed as n - k (== val_len) so the destination bound flows straight from the
62 // n <= cap check above: out + k + (n - k) = out + n <= out + cap, and it reads n - k == val_len bytes
63 // from the val_len-sized val. Provably in bounds; all three callers pass val buffers >= val_len
64 // (data[data_len], scratch[256] with n<=256, idc[5] with idlen<=5). S3519 here explores an infeasible
65 // path (it assumes val_len >= cap - k, e.g. 5 >= 254) that the n <= cap guard rules out.
66 if (n > k)
67 memcpy(out + k, val, n - k); // NOSONAR - see above: bound proven, analyzer follows an infeasible path
68 return n;
69}
70
71// Minimal big-endian unsigned INTEGER content (with a leading 0 if the MSB is set), into buf; returns len.
72size_t int_content(uint32_t v, uint8_t *buf)
73{
74 uint8_t tmp[4];
75 size_t t = 0;
76 if (v == 0)
77 tmp[t++] = 0;
78 else
79 for (uint32_t x = v; x; x >>= 8)
80 tmp[t++] = (uint8_t)x;
81 size_t k = 0;
82 if (tmp[t - 1] & 0x80)
83 buf[k++] = 0x00;
84 for (size_t i = 0; i < t; i++)
85 buf[k++] = tmp[t - 1 - i];
86 return k;
87}
88
89// Decode a BER definite length at pdu[at]. Sets *value and *hdr (octets the length field spans: 1 for
90// short form, 1+nb for long form). Returns false for an out-of-bounds field or an unsupported long form
91// (nb outside 1..2). Only short/2-byte lengths occur in the PDUs this codec accepts.
92bool ber_len(const uint8_t *pdu, size_t len, size_t at, size_t *value, size_t *hdr)
93{
94 if (at >= len)
95 return false;
96 size_t v = pdu[at];
97 if (v & 0x80)
98 {
99 size_t nb = v & 0x7F;
100 if (nb == 0 || nb > 2 || at + 1 + nb > len)
101 return false;
102 v = 0;
103 for (size_t i = 0; i < nb; i++)
104 v = (v << 8) | pdu[at + 1 + i];
105 *hdr = 1 + nb;
106 }
107 else
108 *hdr = 1;
109 *value = v;
110 return true;
111}
112} // namespace
113
114size_t detws_mms_read_request(uint32_t invoke_id, const char *item_name, uint8_t *out, size_t cap)
115{
116 if (!out || !item_name)
117 return 0;
118 size_t name_len = strnlen(item_name, 128 + 1);
119 if (name_len > 128)
120 return 0;
121
122 // Each wrap checks its tlv() result; with name_len capped at 128 (above) and every buffer here 256
123 // bytes, none of these per-wrap overflow guards can fire (the running length maxes ~146). They are
124 // retained as defense-in-depth and marked GCOVR_EXCL_LINE so the report reflects reachable code.
125 uint8_t scratch[256];
126 // innermost: objectName VisibleString (0x1A) with the item name.
127 size_t n = tlv(0x1A, (const uint8_t *)item_name, name_len, scratch, sizeof(scratch));
128 if (!n)
129 return 0; // GCOVR_EXCL_LINE
130 // A0 name [0]
131 uint8_t a0[256];
132 n = tlv(0xA0, scratch, n, a0, sizeof(a0));
133 if (!n)
134 return 0; // GCOVR_EXCL_LINE
135 // 30 SEQUENCE (one VariableSpecification)
136 n = tlv(0x30, a0, n, scratch, sizeof(scratch));
137 if (!n)
138 return 0; // GCOVR_EXCL_LINE
139 // A0 listOfVariable [0]
140 n = tlv(0xA0, scratch, n, a0, sizeof(a0));
141 if (!n)
142 return 0; // GCOVR_EXCL_LINE
143 // A1 variableAccessSpecification [1]
144 n = tlv(0xA1, a0, n, scratch, sizeof(scratch));
145 if (!n)
146 return 0; // GCOVR_EXCL_LINE
147 // A4 read [4]
148 n = tlv(Mms::MMS_SERVICE_READ, scratch, n, a0, sizeof(a0));
149 if (!n)
150 return 0; // GCOVR_EXCL_LINE
151
152 // Prepend the invokeID INTEGER, then wrap in the confirmed-request PDU.
153 uint8_t idc[5];
154 size_t idlen = int_content(invoke_id, idc);
155 uint8_t body[256];
156 size_t bn = tlv(Mms::MMS_TAG_INVOKE_ID, idc, idlen, body, sizeof(body));
157 if (!bn || bn + n > sizeof(body))
158 return 0; // GCOVR_EXCL_LINE bn+n maxes ~152 << 256 (name_len<=128); unreachable, kept defensively
159 memcpy(body + bn, a0, n); // append the A4 read
160 bn += n;
161 return tlv(Mms::MMS_PDU_CONFIRMED_REQUEST, body, bn, out, cap);
162}
163
164size_t detws_mms_read_response(uint32_t invoke_id, const uint8_t *data, size_t data_len, uint8_t *out, size_t cap)
165{
166 if (!out || (data_len && !data))
167 return 0;
168 uint8_t scratch[256];
169 // listOfAccessResult SEQUENCE (0xA1 in Read response) wrapping the caller's Data value(s).
170 size_t n = tlv(0xA1, data, data_len, scratch, sizeof(scratch));
171 if (!n)
172 return 0;
173 // A4 read response [4]
174 uint8_t svc[256];
175 n = tlv(Mms::MMS_SERVICE_READ, scratch, n, svc, sizeof(svc));
176 if (!n)
177 return 0;
178
179 uint8_t idc[5];
180 size_t idlen = int_content(invoke_id, idc);
181 uint8_t body[256];
182 size_t bn = tlv(Mms::MMS_TAG_INVOKE_ID, idc, idlen, body, sizeof(body));
183 if (!bn || bn + n > sizeof(body))
184 return 0;
185 memcpy(body + bn, svc, n);
186 bn += n;
187 return tlv(Mms::MMS_PDU_CONFIRMED_RESPONSE, body, bn, out, cap);
188}
189
190bool detws_mms_parse(const uint8_t *pdu, size_t len, MmsPdu *out)
191{
192 if (!pdu || !out || len < 2)
193 return false;
194 out->pdu_tag = pdu[0];
195 if (out->pdu_tag != Mms::MMS_PDU_CONFIRMED_REQUEST && out->pdu_tag != Mms::MMS_PDU_CONFIRMED_RESPONSE &&
196 out->pdu_tag != Mms::MMS_PDU_CONFIRMED_ERROR)
197 return false;
198 // Decode the outer length.
199 size_t off = 1;
200 size_t body_len = 0;
201 size_t lhdr = 0;
202 if (!ber_len(pdu, len, off, &body_len, &lhdr))
203 return false;
204 off += lhdr;
205 if (off + body_len > len)
206 return false;
207
208 // First inner element must be the invokeID INTEGER.
209 if (off + 2 > len || pdu[off] != Mms::MMS_TAG_INVOKE_ID)
210 return false;
211 size_t idlen = pdu[off + 1];
212 if (idlen > 4 || off + 2 + idlen > len)
213 return false;
214 uint32_t id = 0;
215 for (size_t i = 0; i < idlen; i++)
216 id = (id << 8) | pdu[off + 2 + i];
217 out->invoke_id = id;
218 size_t p = off + 2 + idlen;
219
220 // The next element is the confirmedService (A4 read, A5 write, ...).
221 if (p < off + body_len && p < len)
222 {
223 out->service_tag = pdu[p];
224 size_t sp = p + 1;
225 size_t slen = 0;
226 size_t hdr = 0;
227 if (!ber_len(pdu, len, sp, &slen, &hdr))
228 return false;
229 if (sp + hdr + slen > len)
230 return false;
231 out->service_body = pdu + sp + hdr;
232 out->service_len = slen;
233 }
234 else
235 {
236 out->service_tag = 0;
237 out->service_body = nullptr;
238 out->service_len = 0;
239 }
240 return true;
241}
242
243#endif // DETWS_ENABLE_MMS
IEC 61850 MMS (Manufacturing Message Specification) PDU codec (DETWS_ENABLE_MMS).