DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
dnp3.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 dnp3.cpp
6 * @brief DNP3 (IEEE 1815) data-link frame builder + parser (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_DNP3
12
13#include <string.h>
14
15uint16_t dnp3_crc(const uint8_t *data, size_t len)
16{
17 uint16_t crc = 0x0000;
18 for (size_t i = 0; i < len; i++)
19 {
20 crc ^= data[i];
21 for (int b = 0; b < 8; b++)
22 crc = (crc & 1) ? (uint16_t)((crc >> 1) ^ 0xA6BC) : (uint16_t)(crc >> 1); // 0xA6BC = reflect(0x3D65)
23 }
24 return (uint16_t)(crc ^ 0xFFFF);
25}
26
27// Append a CRC over [data, data+n) low octet first.
28static size_t put_crc(uint8_t *p, const uint8_t *data, size_t n)
29{
30 uint16_t crc = dnp3_crc(data, n);
31 p[0] = (uint8_t)(crc & 0xFF);
32 p[1] = (uint8_t)(crc >> 8);
33 return DNP3_CRC_LEN;
34}
35
36size_t dnp3_build_frame(uint8_t *buf, size_t cap, uint8_t control, uint16_t dest, uint16_t src,
37 const uint8_t *user_data, size_t user_data_len)
38{
39 if (!buf || (user_data_len && !user_data) || user_data_len > DNP3_MAX_USER_DATA)
40 return 0;
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;
43 if (total > cap)
44 return 0;
45
46 size_t p = 0;
47 buf[p++] = DNP3_START0;
48 buf[p++] = DNP3_START1;
49 buf[p++] = (uint8_t)(DNP3_LEN_OVERHEAD + user_data_len); // LEN counts CTRL + DEST + SRC + data
50 buf[p++] = control;
51 buf[p++] = (uint8_t)(dest & 0xFF); // LE
52 buf[p++] = (uint8_t)(dest >> 8);
53 buf[p++] = (uint8_t)(src & 0xFF);
54 buf[p++] = (uint8_t)(src >> 8);
55 p += put_crc(buf + p, buf, DNP3_HEADER_LEN); // header CRC over the header octets
56
57 size_t off = 0;
58 while (off < user_data_len)
59 {
60 size_t blk = user_data_len - off;
61 if (blk > DNP3_BLOCK_LEN)
62 blk = DNP3_BLOCK_LEN;
63 memcpy(buf + p, user_data + off, blk);
64 put_crc(buf + p + blk, buf + p, blk); // CRC over this block's data
65 p += blk + DNP3_CRC_LEN;
66 off += blk;
67 }
68 return total;
69}
70
71bool dnp3_parse_frame(const uint8_t *buf, size_t len, Dnp3Frame *out, uint8_t *out_user, size_t out_cap,
72 size_t *out_user_len)
73{
74 if (!buf || !out || len < DNP3_HEADER_BLOCK_LEN)
75 return false;
76 if (buf[0] != DNP3_START0 || buf[1] != DNP3_START1)
77 return false;
78 uint8_t length = buf[2];
79 if (length < DNP3_LEN_OVERHEAD) // LEN must at least cover CTRL + DEST + SRC
80 return false;
81 size_t user_len = (size_t)length - DNP3_LEN_OVERHEAD;
82 if (user_len > DNP3_MAX_USER_DATA)
83 return false; // GCOVR_EXCL_LINE unreachable: LEN is a uint8 (<=255) so user_len = LEN-5 <= 250 =
84 // DNP3_MAX_USER_DATA
85
86 uint16_t hcrc = dnp3_crc(buf, DNP3_HEADER_LEN);
87 if ((uint16_t)(buf[DNP3_HEADER_LEN] | (buf[DNP3_HEADER_LEN + 1] << 8)) != hcrc)
88 return false; // header CRC mismatch
89
90 size_t nblocks = (user_len + DNP3_BLOCK_LEN - 1) / DNP3_BLOCK_LEN;
91 size_t total = DNP3_HEADER_BLOCK_LEN + user_len + nblocks * DNP3_CRC_LEN;
92 if (total > len)
93 return false; // frame not fully buffered
94
95 if (user_len && (!out_user || user_len > out_cap))
96 return false;
97
98 size_t p = DNP3_HEADER_BLOCK_LEN;
99 size_t off = 0;
100 while (off < user_len)
101 {
102 size_t blk = user_len - off;
103 if (blk > DNP3_BLOCK_LEN)
104 blk = DNP3_BLOCK_LEN;
105 uint16_t bcrc = dnp3_crc(buf + p, blk);
106 if ((uint16_t)(buf[p + blk] | (buf[p + blk + 1] << 8)) != bcrc)
107 return false; // block CRC mismatch
108 memcpy(out_user + off, buf + p, blk);
109 p += blk + DNP3_CRC_LEN;
110 off += blk;
111 }
112
113 out->length = length;
114 out->control = buf[3];
115 out->dest = (uint16_t)(buf[4] | (buf[5] << 8));
116 out->src = (uint16_t)(buf[6] | (buf[7] << 8));
117 if (out_user_len)
118 *out_user_len = user_len;
119 return true;
120}
121
122#endif // DETWS_ENABLE_DNP3
DNP3 (IEEE 1815) data-link frame codec (DETWS_ENABLE_DNP3) - zero-heap builder + CRC-validating parse...