DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
cip.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 cip.cpp
6 * @brief CIP message request builder + response parser (pure, host-tested; constants per Wireshark).
7 */
8
9#include "services/cip/cip.h"
10
11#if DETWS_ENABLE_CIP
12
13#include <string.h>
14
15// Write one logical segment (class/instance/attribute) for @p id; 8-bit when it fits, else
16// 16-bit (segment byte + pad + LE value). Returns the octets written (2 or 4), or 0 if it
17// does not fit in [p, p+cap).
18static size_t write_segment(uint8_t *p, size_t cap, uint8_t logical_type, uint16_t id)
19{
20 if (id <= 0xFF)
21 {
22 if (cap < 2)
23 return 0;
24 p[0] = (uint8_t)(CIP_SEG_LOGICAL | logical_type | CIP_SEG_8BIT);
25 p[1] = (uint8_t)id;
26 return 2;
27 }
28 if (cap < 4)
29 return 0;
30 p[0] = (uint8_t)(CIP_SEG_LOGICAL | logical_type | CIP_SEG_16BIT);
31 p[1] = 0x00; // pad to align the 16-bit value
32 p[2] = (uint8_t)(id & 0xFF);
33 p[3] = (uint8_t)(id >> 8);
34 return 4;
35}
36
37size_t cip_build_epath(uint8_t *buf, size_t cap, uint16_t class_id, uint16_t instance_id, uint16_t attribute_id,
38 bool with_attribute)
39{
40 if (!buf)
41 return 0;
42 size_t p = 0;
43 size_t s = write_segment(buf + p, cap - p, CIP_SEG_CLASS, class_id);
44 if (!s)
45 return 0;
46 p += s;
47 s = write_segment(buf + p, cap - p, CIP_SEG_INSTANCE, instance_id);
48 if (!s)
49 return 0;
50 p += s;
51 if (with_attribute)
52 {
53 s = write_segment(buf + p, cap - p, CIP_SEG_ATTRIBUTE, attribute_id);
54 if (!s)
55 return 0;
56 p += s;
57 }
58 return p;
59}
60
61size_t cip_build_request(uint8_t *buf, size_t cap, uint8_t service, const uint8_t *epath, size_t epath_len,
62 const uint8_t *data, size_t data_len)
63{
64 // EPATH must be whole 16-bit words and fit the 1-octet word count.
65 if (!buf || !epath || (epath_len & 1) || (epath_len / 2) > 0xFF || (data_len && !data))
66 return 0;
67 size_t total = 2 + epath_len + data_len; // service + path size + EPATH + data
68 if (total > cap)
69 return 0;
70 size_t p = 0;
71 buf[p++] = service;
72 buf[p++] = (uint8_t)(epath_len / 2); // path size in words
73 memcpy(buf + p, epath, epath_len);
74 p += epath_len;
75 if (data_len)
76 {
77 memcpy(buf + p, data, data_len);
78 p += data_len;
79 }
80 return p;
81}
82
83size_t cip_build_get_attr_single(uint8_t *buf, size_t cap, uint16_t class_id, uint16_t instance_id,
84 uint16_t attribute_id)
85{
86 uint8_t epath[12];
87 size_t elen = cip_build_epath(epath, sizeof(epath), class_id, instance_id, attribute_id, true);
88 if (!elen)
89 return 0; // GCOVR_EXCL_LINE unreachable: epath[12] holds the worst-case 3x4B logical segments, so build never
90 // fails
91 return cip_build_request(buf, cap, CIP_SC_GET_ATTR_SINGLE, epath, elen, nullptr, 0);
92}
93
94bool cip_parse_response(const uint8_t *buf, size_t len, CipResponse *out)
95{
96 if (!buf || !out || len < 4) // service + reserved + general status + additional-status size
97 return false;
98 out->service = buf[0];
99 out->general_status = buf[2];
100 uint8_t addl_words = buf[3];
101 size_t data_start = 4 + (size_t)addl_words * 2;
102 if (data_start > len)
103 return false;
104 out->data = buf + data_start;
105 out->data_len = len - data_start;
106 return true;
107}
108
109#endif // DETWS_ENABLE_CIP
CIP (Common Industrial Protocol) message codec (DETWS_ENABLE_CIP) - zero-heap request builder + respo...