ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ads.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 ads.cpp
6 * @brief Beckhoff ADS / AMS builder + parser (pure, host-tested). All fields little-endian.
7 */
8
10
11#if PC_ENABLE_ADS
12
13#include <string.h>
14
16
17// Write the AMS/TCP header (with the final total length) + the 32-octet AMS header. The payload
18// is appended by the caller; `payload_len` is cbData. Returns ADS_HDR_LEN, or 0 if too small.
19static size_t write_header(uint8_t *buf, size_t cap, const AdsRequest *r, AdsCommand cmd, uint32_t payload_len)
20{
21 if (!buf || !r || cap < (size_t)ADS_HDR_LEN + payload_len)
22 {
23 return 0;
24 }
25 size_t p = 0;
26 // AMS/TCP header: reserved(2) + length(4). length covers the AMS header + payload.
27 buf[p++] = 0x00;
28 buf[p++] = 0x00;
29 p += pc_wr32le(buf + p, (uint32_t)ADS_AMS_HDR_LEN + payload_len);
30 // AMS header.
31 memcpy(buf + p, r->target.net_id, ADS_NET_ID_LEN);
32 p += ADS_NET_ID_LEN;
33 p += pc_wr16le(buf + p, r->target.port);
34 memcpy(buf + p, r->source.net_id, ADS_NET_ID_LEN);
35 p += ADS_NET_ID_LEN;
36 p += pc_wr16le(buf + p, r->source.port);
37 p += pc_wr16le(buf + p, (uint16_t)cmd); // wire byte in
38 p += pc_wr16le(buf + p, AdsStateFlags::request);
39 p += pc_wr32le(buf + p, payload_len); // cbData
40 p += pc_wr32le(buf + p, 0); // error code (0 on a request)
41 p += pc_wr32le(buf + p, r->invoke_id);
42 return p; // == ADS_HDR_LEN
43}
44
45size_t pc_ads_build_read_device_info(uint8_t *buf, size_t cap, const AdsRequest *r)
46{
47 return write_header(buf, cap, r, AdsCommand::read_device_info, 0);
48}
49
50size_t pc_ads_build_read_state(uint8_t *buf, size_t cap, const AdsRequest *r)
51{
52 return write_header(buf, cap, r, AdsCommand::read_state, 0);
53}
54
55size_t pc_ads_build_read(uint8_t *buf, size_t cap, const AdsRequest *r, uint32_t index_group, uint32_t index_offset,
56 uint32_t read_len)
57{
58 size_t p = write_header(buf, cap, r, AdsCommand::read, 12);
59 if (!p)
60 {
61 return 0;
62 }
63 p += pc_wr32le(buf + p, index_group);
64 p += pc_wr32le(buf + p, index_offset);
65 p += pc_wr32le(buf + p, read_len);
66 return p;
67}
68
69size_t pc_ads_build_write(uint8_t *buf, size_t cap, const AdsRequest *r, uint32_t index_group, uint32_t index_offset,
70 const uint8_t *data, uint32_t len)
71{
72 if (len && !data)
73 {
74 return 0;
75 }
76 size_t p = write_header(buf, cap, r, AdsCommand::write, 12 + len);
77 if (!p)
78 {
79 return 0;
80 }
81 p += pc_wr32le(buf + p, index_group);
82 p += pc_wr32le(buf + p, index_offset);
83 p += pc_wr32le(buf + p, len);
84 if (len)
85 {
86 memcpy(buf + p, data, len);
87 p += len;
88 }
89 return p;
90}
91
92size_t pc_ads_build_read_write(uint8_t *buf, size_t cap, const AdsRequest *r, uint32_t index_group,
93 uint32_t index_offset, uint32_t read_len, const uint8_t *write_data, uint32_t write_len)
94{
95 if (write_len && !write_data)
96 {
97 return 0;
98 }
99 size_t p = write_header(buf, cap, r, AdsCommand::read_write, 16 + write_len);
100 if (!p)
101 {
102 return 0;
103 }
104 p += pc_wr32le(buf + p, index_group);
105 p += pc_wr32le(buf + p, index_offset);
106 p += pc_wr32le(buf + p, read_len);
107 p += pc_wr32le(buf + p, write_len);
108 if (write_len)
109 {
110 memcpy(buf + p, write_data, write_len);
111 p += write_len;
112 }
113 return p;
114}
115
116size_t pc_ads_build_write_control(uint8_t *buf, size_t cap, const AdsRequest *r, uint16_t pc_ads_state,
117 uint16_t device_state, const uint8_t *data, uint32_t len)
118{
119 if (len && !data)
120 {
121 return 0;
122 }
123 size_t p = write_header(buf, cap, r, AdsCommand::write_control, 8 + len);
124 if (!p)
125 {
126 return 0;
127 }
128 p += pc_wr16le(buf + p, pc_ads_state);
129 p += pc_wr16le(buf + p, device_state);
130 p += pc_wr32le(buf + p, len);
131 if (len)
132 {
133 memcpy(buf + p, data, len);
134 p += len;
135 }
136 return p;
137}
138
139size_t pc_ads_build_add_notification(uint8_t *buf, size_t cap, const AdsRequest *r, uint32_t index_group,
140 uint32_t index_offset, uint32_t length, AdsTransMode mode, uint32_t max_delay,
141 uint32_t cycle_time)
142{
143 // IndexGroup + IndexOffset + Length + TransMode + MaxDelay + CycleTime + Reserved(16) = 40.
144 size_t p = write_header(buf, cap, r, AdsCommand::add_notification, 40);
145 if (!p)
146 {
147 return 0;
148 }
149 p += pc_wr32le(buf + p, index_group);
150 p += pc_wr32le(buf + p, index_offset);
151 p += pc_wr32le(buf + p, length);
152 p += pc_wr32le(buf + p, (uint32_t)mode); // wire byte in
153 p += pc_wr32le(buf + p, max_delay);
154 p += pc_wr32le(buf + p, cycle_time);
155 memset(buf + p, 0, 16); // reserved
156 p += 16;
157 return p;
158}
159
160size_t pc_ads_build_del_notification(uint8_t *buf, size_t cap, const AdsRequest *r, uint32_t notification_handle)
161{
162 size_t p = write_header(buf, cap, r, AdsCommand::del_notification, 4);
163 if (!p)
164 {
165 return 0;
166 }
167 p += pc_wr32le(buf + p, notification_handle);
168 return p;
169}
170
171bool pc_ads_parse_ams_header(const uint8_t *buf, size_t len, AdsAmsHeader *out)
172{
173 if (!buf || !out || len < (size_t)ADS_HDR_LEN)
174 {
175 return false;
176 }
177 if (buf[0] != 0x00 || buf[1] != 0x00) // AMS/TCP reserved
178 {
179 return false;
180 }
181 uint32_t frame_len = pc_rd32le(buf + 2); // AMS header + payload
182 if (frame_len < (uint32_t)ADS_AMS_HDR_LEN)
183 {
184 return false;
185 }
186 if ((size_t)ADS_AMSTCP_HDR_LEN + frame_len > len)
187 {
188 return false;
189 }
190 const uint8_t *a = buf + ADS_AMSTCP_HDR_LEN;
191 memcpy(out->target.net_id, a, ADS_NET_ID_LEN);
192 out->target.port = pc_rd16le(a + 6);
193 memcpy(out->source.net_id, a + 8, ADS_NET_ID_LEN);
194 out->source.port = pc_rd16le(a + 14);
195 out->cmd = (AdsCommand)pc_rd16le(a + 16); // wire byte out
196 out->state_flags = pc_rd16le(a + 18);
197 out->data_len = pc_rd32le(a + 20);
198 out->error_code = pc_rd32le(a + 24);
199 out->invoke_id = pc_rd32le(a + 28);
200 // cbData must fit inside the frame the AMS/TCP length promised.
201 if ((uint32_t)ADS_AMS_HDR_LEN + out->data_len > frame_len)
202 {
203 return false;
204 }
205 out->data = a + ADS_AMS_HDR_LEN;
206 return true;
207}
208
209bool pc_ads_parse_read(const uint8_t *data, size_t data_len, AdsReadResult *out)
210{
211 if (!data || !out || data_len < 8)
212 {
213 return false;
214 }
215 out->result = pc_rd32le(data);
216 out->len = pc_rd32le(data + 4);
217 if (8 + (size_t)out->len > data_len)
218 {
219 return false;
220 }
221 out->data = data + 8;
222 return true;
223}
224
225bool pc_ads_parse_result(const uint8_t *data, size_t data_len, uint32_t *result)
226{
227 if (!data || !result || data_len < 4)
228 {
229 return false;
230 }
231 *result = pc_rd32le(data);
232 return true;
233}
234
235bool pc_ads_parse_read_state(const uint8_t *data, size_t data_len, AdsReadStateResult *out)
236{
237 if (!data || !out || data_len < 8)
238 {
239 return false;
240 }
241 out->result = pc_rd32le(data);
242 out->pc_ads_state = pc_rd16le(data + 4);
243 out->device_state = pc_rd16le(data + 6);
244 return true;
245}
246
247bool pc_ads_parse_read_device_info(const uint8_t *data, size_t data_len, AdsDeviceInfo *out)
248{
249 if (!data || !out || data_len < 4 + 4 + ADS_DEVICE_NAME_LEN)
250 {
251 return false;
252 }
253 out->result = pc_rd32le(data);
254 out->version_major = data[4];
255 out->version_minor = data[5];
256 out->version_build = pc_rd16le(data + 6);
257 memcpy(out->device_name, data + 8, ADS_DEVICE_NAME_LEN);
258 out->device_name[ADS_DEVICE_NAME_LEN] = '\0'; // the field is not guaranteed NUL-terminated
259 return true;
260}
261
262bool pc_ads_parse_add_notification(const uint8_t *data, size_t data_len, uint32_t *result, uint32_t *handle)
263{
264 if (!data || !result || !handle || data_len < 8)
265 {
266 return false;
267 }
268 *result = pc_rd32le(data);
269 *handle = pc_rd32le(data + 4);
270 return true;
271}
272
273bool pc_ads_parse_notification(const uint8_t *data, size_t data_len, AdsNotificationSampleFn on_sample, void *user)
274{
275 // Length(4) + Stamps(4), then per stamp: Timestamp(8) + Samples(4) + samples.
276 if (!data || !on_sample || data_len < 8)
277 {
278 return false;
279 }
280 uint32_t length = pc_rd32le(data); // octets after this field
281 uint32_t stamps = pc_rd32le(data + 4);
282 if (4 + (size_t)length > data_len)
283 {
284 return false;
285 }
286 size_t p = 8;
287 for (uint32_t s = 0; s < stamps; s++)
288 {
289 if (p + 12 > data_len) // timestamp(8) + samples(4)
290 {
291 return false;
292 }
293 uint64_t timestamp = pc_rd64le(data + p);
294 uint32_t samples = pc_rd32le(data + p + 8);
295 p += 12;
296 for (uint32_t i = 0; i < samples; i++)
297 {
298 if (p + 8 > data_len) // handle(4) + size(4)
299 {
300 return false;
301 }
302 uint32_t handle = pc_rd32le(data + p);
303 uint32_t size = pc_rd32le(data + p + 4);
304 p += 8;
305 if (p + (size_t)size > data_len)
306 {
307 return false;
308 }
309 on_sample(handle, data + p, size, timestamp, user);
310 p += size;
311 }
312 }
313 return true;
314}
315
316#endif // PC_ENABLE_ADS
Beckhoff ADS / AMS protocol codec (PC_ENABLE_ADS) - zero-heap request builders + response parsers for...
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
uint64_t pc_rd64le(const uint8_t *p)
Read a little-endian u64 at p.
Definition endian.h:72
uint32_t pc_rd32le(const uint8_t *p)
Read a little-endian u32 at p.
Definition endian.h:66
size_t pc_wr32le(uint8_t *p, uint32_t v)
Write v little-endian at p.
Definition endian.h:40
uint16_t pc_rd16le(const uint8_t *p)
Read a little-endian u16 at p.
Definition endian.h:60
size_t pc_wr16le(uint8_t *p, uint16_t v)
Write v little-endian at p.
Definition endian.h:32