DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
s7comm.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 s7comm.cpp
6 * @brief Siemens S7comm PDU builder + parser (pure, host-tested; constants per Wireshark).
7 */
8
10
11#if DETWS_ENABLE_S7COMM
12
13#include <string.h>
14
15static size_t put16(uint8_t *p, uint16_t v)
16{
17 p[0] = (uint8_t)(v >> 8);
18 p[1] = (uint8_t)v;
19 return 2;
20}
21
22static uint16_t get16(const uint8_t *p)
23{
24 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
25}
26
27// Write the 10-octet job/request header (protocol id, ROSCTR, redundancy, pdu-ref, lengths).
28static size_t write_job_header(uint8_t *buf, uint16_t pdu_ref, uint16_t param_len, uint16_t data_len)
29{
30 size_t p = 0;
31 buf[p++] = S7_PROTOCOL_ID;
32 buf[p++] = S7_ROSCTR_JOB;
33 p += put16(buf + p, 0); // redundancy id (reserved)
34 p += put16(buf + p, pdu_ref);
35 p += put16(buf + p, param_len);
36 p += put16(buf + p, data_len);
37 return p; // 10
38}
39
40size_t s7_build_setup(uint8_t *buf, size_t cap, uint16_t pdu_ref, uint16_t max_amq_calling, uint16_t max_amq_called,
41 uint16_t pdu_size)
42{
43 if (!buf)
44 return 0;
45 const uint16_t param_len = 8;
46 size_t total = 10 + param_len;
47 if (total > cap)
48 return 0;
49 size_t p = write_job_header(buf, pdu_ref, param_len, 0);
50 buf[p++] = S7_FUNC_SETUP_COMM;
51 buf[p++] = 0x00; // reserved
52 p += put16(buf + p, max_amq_calling);
53 p += put16(buf + p, max_amq_called);
54 p += put16(buf + p, pdu_size);
55 return p;
56}
57
58size_t s7_build_read_request(uint8_t *buf, size_t cap, uint16_t pdu_ref, const S7ReadItem *items, size_t n)
59{
60 if (!buf || !items || n == 0 || n > 0xFF)
61 return 0;
62 uint16_t param_len = (uint16_t)(2 + 12 * n); // func + count + items
63 size_t total = 10 + param_len;
64 if (total > cap)
65 return 0;
66 size_t p = write_job_header(buf, pdu_ref, param_len, 0);
67 buf[p++] = S7_FUNC_READ_VAR;
68 buf[p++] = (uint8_t)n;
69 for (size_t i = 0; i < n; i++)
70 {
71 const S7ReadItem &it = items[i];
72 buf[p++] = 0x12; // variable specification
73 buf[p++] = 0x0A; // length of the address spec that follows
74 buf[p++] = S7_SYNTAX_S7ANY; // syntax id
75 buf[p++] = it.transport_size;
76 p += put16(buf + p, it.count);
77 p += put16(buf + p, it.db_number);
78 buf[p++] = it.area;
79 uint32_t addr = it.byte_address << 3; // bit address = byte * 8 (bit offset 0)
80 buf[p++] = (uint8_t)(addr >> 16);
81 buf[p++] = (uint8_t)(addr >> 8);
82 buf[p++] = (uint8_t)(addr & 0xFF);
83 }
84 return p;
85}
86
87bool s7_parse_header(const uint8_t *buf, size_t len, S7Header *out)
88{
89 if (!buf || !out || len < 10)
90 return false;
91 if (buf[0] != S7_PROTOCOL_ID)
92 return false;
93 out->rosctr = buf[1];
94 out->pdu_ref = get16(buf + 4);
95 out->param_len = get16(buf + 6);
96 out->data_len = get16(buf + 8);
97 out->error_class = 0;
98 out->error_code = 0;
99 out->header_len = (out->rosctr == S7_ROSCTR_ACK_DATA) ? 12 : 10; // Ack_Data adds a 2-octet error code
100 if (out->header_len == 12)
101 {
102 if (len < 12)
103 return false;
104 out->error_class = buf[10];
105 out->error_code = buf[11];
106 }
107 if (out->header_len + (size_t)out->param_len + (size_t)out->data_len > len)
108 return false; // not fully buffered
109 out->param = buf + out->header_len;
110 out->data = out->param + out->param_len;
111 return true;
112}
113
114bool s7_read_next_item(const uint8_t *data, size_t data_len, size_t *offset, S7DataItem *out)
115{
116 if (!data || !offset || !out)
117 return false;
118 size_t o = *offset;
119 if (o + 4 > data_len) // return code + transport size + 2-octet length
120 return false;
121 out->return_code = data[o];
122 out->transport_size = data[o + 1];
123 uint16_t raw_len = get16(data + o + 2);
124
125 // The length is in bits for the bit/byte/int transport sizes, otherwise in bytes.
126 size_t bytes;
127 if (out->transport_size == S7_DTS_BIT || out->transport_size == S7_DTS_BYTE || out->transport_size == S7_DTS_INT)
128 bytes = (raw_len + 7) / 8;
129 else
130 bytes = raw_len;
131
132 if (o + 4 + bytes > data_len)
133 return false;
134 out->data = data + o + 4;
135 out->data_len = bytes;
136
137 o += 4 + bytes;
138 // Each item except the last is padded to an even length; skip the fill byte.
139 if (o < data_len && (bytes & 1))
140 o++;
141 *offset = o;
142 return true;
143}
144
145#endif // DETWS_ENABLE_S7COMM
Siemens S7comm PDU codec (DETWS_ENABLE_S7COMM) - zero-heap builder + parser for the S7-300/400 commun...