DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
goose.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 goose.cpp
6 * @brief IEC 61850 GOOSE publisher codec (see goose.h).
7 */
8
10
11#if DETWS_ENABLE_GOOSE
12
13#include <string.h>
14
15namespace
16{
17// Number of octets to encode a BER definite length.
18size_t len_octets(size_t len)
19{
20 if (len < 0x80)
21 return 1;
22 size_t n = 0;
23 for (size_t v = len; v; v >>= 8)
24 n++;
25 return 1 + n;
26}
27
28// Write a BER length at p; returns octets written.
29size_t write_len(uint8_t *p, size_t len)
30{
31 if (len < 0x80)
32 {
33 p[0] = (uint8_t)len;
34 return 1;
35 }
36 size_t nb = 0;
37 for (size_t v = len; v; v >>= 8)
38 nb++;
39 p[0] = (uint8_t)(0x80 | nb);
40 for (size_t i = 0; i < nb; i++)
41 p[1 + i] = (uint8_t)(len >> (8 * (nb - 1 - i)));
42 return 1 + nb;
43}
44
45// Append a TLV. Returns false on overflow.
46bool tlv(uint8_t *out, size_t cap, size_t *n, uint8_t tag, const uint8_t *val, size_t val_len)
47{
48 size_t need = 1 + len_octets(val_len) + val_len;
49 if (*n + need > cap)
50 return false;
51 out[(*n)++] = tag;
52 *n += write_len(out + *n, val_len);
53 if (val_len)
54 {
55 memcpy(out + *n, val, val_len);
56 *n += val_len;
57 }
58 return true;
59}
60
61// Append a BER INTEGER (unsigned value, minimal length, leading 0x00 if the MSB is set).
62bool ber_int(uint8_t *out, size_t cap, size_t *n, uint8_t tag, uint32_t v)
63{
64 uint8_t buf[5];
65 size_t k = 0;
66 // big-endian minimal
67 if (v == 0)
68 buf[k++] = 0;
69 else
70 {
71 uint8_t tmp[4];
72 size_t t = 0;
73 for (uint32_t x = v; x; x >>= 8)
74 tmp[t++] = (uint8_t)x;
75 if (tmp[t - 1] & 0x80)
76 buf[k++] = 0x00; // keep it positive
77 for (size_t i = 0; i < t; i++)
78 buf[k++] = tmp[t - 1 - i];
79 }
80 return tlv(out, cap, n, tag, buf, k);
81}
82
83bool ber_str(uint8_t *out, size_t cap, size_t *n, uint8_t tag, const char *s)
84{
85 return tlv(out, cap, n, tag, (const uint8_t *)(s ? s : ""), s ? strnlen(s, cap + 1) : 0);
86}
87
88bool ber_bool(uint8_t *out, size_t cap, size_t *n, uint8_t tag, bool v)
89{
90 uint8_t b = v ? 0xFF : 0x00;
91 return tlv(out, cap, n, tag, &b, 1);
92}
93} // namespace
94
95size_t detws_goose_pdu(const DetwsGoose *g, uint8_t *out, size_t cap)
96{
97 if (!g || !out)
98 return 0;
99 const size_t RESERVE = 4; // 0x61 tag + up to 3 length octets
100 if (cap < RESERVE)
101 return 0;
102
103 size_t n = RESERVE; // build the content after the reserved header area
104 static const uint8_t ZT[8] = {0};
105 bool ok = ber_str(out, cap, &n, 0x80, g->gocb_ref) && ber_int(out, cap, &n, 0x81, g->time_allowed_to_live) &&
106 ber_str(out, cap, &n, 0x82, g->dat_set) && ber_str(out, cap, &n, 0x83, g->go_id) &&
107 tlv(out, cap, &n, 0x84, g->t ? g->t : ZT, 8) && ber_int(out, cap, &n, 0x85, g->st_num) &&
108 ber_int(out, cap, &n, 0x86, g->sq_num) && ber_bool(out, cap, &n, 0x87, g->simulation) &&
109 ber_int(out, cap, &n, 0x88, g->conf_rev) && ber_bool(out, cap, &n, 0x89, g->nds_com) &&
110 ber_int(out, cap, &n, 0x8A, g->num_entries) && tlv(out, cap, &n, 0xAB, g->all_data, g->all_data_len);
111 if (!ok)
112 return 0;
113
114 size_t content_len = n - RESERVE;
115 size_t hdr = 1 + len_octets(content_len);
116 // Move the content down so the 0x61 tag + length sit immediately before it (no gap).
117 memmove(out + hdr, out + RESERVE, content_len);
118 out[0] = 0x61;
119 write_len(out + 1, content_len);
120 return hdr + content_len;
121}
122
123size_t detws_goose_frame(const uint8_t *dst, const uint8_t *src, uint16_t appid, const DetwsGoose *g, uint8_t *out,
124 size_t cap)
125{
126 if (!dst || !src || !g || !out || cap < 22)
127 return 0;
128 // Ethernet II header (ethertype 0x88B8 = GOOSE).
129 memcpy(out, dst, 6);
130 memcpy(out + 6, src, 6);
131 out[12] = 0x88;
132 out[13] = 0xB8;
133 // GOOSE header: APPID(2), length(2, filled below), reserved1(2), reserved2(2).
134 out[14] = (uint8_t)(appid >> 8);
135 out[15] = (uint8_t)appid;
136 out[18] = 0;
137 out[19] = 0;
138 out[20] = 0;
139 out[21] = 0;
140
141 size_t pdu = detws_goose_pdu(g, out + 22, cap - 22);
142 if (!pdu)
143 return 0;
144 uint16_t goose_len = (uint16_t)(8 + pdu); // GOOSE header (8) + APDU
145 out[16] = (uint8_t)(goose_len >> 8);
146 out[17] = (uint8_t)goose_len;
147 return 22 + pdu;
148}
149
150#endif // DETWS_ENABLE_GOOSE
IEC 61850 GOOSE publisher codec (DETWS_ENABLE_GOOSE).