DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
bacnet.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 bacnet.cpp
6 * @brief BACnet/IP BVLC + NPDU builder + parser (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_BACNET
12
13#include <string.h>
14
15size_t bvlc_build(uint8_t *buf, size_t cap, uint8_t function, const uint8_t *npdu, size_t npdu_len)
16{
17 if (!buf || (npdu_len && !npdu))
18 return 0;
19 size_t total = BVLC_HEADER_SIZE + npdu_len;
20 if (total > 0xFFFF || total > cap)
21 return 0;
22 buf[0] = BVLC_TYPE_BIP;
23 buf[1] = function;
24 buf[2] = (uint8_t)(total >> 8); // length, big-endian, the whole BVLL
25 buf[3] = (uint8_t)(total & 0xFF);
26 if (npdu_len)
27 memcpy(buf + BVLC_HEADER_SIZE, npdu, npdu_len);
28 return total;
29}
30
31bool bvlc_parse(const uint8_t *buf, size_t len, uint8_t *function, const uint8_t **npdu, size_t *npdu_len)
32{
33 if (!buf || len < BVLC_HEADER_SIZE || buf[0] != BVLC_TYPE_BIP)
34 return false;
35 size_t total = ((size_t)buf[2] << 8) | buf[3];
36 if (total < BVLC_HEADER_SIZE || total > len)
37 return false;
38 if (function)
39 *function = buf[1];
40 if (npdu)
41 *npdu = buf + BVLC_HEADER_SIZE;
42 if (npdu_len)
43 *npdu_len = total - BVLC_HEADER_SIZE;
44 return true;
45}
46
47size_t npdu_build(uint8_t *buf, size_t cap, bool expecting_reply, uint8_t priority, bool has_dest, uint16_t dnet,
48 const uint8_t *dadr, uint8_t dadr_len, uint8_t hop_count, const uint8_t *apdu, size_t apdu_len)
49{
50 if (!buf || (apdu_len && !apdu) || (dadr_len && !dadr))
51 return 0;
52 size_t need = 2 + apdu_len; // version + control + apdu
53 if (has_dest)
54 need += 2 + 1 + dadr_len + 1; // DNET + DLEN + DADR + hop count
55 if (need > cap)
56 return 0;
57
58 size_t p = 0;
59 buf[p++] = NPDU_VERSION;
60 uint8_t control = (uint8_t)(priority & NPCI_PRIORITY_MASK);
61 if (expecting_reply)
62 control |= NPCI_EXPECTING_REPLY;
63 if (has_dest)
64 control |= NPCI_DEST_PRESENT;
65 buf[p++] = control;
66 if (has_dest)
67 {
68 buf[p++] = (uint8_t)(dnet >> 8);
69 buf[p++] = (uint8_t)(dnet & 0xFF);
70 buf[p++] = dadr_len;
71 if (dadr_len)
72 {
73 memcpy(buf + p, dadr, dadr_len);
74 p += dadr_len;
75 }
76 buf[p++] = hop_count; // follows the (absent) source fields when a destination is present
77 }
78 if (apdu_len)
79 {
80 memcpy(buf + p, apdu, apdu_len);
81 p += apdu_len;
82 }
83 return p;
84}
85
86bool npdu_parse(const uint8_t *buf, size_t len, NpduInfo *out)
87{
88 if (!buf || !out || len < 2 || buf[0] != NPDU_VERSION)
89 return false;
90 uint8_t control = buf[1];
91 size_t p = 2;
92
93 out->control = control;
94 out->network_message = (control & NPCI_NETWORK_MSG) != 0;
95 out->dest_present = (control & NPCI_DEST_PRESENT) != 0;
96 out->src_present = (control & NPCI_SRC_PRESENT) != 0;
97 out->dnet = 0;
98 out->snet = 0;
99 out->hop_count = 0;
100
101 if (out->dest_present)
102 {
103 if (p + 3 > len)
104 return false;
105 out->dnet = (uint16_t)((buf[p] << 8) | buf[p + 1]);
106 uint8_t dlen = buf[p + 2];
107 p += 3 + dlen;
108 if (p > len)
109 return false;
110 }
111 if (out->src_present)
112 {
113 if (p + 3 > len)
114 return false;
115 out->snet = (uint16_t)((buf[p] << 8) | buf[p + 1]);
116 uint8_t slen = buf[p + 2];
117 p += 3 + slen;
118 if (p > len)
119 return false;
120 }
121 if (out->dest_present) // the hop count follows the source fields
122 {
123 if (p + 1 > len)
124 return false;
125 out->hop_count = buf[p++];
126 }
127 out->apdu = buf + p;
128 out->apdu_len = len - p;
129 return true;
130}
131
132#endif // DETWS_ENABLE_BACNET
BACnet/IP BVLC + NPDU codec (DETWS_ENABLE_BACNET) - zero-heap framing for the ASHRAE 135 building-aut...