ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
cotp.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file cotp.h
6 * @brief TPKT (RFC 1006) + COTP / ISO 8073 X.224 class-0 frame codec (PROTOCORE_ENABLE_COTP) -
7 * zero-heap "ISO transport on TCP" framing, the reusable foundation under S7comm and
8 * IEC 61850 MMS.
9 *
10 * Two stacked layers over TCP:
11 * - TPKT (RFC 1006): a 4-octet envelope - version(1)=3, reserved(1)=0, length(2,
12 * big-endian, the whole packet including this header) - then an X.224 TPDU.
13 * - COTP / X.224 class 0: a Data TPDU is `LI(1) 0xF0 (EOT|TPDU-NR)` then the user data,
14 * where LI is the count of header octets after itself. Connection Request / Confirm use
15 * codes 0xE0 / 0xD0 and carry a destination ref, a source ref, a class octet, and
16 * variable parameters (e.g. the TPDU-size parameter 0xC0).
17 *
18 * The builders frame a payload into a caller buffer (fail-closed); the parsers validate and
19 * report the slices. TPKT/X.224 layout verified against RFC 1006 / ISO 8073.
20 *
21 * @author Douglas Quigg (dstroy0)
22 * @date 2026
23 */
24
25#ifndef PROTOCORE_COTP_H
26#define PROTOCORE_COTP_H
27
28#include "protocore_config.h" // the entry point: protocore_types.h for the widths
29
30#if PROTOCORE_ENABLE_COTP
31
33
34// This module holds nothing between calls, so it carves no borrow and states none. An entry
35// takes one all the same, and never reads it, so every namespace in the tree is invoked the
36// same way.
37
38#define TPKT_VERSION 0x03 ///< RFC 1006 TPKT version (always 3)
39#define TPKT_HEADER_SIZE 4 ///< version + reserved + 2-octet length
40
41// X.224 TPDU type codes (the high nibble of the code octet; the low nibble is the CDT /
42// credit, which is 0 for class 0).
43#define COTP_DT 0xF0 ///< Data
44#define COTP_CR 0xE0 ///< Connection Request
45#define COTP_CC 0xD0 ///< Connection Confirm
46#define COTP_DR 0x80 ///< Disconnect Request
47#define COTP_DC 0xC0 ///< Disconnect Confirm
48#define COTP_ER 0x70 ///< TPDU Error
49
50#define COTP_EOT 0x80 ///< end-of-TSDU bit in the DT TPDU-NR octet
51#define COTP_PARAM_TPDU_SIZE 0xC0 ///< variable-parameter code: TPDU size (value = size exponent)
52#define COTP_DT_HEADER_LEN 3 ///< DT TPDU header octets: LI + code + (EOT|NR)
53
54/** @brief A parsed COTP header. For DT, @ref data is the user data; for CR/CC, the refs. */
55typedef struct
56{
57 uint8_t code; ///< TPDU type (high nibble): COTP_DT / COTP_CR / ...
58 uint16_t dst_ref; ///< CR / CC destination reference
59 uint16_t src_ref; ///< CR / CC source reference
60 proto_bool eot; ///< DT end-of-TSDU flag
61 const uint8_t *data; ///< DT user data (points INTO the source buffer)
62 size_t data_len;
63} CotpHeader;
64
65/** @brief What tpkt_build takes: buf, cap, payload, payload_len. */
66typedef struct
67{
68 uint8_t *buf;
69 size_t cap;
70 const uint8_t *payload;
71 size_t payload_len;
72} CotpTpktBuildArgs;
73
74/** @brief What tpkt_parse takes: buf, len, payload, payload_len, ... */
75typedef struct
76{
77 const uint8_t *buf;
78 size_t len;
79 const uint8_t **payload;
80 size_t *payload_len;
81 size_t *consumed;
82} CotpTpktParseArgs;
83
84/** @brief What build_dt takes: buf, cap, data, data_len, eot. */
85typedef struct
86{
87 uint8_t *buf;
88 size_t cap;
89 const uint8_t *data;
90 size_t data_len;
91 proto_bool eot;
92} CotpBuildDtArgs;
93
94/** @brief What build_cr takes: buf, cap, src_ref, tpdu_size_code, ... */
95typedef struct
96{
97 uint8_t *buf;
98 size_t cap;
99 uint16_t src_ref;
100 uint8_t tpdu_size_code; ///< the TPDU-size exponent (e.g. 0x0A = 1024)
101 const uint8_t *extra_params;
102 size_t extra_len;
103} CotpBuildCrArgs;
104
105/** @brief What build_cc takes: buf, cap, dst_ref, src_ref, ... */
106typedef struct
107{
108 uint8_t *buf;
109 size_t cap;
110 uint16_t dst_ref; ///< the connecting peer's source reference, echoed back as the destination reference
111 uint16_t src_ref; ///< this end's source reference
112 uint8_t tpdu_size_code; ///< the negotiated TPDU-size exponent (e.g. 0x0A = 1024)
113 const uint8_t *extra_params;
114 size_t extra_len;
115} CotpBuildCcArgs;
116
117/** @brief What parse takes: buf, len, out. */
118typedef struct
119{
120 const uint8_t *buf;
121 size_t len;
122 CotpHeader *out;
123} CotpParseArgs;
124
125/**
126 * @brief TPKT (RFC 1006) + COTP / ISO 8073 X.224 class-0 frame codec (PROTOCORE_ENABLE_COTP) - zero-heap "ISO transport
127 * on TCP" framing, the reusable foundation under S7comm and IEC 61850 MMS.
128 *
129 * A caller sets the members a call takes, invokes it through ::Cotp with the bytes it runs
130 * out of, and reads the outcome off the same handle.
131 *
132 * Cotp.tpkt_build_args.buf = ...;
133 * Cotp.tpkt_build_args.cap = ...;
134 * Cotp.tpkt_build_args.payload = ...;
135 * Cotp.tpkt_build_args.payload_len = ...;
136 * Cotp.tpkt_build(work);
137 * // Cotp.n is what the call reports
138 *
139 * @var CotpNs::tpkt_build_args what tpkt_build takes: buf, cap, payload, payload_len
140 * @var CotpNs::tpkt_parse_args what tpkt_parse takes: buf, len, payload, payload_len,
141 * @var CotpNs::build_dt_args what build_dt takes: buf, cap, data, data_len, eot
142 * @var CotpNs::build_cr_args what build_cr takes: buf, cap, src_ref, tpdu_size_code,
143 * @var CotpNs::build_cc_args what build_cc takes: buf, cap, dst_ref, src_ref,
144 * @var CotpNs::parse_args what parse takes: buf, len, out
145 * @var CotpNs::ok true on a complete, version-3 packet; false on bad version / ...
146 * @var CotpNs::n the count a call reports
147 * @var CotpNs::tpkt_build wrap payload in a TPKT envelope. Returns total octets, or 0 on ...
148 * @var CotpNs::tpkt_parse parse a TPKT envelope; reports the X.224 payload slice and bytes ...
149 * @var CotpNs::build_dt build a COTP Data TPDU around data: `LI=2, 0xF0, (EOT|0)` + data
150 * @var CotpNs::build_cr build a COTP Connection Request: `LI 0xE0 dst-ref(0) src-ref ...
151 * @var CotpNs::build_cc build a COTP Connection Confirm (the server's response to a CR): ...
152 * @var CotpNs::parse parse a COTP TPDU (typically the TPKT payload)
153 *
154 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
155 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
156 * a caller drives every namespace the same way.
157 */
158typedef struct
159{
160 CotpTpktBuildArgs tpkt_build_args;
161 CotpTpktParseArgs tpkt_parse_args;
162 CotpBuildDtArgs build_dt_args;
163 CotpBuildCrArgs build_cr_args;
164 CotpBuildCcArgs build_cc_args;
165 CotpParseArgs parse_args;
166 proto_bool ok;
167 size_t n;
168} CotpVars;
169
170/** @brief The operands and the outcome. */
171extern CotpVars CotpV;
172
173/** @brief The entries. */
174typedef struct
175{
176 void (*const tpkt_build)(uint8_t *work);
177 void (*const tpkt_parse)(uint8_t *work);
178 void (*const build_dt)(uint8_t *work);
179 void (*const build_cr)(uint8_t *work);
180 void (*const build_cc)(uint8_t *work);
181 void (*const parse)(uint8_t *work);
182} CotpNs;
183
184// What the table binds, defined once in the .c and taking one parameter each: everything
185// else an entry needs is an operand in CotpV or a region of the borrow at a fixed offset.
186void protocore_cotp_tpkt_build(uint8_t *work);
187void protocore_cotp_tpkt_parse(uint8_t *work);
188void protocore_cotp_build_dt(uint8_t *work);
189void protocore_cotp_build_cr(uint8_t *work);
190void protocore_cotp_build_cc(uint8_t *work);
191void protocore_cotp_parse(uint8_t *work);
192
193// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
194// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
195// `Cotp.tpkt_build(work)` resolves to a named function and becomes a DIRECT call. An extern table
196// leaves the call indirect and the symbol live at every level, -O2 -flto included.
197static const CotpNs Cotp __attribute__((unused)) = {
198 .tpkt_build = protocore_cotp_tpkt_build,
199 .tpkt_parse = protocore_cotp_tpkt_parse,
200 .build_dt = protocore_cotp_build_dt,
201 .build_cr = protocore_cotp_build_cr,
202 .build_cc = protocore_cotp_build_cc,
203 .parse = protocore_cotp_parse,
204};
205
207
208#endif // PROTOCORE_ENABLE_COTP
209
210#endif // PROTOCORE_COTP_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97