ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
cotp.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 cotp.cpp
6 * @brief TPKT + COTP (X.224 class 0) frame builder + parser (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_COTP
12
13#include <string.h>
14
15size_t pc_tpkt_build(uint8_t *buf, size_t cap, const uint8_t *payload, size_t payload_len)
16{
17 if (!buf || (payload_len && !payload))
18 {
19 return 0;
20 }
21 size_t total = TPKT_HEADER_SIZE + payload_len;
22 if (total > 0xFFFF || total > cap)
23 {
24 return 0;
25 }
26 buf[0] = TPKT_VERSION;
27 buf[1] = 0x00; // reserved
28 buf[2] = (uint8_t)(total >> 8); // length, big-endian, whole packet
29 buf[3] = (uint8_t)(total & 0xFF);
30 if (payload_len)
31 {
32 memcpy(buf + TPKT_HEADER_SIZE, payload, payload_len);
33 }
34 return total;
35}
36
37bool pc_tpkt_parse(const uint8_t *buf, size_t len, const uint8_t **payload, size_t *payload_len, size_t *consumed)
38{
39 if (!buf || len < TPKT_HEADER_SIZE)
40 {
41 return false;
42 }
43 if (buf[0] != TPKT_VERSION)
44 {
45 return false;
46 }
47 size_t total = ((size_t)buf[2] << 8) | buf[3];
48 if (total < TPKT_HEADER_SIZE || total > len)
49 {
50 return false; // invalid / not fully buffered
51 }
52 if (payload)
53 {
54 *payload = buf + TPKT_HEADER_SIZE;
55 }
56 if (payload_len)
57 {
58 *payload_len = total - TPKT_HEADER_SIZE;
59 }
60 if (consumed)
61 {
62 *consumed = total;
63 }
64 return true;
65}
66
67size_t pc_cotp_build_dt(uint8_t *buf, size_t cap, const uint8_t *data, size_t data_len, bool eot)
68{
69 if (!buf || (data_len && !data))
70 {
71 return 0;
72 }
73 size_t total = COTP_DT_HEADER_LEN + data_len;
74 if (total > cap)
75 {
76 return 0;
77 }
78 buf[0] = COTP_DT_HEADER_LEN - 1; // LI = octets after LI (code + nr/eot)
79 buf[1] = COTP_DT; // Data TPDU
80 buf[2] = (uint8_t)(eot ? COTP_EOT : 0); // EOT flag | TPDU-NR (0 for class 0)
81 if (data_len)
82 {
83 memcpy(buf + COTP_DT_HEADER_LEN, data, data_len);
84 }
85 return total;
86}
87
88size_t pc_cotp_build_cr(uint8_t *buf, size_t cap, uint16_t src_ref, uint8_t tpdu_size_code, const uint8_t *extra_params,
89 size_t extra_len)
90{
91 if (!buf || (extra_len && !extra_params))
92 {
93 return 0;
94 }
95 // after the LI octet the header is the one-octet code, the two-octet destination and source references,
96 // the one-octet class, the three-octet TPDU-size parameter, then any extra parameters
97 size_t after_li = 1 + 2 + 2 + 1 + 3 + extra_len;
98 size_t total = 1 + after_li; // + the LI octet itself
99 if (after_li > 0xFF || total > cap)
100 {
101 return 0;
102 }
103 size_t p = 0;
104 buf[p++] = (uint8_t)after_li; // LI
105 buf[p++] = COTP_CR;
106 buf[p++] = 0x00; // dst-ref = 0 (unknown on a request)
107 buf[p++] = 0x00;
108 buf[p++] = (uint8_t)(src_ref >> 8);
109 buf[p++] = (uint8_t)(src_ref & 0xFF);
110 buf[p++] = 0x00; // class 0, no options
111 buf[p++] = COTP_PARAM_TPDU_SIZE;
112 buf[p++] = 0x01; // parameter length
113 buf[p++] = tpdu_size_code;
114 if (extra_len)
115 {
116 memcpy(buf + p, extra_params, extra_len);
117 p += extra_len;
118 }
119 return p;
120}
121
122size_t pc_cotp_build_cc(uint8_t *buf, size_t cap, uint16_t dst_ref, uint16_t src_ref, uint8_t tpdu_size_code,
123 const uint8_t *extra_params, size_t extra_len)
124{
125 if (!buf || (extra_len && !extra_params))
126 {
127 return 0;
128 }
129 // after the LI octet the header is the one-octet code, the two-octet destination and source references,
130 // the one-octet class, the three-octet TPDU-size parameter, then any extra parameters
131 size_t after_li = 1 + 2 + 2 + 1 + 3 + extra_len;
132 size_t total = 1 + after_li; // + the LI octet itself
133 if (after_li > 0xFF || total > cap)
134 {
135 return 0;
136 }
137 size_t p = 0;
138 buf[p++] = (uint8_t)after_li; // LI
139 buf[p++] = COTP_CC;
140 buf[p++] = (uint8_t)(dst_ref >> 8); // dst-ref = the peer's src-ref, echoed
141 buf[p++] = (uint8_t)(dst_ref & 0xFF);
142 buf[p++] = (uint8_t)(src_ref >> 8);
143 buf[p++] = (uint8_t)(src_ref & 0xFF);
144 buf[p++] = 0x00; // class 0, no options
145 buf[p++] = COTP_PARAM_TPDU_SIZE;
146 buf[p++] = 0x01; // parameter length
147 buf[p++] = tpdu_size_code;
148 if (extra_len)
149 {
150 memcpy(buf + p, extra_params, extra_len);
151 p += extra_len;
152 }
153 return p;
154}
155
156bool pc_cotp_parse(const uint8_t *buf, size_t len, CotpHeader *out)
157{
158 if (!buf || !out || len < 2)
159 {
160 return false;
161 }
162 uint8_t li = buf[0];
163 size_t header = (size_t)li + 1; // LI counts the octets after itself
164 if (header > len || li < 1)
165 {
166 return false;
167 }
168
169 out->code = (uint8_t)(buf[1] & 0xF0); // type is the high nibble
170 out->dst_ref = 0;
171 out->src_ref = 0;
172 out->eot = false;
173 out->data = nullptr;
174 out->data_len = 0;
175
176 if (out->code == COTP_DT)
177 {
178 if (li < 2) // need the TPDU-NR/EOT octet
179 {
180 return false;
181 }
182 out->eot = (buf[2] & COTP_EOT) != 0;
183 out->data = buf + header;
184 out->data_len = len - header;
185 return true;
186 }
187 if (out->code == COTP_CR || out->code == COTP_CC)
188 {
189 if (li < 6) // code + dst-ref(2) + src-ref(2) + class(1)
190 {
191 return false;
192 }
193 out->dst_ref = (uint16_t)((buf[2] << 8) | buf[3]);
194 out->src_ref = (uint16_t)((buf[4] << 8) | buf[5]);
195 return true;
196 }
197 // Other TPDU types (DR/DC/ER/...): the type code is reported; no body extracted.
198 return true;
199}
200
201#endif // PC_ENABLE_COTP
TPKT (RFC 1006) + COTP / ISO 8073 X.224 class-0 frame codec (PC_ENABLE_COTP) - zero-heap "ISO transpo...