ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
s7comm.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 s7comm.h
6 * @brief Siemens S7comm PDU codec (PROTOCORE_ENABLE_S7COMM) - zero-heap builder + parser for the
7 * S7-300/400 communication PDUs, carried inside a COTP Data TPDU (services/fieldbus/cotp) over
8 * ISO-on-TCP (port 102).
9 *
10 * An S7comm PDU starts with a header then a parameter section then an optional data section:
11 * @code
12 * 0x32 ROSCTR redundancy(2) pdu-ref(2) param-len(2) data-len(2) [err-class err-code]
13 * <parameter ...> <data ...>
14 * @endcode
15 * The header is 10 octets, or 12 for a response ROSCTR (Ack or Ack_Data), which adds a 2-octet error code.
16 * A Read Var job (function 0x04) carries one or more S7-ANY request items (area / DB / byte
17 * address / element count); the Ack_Data response carries, per item, a return code + a data
18 * transport size + a length + the value bytes. Per the protocol, the response length is in
19 * BITS for the bit/byte/int transport sizes (3/4/5) and in BYTES otherwise, and each item
20 * is padded to an even length except the last.
21 *
22 * Constants and the length rule are verified against the Wireshark S7comm dissector. This
23 * codec produces / consumes the S7 PDU; wrap it with `Cotp.build_dt` + `Cotp.tpkt_build`.
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef PROTOCORE_S7COMM_H
30#define PROTOCORE_S7COMM_H
31
32#include "protocore_config.h" // the entry point: protocore_types.h for the widths
33
34#if PROTOCORE_ENABLE_S7COMM
35
37
38// This module holds nothing between calls, so it carves no borrow and states none. An entry
39// takes one all the same, and never reads it, so every namespace in the tree is invoked the
40// same way.
41
42#define S7_PROTOCOL_ID 0x32 ///< constant first octet of every S7comm PDU
43
44// ROSCTR (message type).
45#define S7_ROSCTR_JOB 0x01
46#define S7_ROSCTR_ACK 0x02
47#define S7_ROSCTR_ACK_DATA 0x03
48#define S7_ROSCTR_USERDATA 0x07
49
50// Parameter function codes.
51#define S7_FUNC_SETUP_COMM 0xF0
52#define S7_FUNC_READ_VAR 0x04
53#define S7_FUNC_WRITE_VAR 0x05
54
55// Memory area codes (in an S7-ANY item).
56#define S7_AREA_INPUTS 0x81 ///< process inputs (I/E)
57#define S7_AREA_OUTPUTS 0x82 ///< process outputs (Q/A)
58#define S7_AREA_FLAGS 0x83 ///< flags / merker (M)
59#define S7_AREA_DB 0x84 ///< data blocks (DB)
60#define S7_AREA_COUNTER 0x1C
61#define S7_AREA_TIMER 0x1D
62
63// Request-item transport sizes (the element type).
64#define S7_TS_BIT 1
65#define S7_TS_BYTE 2
66#define S7_TS_CHAR 3
67#define S7_TS_WORD 4
68#define S7_TS_INT 5
69#define S7_TS_DWORD 6
70#define S7_TS_DINT 7
71#define S7_TS_REAL 8
72
73// Response data transport sizes (length-in-bits for BIT/BYTE/INT = 3/4/5).
74#define S7_DTS_NULL 0
75#define S7_DTS_BIT 3
76#define S7_DTS_BYTE 4
77#define S7_DTS_INT 5
78#define S7_DTS_DINT 6
79#define S7_DTS_REAL 7
80#define S7_DTS_OCTET 9
81
82#define S7_SYNTAX_S7ANY 0x10 ///< S7-ANY address syntax id
83#define S7_RET_OK 0xFF ///< data item return code: success
84
85/** @brief One Read Var item (an S7-ANY pointer). */
86typedef struct
87{
88 uint8_t area; ///< S7_AREA_*
89 uint16_t db_number; ///< DB number (0 for non-DB areas)
90 uint32_t byte_address; ///< starting byte address
91 uint8_t transport_size; ///< S7_TS_* (element type)
92 uint16_t count; ///< number of elements
93} S7ReadItem;
94
95/** @brief One Write Var item: an S7-ANY pointer (as for a read) plus the value bytes to write. */
96typedef struct
97{
98 uint8_t area; ///< S7_AREA_*
99 uint16_t db_number; ///< DB number (0 for non-DB areas)
100 uint32_t byte_address; ///< starting byte address
101 uint8_t transport_size; ///< S7_TS_* (parameter-spec element type)
102 uint16_t count; ///< number of elements (parameter spec)
103 uint8_t data_transport_size; ///< S7_DTS_* (data item; sets the bit/byte length rule)
104 const uint8_t *data; ///< value bytes to write
105 uint16_t data_len; ///< value length in BYTES
106} S7WriteItem;
107
108/** @brief A parsed S7comm header. @ref param / @ref data point INTO the source buffer. */
109typedef struct
110{
111 uint8_t rosctr;
112 uint16_t pdu_ref;
113 uint16_t param_len;
114 uint16_t data_len;
115 uint8_t error_class; ///< Ack / Ack_Data only
116 uint8_t error_code; ///< Ack / Ack_Data only
117 size_t header_len; ///< 10 or 12
118 const uint8_t *param;
119 const uint8_t *data;
120} S7Header;
121
122/** @brief One Read Var response data item. @ref data points INTO the source buffer. */
123typedef struct
124{
125 uint8_t return_code; ///< S7_RET_OK on success
126 uint8_t transport_size; ///< S7_DTS_*
127 const uint8_t *data; ///< value bytes
128 size_t data_len; ///< value length in BYTES (the bit length is converted)
129} S7DataItem;
130
131/** @brief What build_setup takes: buf, cap, pdu_ref, max_amq_calling, ... */
132typedef struct
133{
134 uint8_t *buf;
135 size_t cap;
136 uint16_t pdu_ref;
137 uint16_t max_amq_calling;
138 uint16_t max_amq_called;
139 uint16_t pdu_size;
140} S7commBuildSetupArgs;
141
142/** @brief What build_read_request takes: buf, cap, pdu_ref, items, n. */
143typedef struct
144{
145 uint8_t *buf;
146 size_t cap;
147 uint16_t pdu_ref;
148 const S7ReadItem *items;
149 size_t n;
150} S7commBuildReadRequestArgs;
151
152/** @brief What build_write_request takes: buf, cap, pdu_ref, items, n. */
153typedef struct
154{
155 uint8_t *buf;
156 size_t cap;
157 uint16_t pdu_ref;
158 const S7WriteItem *items;
159 size_t n;
160} S7commBuildWriteRequestArgs;
161
162/** @brief What parse_header takes: buf, len, out. */
163typedef struct
164{
165 const uint8_t *buf;
166 size_t len;
167 S7Header *out;
168} S7commParseHeaderArgs;
169
170/** @brief What read_next_item takes: data, data_len, offset, out. */
171typedef struct
172{
173 const uint8_t *data; ///< the S7Header data pointer; data_len its data_len
174 size_t data_len;
175 size_t *offset; ///< in/out cursor, start at 0; advanced past the item (and its even-pad)
176 S7DataItem *out;
177} S7commReadNextItemArgs;
178
179/**
180 * @brief Siemens S7comm PDU codec (PROTOCORE_ENABLE_S7COMM) - zero-heap builder + parser for the S7-300/400
181 * communication PDUs, carried inside a COTP Data TPDU (services/fieldbus/cotp) over ISO-on-TCP (port 102).
182 *
183 * A caller sets the members a call takes, invokes it through ::S7comm with the bytes it runs
184 * out of, and reads the outcome off the same handle.
185 *
186 * S7comm.build_setup_args.buf = ...;
187 * S7comm.build_setup_args.cap = ...;
188 * S7comm.build_setup_args.pdu_ref = ...;
189 * S7comm.build_setup_args.max_amq_calling = ...;
190 * S7comm.build_setup_args.max_amq_called = ...;
191 * S7comm.build_setup_args.pdu_size = ...;
192 * S7comm.build_setup(work);
193 * // S7comm.n is what the call reports
194 *
195 * @var S7commNs::build_setup_args what build_setup takes: buf, cap, pdu_ref, max_amq_calling,
196 * @var S7commNs::build_read_request_args what build_read_request takes: buf, cap, pdu_ref, items, n
197 * @var S7commNs::build_write_request_args what build_write_request takes: buf, cap, pdu_ref, items, n
198 * @var S7commNs::parse_header_args what parse_header takes: buf, len, out
199 * @var S7commNs::read_next_item_args what read_next_item takes: data, data_len, offset, out
200 * @var S7commNs::ok true on a complete item; false at end-of-section or on truncation
201 * @var S7commNs::n the count a call reports
202 * @var S7commNs::build_setup build a Setup Communication job. Returns the PDU length, or 0 on ...
203 * @var S7commNs::build_read_request build a Read Var job for n items. Returns the PDU length, or 0 on ...
204 * @var S7commNs::build_write_request build a Write Var job (function 0x05) for n items. Mirrors the read ...
205 * @var S7commNs::parse_header parse + validate an S7comm header (protocol id, lengths)
206 * @var S7commNs::read_next_item read the next Read Var response data item from the data section
207 *
208 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
209 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
210 * a caller drives every namespace the same way.
211 */
212typedef struct
213{
214 S7commBuildSetupArgs build_setup_args;
215 S7commBuildReadRequestArgs build_read_request_args;
216 S7commBuildWriteRequestArgs build_write_request_args;
217 S7commParseHeaderArgs parse_header_args;
218 S7commReadNextItemArgs read_next_item_args;
219 proto_bool ok;
220 size_t n;
221} S7commVars;
222
223/** @brief The operands and the outcome. */
224extern S7commVars S7commV;
225
226/** @brief The entries. */
227typedef struct
228{
229 void (*const build_setup)(uint8_t *work);
230 void (*const build_read_request)(uint8_t *work);
231 void (*const build_write_request)(uint8_t *work);
232 void (*const parse_header)(uint8_t *work);
233 void (*const read_next_item)(uint8_t *work);
234} S7commNs;
235
236// What the table binds, defined once in the .c and taking one parameter each: everything
237// else an entry needs is an operand in S7commV or a region of the borrow at a fixed offset.
238void protocore_s7comm_build_setup(uint8_t *work);
239void protocore_s7comm_build_read_request(uint8_t *work);
240void protocore_s7comm_build_write_request(uint8_t *work);
241void protocore_s7comm_parse_header(uint8_t *work);
242void protocore_s7comm_read_next_item(uint8_t *work);
243
244// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
245// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
246// `S7comm.build_setup(work)` resolves to a named function and becomes a DIRECT call. An extern table
247// leaves the call indirect and the symbol live at every level, -O2 -flto included.
248static const S7commNs S7comm __attribute__((unused)) = {
249 .build_setup = protocore_s7comm_build_setup,
250 .build_read_request = protocore_s7comm_build_read_request,
251 .build_write_request = protocore_s7comm_build_write_request,
252 .parse_header = protocore_s7comm_parse_header,
253 .read_next_item = protocore_s7comm_read_next_item,
254};
255
257
258#endif // PROTOCORE_ENABLE_S7COMM
259
260#endif // PROTOCORE_S7COMM_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