ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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
10
11#if PC_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 {
24 return 0;
25 }
26 p[0] = (uint8_t)(CIP_SEG_LOGICAL | logical_type | CIP_SEG_8BIT);
27 p[1] = (uint8_t)id;
28 return 2;
29 }
30 if (cap < 4)
31 {
32 return 0;
33 }
34 p[0] = (uint8_t)(CIP_SEG_LOGICAL | logical_type | CIP_SEG_16BIT);
35 p[1] = 0x00; // pad to align the 16-bit value
36 p[2] = (uint8_t)(id & 0xFF);
37 p[3] = (uint8_t)(id >> 8);
38 return 4;
39}
40
41size_t pc_cip_build_epath(uint8_t *buf, size_t cap, uint16_t class_id, uint16_t instance_id, uint16_t attribute_id,
42 bool with_attribute)
43{
44 if (!buf)
45 {
46 return 0;
47 }
48 size_t p = 0;
49 size_t s = write_segment(buf + p, cap - p, CIP_SEG_CLASS, class_id);
50 if (!s)
51 {
52 return 0;
53 }
54 p += s;
55 s = write_segment(buf + p, cap - p, CIP_SEG_INSTANCE, instance_id);
56 if (!s)
57 {
58 return 0;
59 }
60 p += s;
61 if (with_attribute)
62 {
63 s = write_segment(buf + p, cap - p, CIP_SEG_ATTRIBUTE, attribute_id);
64 if (!s)
65 {
66 return 0;
67 }
68 p += s;
69 }
70 return p;
71}
72
73size_t pc_cip_build_request(uint8_t *buf, size_t cap, uint8_t service, const uint8_t *epath, size_t epath_len,
74 const uint8_t *data, size_t data_len)
75{
76 // EPATH must be whole 16-bit words and fit the 1-octet word count.
77 if (!buf || !epath || (epath_len & 1) || (epath_len / 2) > 0xFF || (data_len && !data))
78 {
79 return 0;
80 }
81 size_t total = 2 + epath_len + data_len; // service + path size + EPATH + data
82 if (total > cap)
83 {
84 return 0;
85 }
86 size_t p = 0;
87 buf[p++] = service;
88 buf[p++] = (uint8_t)(epath_len / 2); // path size in words
89 memcpy(buf + p, epath, epath_len);
90 p += epath_len;
91 if (data_len)
92 {
93 memcpy(buf + p, data, data_len);
94 p += data_len;
95 }
96 return p;
97}
98
99size_t pc_cip_build_get_attr_single(uint8_t *buf, size_t cap, uint16_t class_id, uint16_t instance_id,
100 uint16_t attribute_id)
101{
102 uint8_t epath[12];
103 size_t elen = pc_cip_build_epath(epath, sizeof(epath), class_id, instance_id, attribute_id, true);
104 // GCOVR_EXCL_START unreachable: epath[12] holds the worst-case 3x4B logical segments, so
105 // write_segment() inside pc_cip_build_epath() never fails and elen is never 0.
106 if (!elen)
107 {
108 return 0;
109 }
110 // GCOVR_EXCL_STOP
111 return pc_cip_build_request(buf, cap, CIP_SC_GET_ATTR_SINGLE, epath, elen, nullptr, 0);
112}
113
114size_t pc_cip_build_get_attr_all(uint8_t *buf, size_t cap, uint16_t class_id, uint16_t instance_id)
115{
116 uint8_t epath[8]; // class + instance logical segments only (no attribute), worst case 4B each
117 size_t elen = pc_cip_build_epath(epath, sizeof(epath), class_id, instance_id, 0, false);
118 // GCOVR_EXCL_START unreachable: epath[8] holds the worst-case 2x4B logical segments, so the epath build
119 // never overflows and elen is never 0.
120 if (!elen)
121 {
122 return 0;
123 }
124 // GCOVR_EXCL_STOP
125 return pc_cip_build_request(buf, cap, CIP_SC_GET_ATTR_ALL, epath, elen, nullptr, 0);
126}
127
128size_t pc_cip_build_set_attr_single(uint8_t *buf, size_t cap, uint16_t class_id, uint16_t instance_id,
129 uint16_t attribute_id, const uint8_t *value, size_t value_len)
130{
131 uint8_t epath[12];
132 size_t elen = pc_cip_build_epath(epath, sizeof(epath), class_id, instance_id, attribute_id, true);
133 // GCOVR_EXCL_START unreachable: epath[12] holds the worst-case 3x4B logical segments, so
134 // write_segment() inside pc_cip_build_epath() never fails and elen is never 0.
135 if (!elen)
136 {
137 return 0;
138 }
139 // GCOVR_EXCL_STOP
140 return pc_cip_build_request(buf, cap, CIP_SC_SET_ATTR_SINGLE, epath, elen, value, value_len);
141}
142
143bool pc_cip_parse_response(const uint8_t *buf, size_t len, CipResponse *out)
144{
145 if (!buf || !out || len < 4) // service + reserved + general status + additional-status size
146 {
147 return false;
148 }
149 out->service = buf[0];
150 out->general_status = buf[2];
151 uint8_t addl_words = buf[3];
152 size_t data_start = 4 + (size_t)addl_words * 2;
153 if (data_start > len)
154 {
155 return false;
156 }
157 out->data = buf + data_start;
158 out->data_len = len - data_start;
159 return true;
160}
161
162#endif // PC_ENABLE_CIP
CIP (Common Industrial Protocol) message codec (PC_ENABLE_CIP) - zero-heap request builder + response...