ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
devicenet.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 devicenet.cpp
6 * @brief DeviceNet link-adaptation codec (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_DEVICENET
12
13#include <string.h>
14
15bool pc_devicenet_encode_id(uint32_t *id, DeviceNetGroup group, uint8_t msg_id, uint8_t mac_id)
16{
17 if (!id || mac_id > DEVICENET_MAC_MASK)
18 {
19 return false;
20 }
21 switch (group)
22 {
23 case DeviceNetGroup::DEVICENET_GROUP_1:
24 if (msg_id > 0x0Fu)
25 {
26 return false;
27 }
28 *id = DEVICENET_G1_BASE | ((uint32_t)msg_id << 6) | mac_id;
29 return true;
30 case DeviceNetGroup::DEVICENET_GROUP_2:
31 if (msg_id > 0x07u)
32 {
33 return false;
34 }
35 *id = DEVICENET_G2_BASE | ((uint32_t)mac_id << 3) | msg_id;
36 return true;
37 case DeviceNetGroup::DEVICENET_GROUP_3:
38 if (msg_id > 0x07u)
39 {
40 return false;
41 }
42 *id = DEVICENET_G3_BASE | ((uint32_t)msg_id << 6) | mac_id;
43 return true;
44 case DeviceNetGroup::DEVICENET_GROUP_4:
45 if (msg_id > 0x2Fu) // Group 4 has no MAC id; message ids 0x00..0x2F
46 {
47 return false;
48 }
49 *id = DEVICENET_G4_BASE | msg_id;
50 return true;
51 default:
52 return false;
53 }
54}
55
56bool pc_devicenet_decode_id(uint32_t can_id, DeviceNetId *out)
57{
58 if (!out)
59 {
60 return false;
61 }
62 uint32_t id = can_id & PC_CAN_STD_ID_MASK;
63 if (id < DEVICENET_G2_BASE) // Group 1: 0 MsgID(4) MAC(6)
64 {
65 out->group = DeviceNetGroup::DEVICENET_GROUP_1;
66 out->msg_id = (uint8_t)((id >> 6) & 0x0Fu);
67 out->mac_id = (uint8_t)(id & DEVICENET_MAC_MASK);
68 return true;
69 }
70 if (id < DEVICENET_G3_BASE) // Group 2: 10 MAC(6) MsgID(3)
71 {
72 out->group = DeviceNetGroup::DEVICENET_GROUP_2;
73 out->mac_id = (uint8_t)((id >> 3) & DEVICENET_MAC_MASK);
74 out->msg_id = (uint8_t)(id & 0x07u);
75 return true;
76 }
77 if (id < DEVICENET_G4_BASE) // Group 3: 11 MsgID(3) MAC(6)
78 {
79 out->group = DeviceNetGroup::DEVICENET_GROUP_3;
80 out->msg_id = (uint8_t)((id >> 6) & 0x07u);
81 out->mac_id = (uint8_t)(id & DEVICENET_MAC_MASK);
82 return true;
83 }
84 if (id <= 0x7EFu) // Group 4: 11111 MsgID(6)
85 {
86 out->group = DeviceNetGroup::DEVICENET_GROUP_4;
87 out->msg_id = (uint8_t)(id & 0x3Fu);
88 out->mac_id = 0;
89 return true;
90 }
91 return false; // 0x7F0..0x7FF are invalid identifiers
92}
93
94uint8_t pc_devicenet_msg_header(bool frag, bool xid, uint8_t mac_id)
95{
96 return (uint8_t)((frag ? DEVICENET_HDR_FRAG : 0u) | (xid ? DEVICENET_HDR_XID : 0u) | (mac_id & DEVICENET_MAC_MASK));
97}
98
99uint8_t pc_devicenet_frag_octet(uint8_t type, uint8_t count)
100{
101 return (uint8_t)((type & DEVICENET_FRAG_TYPE_MASK) | (count & DEVICENET_FRAG_COUNT_MASK));
102}
103
104bool pc_devicenet_build_explicit(CanFrame *out, DeviceNetGroup group, uint8_t msg_id, uint8_t mac_id,
105 const uint8_t *body, uint8_t body_len)
106{
107 if (!out || body_len > 7 || (body_len && !body)) // 1 header octet + up to 7 body octets
108 {
109 return false;
110 }
111 uint32_t id;
112 if (!pc_devicenet_encode_id(&id, group, msg_id, mac_id))
113 {
114 return false;
115 }
116 out->id = id;
117 out->extended = false;
118 out->rtr = false;
119 out->dlc = (uint8_t)(1 + body_len);
120 memset(out->data, 0, sizeof(out->data));
121 out->data[0] = pc_devicenet_msg_header(false, false, mac_id); // not fragmented
122 if (body_len)
123 {
124 memcpy(out->data + 1, body, body_len);
125 }
126 return true;
127}
128
129bool pc_devicenet_build_fragment(CanFrame *out, DeviceNetGroup group, uint8_t msg_id, uint8_t mac_id, bool xid,
130 uint8_t frag_type, uint8_t frag_count, const uint8_t *data, uint8_t data_len)
131{
132 // 1 header octet + 1 fragmentation octet + up to 6 data octets fill the 8-octet CAN frame.
133 if (!out || data_len > 6 || (data_len && !data) || (frag_type & (uint8_t)~DEVICENET_FRAG_TYPE_MASK) ||
134 (frag_count & (uint8_t)~DEVICENET_FRAG_COUNT_MASK))
135 {
136 return false;
137 }
138 uint32_t id;
139 if (!pc_devicenet_encode_id(&id, group, msg_id, mac_id))
140 {
141 return false;
142 }
143 out->id = id;
144 out->extended = false;
145 out->rtr = false;
146 out->dlc = (uint8_t)(2 + data_len);
147 memset(out->data, 0, sizeof(out->data));
148 out->data[0] = pc_devicenet_msg_header(true, xid, mac_id); // FRAG set
149 out->data[1] = pc_devicenet_frag_octet(frag_type, frag_count);
150 if (data_len)
151 {
152 memcpy(out->data + 2, data, data_len);
153 }
154 return true;
155}
156
157void pc_devicenet_frag_reset(DeviceNetFragRx *rx)
158{
159 if (rx)
160 {
161 memset(rx, 0, sizeof(*rx));
162 }
163}
164
165// The two "cannot overflow" exclusions below (the non-fragmented and FIRST appends) hold only because a
166// single append into a freshly reset buffer fits PC_DEVICENET_MSG_MAX. body_len is a uint8_t, so the
167// largest such append is body_len - 1 == 254 octets; anything smaller than that turns those excluded
168// error returns into live code. PC_DEVICENET_MSG_MAX is a plain #ifndef in protocore_config.h and can be
169// overridden, so pin the invariant here rather than trusting the default.
170static_assert(PC_DEVICENET_MSG_MAX >= 254,
171 "PC_DEVICENET_MSG_MAX must be >= 254 (the largest single-frame append, body_len - 1 with "
172 "body_len at its uint8_t maximum) or frag_append can overflow on the first frame");
173
174// Append @p n octets to the reassembly buffer; false if it would overflow.
175static bool frag_append(DeviceNetFragRx *rx, const uint8_t *p, uint8_t n)
176{
177 if ((uint32_t)rx->len + n > PC_DEVICENET_MSG_MAX)
178 {
179 return false;
180 }
181 memcpy(rx->buf + rx->len, p, n);
182 rx->len = (uint16_t)(rx->len + n);
183 return true;
184}
185
186DeviceNetFragResult pc_devicenet_frag_feed(DeviceNetFragRx *rx, const uint8_t *body, uint8_t body_len)
187{
188 if (!rx || !body || body_len < 1)
189 {
190 return DeviceNetFragResult::DEVICENET_FRAG_IGNORED;
191 }
192
193 if (!(body[0] & DEVICENET_HDR_FRAG)) // a complete, non-fragmented message in one frame
194 {
195 pc_devicenet_frag_reset(rx);
196 // GCOVR_EXCL_LINE below: the append half cannot fail - reset() leaves len 0 and body_len is a uint8_t,
197 // so this appends at most 254 octets, which the static_assert above guarantees MSG_MAX can hold.
198 if (body_len > 1 && !frag_append(rx, body + 1, (uint8_t)(body_len - 1))) // GCOVR_EXCL_LINE
199 {
200 return DeviceNetFragResult::DEVICENET_FRAG_ERR; // GCOVR_EXCL_LINE
201 }
202 return DeviceNetFragResult::DEVICENET_FRAG_COMPLETE;
203 }
204 if (body_len < 2)
205 {
206 return DeviceNetFragResult::DEVICENET_FRAG_ERR; // FRAG set but no fragmentation octet
207 }
208 uint8_t type = body[1] & DEVICENET_FRAG_TYPE_MASK;
209 uint8_t count = body[1] & DEVICENET_FRAG_COUNT_MASK;
210 const uint8_t *data = body + 2;
211 uint8_t data_len = (uint8_t)(body_len - 2);
212
213 switch (type)
214 {
215 case DEVICENET_FRAG_FIRST:
216 pc_devicenet_frag_reset(rx);
217 rx->active = true;
218 rx->next_count = (uint8_t)((count + 1u) & DEVICENET_FRAG_COUNT_MASK);
219 // GCOVR_EXCL_LINE below: as above, FIRST resets first, so this appends at most 253 octets (uint8
220 // body_len - 2) into an empty buffer, which the static_assert above guarantees MSG_MAX can hold.
221 if (data_len && !frag_append(rx, data, data_len)) // GCOVR_EXCL_LINE
222 {
223 return DeviceNetFragResult::DEVICENET_FRAG_ERR; // GCOVR_EXCL_LINE
224 }
225 return DeviceNetFragResult::DEVICENET_FRAG_STARTED;
226 case DEVICENET_FRAG_MIDDLE:
227 if (!rx->active || count != rx->next_count)
228 {
229 pc_devicenet_frag_reset(rx);
230 return DeviceNetFragResult::DEVICENET_FRAG_ERR;
231 }
232 if (data_len && !frag_append(rx, data, data_len))
233 {
234 pc_devicenet_frag_reset(rx);
235 return DeviceNetFragResult::DEVICENET_FRAG_ERR;
236 }
237 rx->next_count = (uint8_t)((count + 1u) & DEVICENET_FRAG_COUNT_MASK);
238 return DeviceNetFragResult::DEVICENET_FRAG_PROGRESS;
239 case DEVICENET_FRAG_LAST:
240 if (!rx->active || count != rx->next_count)
241 {
242 pc_devicenet_frag_reset(rx);
243 return DeviceNetFragResult::DEVICENET_FRAG_ERR;
244 }
245 if (data_len && !frag_append(rx, data, data_len))
246 {
247 pc_devicenet_frag_reset(rx);
248 return DeviceNetFragResult::DEVICENET_FRAG_ERR;
249 }
250 rx->active = false;
251 return DeviceNetFragResult::DEVICENET_FRAG_COMPLETE;
252 default: // DEVICENET_FRAG_ACK is flow control, not data
253 return DeviceNetFragResult::DEVICENET_FRAG_IGNORED;
254 }
255}
256
257#endif // PC_ENABLE_DEVICENET
#define PC_CAN_STD_ID_MASK
11-bit standard identifier.
Definition can.h:31
DeviceNet link-adaptation codec (PC_ENABLE_DEVICENET) - the CAN-specific layer of "CIP over CAN".
Definition can.h:44
uint8_t data[PC_CAN_MAX_DLC]
Definition can.h:49
bool rtr
Definition can.h:47
uint32_t id
Definition can.h:45
uint8_t dlc
Definition can.h:48
bool extended
Definition can.h:46