17uint16_t pc_dnp3_crc(
const uint8_t *data,
size_t len)
26static size_t put_crc(uint8_t *p,
const uint8_t *data,
size_t n)
28 uint16_t crc = pc_dnp3_crc(data, n);
29 p[0] = (uint8_t)(crc & 0xFF);
30 p[1] = (uint8_t)(crc >> 8);
34size_t pc_dnp3_build_frame(uint8_t *buf,
size_t cap, uint8_t control, uint16_t dest, uint16_t src,
35 const uint8_t *user_data,
size_t user_data_len)
37 if (!buf || (user_data_len && !user_data) || user_data_len > DNP3_MAX_USER_DATA)
41 size_t nblocks = (user_data_len + DNP3_BLOCK_LEN - 1) / DNP3_BLOCK_LEN;
42 size_t total = DNP3_HEADER_BLOCK_LEN + user_data_len + nblocks * DNP3_CRC_LEN;
49 buf[p++] = DNP3_START0;
50 buf[p++] = DNP3_START1;
51 buf[p++] = (uint8_t)(DNP3_LEN_OVERHEAD + user_data_len);
53 buf[p++] = (uint8_t)(dest & 0xFF);
54 buf[p++] = (uint8_t)(dest >> 8);
55 buf[p++] = (uint8_t)(src & 0xFF);
56 buf[p++] = (uint8_t)(src >> 8);
57 p += put_crc(buf + p, buf, DNP3_HEADER_LEN);
60 while (off < user_data_len)
62 size_t blk = user_data_len - off;
63 if (blk > DNP3_BLOCK_LEN)
67 memcpy(buf + p, user_data + off, blk);
68 put_crc(buf + p + blk, buf + p, blk);
69 p += blk + DNP3_CRC_LEN;
75bool pc_dnp3_parse_frame(
const uint8_t *buf,
size_t len, Dnp3Frame *out, uint8_t *out_user,
size_t out_cap,
78 if (!buf || !out || len < DNP3_HEADER_BLOCK_LEN)
82 if (buf[0] != DNP3_START0 || buf[1] != DNP3_START1)
86 uint8_t length = buf[2];
87 if (length < DNP3_LEN_OVERHEAD)
91 size_t user_len = (size_t)length - DNP3_LEN_OVERHEAD;
92 if (user_len > DNP3_MAX_USER_DATA)
103 uint16_t hcrc = pc_dnp3_crc(buf, DNP3_HEADER_LEN);
104 if ((uint16_t)(buf[DNP3_HEADER_LEN] | (buf[DNP3_HEADER_LEN + 1] << 8)) != hcrc)
109 size_t nblocks = (user_len + DNP3_BLOCK_LEN - 1) / DNP3_BLOCK_LEN;
110 size_t total = DNP3_HEADER_BLOCK_LEN + user_len + nblocks * DNP3_CRC_LEN;
116 if (user_len && (!out_user || user_len > out_cap))
121 size_t p = DNP3_HEADER_BLOCK_LEN;
123 while (off < user_len)
125 size_t blk = user_len - off;
126 if (blk > DNP3_BLOCK_LEN)
128 blk = DNP3_BLOCK_LEN;
130 uint16_t bcrc = pc_dnp3_crc(buf + p, blk);
131 if ((uint16_t)(buf[p + blk] | (buf[p + blk + 1] << 8)) != bcrc)
135 memcpy(out_user + off, buf + p, blk);
136 p += blk + DNP3_CRC_LEN;
140 out->length = length;
141 out->control = buf[3];
142 out->dest = (uint16_t)(buf[4] | (buf[5] << 8));
143 out->src = (uint16_t)(buf[6] | (buf[7] << 8));
146 *out_user_len = user_len;
153uint8_t pc_dnp3_transport_header(
bool fir,
bool fin, uint8_t seq)
155 return (uint8_t)((fin ? DNP3_TR_FIN : 0u) | (fir ? DNP3_TR_FIR : 0u) | (seq & DNP3_TR_SEQ_MASK));
158size_t pc_dnp3_build_transport_segment(uint8_t *out,
size_t cap,
bool fir,
bool fin, uint8_t seq,
159 const uint8_t *app_data,
size_t app_len)
161 if (!out || (app_len && !app_data) || app_len > DNP3_TR_MAX_APP)
165 if (cap < 1 + app_len)
169 out[0] = pc_dnp3_transport_header(fir, fin, seq);
172 memcpy(out + 1, app_data, app_len);
177void pc_dnp3_transport_rx_init(Dnp3TransportRx *r, uint8_t *buf,
size_t cap)
191int pc_dnp3_transport_feed(Dnp3TransportRx *r,
const uint8_t *user,
size_t user_len)
193 if (!r || !r->buf || !user || user_len < 1)
195 return DNP3_TR_IGNORED;
197 uint8_t hdr = user[0];
198 bool fir = (hdr & DNP3_TR_FIR) != 0;
199 bool fin = (hdr & DNP3_TR_FIN) != 0;
200 uint8_t seq = (uint8_t)(hdr & DNP3_TR_SEQ_MASK);
201 const uint8_t *app = user + 1;
202 size_t app_len = user_len - 1;
215 return DNP3_TR_IGNORED;
217 if (seq != r->expect_seq)
220 return DNP3_TR_IGNORED;
224 if (app_len > r->cap - r->len)
227 return DNP3_TR_ERROR;
231 memcpy(r->buf + r->len, app, app_len);
234 r->expect_seq = (uint8_t)((r->expect_seq + 1) & DNP3_TR_SEQ_MASK);
239 return DNP3_TR_COMPLETE;
241 return DNP3_TR_PROGRESS;
244uint8_t pc_dnp3_app_control(
bool fir,
bool fin,
bool con,
bool uns, uint8_t seq)
246 return (uint8_t)((fir ? DNP3_AC_FIR : 0u) | (fin ? DNP3_AC_FIN : 0u) | (con ? DNP3_AC_CON : 0u) |
247 (uns ? DNP3_AC_UNS : 0u) | (seq & DNP3_AC_SEQ_MASK));
250size_t pc_dnp3_build_app_request(uint8_t *out,
size_t cap, uint8_t app_control, uint8_t fc,
const uint8_t *objects,
253 if (!out || (obj_len && !objects) || cap < 2 + obj_len)
257 out[0] = app_control;
261 memcpy(out + 2, objects, obj_len);
266size_t pc_dnp3_build_app_response(uint8_t *out,
size_t cap, uint8_t app_control, uint8_t fc, uint16_t iin,
267 const uint8_t *objects,
size_t obj_len)
269 if (!out || (obj_len && !objects) || cap < 4 + obj_len)
273 out[0] = app_control;
275 out[2] = (uint8_t)iin;
276 out[3] = (uint8_t)(iin >> 8);
279 memcpy(out + 4, objects, obj_len);
284bool pc_dnp3_parse_app_header(
const uint8_t *frag,
size_t len, Dnp3AppHeader *out)
286 if (!frag || !out || len < 2)
290 uint8_t ac = frag[0];
291 uint8_t fc = frag[1];
292 bool is_response = (fc == DNP3_FC_RESPONSE || fc == DNP3_FC_UNSOLICITED_RESPONSE);
293 size_t hdr_len = is_response ? 4u : 2u;
298 out->app_control = ac;
299 out->fir = (ac & DNP3_AC_FIR) != 0;
300 out->fin = (ac & DNP3_AC_FIN) != 0;
301 out->con = (ac & DNP3_AC_CON) != 0;
302 out->uns = (ac & DNP3_AC_UNS) != 0;
303 out->seq = (uint8_t)(ac & DNP3_AC_SEQ_MASK);
305 out->is_response = is_response;
306 out->iin = is_response ? (uint16_t)(frag[2] | (frag[3] << 8)) : 0u;
307 out->obj_len = len - hdr_len;
308 out->objects = out->obj_len ? frag + hdr_len :
nullptr;
312size_t pc_dnp3_build_object_header_range(uint8_t *buf,
size_t cap, uint8_t group, uint8_t variation, uint32_t start,
315 if (!buf || stop < start)
319 uint8_t range_code = 0;
320 size_t range_len = 0;
323 range_code = DNP3_RANGE_START_STOP_1;
326 else if (stop <= 0xFFFFu)
328 range_code = DNP3_RANGE_START_STOP_2;
333 range_code = DNP3_RANGE_START_STOP_4;
336 size_t total = 3 + range_len;
344 if (range_code == DNP3_RANGE_START_STOP_1)
346 buf[3] = (uint8_t)start;
347 buf[4] = (uint8_t)stop;
349 else if (range_code == DNP3_RANGE_START_STOP_2)
362size_t pc_dnp3_build_object_header_all(uint8_t *buf,
size_t cap, uint8_t group, uint8_t variation)
370 buf[2] = DNP3_RANGE_NO_RANGE;
374size_t pc_dnp3_build_crob(uint8_t *buf,
size_t cap, uint8_t op_type, uint8_t tcc,
bool clear, uint8_t count,
375 uint32_t on_time_ms, uint32_t off_time_ms)
377 if (!buf || op_type > 0x0Fu || tcc > 0x03u || cap < DNP3_CROB_LEN)
382 buf[0] = (uint8_t)((op_type & 0x0Fu) | (clear ? 0x20u : 0x00u) | (uint8_t)((tcc & 0x03u) << 6));
387 return DNP3_CROB_LEN;
390size_t pc_dnp3_build_aob32(uint8_t *buf,
size_t cap, int32_t value)
392 if (!buf || cap < DNP3_AOB_LEN)
401size_t pc_dnp3_build_aob_float(uint8_t *buf,
size_t cap,
float value)
403 if (!buf || cap < DNP3_AOB_LEN)
408 memcpy(&bits, &value, 4);
414bool pc_dnp3_parse_object_header(
const uint8_t *buf,
size_t len, Dnp3ObjectHeader *out)
416 if (!buf || !out || len < 3)
420 uint8_t range_code = (uint8_t)(buf[2] & DNP3_QUAL_RANGE_MASK);
425 bool is_count =
false;
428 case DNP3_RANGE_START_STOP_1:
436 count = stop - start + 1;
438 case DNP3_RANGE_START_STOP_2:
446 count = stop - start + 1;
448 case DNP3_RANGE_START_STOP_4:
456 count = stop - start + 1;
458 case DNP3_RANGE_NO_RANGE:
460 case DNP3_RANGE_COUNT_1:
469 case DNP3_RANGE_COUNT_2:
478 case DNP3_RANGE_COUNT_4:
491 out->variation = buf[1];
492 out->qualifier = buf[2];
493 out->prefix_code = (uint8_t)((buf[2] & DNP3_QUAL_PREFIX_MASK) >> DNP3_QUAL_PREFIX_SHIFT);
494 out->range_code = range_code;
495 out->is_count = is_count;
499 out->objects = (p < len) ? buf + p : nullptr;
500 out->objects_len = len - p;
Parameterized CRC engine - one source of truth for every cyclic redundancy check.
constexpr pc_crc_params PC_CRC16_DNP
CRC-16/DNP (DNP3 link-layer block check). check = 0xEA82. Used by services/dnp3.
uint32_t pc_crc(const pc_crc_params *p, const uint8_t *data, size_t len)
One-shot CRC of len octets at data.
DNP3 (IEEE 1815) data-link frame codec (PC_ENABLE_DNP3) - zero-heap builder + CRC-validating parser f...
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
uint32_t pc_rd32le(const uint8_t *p)
Read a little-endian u32 at p.
size_t pc_wr32le(uint8_t *p, uint32_t v)
Write v little-endian at p.
uint16_t pc_rd16le(const uint8_t *p)
Read a little-endian u16 at p.
size_t pc_wr16le(uint8_t *p, uint16_t v)
Write v little-endian at p.