ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
lsv2.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 lsv2.cpp
6 * @brief Heidenhain LSV/2 telegram codec (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_LSV2
12
13#include <string.h> // memcpy / memcmp / memset (framing + parsing are hand-rolled)
14
15// Write the fixed 8-byte header (big-endian payload length + 4-char mnemonic) once the payload is in
16// place, and return the total telegram length. Callers guarantee the buffer holds header + payload.
17static size_t finalize(uint8_t *buf, const char *mnemonic, size_t payload_len)
18{
19 buf[0] = (uint8_t)(payload_len >> 24);
20 buf[1] = (uint8_t)(payload_len >> 16);
21 buf[2] = (uint8_t)(payload_len >> 8);
22 buf[3] = (uint8_t)(payload_len);
23 memcpy(buf + 4, mnemonic, PC_LSV2_MNEMONIC_LEN);
24 return PC_LSV2_HEADER_LEN + payload_len;
25}
26
27// Append the bytes of NUL-terminated src plus one trailing NUL into buf at *pos, bounded by cap.
28// Returns false on overflow (leaving *pos partially advanced - the caller fails the whole build).
29static bool append_cstr_nul(uint8_t *buf, size_t cap, size_t *pos, const char *src)
30{
31 size_t p = *pos;
32 for (const char *s = src; *s != '\0'; ++s)
33 {
34 if (p >= cap)
35 {
36 return false;
37 }
38 buf[p++] = (uint8_t)*s;
39 }
40 if (p >= cap)
41 {
42 return false;
43 }
44 buf[p++] = 0x00;
45 *pos = p;
46 return true;
47}
48
49size_t pc_lsv2_build(uint8_t *buf, size_t cap, const char *mnemonic, const uint8_t *payload, size_t payload_len)
50{
51 if (!buf || !mnemonic || cap < PC_LSV2_HEADER_LEN)
52 {
53 return 0;
54 }
55 if (payload_len && !payload)
56 {
57 return 0;
58 }
59 if ((uint64_t)payload_len > 0xFFFFFFFFULL) // the length field is 32-bit
60 {
61 return 0;
62 }
63 if (payload_len > cap - PC_LSV2_HEADER_LEN)
64 {
65 return 0;
66 }
67 if (payload_len)
68 {
69 memcpy(buf + PC_LSV2_HEADER_LEN, payload, payload_len);
70 }
71 return finalize(buf, mnemonic, payload_len);
72}
73
74bool pc_lsv2_parse(const uint8_t *buf, size_t len, Lsv2Telegram *out, size_t *consumed)
75{
76 if (!out)
77 {
78 return false;
79 }
80 memset(out->mnemonic, 0, PC_LSV2_MNEMONIC_LEN);
81 out->payload = nullptr;
82 out->payload_len = 0;
83
84 if (!buf || len < PC_LSV2_HEADER_LEN)
85 {
86 return false;
87 }
88
89 uint32_t plen = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | (uint32_t)buf[3];
90 size_t total = (size_t)PC_LSV2_HEADER_LEN + plen;
91 if (len < total)
92 {
93 return false; // incomplete - caller accumulates more
94 }
95
96 memcpy(out->mnemonic, buf + 4, PC_LSV2_MNEMONIC_LEN);
97 out->payload = plen ? buf + PC_LSV2_HEADER_LEN : nullptr;
98 out->payload_len = plen;
99 if (consumed)
100 {
101 *consumed = total;
102 }
103 return true;
104}
105
106bool pc_lsv2_is(const Lsv2Telegram *t, const char *mnemonic4)
107{
108 return t && mnemonic4 && memcmp(t->mnemonic, mnemonic4, PC_LSV2_MNEMONIC_LEN) == 0;
109}
110
111size_t pc_lsv2_build_login(uint8_t *buf, size_t cap, const char *login, const char *password)
112{
113 if (!buf || !login || cap < PC_LSV2_HEADER_LEN)
114 {
115 return 0;
116 }
117 size_t pos = PC_LSV2_HEADER_LEN;
118 if (!append_cstr_nul(buf, cap, &pos, login))
119 {
120 return 0;
121 }
122 if (password && !append_cstr_nul(buf, cap, &pos, password))
123 {
124 return 0;
125 }
126 return finalize(buf, PC_LSV2_CMD_LOGIN, pos - PC_LSV2_HEADER_LEN);
127}
128
129size_t pc_lsv2_build_logout(uint8_t *buf, size_t cap, const char *login)
130{
131 if (!buf || cap < PC_LSV2_HEADER_LEN)
132 {
133 return 0;
134 }
135 size_t pos = PC_LSV2_HEADER_LEN;
136 if (login && *login != '\0' && !append_cstr_nul(buf, cap, &pos, login))
137 {
138 return 0;
139 }
140 return finalize(buf, PC_LSV2_CMD_LOGOUT, pos - PC_LSV2_HEADER_LEN);
141}
142
143size_t pc_lsv2_build_filename(uint8_t *buf, size_t cap, const char *mnemonic, const char *filename)
144{
145 if (!buf || !mnemonic || !filename || cap < PC_LSV2_HEADER_LEN)
146 {
147 return 0;
148 }
149 size_t pos = PC_LSV2_HEADER_LEN;
150 if (!append_cstr_nul(buf, cap, &pos, filename))
151 {
152 return 0;
153 }
154 return finalize(buf, mnemonic, pos - PC_LSV2_HEADER_LEN);
155}
156
157size_t pc_lsv2_build_run_info(uint8_t *buf, size_t cap, uint16_t info_code)
158{
159 if (!buf || cap < PC_LSV2_HEADER_LEN + 2)
160 {
161 return 0;
162 }
163 buf[PC_LSV2_HEADER_LEN] = (uint8_t)(info_code >> 8);
164 buf[PC_LSV2_HEADER_LEN + 1] = (uint8_t)(info_code);
165 return finalize(buf, PC_LSV2_CMD_RUN_INFO, 2);
166}
167
168bool pc_lsv2_is_ok(const Lsv2Telegram *t)
169{
170 return pc_lsv2_is(t, PC_LSV2_RSP_OK);
171}
172
173bool pc_lsv2_is_error(const Lsv2Telegram *t)
174{
175 return pc_lsv2_is(t, PC_LSV2_RSP_ERROR) || pc_lsv2_is(t, PC_LSV2_RSP_XFER_ERR);
176}
177
178bool pc_lsv2_error(const Lsv2Telegram *t, uint8_t *err_class, uint8_t *err_code)
179{
180 if (!pc_lsv2_is_error(t) || t->payload_len != 2 || !t->payload)
181 {
182 return false;
183 }
184 if (err_class)
185 {
186 *err_class = t->payload[0];
187 }
188 if (err_code)
189 {
190 *err_code = t->payload[1];
191 }
192 return true;
193}
194
195#endif // PC_ENABLE_LSV2
Heidenhain LSV/2 telegram codec (PC_ENABLE_LSV2) - a zero-heap codec for the LSV/2 protocol Heidenhai...