PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
rpc.cpp
Go to the documentation of this file.
1
9
10#include "profinet/rpc.h"
11
12#include <arpa/inet.h>
13#include <netdb.h>
14#include <netinet/in.h>
15#include <sys/socket.h>
16#include <sys/types.h>
17#include <unistd.h>
18
19#include <algorithm>
20#include <cstdio>
21#include <cstring>
22#include <iostream>
23#include <random>
24#include <ranges>
25#include <sstream>
26
27#include "profinet/cyclic.h"
28
29namespace profinet::rpc
30{
31// NOLINTBEGIN(readability-identifier-naming)
32// RPC blocks
34inline constexpr std::uint8_t VERSION_HIGH = 0x01;
36inline constexpr std::uint8_t VERSION_LOW = 0x00;
37// NOLINTEND(readability-identifier-naming)
38
39namespace
40{
41
42std::array<std::uint8_t, uuidLenght> RandomUuidBytes()
43{
44 static thread_local std::mt19937 rng{std::random_device{}()};
45 static constexpr int maxByteValue = 255;
46 static thread_local std::uniform_int_distribution<int> dist(0, maxByteValue);
47 std::array<std::uint8_t, uuidLenght> out{};
48 for (auto& b : out)
49 {
50 b = static_cast<std::uint8_t>(dist(rng));
51 }
52 return out;
53}
54
55std::uint16_t RandomNonzeroU16()
56{
57 static thread_local std::mt19937 rng{std::random_device{}()};
58
59 static constexpr int maxU16Value = 0xFFFF;
60 static thread_local std::uniform_int_distribution<int> dist(0, maxU16Value);
61 const int v = dist(rng);
62 return static_cast<std::uint16_t>(v == 0 ? 1 : v);
63}
64
65std::array<std::uint8_t, uuidLenght> BuildObjectUuid(std::uint8_t b10, std::uint8_t b11, std::uint8_t b12, std::uint8_t b13,
66 std::uint8_t b14, std::uint8_t b15)
67{
68 // OBJECT_UUID_PREFIX (10 bytes, per protocol.py): DE A0 00 00 + first 6
69 // bytes of the shared PROFINET UUID suffix (6C 97 11 D1 82 71).
70
71 // Protocol constant hex values for the header
72 static constexpr std::uint8_t pnPrefixByte0 = 0xDE;
73 static constexpr std::uint8_t pnPrefixByte1 = 0xA0;
74 static constexpr std::uint8_t pnPrefixByte2 = 0x00;
75 static constexpr std::uint8_t pnPrefixByte3 = 0x00;
76
77 // UUID field indices mapped to official UUID structure
78 static constexpr std::size_t uuidIdxTimeLow0 = 0;
79 static constexpr std::size_t uuidIdxTimeLow1 = 1;
80 static constexpr std::size_t uuidIdxTimeLow2 = 2;
81 static constexpr std::size_t uuidIdxTimeLow3 = 3;
82
83 static constexpr std::size_t uuidIdxTimeMid0 = 4;
84 static constexpr std::size_t suffixBytesToCopy = 6;
85
86 static constexpr std::size_t uuidIdxNode0 = 10;
87 static constexpr std::size_t uuidIdxNode1 = 11;
88 static constexpr std::size_t uuidIdxNode2 = 12;
89 static constexpr std::size_t uuidIdxNode3 = 13;
90 static constexpr std::size_t uuidIdxNode4 = 14;
91 static constexpr std::size_t uuidIdxNode5 = 15;
92
93 std::array<std::uint8_t, uuidLenght> objectUuid{};
94
95 // Write the fixed 4-byte PROFINET protocol prefix (into time_low)
96 objectUuid[uuidIdxTimeLow0] = pnPrefixByte0;
97 objectUuid[uuidIdxTimeLow1] = pnPrefixByte1;
98 objectUuid[uuidIdxTimeLow2] = pnPrefixByte2;
99 objectUuid[uuidIdxTimeLow3] = pnPrefixByte3;
100
101 // Copy the 6-byte shared suffix (populates time_mid, time_hi, and clock_seq)
102 std::ranges::copy(kPnUuidSuffix | std::views::take(suffixBytesToCopy),
103 (objectUuid | std::views::drop(uuidIdxTimeMid0)).begin());
104
105 // Populate the trailing 6 unique bytes (populates node ID)
106 objectUuid[uuidIdxNode0] = b10;
107 objectUuid[uuidIdxNode1] = b11;
108 objectUuid[uuidIdxNode2] = b12;
109 objectUuid[uuidIdxNode3] = b13;
110 objectUuid[uuidIdxNode4] = b14;
111 objectUuid[uuidIdxNode5] = b15;
112
113 return objectUuid;
114}
115
116} // namespace
117
118// =============================================================================
119// UUID helpers
120// =============================================================================
121
122std::string UuidBytesToString(const std::array<std::uint8_t, uuidLenght>& data)
123{
124 // Explicit indices for each byte position in the 16-byte UUID array
125 static constexpr std::size_t timeLow0 = 0;
126 static constexpr std::size_t timeLow1 = 1;
127 static constexpr std::size_t timeLow2 = 2;
128 static constexpr std::size_t timeLow3 = 3;
129
130 static constexpr std::size_t timeMid0 = 4;
131 static constexpr std::size_t timeMid1 = 5;
132
133 static constexpr std::size_t timeHi0 = 6;
134 static constexpr std::size_t timeHi1 = 7;
135
136 static constexpr std::size_t clockSeq0 = 8;
137 static constexpr std::size_t clockSeq1 = 9;
138
139 static constexpr std::size_t nodeDataStartIndex = 10;
140 static constexpr int nodeDataLength = 6;
141
142 // DCE/RPC UUIDs: first 3 fields little-endian, last 2 big-endian.
143 const std::uint32_t timeLow = static_cast<std::uint32_t>(data[timeLow0]) |
144 (static_cast<std::uint32_t>(data[timeLow1]) << OneOctetShift) |
145 (static_cast<std::uint32_t>(data[timeLow2]) << TwoOctetsShift) |
146 (static_cast<std::uint32_t>(data[timeLow3]) << ThreeOctetsShift);
147
148 auto timeMid = static_cast<std::uint16_t>(data[timeMid0] | (data[timeMid1] << OneOctetShift));
149 auto timeHi = static_cast<std::uint16_t>(data[timeHi0] | (data[timeHi1] << OneOctetShift));
150 auto clockSeq = static_cast<std::uint16_t>((data[clockSeq0] << OneOctetShift) | data[clockSeq1]);
151
152 // Format the first tracking segments and cleanly append the remaining hex data
153 const std::string out = std::format("{:08x}-{:04x}-{:04x}-{:04x}-{}",
154 timeLow, timeMid, timeHi, clockSeq,
155 ToHex(&data[nodeDataStartIndex], nodeDataLength));
156 return out;
157}
158
159std::array<std::uint8_t, uuidLenght> StringToUuidBytes(const std::string& uuidStr)
160{
161 // UUID Structural Constants
162 static constexpr std::size_t expectedHexLength = 32;
163 static constexpr int hexBase = 16;
164 static constexpr std::size_t singleByteHexLength = 2;
165
166 // Component Substring Offsets and Lengths
167 static constexpr std::size_t timeLowOffset = 0;
168 static constexpr std::size_t timeLowLength = 8;
169
170 static constexpr std::size_t timeMidOffset = 8;
171 static constexpr std::size_t timeMidLength = 4;
172
173 static constexpr std::size_t timeHiOffset = 12;
174 static constexpr std::size_t timeHiLength = 4;
175
176 static constexpr std::size_t clockSeqOffset = 16;
177 static constexpr std::size_t clockSeqLength = 4;
178
179 // Explicit indices for each byte position in the 16-byte UUID array
180 static constexpr std::size_t timeLow0 = 0;
181 static constexpr std::size_t timeLow1 = 1;
182 static constexpr std::size_t timeLow2 = 2;
183 static constexpr std::size_t timeLow3 = 3;
184
185 static constexpr std::size_t timeMid0 = 4;
186 static constexpr std::size_t timeMid1 = 5;
187
188 static constexpr std::size_t timeHi0 = 6;
189 static constexpr std::size_t timeHi1 = 7;
190
191 static constexpr std::size_t clockSeq0 = 8;
192 static constexpr std::size_t clockSeq1 = 9;
193
194 // Data Loop Constants
195 static constexpr int nodeDataLength = 6;
196 static constexpr std::size_t nodeDataStartHexOffset = 20;
197 static constexpr std::size_t nodeDataOutputStartIndex = 10;
198
199 std::string hex;
200 hex.reserve(expectedHexLength);
201 for (const char c : uuidStr)
202 {
203 if (c != '-')
204 {
205 hex.push_back(c);
206 }
207 }
208 if (hex.size() != expectedHexLength)
209 {
210 throw std::invalid_argument("Invalid UUID string: " + uuidStr);
211 }
212
213 // Helper lambda to parse a single 2-character hex pair into a byte
214 auto byteAt = [&](std::size_t stringIdx) -> std::uint8_t
215 {
216 return static_cast<std::uint8_t>(std::stoul(hex.substr(stringIdx, singleByteHexLength), nullptr, hexBase));
217 };
218
219 const std::uint32_t timeLow = static_cast<std::uint32_t>(std::stoul(hex.substr(timeLowOffset, timeLowLength), nullptr, hexBase));
220 const std::uint16_t timeMid = static_cast<std::uint16_t>(std::stoul(hex.substr(timeMidOffset, timeMidLength), nullptr, hexBase));
221 const std::uint16_t timeHi = static_cast<std::uint16_t>(std::stoul(hex.substr(timeHiOffset, timeHiLength), nullptr, hexBase));
222 const std::uint16_t clockSeq = static_cast<std::uint16_t>(std::stoul(hex.substr(clockSeqOffset, clockSeqLength), nullptr, hexBase));
223
224 std::array<std::uint8_t, uuidLenght> out{};
225 // Unpack fields into big-endian network byte order using your shifts and masks
226 out[timeLow0] = static_cast<std::uint8_t>(timeLow & LowByteMask);
227 out[timeLow1] = static_cast<std::uint8_t>((timeLow >> OneOctetShift) & LowByteMask);
228 out[timeLow2] = static_cast<std::uint8_t>((timeLow >> TwoOctetsShift) & LowByteMask);
229 out[timeLow3] = static_cast<std::uint8_t>((timeLow >> ThreeOctetsShift) & LowByteMask);
230 out[timeMid0] = static_cast<std::uint8_t>(timeMid & LowByteMask);
231 out[timeMid1] = static_cast<std::uint8_t>((timeMid >> OneOctetShift) & LowByteMask);
232 out[timeHi0] = static_cast<std::uint8_t>(timeHi & LowByteMask);
233 out[timeHi1] = static_cast<std::uint8_t>((timeHi >> OneOctetShift) & LowByteMask);
234 out[clockSeq0] = static_cast<std::uint8_t>((clockSeq >> OneOctetShift) & LowByteMask);
235 out[clockSeq1] = static_cast<std::uint8_t>(clockSeq & LowByteMask);
236
237 // Fill the remaining 6 bytes (Node ID segment)
238 for (std::size_t i = 0; i < nodeDataLength; ++i)
239 {
240 out.at(nodeDataOutputStartIndex + i) = byteAt(nodeDataStartHexOffset + (i * singleByteHexLength));
241 }
242 return out;
243}
244
245// =============================================================================
246// EPM
247// =============================================================================
248
249std::string EPMEndpoint::InterfaceName() const
250{
251 std::string lower = interfaceUuid;
252 std::ranges::transform(lower, lower.begin(), ::tolower);
253
254 if (lower == UUID_PNIO_DEVICE)
255 {
256 return "PNIO-Device";
257 }
258 if (lower == UUID_PNIO_CONTROLLER)
259 {
260 return "PNIO-Controller";
261 }
262 if (lower == "dea00003-6c97-11d1-8271-00a02442df7d")
263 {
264 return "PNIO-Supervisor";
265 }
266 if (lower == "dea00004-6c97-11d1-8271-00a02442df7d")
267 {
268 return "PNIO-ParameterServer";
269 }
270 if (lower == UUID_EPM_V4)
271 {
272 return "EPM";
273 }
274 return "Unknown(" + interfaceUuid + ")";
275}
276
277namespace
278{
279
280std::optional<EPMEndpoint> ParseEpmTower(const Bytes& towerData)
281{
282 if (towerData.size() < 4)
283 {
284 return std::nullopt;
285 }
286
287 std::size_t offset = 0;
288 auto floorCount = static_cast<std::uint16_t>(towerData[0] | (towerData[1] << OneOctetShift));
289
290 offset += 2;
291
292 EPMEndpoint endpoint;
293
294 for (std::uint16_t floorIdx = 0; floorIdx < floorCount; ++floorIdx)
295 {
296 if (offset + 4 > towerData.size())
297 {
298 break;
299 }
300
301 auto lhsLen = static_cast<std::uint16_t>(towerData[offset] | (towerData[offset + 1] << OneOctetShift));
302 offset += 2;
303 if (offset + lhsLen > towerData.size())
304 {
305 break;
306 }
307 const std::uint8_t* lhsData = &towerData[offset];
308 offset += lhsLen;
309
310 if (offset + 2 > towerData.size())
311 {
312 break;
313 }
314 auto rhsLen = static_cast<std::uint16_t>(towerData[offset] | (towerData[offset + 1] << OneOctetShift));
315 offset += 2;
316 if (offset + rhsLen > towerData.size())
317 {
318 break;
319 }
320 const std::uint8_t* rhsData = &towerData[offset];
321 offset += rhsLen;
322
323 if (lhsLen < 1)
324 {
325 continue;
326 }
327 const std::uint8_t protocolId = lhsData[0];
328
329 if (protocolId == 0x0D && lhsLen >= 19)
330 {
331 if (floorIdx == 0)
332 {
333 std::array<std::uint8_t, uuidLenght> uuidBytes{};
334 std::memcpy(uuidBytes.data(), lhsData + 1, uuidLenght);
335 endpoint.interfaceUuid = UuidBytesToString(uuidBytes);
336 endpoint.interfaceVersionMajor = static_cast<std::uint16_t>(lhsData[17] | (lhsData[18] << OneOctetShift));
337 if (rhsLen >= 2)
338 {
339 endpoint.interfaceVersionMinor = static_cast<std::uint16_t>(rhsData[0] | (rhsData[1] << OneOctetShift));
340 }
341 }
342 }
343 else if (protocolId == 0x0A)
344 {
345 endpoint.protocol = "ncadg_ip_udp";
346 }
347 else if (protocolId == 0x08 && rhsLen >= 2)
348 {
349 endpoint.port = static_cast<std::uint16_t>((rhsData[0] << OneOctetShift) | rhsData[1]); // big-endian
350 }
351 else if (protocolId == 0x09 && rhsLen >= 4)
352 {
353 endpoint.ipAddress = std::to_string(rhsData[0]) + "." + std::to_string(rhsData[1]) + "." +
354 std::to_string(rhsData[2]) + "." + std::to_string(rhsData[3]);
355 }
356 }
357
358 if (endpoint.interfaceUuid.empty())
359 {
360 return std::nullopt;
361 }
362 return endpoint;
363}
364
365std::uint32_t ReadU32Le(const std::uint8_t* p)
366{
367 return static_cast<std::uint32_t>(p[0]) | (static_cast<std::uint32_t>(p[1]) << OneOctetShift) |
368 (static_cast<std::uint32_t>(p[2]) << TwoOctetsShift) | (static_cast<std::uint32_t>(p[3]) << ThreeOctetsShift);
369}
370std::uint16_t ReadU16Le(const std::uint8_t* p)
371{
372 return static_cast<std::uint16_t>(p[0] | (p[1] << OneOctetShift));
373}
374void WriteU16Le(Bytes& out, std::uint16_t v)
375{
376 out.push_back(static_cast<std::uint8_t>(v & LowByteMask));
377 out.push_back(static_cast<std::uint8_t>(v >> OneOctetShift));
378}
379void WriteU32Le(Bytes& out, std::uint32_t v)
380{
381 out.push_back(static_cast<std::uint8_t>(v & LowByteMask));
382 out.push_back(static_cast<std::uint8_t>((v >> OneOctetShift) & LowByteMask));
383 out.push_back(static_cast<std::uint8_t>((v >> TwoOctetsShift) & LowByteMask));
384 out.push_back(static_cast<std::uint8_t>((v >> ThreeOctetsShift) & LowByteMask));
385}
386
387} // namespace
388
389std::vector<EPMEndpoint> EpmLookup(asio::io_context& ioContext, const std::string& ip, std::uint16_t port, double timeoutSec,
390 std::optional<std::string> interfaceFilter)
391{
392 std::vector<EPMEndpoint> results;
393
394 // const int fd = ::socket(AF_INET, SOCK_DGRAM, 0);
395 // if (fd < 0)
396 // {
397 // return results;
398 // }
399
400 // timeval tv{};
401 // tv.tv_sec = static_cast<time_t>(timeoutSec);
402 // static constexpr int microsecondsPerSecond = 1'000'000;
403 // tv.tv_usec = static_cast<suseconds_t>((timeoutSec - static_cast<double>(tv.tv_sec)) * microsecondsPerSecond);
404 // ::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
405
406 // sockaddr_in addr{};
407 // addr.sin_family = AF_INET;
408 // addr.sin_port = htons(port);
409 // if (::inet_pton(AF_INET, ip.c_str(), &addr.sin_addr) != 1)
410 // {
411 // ::close(fd);
412 // return results;
413 // }
414 RpcTransport transport(ioContext);
415
416 transport.Open(ip, port);
417
418 // Build little-endian RPC header (kFixedSize, 80 bytes).
419 auto activityUuidArr = RandomUuidBytes();
420 Bytes activityUuidBytes(activityUuidArr.begin(), activityUuidArr.end());
421 auto objectUuidBytes = std::array<std::uint8_t, uuidLenght>{}; // NULL object UUID
422 auto interfaceUuidBytes = StringToUuidBytes(UUID_EPM_V4);
423
424 Bytes header;
425 header.push_back(4); // version
426 header.push_back(PNRPCHeader::REQUEST); // packet_type = REQUEST, 0x00
427 header.push_back(0x20); // flags1
428 header.push_back(0x00); // flags2
429 header.push_back(0x10);
430 header.push_back(0x00);
431 header.push_back(0x00); // drep (little-endian)
432 header.push_back(0); // serial_high
433 header.insert(header.end(), objectUuidBytes.begin(), objectUuidBytes.end());
434 header.insert(header.end(), interfaceUuidBytes.begin(), interfaceUuidBytes.end());
435 header.insert(header.end(), activityUuidBytes.begin(), activityUuidBytes.end());
436 WriteU32Le(header, 0); // server_boot_time
437 WriteU32Le(header, 3); // interface_version
438 WriteU32Le(header, 0); // sequence_number
439 WriteU16Le(header, EPM_LOOKUP);
440 WriteU16Le(header, 0xFFFF); // interface_hint
441 WriteU16Le(header, 0xFFFF); // activity_hint
442 const std::size_t lengthOfBodyOffset = header.size();
443 WriteU16Le(header, 0); // length_of_body placeholder, patched below
444 WriteU16Le(header, 0); // fragment_number
445 header.push_back(0); // authentication_protocol
446 header.push_back(0); // serial_low
447
448 const std::uint32_t inquiryType = interfaceFilter ? EPM_INQUIRY_INTERFACE : EPM_INQUIRY_ALL;
449 Bytes body;
450 WriteU32Le(body, inquiryType);
451 if (interfaceFilter)
452 {
453 auto ifaceBytes = StringToUuidBytes(*interfaceFilter);
454 body.insert(body.end(), ifaceBytes.begin(), ifaceBytes.end());
455 WriteU16Le(body, 1);
456 WriteU16Le(body, 0);
457 }
458 else
459 {
460 body.insert(body.end(), 16, 0);
461 WriteU16Le(body, 0);
462 WriteU16Le(body, 0);
463 }
464 WriteU32Le(body, 0); // vers option
465 WriteU32Le(body, 0); // entry handle
466 WriteU32Le(body, 100); // max ents
467
468 header[lengthOfBodyOffset] = static_cast<std::uint8_t>(body.size() & LowByteMask);
469 header[lengthOfBodyOffset + 1] = static_cast<std::uint8_t>((body.size() >> OneOctetShift) & LowByteMask);
470 // header was built with a 2-byte placeholder at lengthOfBodyOffset and
471 // is already exactly 80 bytes (the fixed RPC header size); no resize needed.
472
473 Bytes request = header;
474 request.insert(request.end(), body.begin(), body.end());
475
476 // if (::sendto(fd, request.data(), request.size(), 0, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0)
477 // {
478 // ::close(fd);
479 // return results;
480 // }
481 auto timeoutInMilliSeconds = std::chrono::floor<std::chrono::milliseconds>(std::chrono::duration<double>(timeoutSec));
482 Bytes data =
483 transport.SendReceive(
484 request,
485 timeoutInMilliSeconds);
486
487 // Bytes data(RECEIVE_BUFFER_LENGTH);
488 // const ssize_t n = ::recv(fd, data.data(), data.size(), 0);
489 // ::close(fd);
490 // if (n <= 0)
491 // {
492 // return results;
493 // }
494
495 if (static_cast<std::size_t>(data.size()) < PNRPCHeader::kFixedSize)
496 {
497 return results;
498 }
499 // data.resize(static_cast<std::size_t>(n));
500
501 const std::uint8_t respType = data[1];
502 if (respType == 0x03 || respType != 0x02)
503 {
504 return results; // FAULT or not RESPONSE
505 }
506
507 std::uint16_t bodyLen = ReadU16Le(&data[74]);
508 if (PNRPCHeader::kFixedSize + static_cast<std::size_t>(bodyLen) > data.size())
509 {
510 bodyLen = static_cast<std::uint16_t>(data.size() - PNRPCHeader::kFixedSize);
511 }
512 Bytes bodyData(data.begin() + PNRPCHeader::kFixedSize, data.begin() + PNRPCHeader::kFixedSize + bodyLen);
513 if (bodyData.size() < 12)
514 {
515 return results;
516 }
517
518 std::size_t offset = 4; // skip entry_handle
519 const std::uint32_t numEnts = ReadU32Le(&bodyData[offset]);
520 offset += 4;
521 offset += 12; // skip array metadata (max_count, offset, actual_count)
522
523 for (std::uint32_t i = 0; i < numEnts; ++i)
524 {
525 if (offset + uuidLenght > bodyData.size())
526 {
527 break;
528 }
529 std::array<std::uint8_t, uuidLenght> entryUuidBytes{};
530 std::memcpy(entryUuidBytes.data(), &bodyData[offset], uuidLenght);
531 const std::string entryObjectUuid = UuidBytesToString(entryUuidBytes);
532 offset += uuidLenght;
533
534 if (offset + 4 > bodyData.size())
535 {
536 break;
537 }
538 offset += 4; // tower_p pointer
539
540 if (offset + 4 > bodyData.size())
541 {
542 break;
543 }
544 const std::uint32_t annotationLen = ReadU32Le(&bodyData[offset]);
545 offset += 4;
546
547 std::string annotation;
548 if (annotationLen > 0 && offset + annotationLen <= bodyData.size())
549 {
550 const std::size_t end = offset + annotationLen;
551 std::size_t trim = end;
552 while (trim > offset && bodyData[trim - 1] == 0)
553 {
554 --trim;
555 }
556 annotation = std::string(bodyData.begin() + offset, bodyData.begin() + trim);
557 }
558 offset += annotationLen;
559 offset = (offset + 3) & ~static_cast<std::size_t>(3);
560
561 if (offset + 4 > bodyData.size())
562 {
563 break;
564 }
565 const std::uint32_t towerLen = ReadU32Le(&bodyData[offset]);
566 offset += 4;
567
568 if (offset + towerLen > bodyData.size())
569 {
570 break;
571 }
572 const Bytes towerData(bodyData.begin() + offset, bodyData.begin() + offset + towerLen);
573 offset += towerLen;
574 offset = (offset + 3) & ~static_cast<std::size_t>(3);
575
576 auto endpoint = ParseEpmTower(towerData);
577 if (endpoint)
578 {
579 endpoint->objectUuid = entryObjectUuid;
580 endpoint->annotation = annotation;
581 results.push_back(*endpoint);
582 }
583 }
584
585 return results;
586}
587
589 const MacAddress& src,
590 const std::string& name,
591 int timeoutSec)
592{
594 Bytes(name.begin(), name.end()));
595 auto responses = dcp::ReadResponse(sock, src, timeoutSec, /*once=*/true);
596
597 if (responses.empty())
598 {
599 dcp::SendDiscover(sock, src);
600 responses = dcp::ReadResponse(sock, src, timeoutSec);
601 for (const auto& [Mac, Blocks] : responses)
602 {
603 dcp::DCPDeviceDescription desc(Mac, Blocks);
604 std::string lowerDescName = desc.name;
605 std::string lowerTarget = name;
606 std::ranges::transform(lowerDescName, lowerDescName.begin(), ::tolower);
607 std::ranges::transform(lowerTarget, lowerTarget.begin(), ::tolower);
608 if (lowerDescName == lowerTarget)
609 {
610 return desc;
611 }
612 }
613 throw DCPDeviceNotFoundError("Device with name '" + name + "' not found");
614 }
615
616 const auto& [Mac, Blocks] = *responses.begin();
617 // NOLINTNEXTLINE(modernize-return-braced-init-list)
618 return dcp::DCPDeviceDescription(Mac, Blocks);
619}
620
621// =============================================================================
622// IOCRSetup
623// =============================================================================
624
625std::vector<std::string> IOCRSetup::Validate() const
626{
627 std::vector<std::string> warnings;
628 const double cycleMs = CycleTimeMs();
629 if (cycleMs < 1)
630 {
631 warnings.emplace_back("Cycle time too fast - sub-1ms is impractical without hardware support");
632 }
633 else if (cycleMs < cyclic::MIN_CYCLE_MS)
634 {
635 warnings.emplace_back("Cycle time may cause jitter (8ms+ recommended)");
636 }
637 if (watchdogFactor < 3)
638 {
639 warnings.emplace_back("Watchdog factor too low (use 6+)");
640 }
641 if (slots.empty())
642 {
643 warnings.emplace_back("No IO slots configured");
644 }
645 return warnings;
646}
647
648// =============================================================================
649// RPCCon
650// =============================================================================
651
653 : info(info),
654 timeout(options.timeoutSec),
655 sessionKey(RandomNonzeroU16()),
656 rpcTransport{std::make_unique<RpcTransport>(ioContext)},
657 ccontrolTransport{std::make_unique<RpcTransport>(ioContext)}
658{
659 arUuid = RandomUuidBytes();
660 activityUuid = RandomUuidBytes();
661
662 localObjectUuid = BuildObjectUuid(0x00, 0x01, 0x76, 0x54, 0x32, 0x10);
664 BuildObjectUuid(0x00, 0x01, info.deviceHigh, info.deviceLow, info.vendorHigh, info.vendorLow);
665
666 rpcTransport->Open(info.ip, options.rpcPort);
667 std::string anyIp = asio::ip::address_v4(asio::detail::socket_ops::network_to_host_long(INADDR_ANY)).to_string();
668 ccontrolTransport->Open(anyIp, options.rpcPort, true);
669}
670
672{
673 Close();
674}
675
676PNRPCHeader RPCCon::CreateRpc(std::uint16_t operation, const Bytes& nrd)
677{
678 PNRPCHeader h;
679 h.version = 4;
681 h.flags1 = 0x20;
682 h.flags2 = 0x00;
683 h.dataRepresentation = {0x00, 0x00, 0x00};
684 h.serialNrHigh = 0;
688 h.serverBootTime = 0;
689 h.interfaceVersion = 1;
691 h.operationNumber = operation;
692 h.interfaceHint = 0xFFFF;
693 h.activityHint = 0xFFFF;
694 h.lengthOfBody = static_cast<std::uint16_t>(nrd.size());
695 h.fragmentNumber = 0;
697 h.serialNrLow = 0;
698 h.payload = nrd;
699 return h;
700}
701
703{
704 PNNRDData nrd;
705 nrd.argsMaximumStatus = 1500;
706 nrd.argsLength = static_cast<std::uint32_t>(payload.size());
707 nrd.maximumCount = 1500;
708 nrd.offset = 0;
709 nrd.actualCount = static_cast<std::uint32_t>(payload.size());
710 nrd.payload = payload;
711 return nrd;
712}
713
714Bytes RPCCon::BuildAlarmCrBlock(int transport, int priority) const
715{
716 auto properties = static_cast<std::uint32_t>((transport << 1) | priority);
717
718 PNBlockHeader header;
720 header.blockLength = static_cast<std::uint16_t>(PNAlarmCRBlockReq::kSize - 4);
723
724 PNAlarmCRBlockReq alarmCr;
725 std::memcpy(alarmCr.blockHeader.data(), header.ToBytes().data(), blockHeaderLenght);
726 alarmCr.alarmCrType = 0x0001;
727 alarmCr.etherTypeLT = transport == 0 ? PROFINET_ETHERTYPE : IP_ETHERTYPE;
728 alarmCr.alarmCrProperties = properties;
735
736 return alarmCr.ToBytes();
737}
738
739int RPCCon::ParseAlarmCrResponse(const Bytes& responseData)
740{
741 std::size_t offset = 0;
742 while (offset + blockHeaderLenght <= responseData.size())
743 {
744 auto hdr = PeekBlockHeader(responseData, offset);
745 if (hdr.blockType == PNAlarmCRBlockRes::BLOCK_TYPE)
746 {
747 const Bytes blockBytes(responseData.begin() + offset, responseData.end());
748 try
749 {
750 auto res = PNAlarmCRBlockRes::Parse(blockBytes);
751 return res.localAlarmReference;
752 }
753 // NOLINTNEXTLINE(bugprone-empty-catch)
754 catch (const std::exception&)
755 {
756 // fall through and keep scanning
757 }
758 }
759 offset += 4 + hdr.blockLength;
760 }
761 return -1;
762}
763/*
764dit zou ook moeten werken... zie ff niet waarom niet voor de auma,,,
765
766Bytes RPCCon::BuildIocrBlock(int iocrType, int iocrReference, const IOCRSetup& setup)
767{
768 Bytes objectsData;
769 std::uint16_t frameOffset = 0;
770
771 for (const auto& slot : setup.slots)
772 {
773 const std::uint16_t dataLen = iocrType == 1 ? slot.inputLength : slot.outputLength;
774 if (dataLen > 0)
775 {
776 IOCRAPIObject obj;
777 obj.slotNumber = slot.slot;
778 obj.subslotNumber = slot.subslot;
779 obj.frameOffset = frameOffset;
780 Bytes objBytes = obj.ToBytes();
781 objectsData.insert(objectsData.end(), objBytes.begin(), objBytes.end());
782
783 frameOffset = static_cast<std::uint16_t>(frameOffset + dataLen + 1);
784 }
785 }
786
787 Bytes iocsData;
788 std::uint16_t iocsCount = 0;
789 for (const auto& slot : setup.slots)
790 {
791 if (iocrType == 1 && slot.inputLength > 0)
792 {
793 continue;
794 }
795 if (iocrType == 2 && slot.outputLength > 0)
796 {
797 continue;
798 }
799 IOCRAPIObject iocsObj;
800 iocsObj.slotNumber = slot.slot;
801 iocsObj.subslotNumber = slot.subslot;
802 iocsObj.frameOffset = frameOffset;
803 Bytes objBytes = iocsObj.ToBytes();
804 iocsData.insert(iocsData.end(), objBytes.begin(), objBytes.end());
805 frameOffset = static_cast<std::uint16_t>(frameOffset + 1);
806 ++iocsCount;
807 }
808
809 const std::uint16_t dataLength = std::max<std::uint16_t>(40, frameOffset);
810 const std::uint32_t iocrProperties = 0x00000001; // RT_CLASS_1
811
812 std::uint16_t numObjects = 0;
813 for (const auto& slot : setup.slots)
814 {
815 if ((iocrType == 1 && slot.inputLength > 0) || (iocrType == 2 && slot.outputLength > 0))
816 {
817 ++numObjects;
818 }
819 }
820
821 Bytes apiBlock;
822 wire::PutU32(apiBlock, 0); // api
823 wire::PutU16(apiBlock, numObjects);
824 apiBlock.insert(apiBlock.end(), objectsData.begin(), objectsData.end());
825 wire::PutU16(apiBlock, iocsCount);
826 apiBlock.insert(apiBlock.end(), iocsData.begin(), iocsData.end());
827
828 const std::size_t totalSize = PNIOCRBlockReqHeader::kSize + apiBlock.size();
829 auto blockLength = static_cast<std::uint16_t>(totalSize - 4);
830
831 PNBlockHeader header;
832 header.blockType = PNIOCRBlockReqHeader::BLOCK_TYPE;
833 header.blockLength = blockLength;
834 header.blockVersionHigh = VERSION_HIGH;
835 header.blockVersionLow = VERSION_LOW;
836
837 PNIOCRBlockReqHeader iocrHeader;
838 std::memcpy(iocrHeader.blockHeader.data(), header.ToBytes().data(), blockHeaderLenght);
839 iocrHeader.iocrType = static_cast<std::uint16_t>(iocrType);
840 iocrHeader.iocrReference = static_cast<std::uint16_t>(iocrReference);
841 iocrHeader.etherTypeLT = PROFINET_ETHERTYPE;
842 iocrHeader.iocrProperties = iocrProperties;
843 iocrHeader.dataLength = dataLength;
844 iocrHeader.frameId = static_cast<std::uint16_t>((iocrType == 1 ? 0xC000 : 0xC000) + iocrReference);
845 iocrHeader.sendClockFactor = setup.sendClockFactor;
846 iocrHeader.reductionRatio = setup.reductionRatio;
847 // iocrHeader.phase = 1;
848 iocrHeader.phase = 8;
849 iocrHeader.sequence = 0;
850 iocrHeader.frameSendOffset = 0xFFFFFFFF;
851 iocrHeader.watchdogFactor = setup.watchdogFactor;
852 iocrHeader.dataHoldFactor = setup.dataHoldFactor;
853 iocrHeader.iocrTagHeader = 0xC000;
854 iocrHeader.iocrMulticastMac = {};
855 iocrHeader.numberOfApis = 1;
856
857 Bytes out = iocrHeader.ToBytes();
858 out.insert(out.end(), apiBlock.begin(), apiBlock.end());
859 return out;
860}
861*/
862
863/* Dit werkt! maar is volgens mij fragiel en incorrect
864Bytes RPCCon::BuildIocrBlock(int iocrType,
865 int iocrReference,
866 const IOCRSetup& setup)
867{
868 // ------------------------------------------------------------
869 // Build IO Data Objects
870 // ------------------------------------------------------------
871
872 Bytes objectsData;
873
874 int frameOffset = -1;
875 std::uint16_t numObjects = 0;
876
877 for (const auto& slot : setup.slots)
878 {
879 ++frameOffset;
880
881 @*
882 * Python:
883 *
884 * if iocr_type == 2 and slot.output_length == 0:
885 * continue
886 * elif iocr_type == 1 and slot.output_length > 0:
887 * continue
888 *
889 * if iocr_type == 1:
890 * ...
891 * else:
892 * continue
893 *@
894
895 if (iocrType == 2 && slot.outputLength == 0)
896 {
897 continue;
898 }
899 else if (iocrType == 1 && slot.outputLength > 0)
900 {
901 continue;
902 }
903
904 // if (iocrType == 1)
905 //{
906 IOCRAPIObject obj;
907 obj.slotNumber = slot.slot;
908 obj.subslotNumber = slot.subslot;
909 obj.frameOffset = static_cast<std::uint16_t>(frameOffset);
910
911 Bytes objBytes = obj.ToBytes();
912
913 objectsData.insert(objectsData.end(),
914 objBytes.begin(),
915 objBytes.end());
916
917 ++numObjects;
918 //}
919 // else
920 //{
921 // // Exact Python behaviour.
922 // continue;
923 //}
924 }
925
926 // ------------------------------------------------------------
927 // Build IOCS Objects
928 // ------------------------------------------------------------
929
930 Bytes iocsData;
931
932 frameOffset = -1;
933 std::uint16_t iocsCount = 0;
934
935 for (const auto& slot : setup.slots)
936 {
937 ++frameOffset;
938
939 @*
940 * Python:
941 *
942 * if iocr_type == 1 and slot.output_length == 0:
943 * continue
944 *
945 * if iocr_type == 2 and slot.output_length > 0:
946 * continue
947 *
948
949 if (iocrType == 1 && slot.outputLength == 0)
950 {
951 continue;
952 }
953
954 if (iocrType == 2 && slot.outputLength > 0)
955 {
956 continue;
957 }
958
959 IOCRAPIObject iocsObj;
960
961 iocsObj.slotNumber = slot.slot;
962 iocsObj.subslotNumber = slot.subslot;
963
964 if (iocrType == 1)
965 {
966 // Python hard-coded value.
967 iocsObj.frameOffset = 42;
968 }
969 else
970 {
971 // Python uses slot index.
972 iocsObj.frameOffset =
973 static_cast<std::uint16_t>(frameOffset);
974 }
975
976 Bytes objBytes = iocsObj.ToBytes();
977
978 iocsData.insert(iocsData.end(),
979 objBytes.begin(),
980 objBytes.end());
981
982 ++iocsCount;
983 }
984
985
986 // ------------------------------------------------------------
987 // Data length
988 // ------------------------------------------------------------
989
990 @*
991 * Python:
992 *
993 * data_length = max(42, frame_offset) + 1
994 *@
995
996 const std::uint16_t dataLength =
997 static_cast<std::uint16_t>(
998 std::max(42, frameOffset) + 1);
999
1000 const std::uint32_t iocrProperties = 0x00000001;
1001
1002 // ------------------------------------------------------------
1003 // Build API block
1004 // ------------------------------------------------------------
1005
1006 Bytes apiBlock;
1007
1008 wire::PutU32(apiBlock, 0); // API
1009 wire::PutU16(apiBlock, numObjects);
1010
1011 apiBlock.insert(apiBlock.end(),
1012 objectsData.begin(),
1013 objectsData.end());
1014
1015 wire::PutU16(apiBlock, iocsCount);
1016
1017 apiBlock.insert(apiBlock.end(),
1018 iocsData.begin(),
1019 iocsData.end());
1020
1021 // ------------------------------------------------------------
1022 // Block length
1023 // ------------------------------------------------------------
1024
1025 const std::size_t totalSize =
1026 PNIOCRBlockReqHeader::kSize + apiBlock.size();
1027
1028 const std::uint16_t blockLength =
1029 static_cast<std::uint16_t>(totalSize - 4);
1030
1031 PNBlockHeader blockHeader;
1032
1033 blockHeader.blockType = PNIOCRBlockReqHeader::BLOCK_TYPE;
1034 blockHeader.blockLength = blockLength;
1035 blockHeader.blockVersionHigh = VERSION_HIGH;
1036 blockHeader.blockVersionLow = VERSION_LOW;
1037
1038 // ------------------------------------------------------------
1039 // Build IOCR header
1040 // ------------------------------------------------------------
1041
1042 PNIOCRBlockReqHeader iocrHeader;
1043
1044 const Bytes headerBytes = blockHeader.ToBytes();
1045
1046 std::copy(headerBytes.begin(),
1047 headerBytes.end(),
1048 iocrHeader.blockHeader.begin());
1049
1050 iocrHeader.iocrType =
1051 static_cast<std::uint16_t>(iocrType);
1052
1053 iocrHeader.iocrReference =
1054 static_cast<std::uint16_t>(iocrReference);
1055
1056 iocrHeader.etherTypeLT =
1057 PROFINET_ETHERTYPE;
1058
1059 iocrHeader.iocrProperties =
1060 iocrProperties;
1061
1062 iocrHeader.dataLength =
1063 dataLength;
1064
1065 @*
1066 * Python:
1067 *
1068 * frame_id = 0xC000 + iocr_reference - 1
1069 *@
1070
1071 iocrHeader.frameId =
1072 static_cast<std::uint16_t>(
1073 0xC000 + iocrReference - 1);
1074
1075 iocrHeader.sendClockFactor =
1076 setup.sendClockFactor;
1077
1078 iocrHeader.reductionRatio =
1079 setup.reductionRatio;
1080
1081 iocrHeader.phase = 8;
1082
1083 iocrHeader.sequence = 0;
1084
1085 iocrHeader.frameSendOffset =
1086 0xFFFFFFFF;
1087
1088 iocrHeader.watchdogFactor =
1089 setup.watchdogFactor;
1090
1091 iocrHeader.dataHoldFactor =
1092 setup.dataHoldFactor;
1093
1094 iocrHeader.iocrTagHeader =
1095 0xC000;
1096
1097 iocrHeader.iocrMulticastMac = {};
1098
1099 iocrHeader.numberOfApis = 1;
1100
1101 // ------------------------------------------------------------
1102 // Serialize
1103 // ------------------------------------------------------------
1104
1105 Bytes out = iocrHeader.ToBytes();
1106
1107 out.insert(out.end(),
1108 apiBlock.begin(),
1109 apiBlock.end());
1110 std::string msg = ToHex(out);
1111 return out;
1112}
1113*/
1114/* troep!
1115Bytes RPCCon::BuildIocrBlock(int iocrType,
1116 int iocrReference,
1117 const IOCRSetup& setup)
1118{
1119 Bytes objectsData;
1120 Bytes iocsData;
1121
1122 std::uint16_t frameOffset = 0;
1123 std::uint16_t numObjects = 0;
1124 std::uint16_t iocsCount = 0;
1125
1126 // -------------------------------------------------------------------------
1127 // 1. Build IO Data Objects & IOCS Objects
1128 // -------------------------------------------------------------------------
1129 for (const auto& slot : setup.slots)
1130 {
1131 // OUTPUT CR (0x0001): Controller Output -> Device Input
1132 if (iocrType == 1)
1133 {
1134 if (slot.outputLength > 0)
1135 {
1136 // Add Output Data Object
1137 IOCRAPIObject obj;
1138 obj.slotNumber = slot.slot;
1139 obj.subslotNumber = slot.subslot;
1140 obj.frameOffset = frameOffset;
1141
1142 Bytes objBytes = obj.ToBytes();
1143 objectsData.insert(objectsData.end(), objBytes.begin(), objBytes.end());
1144
1145 // Offset advances by Data Length + 1 Byte IOPS (Provider Status)
1146 frameOffset += static_cast<std::uint16_t>(slot.outputLength + 1);
1147 ++numObjects;
1148 }
1149
1150 // Consumer Status (IOCS) returned for Input subslots
1151 if (slot.inputLength > 0)
1152 {
1153 IOCRAPIObject iocsObj;
1154 iocsObj.slotNumber = slot.slot;
1155 iocsObj.subslotNumber = slot.subslot;
1156 iocsObj.frameOffset = iocsCount; // 1-byte IOCS per subslot
1157
1158 Bytes iocsBytes = iocsObj.ToBytes();
1159 iocsData.insert(iocsData.end(), iocsBytes.begin(), iocsBytes.end());
1160 ++iocsCount;
1161 }
1162 }
1163 // INPUT CR (0x0002): Device Output -> Controller Input
1164 else if (iocrType == 2)
1165 {
1166 if (slot.inputLength > 0)
1167 {
1168 // Add Input Data Object
1169 IOCRAPIObject obj;
1170 obj.slotNumber = slot.slot;
1171 obj.subslotNumber = slot.subslot;
1172 obj.frameOffset = frameOffset;
1173
1174 Bytes objBytes = obj.ToBytes();
1175 objectsData.insert(objectsData.end(), objBytes.begin(), objBytes.end());
1176
1177 // Offset advances by Data Length + 1 Byte IOPS (Provider Status)
1178 frameOffset += static_cast<std::uint16_t>(slot.inputLength + 1);
1179 ++numObjects;
1180 }
1181
1182 // Consumer Status (IOCS) returned for Output subslots
1183 if (slot.outputLength > 0)
1184 {
1185 IOCRAPIObject iocsObj;
1186 iocsObj.slotNumber = slot.slot;
1187 iocsObj.subslotNumber = slot.subslot;
1188 iocsObj.frameOffset = iocsCount; // 1-byte IOCS per subslot
1189
1190 Bytes iocsBytes = iocsObj.ToBytes();
1191 iocsData.insert(iocsData.end(), iocsBytes.begin(), iocsBytes.end());
1192 ++iocsCount;
1193 }
1194 }
1195 }
1196
1197 // -------------------------------------------------------------------------
1198 // 2. Data Length & Minimum Frame Padding (40-byte Ethernet minimum)
1199 // -------------------------------------------------------------------------
1200 // Total data length = IO Data + IOPS + IOCS
1201 std::uint16_t calculatedLength = frameOffset + iocsCount;
1202
1203 // PROFINET RT frame payload must be at least 40 bytes
1204 const std::uint16_t dataLength = std::max<std::uint16_t>(40, calculatedLength);
1205
1206 // -------------------------------------------------------------------------
1207 // 3. Assemble API Block Header & Data
1208 // -------------------------------------------------------------------------
1209 Bytes apiBlock;
1210 wire::PutU32(apiBlock, 0); // API = 0
1211
1212 wire::PutU16(apiBlock, numObjects);
1213 apiBlock.insert(apiBlock.end(), objectsData.begin(), objectsData.end());
1214
1215 wire::PutU16(apiBlock, iocsCount);
1216 apiBlock.insert(apiBlock.end(), iocsData.begin(), iocsData.end());
1217
1218 // -------------------------------------------------------------------------
1219 // 4. Assemble Main IOCR Header
1220 // -------------------------------------------------------------------------
1221 const std::size_t totalSize = PNIOCRBlockReqHeader::kSize + apiBlock.size();
1222 const std::uint16_t blockLength = static_cast<std::uint16_t>(totalSize - 4);
1223
1224 PNBlockHeader blockHeader;
1225 blockHeader.blockType = PNIOCRBlockReqHeader::BLOCK_TYPE; // 0x0102
1226 blockHeader.blockLength = blockLength;
1227 blockHeader.blockVersionHigh = VERSION_HIGH;
1228 blockHeader.blockVersionLow = VERSION_LOW;
1229
1230 PNIOCRBlockReqHeader iocrHeader;
1231 const Bytes headerBytes = blockHeader.ToBytes();
1232 std::copy(headerBytes.begin(), headerBytes.end(), iocrHeader.blockHeader.begin());
1233
1234 iocrHeader.iocrType = static_cast<std::uint16_t>(iocrType);
1235 iocrHeader.iocrReference = static_cast<std::uint16_t>(iocrReference);
1236 iocrHeader.etherTypeLT = PROFINET_ETHERTYPE;
1237 iocrHeader.iocrProperties = 0x00000001; // RT_CLASS_1
1238 iocrHeader.dataLength = dataLength;
1239 iocrHeader.frameId = static_cast<std::uint16_t>(0xC000 + iocrReference - 1);
1240
1241 iocrHeader.sendClockFactor = setup.sendClockFactor;
1242 iocrHeader.reductionRatio = setup.reductionRatio;
1243 iocrHeader.phase = 8;
1244 iocrHeader.sequence = 0;
1245 iocrHeader.frameSendOffset = 0xFFFFFFFF;
1246 iocrHeader.watchdogFactor = setup.watchdogFactor;
1247 iocrHeader.dataHoldFactor = setup.dataHoldFactor;
1248 iocrHeader.iocrTagHeader = 0xC000;
1249 iocrHeader.iocrMulticastMac = {};
1250 iocrHeader.numberOfApis = 1;
1251
1252 // -------------------------------------------------------------------------
1253 // 5. Serialize
1254 // -------------------------------------------------------------------------
1255 Bytes out = iocrHeader.ToBytes();
1256 out.insert(out.end(), apiBlock.begin(), apiBlock.end());
1257
1258 return out;
1259}
1260*/
1261/*
1262Bytes RPCCon::BuildIocrBlock(int iocrType,
1263 int iocrReference,
1264 const IOCRSetup& setup)
1265{
1266 // ===========================================================================
1267 // AUMA SGx / CDT IOCR layout
1268 //
1269 // The AUMA CDT uses the following IOCR arrangement:
1270 //
1271 // INPUT CR (iocrType == 1)
1272 //
1273 // IODataObjects:
1274 // slot 0 / 0x0001 -> frame offset 0
1275 // slot 0 / 0x0002 -> frame offset 1
1276 // slot 0 / 0x0003 -> frame offset 2
1277 // slot 0 / 0x0004 -> frame offset 3
1278 // slot 0 / 0x8000 -> frame offset 4
1279 // slot 0 / 0x8001 -> frame offset 5
1280 // slot 0 / 0x8002 -> frame offset 6
1281 // slot 1 / 0x0001 -> frame offset 7
1282 //
1283 // IOCSObjects:
1284 // slot 2 / 0x0001 -> frame offset 42
1285 //
1286 //
1287 // OUTPUT CR (iocrType == 2)
1288 //
1289 // IODataObjects:
1290 // slot 2 / 0x0001 -> frame offset 8
1291 //
1292 // IOCSObjects:
1293 // slot 0 / 0x0001 -> frame offset 0
1294 // slot 0 / 0x0002 -> frame offset 1
1295 // slot 0 / 0x0003 -> frame offset 2
1296 // slot 0 / 0x0004 -> frame offset 3
1297 // slot 0 / 0x8000 -> frame offset 4
1298 // slot 0 / 0x8001 -> frame offset 5
1299 // slot 0 / 0x8002 -> frame offset 6
1300 // slot 1 / 0x0001 -> frame offset 7
1301 //
1302 // DataLength is 43 (0x002b) for both IOCRs.
1303 //
1304 // ===========================================================================
1305
1306 // ---------------------------------------------------------------------------
1307 // Locate the configured slots.
1308 // ---------------------------------------------------------------------------
1309
1310 const auto findSlot = [&setup](std::uint16_t slotNumber,
1311 std::uint16_t subslotNumber)
1312 -> const IOSlot*
1313 {
1314 const auto it = std::ranges::find_if(
1315 setup.slots,
1316 [slotNumber, subslotNumber](const IOSlot& slot)
1317 {
1318 return slot.slot == slotNumber &&
1319 slot.subslot == subslotNumber;
1320 });
1321
1322 return it != setup.slots.end() ? &(*it) : nullptr;
1323 };
1324
1325 // ---------------------------------------------------------------------------
1326 // The AUMA configuration requires these exact entries.
1327 // ---------------------------------------------------------------------------
1328
1329 static constexpr std::array<std::pair<std::uint16_t, std::uint16_t>, 7>
1330 kDapSubslots{
1331 {
1332 {0, 0x0001},
1333 {0, 0x0002},
1334 {0, 0x0003},
1335 {0, 0x0004},
1336 {0, 0x8000},
1337 {0, 0x8001},
1338 {0, 0x8002},
1339 }};
1340
1341 const IOSlot* actuatorInput = findSlot(1, 0x0001);
1342 const IOSlot* actuatorOutput = findSlot(2, 0x0001);
1343
1344 if (actuatorInput == nullptr)
1345 {
1346 throw std::runtime_error(
1347 "AUMA IOCR configuration is missing Slot 1 / Subslot 0x0001");
1348 }
1349
1350 if (actuatorOutput == nullptr)
1351 {
1352 throw std::runtime_error(
1353 "AUMA IOCR configuration is missing Slot 2 / Subslot 0x0001");
1354 }
1355
1356 // ---------------------------------------------------------------------------
1357 // Build IOData and IOCS API object lists.
1358 // ---------------------------------------------------------------------------
1359
1360 Bytes objectsData;
1361 Bytes iocsData;
1362
1363 std::uint16_t numObjects = 0;
1364 std::uint16_t iocsCount = 0;
1365
1366 // ===========================================================================
1367 // INPUT CR
1368 // ===========================================================================
1369 //
1370 // CDT:
1371 //
1372 // NumberOfIODataObjects = 8
1373 //
1374 // slot/subslot offset
1375 // --------------------------------
1376 // 0/0001 0
1377 // 0/0002 1
1378 // 0/0003 2
1379 // 0/0004 3
1380 // 0/8000 4
1381 // 0/8001 5
1382 // 0/8002 6
1383 // 1/0001 7
1384 //
1385 // NumberOfIOCSObjects = 1
1386 //
1387 // 2/0001 42
1388 //
1389 // ===========================================================================
1390
1391 if (iocrType == 1)
1392 {
1393 std::uint16_t frameOffset = 0;
1394
1395 // Seven DAP/interface/port objects.
1396 for (const auto& [slotNumber, subslotNumber] : kDapSubslots)
1397 {
1398 const IOSlot* slot = findSlot(slotNumber, subslotNumber);
1399
1400 if (slot == nullptr)
1401 {
1402 throw std::runtime_error(
1403 "AUMA IOCR configuration is missing DAP subslot");
1404 }
1405
1406 IOCRAPIObject obj;
1407 obj.slotNumber = slot->slot;
1408 obj.subslotNumber = slot->subslot;
1409 obj.frameOffset = frameOffset++;
1410
1411 const Bytes objBytes = obj.ToBytes();
1412 objectsData.insert(
1413 objectsData.end(),
1414 objBytes.begin(),
1415 objBytes.end());
1416
1417 ++numObjects;
1418 }
1419
1420 // Actuator input data.
1421 //
1422 // CDT puts this object at offset 7.
1423 {
1424 IOCRAPIObject obj;
1425 obj.slotNumber = actuatorInput->slot;
1426 obj.subslotNumber = actuatorInput->subslot;
1427 obj.frameOffset = frameOffset++;
1428
1429 const Bytes objBytes = obj.ToBytes();
1430 objectsData.insert(
1431 objectsData.end(),
1432 objBytes.begin(),
1433 objBytes.end());
1434
1435 ++numObjects;
1436 }
1437
1438 // The output submodule is the consumer of this INPUT CR.
1439 //
1440 // Its IOCS is at byte offset 42.
1441 {
1442 IOCRAPIObject obj;
1443 obj.slotNumber = actuatorOutput->slot;
1444 obj.subslotNumber = actuatorOutput->subslot;
1445 obj.frameOffset = 42;
1446
1447 const Bytes objBytes = obj.ToBytes();
1448 iocsData.insert(
1449 iocsData.end(),
1450 objBytes.begin(),
1451 objBytes.end());
1452
1453 ++iocsCount;
1454 }
1455 }
1456
1457 // ===========================================================================
1458 // OUTPUT CR
1459 // ===========================================================================
1460 //
1461 // CDT:
1462 //
1463 // NumberOfIODataObjects = 1
1464 //
1465 // slot/subslot offset
1466 // --------------------------------
1467 // 2/0001 8
1468 //
1469 // NumberOfIOCSObjects = 8
1470 //
1471 // slot/subslot offset
1472 // --------------------------------
1473 // 0/0001 0
1474 // 0/0002 1
1475 // 0/0003 2
1476 // 0/0004 3
1477 // 0/8000 4
1478 // 0/8001 5
1479 // 0/8002 6
1480 // 1/0001 7
1481 //
1482 // ===========================================================================
1483
1484 if (iocrType == 2)
1485 {
1486 // Eight IOCS entries for the input-side objects.
1487 std::uint16_t iocsOffset = 0;
1488
1489 for (const auto& [slotNumber, subslotNumber] : kDapSubslots)
1490 {
1491 const IOSlot* slot = findSlot(slotNumber, subslotNumber);
1492
1493 if (slot == nullptr)
1494 {
1495 throw std::runtime_error(
1496 "AUMA IOCR configuration is missing DAP subslot");
1497 }
1498
1499 IOCRAPIObject obj;
1500 obj.slotNumber = slot->slot;
1501 obj.subslotNumber = slot->subslot;
1502 obj.frameOffset = iocsOffset++;
1503
1504 const Bytes objBytes = obj.ToBytes();
1505 iocsData.insert(
1506 iocsData.end(),
1507 objBytes.begin(),
1508 objBytes.end());
1509
1510 ++iocsCount;
1511 }
1512
1513 // Actuator input IOCS.
1514 {
1515 IOCRAPIObject obj;
1516 obj.slotNumber = actuatorInput->slot;
1517 obj.subslotNumber = actuatorInput->subslot;
1518 obj.frameOffset = iocsOffset++;
1519
1520 const Bytes objBytes = obj.ToBytes();
1521 iocsData.insert(
1522 iocsData.end(),
1523 objBytes.begin(),
1524 objBytes.end());
1525
1526 ++iocsCount;
1527 }
1528
1529 // Actuator output data.
1530 //
1531 // CDT starts this at offset 8 because the first eight bytes
1532 // of the output frame are occupied by the IOCS data.
1533 {
1534 IOCRAPIObject obj;
1535 obj.slotNumber = actuatorOutput->slot;
1536 obj.subslotNumber = actuatorOutput->subslot;
1537 obj.frameOffset = 8;
1538
1539 const Bytes objBytes = obj.ToBytes();
1540 objectsData.insert(
1541 objectsData.end(),
1542 objBytes.begin(),
1543 objBytes.end());
1544
1545 ++numObjects;
1546 }
1547 }
1548
1549 // ---------------------------------------------------------------------------
1550 // AUMA CDT uses DataLength = 0x002b (43) for both IOCRs.
1551 //
1552 // Input:
1553 //
1554 // DAP/status area 7 bytes
1555 // actuator input 34 bytes
1556 // actuator IOPS 1 byte
1557 // actuator IOCS 1 byte
1558 // --------------------------------
1559 // 43 bytes
1560 //
1561 // Output:
1562 //
1563 // IOCS area 8 bytes
1564 // actuator output 16 bytes
1565 // remaining IOCR area 19 bytes
1566 // --------------------------------
1567 // 43 bytes
1568 //
1569 // ---------------------------------------------------------------------------
1570
1571 constexpr std::uint16_t dataLength = 0x002b;
1572
1573 // ---------------------------------------------------------------------------
1574 // Build API block.
1575 //
1576 // NumberOfAPIs = 1
1577 // API = 0
1578 // ---------------------------------------------------------------------------
1579
1580 Bytes apiBlock;
1581
1582 wire::PutU32(apiBlock, 0); // API = 0
1583
1584 wire::PutU16(
1585 apiBlock,
1586 numObjects);
1587
1588 apiBlock.insert(
1589 apiBlock.end(),
1590 objectsData.begin(),
1591 objectsData.end());
1592
1593 wire::PutU16(
1594 apiBlock,
1595 iocsCount);
1596
1597 apiBlock.insert(
1598 apiBlock.end(),
1599 iocsData.begin(),
1600 iocsData.end());
1601
1602 // ---------------------------------------------------------------------------
1603 // IOCR header.
1604 // ---------------------------------------------------------------------------
1605
1606 PNBlockHeader blockHeader;
1607 blockHeader.blockType = PNIOCRBlockReqHeader::BLOCK_TYPE; // 0x0102
1608
1609 // IOCR header + API block, excluding the 4-byte BlockType/BlockLength.
1610 const std::size_t totalSize =
1611 PNIOCRBlockReqHeader::kSize + apiBlock.size();
1612
1613 blockHeader.blockLength =
1614 static_cast<std::uint16_t>(totalSize - 4);
1615
1616 blockHeader.blockVersionHigh = VERSION_HIGH;
1617 blockHeader.blockVersionLow = VERSION_LOW;
1618
1619 PNIOCRBlockReqHeader iocrHeader;
1620
1621 const Bytes headerBytes = blockHeader.ToBytes();
1622
1623 std::copy(
1624 headerBytes.begin(),
1625 headerBytes.end(),
1626 iocrHeader.blockHeader.begin());
1627
1628 iocrHeader.iocrType =
1629 static_cast<std::uint16_t>(iocrType);
1630
1631 iocrHeader.iocrReference =
1632 static_cast<std::uint16_t>(iocrReference);
1633
1634 iocrHeader.etherTypeLT =
1635 PROFINET_ETHERTYPE; // 0x8892
1636
1637 iocrHeader.iocrProperties =
1638 0x00000001; // RT_CLASS_1
1639
1640 iocrHeader.dataLength =
1641 dataLength; // 0x002b
1642
1643 // CDT:
1644 // Input CR -> 0xC000
1645 // Output CR -> 0xC001
1646 iocrHeader.frameId =
1647 static_cast<std::uint16_t>(
1648 0xC000 + iocrReference - 1);
1649
1650 iocrHeader.sendClockFactor =
1651 setup.sendClockFactor;
1652
1653 iocrHeader.reductionRatio =
1654 setup.reductionRatio;
1655
1656 iocrHeader.phase = 8;
1657 iocrHeader.sequence = 0;
1658
1659 iocrHeader.frameSendOffset =
1660 0xFFFFFFFF;
1661
1662 iocrHeader.watchdogFactor =
1663 setup.watchdogFactor;
1664
1665 iocrHeader.dataHoldFactor =
1666 setup.dataHoldFactor;
1667
1668 iocrHeader.iocrTagHeader =
1669 0xC000;
1670
1671 iocrHeader.iocrMulticastMac = {};
1672
1673 // Exactly one API: API 0.
1674 iocrHeader.numberOfApis = 1;
1675
1676 // ---------------------------------------------------------------------------
1677 // Serialize.
1678 // ---------------------------------------------------------------------------
1679
1680 Bytes out = iocrHeader.ToBytes();
1681
1682 out.insert(
1683 out.end(),
1684 apiBlock.begin(),
1685 apiBlock.end());
1686
1687 return out;
1688}
1689*/
1690Bytes RPCCon::BuildIocrBlock(int iocrType, int iocrReference, const IOCRSetup& setup)
1691{
1692 // ===========================================================================
1693 // Build the IOCR from the resolved IO configuration.
1694 //
1695 // IOCRSetup::slots is the canonical resolved configuration produced by
1696 // BuildIoSlots().
1697 //
1698 // The two IOSlot flags determine whether a slot participates in the
1699 // corresponding IOCR:
1700 //
1701 // inputIocrObject -> Input CR
1702 // outputIocrObject -> Output CR
1703 //
1704 // Within an IOCR:
1705 //
1706 // INPUT CR:
1707 // inputIocrObject -> IODataObject
1708 // outputIocrObject -> IOCSObject
1709 //
1710 // OUTPUT CR:
1711 // outputIocrObject -> IODataObject
1712 // inputIocrObject -> IOCSObject
1713 //
1714 // This deliberately contains no knowledge of AUMA, Phoenix, slot numbers,
1715 // subslot numbers, module identifiers, or application data sizes.
1716 // ===========================================================================
1717
1718 constexpr std::uint16_t kInputIocrType = 1;
1719 constexpr std::uint16_t kOutputIocrType = 2;
1720
1721 if (iocrType != kInputIocrType && iocrType != kOutputIocrType)
1722 {
1723 throw std::invalid_argument("BuildIocrBlock: unsupported IOCR type " + std::to_string(iocrType));
1724 }
1725
1726 if (setup.dataLength == 0)
1727 {
1728 throw std::invalid_argument("BuildIocrBlock: IOCR dataLength must be greater than zero");
1729 }
1730
1731 if (setup.dataLength > std::numeric_limits<std::uint16_t>::max())
1732 {
1733 throw std::invalid_argument("BuildIocrBlock: IOCR dataLength exceeds uint16_t");
1734 }
1735
1736 // ---------------------------------------------------------------------------
1737 // Collect IOData and IOCS objects.
1738 //
1739 // We keep the objects in the same order as setup.slots. This is important:
1740 // BuildIoSlots() defines the resolved configuration order, and therefore
1741 // also defines the deterministic wire representation.
1742 // ---------------------------------------------------------------------------
1743
1744 std::vector<IOCRAPIObject> ioDataObjects;
1745 std::vector<IOCRAPIObject> iocsObjects;
1746
1747 for (const IOSlot& slot : setup.slots)
1748 {
1749 std::cerr
1750 << "IO slot "
1751 << slot.slot << "/"
1752 << std::hex << slot.subslot << std::dec
1753 << " in=" << slot.inputLength
1754 << " out=" << slot.outputLength
1755 << " inputIoData=" << slot.inputIoData
1756 << " outputIoData=" << slot.outputIoData
1757 << " inputIocs=" << slot.inputIocs
1758 << " outputIocs=" << slot.outputIocs
1759 << '\n';
1760 // const bool inputObject = slot.includeInInputIocr;
1761 // const bool outputObject = slot.includeInOutputIocr;
1762
1763 if (iocrType == kInputIocrType)
1764 {
1765 if (slot.inputIoData)
1766 {
1767 ioDataObjects.push_back({slot.slot, slot.subslot, 0});
1768 }
1769
1770 if (slot.outputIocs)
1771 {
1772 iocsObjects.push_back({slot.slot, slot.subslot, 0});
1773 }
1774 }
1775 else
1776 {
1777 if (slot.outputIoData)
1778 {
1779 ioDataObjects.push_back({slot.slot, slot.subslot, 0});
1780 }
1781
1782 if (slot.inputIocs)
1783 {
1784 iocsObjects.push_back({slot.slot, slot.subslot, 0});
1785 }
1786 }
1787 }
1788
1789 // ---------------------------------------------------------------------------
1790 // Validate object counts before converting to the wire uint16_t fields.
1791 // ---------------------------------------------------------------------------
1792
1793 if (ioDataObjects.size() > std::numeric_limits<std::uint16_t>::max())
1794 {
1795 throw std::invalid_argument("BuildIocrBlock: too many IODataObjects");
1796 }
1797
1798 if (iocsObjects.size() > std::numeric_limits<std::uint16_t>::max())
1799 {
1800 throw std::invalid_argument("BuildIocrBlock: too many IOCSObjects");
1801 }
1802
1803 // ---------------------------------------------------------------------------
1804 // Calculate frame offsets.
1805 //
1806 // The AUMA/CDT layout demonstrates an important distinction:
1807 //
1808 // - an IODataObject's frameOffset points to its process-data bytes;
1809 // - an IOCSObject's frameOffset points to its one-byte IOCS;
1810 // - an IODataObject also has an implicit one-byte IOPS following its data.
1811 //
1812 // Zero-length IODataObjects used by the DAP/PDEV configuration still occupy
1813 // one byte in the cyclic frame layout in the AUMA configuration. Therefore
1814 // a zero-length IODataObject advances the data offset by one byte.
1815 //
1816 // For a data-bearing IODataObject, advance by:
1817 //
1818 // data length + one IOPS byte
1819 //
1820 // IOCS objects each consume one byte.
1821 // ---------------------------------------------------------------------------
1822
1823 std::size_t dataOffset = 0;
1824 if (iocrType == kInputIocrType)
1825 {
1826 // -------------------------------------------------------------------------
1827 // INPUT CR
1828 //
1829 // IOData:
1830 // input data / DAP bytes / IOPS
1831 //
1832 // followed by:
1833 // IOCS
1834 // -------------------------------------------------------------------------
1835
1836 for (auto& object : ioDataObjects)
1837 {
1838 const auto slotIt = std::ranges::find_if(setup.slots,
1839 [&object](const IOSlot& slot)
1840 {
1841 return slot.slot == object.slotNumber && slot.subslot == object.subslotNumber;
1842 });
1843
1844 if (slotIt == setup.slots.end())
1845 {
1846 throw std::logic_error("BuildIocrBlock: IODataObject no longer exists in configuration");
1847 }
1848
1849 const IOSlot& slot = *slotIt;
1850
1851 const std::size_t dataSize = slot.inputLength;
1852 /*
1853 const std::size_t dataSize =
1854 (iocrType == kInputIocrType)
1855 ? slot.inputLength
1856 : slot.outputLength;
1857 */
1858 if (dataOffset > std::numeric_limits<std::uint16_t>::max())
1859 {
1860 throw std::invalid_argument("BuildIocrBlock: IOData frame offset exceeds uint16_t");
1861 }
1862 const std::size_t before = dataOffset;
1863 object.frameOffset = static_cast<std::uint16_t>(dataOffset);
1864
1865 // Even a zero-length DAP/PDEV IODataObject occupies one byte in the
1866 // configured AUMA cyclic frame layout.
1867 const std::size_t objectSize = std::max<std::size_t>(dataSize, 1);
1868
1869 // Data followed by its IOPS byte.
1870 dataOffset += objectSize;
1871
1872 if (dataSize > 0)
1873 {
1874 ++dataOffset;
1875 }
1876 std::cerr
1877 << "slot "
1878 << slot.slot << "/"
1879 << std::hex << slot.subslot << std::dec
1880 << " dataSize=" << dataSize
1881 << " before=" << before
1882 << " after=" << dataOffset
1883 << '\n';
1884 }
1885
1886 // ---------------------------------------------------------------------------
1887 // IOCS offsets.
1888 //
1889 // IOCS objects occupy one byte each.
1890 //
1891 // For the AUMA layout:
1892 //
1893 // INPUT CR:
1894 // input data:
1895 // DAP objects 0..6
1896 // actuator input 7
1897 // actuator IOPS 41
1898 // actuator IOCS 42
1899 //
1900 // OUTPUT CR:
1901 // IOCS:
1902 // DAP/PDEV objects 0..7
1903 // output data:
1904 // actuator output 8
1905 //
1906 // The IOCS area is therefore not simply appended after all IODataObjects.
1907 // It has to be placed according to the direction-specific cyclic layout.
1908 //
1909 // For INPUT CR, IOCS follows the Input IOData/IOPS area.
1910 // For OUTPUT CR, IOCS precedes the Output IOData area.
1911 // ---------------------------------------------------------------------------
1912
1913 // IOCS follows the input IOData/IOPS area.
1914 std::size_t iocsOffset = dataOffset;
1915
1916 /* if (iocrType == kInputIocrType)
1917 {
1918 // Input IOData area comes first.
1919 iocsOffset = dataOffset;
1920 }
1921 else
1922 {
1923 // Output CR places IOCS before output process data.
1924 iocsOffset = iocsObjects.size();
1925 }
1926 */
1927 for (auto& object : iocsObjects)
1928 {
1929 if (iocsOffset > std::numeric_limits<std::uint16_t>::max())
1930 {
1931 throw std::invalid_argument("BuildIocrBlock: IOCS frame offset exceeds uint16_t");
1932 }
1933
1934 object.frameOffset = static_cast<std::uint16_t>(iocsOffset);
1935 ++iocsOffset;
1936 }
1937
1938 // ---------------------------------------------------------------------------
1939 // Determine the minimum cyclic frame size required by the generated objects.
1940 //
1941 // The configured IOCR dataLength may be larger than this minimum. That is
1942 // allowed because the device can have reserved/padding bytes in the cyclic
1943 // data area.
1944 // ---------------------------------------------------------------------------
1945
1946 // std::size_t minimumDataLength = 0;
1947 const std::size_t minimumDataLength =
1948 dataOffset + iocsObjects.size();
1949 /*
1950 if (iocrType == kInputIocrType)
1951 {
1952 // Input IOData + their IOPS bytes.
1953 minimumDataLength = dataOffset;
1954 // IOCS follows the input data area.
1955 minimumDataLength = std::max(minimumDataLength, iocsObjects.size() + dataOffset);
1956 }
1957 else
1958 {
1959 // Output IOCS area followed by output process data.
1960 minimumDataLength = iocsObjects.size() + dataOffset;
1961 }
1962 */
1963 if (minimumDataLength > setup.dataLength)
1964 {
1965 throw std::invalid_argument(
1966 "BuildIocrBlock: configured dataLength " +
1967 std::to_string(setup.dataLength) +
1968 " is smaller than the minimum required cyclic data length " +
1969 std::to_string(minimumDataLength));
1970 }
1971 }
1972 else
1973 {
1974 // -------------------------------------------------------------------------
1975 // OUTPUT CR
1976 //
1977 // IOCS comes first:
1978 //
1979 // IOCS
1980 // output IOData
1981 // -------------------------------------------------------------------------
1982
1983 const std::size_t iocsLength = iocsObjects.size();
1984
1985 for (auto& object : iocsObjects)
1986 {
1987 if (dataOffset > std::numeric_limits<std::uint16_t>::max())
1988 {
1989 throw std::invalid_argument("BuildIocrBlock: IOCS frame offset exceeds uint16_t");
1990 }
1991
1992 object.frameOffset = static_cast<std::uint16_t>(dataOffset);
1993 ++dataOffset;
1994 }
1995
1996 // dataOffset now points immediately after the IOCS area.
1997 for (auto& object : ioDataObjects)
1998 {
1999 const auto slotIt = std::ranges::find_if(setup.slots, [&object](const IOSlot& slot)
2000 {
2001 return slot.slot == object.slotNumber && slot.subslot == object.subslotNumber;
2002 });
2003
2004 if (slotIt == setup.slots.end())
2005 {
2006 throw std::logic_error("BuildIocrBlock: IODataObject no longer exists in configuration");
2007 }
2008
2009 const IOSlot& slot = *slotIt;
2010
2011 const std::size_t dataSize = slot.outputLength;
2012
2013 if (dataOffset > std::numeric_limits<std::uint16_t>::max())
2014 {
2015 throw std::invalid_argument("BuildIocrBlock: IOData frame offset exceeds uint16_t");
2016 }
2017
2018 object.frameOffset = static_cast<std::uint16_t>(dataOffset);
2019
2020 const std::size_t objectSize = std::max<std::size_t>(dataSize, 1);
2021
2022 dataOffset += objectSize;
2023
2024 // Output data has no IOPS included in this offset calculation here.
2025 //
2026 // If your wire layout requires an IOPS byte for output IOData objects,
2027 // add it here exactly as done for input data.... Ralph
2028 }
2029
2030 const std::size_t minimumDataLength = dataOffset;
2031
2032 if (minimumDataLength > setup.dataLength)
2033 {
2034 throw std::invalid_argument(
2035 "BuildIocrBlock: configured dataLength " +
2036 std::to_string(setup.dataLength) +
2037 " is smaller than the minimum required cyclic data length " +
2038 std::to_string(minimumDataLength));
2039 }
2040 }
2041
2042 // ---------------------------------------------------------------------------
2043 // Serialize API 0.
2044 // ---------------------------------------------------------------------------
2045 std::cerr
2046 << "IOCR type=" << iocrType
2047 << " reference=" << iocrReference
2048 << " dataLength=" << setup.dataLength
2049 << " IODataObjects=" << ioDataObjects.size()
2050 << " IOCSObjects=" << iocsObjects.size()
2051 << '\n';
2052
2053 for (const auto& object : ioDataObjects)
2054 {
2055 std::cerr
2056 << " IOData slot=" << object.slotNumber
2057 << " subslot=0x" << std::hex << object.subslotNumber
2058 << std::dec
2059 << " offset=" << object.frameOffset
2060 << '\n';
2061 }
2062
2063 for (const auto& object : iocsObjects)
2064 {
2065 std::cerr
2066 << " IOCS slot=" << object.slotNumber
2067 << " subslot=0x" << std::hex << object.subslotNumber
2068 << std::dec
2069 << " offset=" << object.frameOffset
2070 << '\n';
2071 }
2072
2073 Bytes apiBlock;
2074
2075 // API = 0
2076 wire::PutU32(apiBlock, 0);
2077
2078 wire::PutU16(apiBlock, static_cast<std::uint16_t>(ioDataObjects.size()));
2079
2080 for (const IOCRAPIObject& object : ioDataObjects)
2081 {
2082 const Bytes bytes = object.ToBytes();
2083 apiBlock.insert(apiBlock.end(), bytes.begin(), bytes.end());
2084 }
2085
2086 wire::PutU16(apiBlock, static_cast<std::uint16_t>(iocsObjects.size()));
2087
2088 for (const IOCRAPIObject& object : iocsObjects)
2089 {
2090 const Bytes bytes = object.ToBytes();
2091 apiBlock.insert(apiBlock.end(), bytes.begin(), bytes.end());
2092 }
2093
2094 // ---------------------------------------------------------------------------
2095 // Build IOCR block header.
2096 // ---------------------------------------------------------------------------
2097
2098 PNBlockHeader blockHeader;
2099
2100 blockHeader.blockType = PNIOCRBlockReqHeader::BLOCK_TYPE; // 0x0102
2101
2102 // The IOCR fixed header is followed by the API block.
2103 //
2104 // blockLength excludes:
2105 // - BlockType 2 bytes
2106 // - BlockLength 2 bytes
2107 //
2108 const std::size_t totalSize = PNIOCRBlockReqHeader::kSize + apiBlock.size();
2109
2110 if (totalSize < 4 || totalSize - 4 > std::numeric_limits<std::uint16_t>::max())
2111 {
2112 throw std::invalid_argument("BuildIocrBlock: IOCR block is too large");
2113 }
2114
2115 blockHeader.blockLength = static_cast<std::uint16_t>(totalSize - 4);
2116
2117 blockHeader.blockVersionHigh = VERSION_HIGH;
2118 blockHeader.blockVersionLow = VERSION_LOW;
2119
2120 PNIOCRBlockReqHeader iocrHeader;
2121
2122 const Bytes blockHeaderBytes = blockHeader.ToBytes();
2123
2124 std::ranges::copy(blockHeaderBytes, iocrHeader.blockHeader.begin());
2125
2126 iocrHeader.iocrType = static_cast<std::uint16_t>(iocrType);
2127 iocrHeader.iocrReference = static_cast<std::uint16_t>(iocrReference);
2128 iocrHeader.etherTypeLT = PROFINET_ETHERTYPE; // 0x8892
2129 iocrHeader.iocrProperties = 0x00000001; // RT_CLASS_1
2130 iocrHeader.dataLength = static_cast<std::uint16_t>(setup.dataLength);
2131
2132 // Preserve the existing controller convention:
2133 //
2134 // Input CR -> 0xC000
2135 // Output CR -> 0xC001
2136 //
2137 iocrHeader.frameId = static_cast<std::uint16_t>(0xC000 + iocrReference - 1);
2138
2139 iocrHeader.sendClockFactor = setup.sendClockFactor;
2140 iocrHeader.reductionRatio = setup.reductionRatio;
2141
2142 // These are currently controller/protocol configuration values rather
2143 // than GSDML-derived slot properties.
2144 iocrHeader.phase = 8;
2145 iocrHeader.sequence = 0;
2146 iocrHeader.frameSendOffset = 0xFFFFFFFF;
2147 iocrHeader.watchdogFactor = setup.watchdogFactor;
2148 iocrHeader.dataHoldFactor = setup.dataHoldFactor;
2149 iocrHeader.iocrTagHeader = 0xC000;
2150 iocrHeader.iocrMulticastMac = {};
2151
2152 // One API: API 0.
2153 iocrHeader.numberOfApis = 1;
2154
2155 // ---------------------------------------------------------------------------
2156 // Serialize complete block.
2157 // ---------------------------------------------------------------------------
2158
2159 Bytes out = iocrHeader.ToBytes();
2160
2161 out.insert(out.end(), apiBlock.begin(), apiBlock.end());
2162
2163 return out;
2164}
2165/*
2166template <typename SlotT>
2167std::pair<rt::IOCRConfig, rt::IOCRConfig> BuildIocrConfigs(
2168 const std::vector<SlotT>& slots,
2169 std::uint16_t inputFrameId,
2170 std::uint16_t outputFrameId,
2171 std::uint16_t sendClockFactor = 32,
2172 std::uint16_t reductionRatio = 32,
2173 int watchdogFactor = 3)
2174{
2175 // =========================================================================
2176 // INPUT IOCR: Device -> Controller
2177 //
2178 // C_SDU layout:
2179 //
2180 // IOCS for every submodule without input data
2181 // input process data
2182 // IOPS for every input-data submodule
2183 //
2184 // For this AUMA device:
2185 //
2186 // 8 IOCS bytes
2187 // 34 input bytes
2188 // 1 IOPS byte
2189 // = 43 bytes
2190 // =========================================================================
2191
2192 std::vector<rt::IODataObject> inputObjects;
2193
2194 int frameOffset = 0;
2195
2196 // First reserve IOCS positions for every submodule that has
2197 // no input process data.
2198 for (const auto& s : slots)
2199 {
2200 if (s.inputLength == 0)
2201 {
2202 inputObjects.push_back(
2203 {
2204 s.slot,
2205 s.subslot,
2206 frameOffset, // no process data
2207 0, // dataLength
2208 -1, // no IOPS
2209 frameOffset // IOCS
2210 });
2211
2212 ++frameOffset;
2213 }
2214 }
2215
2216 // Then append the actual input process data.
2217 for (const auto& s : slots)
2218 {
2219 if (s.inputLength > 0)
2220 {
2221 const int dataOffset = frameOffset;
2222
2223 frameOffset += s.inputLength;
2224
2225 const int iopsOffset = frameOffset++;
2226
2227 inputObjects.push_back(
2228 {
2229 s.slot,
2230 s.subslot,
2231 dataOffset,
2232 s.inputLength,
2233 iopsOffset,
2234 -1 // no IOCS
2235 });
2236 }
2237 }
2238
2239 rt::IOCRConfig inputIocr;
2240 inputIocr.typeIOCR = rt::IOCR_TYPE_INPUT;
2241 inputIocr.referenceIOCR = 1;
2242 inputIocr.frameId = inputFrameId;
2243 inputIocr.sendClockFactor = sendClockFactor;
2244 inputIocr.reductionRatio = reductionRatio;
2245 inputIocr.watchdogFactor = watchdogFactor;
2246 inputIocr.dataLength = frameOffset;
2247 inputIocr.objects = std::move(inputObjects);
2248
2249 // =========================================================================
2250 // OUTPUT IOCR: Controller -> Device
2251 //
2252 // C_SDU layout:
2253 //
2254 // IOCS for every submodule without output data
2255 // output process data
2256 // IOPS for every output-data submodule
2257 //
2258 // For this AUMA device:
2259 //
2260 // 8 IOCS bytes
2261 // 16 output bytes
2262 // 1 IOPS byte
2263 // = 25 bytes
2264 // =========================================================================
2265
2266 std::vector<rt::IODataObject> outputObjects;
2267
2268 frameOffset = 0;
2269
2270 // First reserve IOCS positions for every submodule that has
2271 // no output process data.
2272 for (const auto& s : slots)
2273 {
2274 if (s.outputLength == 0)
2275 {
2276 outputObjects.push_back(
2277 {
2278 s.slot,
2279 s.subslot,
2280 frameOffset, // no process data
2281 0, // dataLength
2282 -1, // no IOPS
2283 frameOffset // IOCS
2284 });
2285
2286 ++frameOffset;
2287 }
2288 }
2289
2290 // Then append the actual output process data.
2291 for (const auto& s : slots)
2292 {
2293 if (s.outputLength > 0)
2294 {
2295 const int dataOffset = frameOffset;
2296
2297 frameOffset += s.outputLength;
2298
2299 const int iopsOffset = frameOffset++;
2300
2301 outputObjects.push_back(
2302 {
2303 s.slot,
2304 s.subslot,
2305 dataOffset,
2306 s.outputLength,
2307 iopsOffset,
2308 -1 // no IOCS
2309 });
2310 }
2311 }
2312
2313 rt::IOCRConfig outputIocr;
2314 outputIocr.typeIOCR = rt::IOCR_TYPE_OUTPUT;
2315 outputIocr.referenceIOCR = 2;
2316 outputIocr.frameId = outputFrameId;
2317 outputIocr.sendClockFactor = sendClockFactor;
2318 outputIocr.reductionRatio = reductionRatio;
2319 outputIocr.watchdogFactor = watchdogFactor;
2320 outputIocr.dataLength = frameOffset;
2321 outputIocr.objects = std::move(outputObjects);
2322
2323 return {std::move(inputIocr), std::move(outputIocr)};
2324}
2325*/
2326
2327/* werkt prima maar ik wil wat generiekers
2328Bytes RPCCon::BuildExpectedSubmoduleBlock(const IOCRSetup& setup, blocks::ModuleType dataDescription)
2329{
2330 // ExpectedSubmoduleBlockReq (0x0104). Layout per IEC 61158-6-10:
2331 // BlockHeader(6) + NumberOfAPIs(2) +
2332 // [API(4) + NumberOfSubmodules(2) +
2333 // [Slot(2)+ModuleIdent(4)+ModuleProperties(2)+Subslot(2)+
2334 // SubmoduleIdent(4)+SubmoduleProperties(2)+DataDescription...]...]...
2335 //
2336 // We use a single API (0) with one module-per-slot, one submodule each
2337 // (matching the flattened slot model rpc.py's IOSlot/ExpectedSubmoduleBlockReq
2338 // builder uses).
2339 blocks::ExpectedSubmoduleBlockReq builder;
2340 Bytes apiEntries;
2341 for (const auto& slot : setup.slots)
2342 {
2343 const bool hasInput = slot.inputLength > 0;
2344 const bool hasOutput = slot.outputLength > 0;
2345
2346 const blocks::ModuleType submoduleType = [&]()
2347 {
2348 if (hasInput && hasOutput)
2349 {
2350 return blocks::ModuleType::inputAndOutput;
2351 }
2352 if (hasInput)
2353 {
2354 return blocks::ModuleType::inputOnly;
2355 }
2356 if (hasOutput)
2357 {
2358 return blocks::ModuleType::outputOnly;
2359 }
2360 return blocks::ModuleType::none;
2361 }();
2362 if (dataDescription == submoduleType)
2363 {
2364 builder.AddSubmodule(
2365 0,
2366 slot.slot,
2367 slot.subslot,
2368 slot.moduleIdent,
2369 slot.submoduleIdent,
2370 static_cast<std::uint16_t>(submoduleType),
2371 slot.inputLength,
2372 slot.outputLength);
2373 }
2374 *
2375 Bytes entry;
2376 wire::PutU16(entry, slot.slot); // Slot
2377 wire::PutU32(entry, slot.moduleIdent); // ModuleIdentNumber
2378 wire::PutU16(entry, 0); // ModuleProperties
2379 wire::PutU16(entry, 1); // NumberOfSubmodules
2380 wire::PutU16(entry, slot.subslot); // Subslot
2381 wire::PutU32(entry, slot.submoduleIdent); // SubmoduleIdentNumber
2382
2383 wire::PutU16(entry, static_cast<std::uint16_t>(submoduleType)); // SubmoduleProperties (encodes I/O direction)
2384
2385 if (hasInput)
2386 {
2387 PNExpectedSubmoduleDataDescription desc;
2388 desc.dataDescription = 1; // Input
2389 desc.submoduleDataLength = slot.inputLength;
2390 desc.lengthIocs = 1;
2391 desc.lengthIops = 1;
2392 Bytes d = desc.ToBytes();
2393 entry.insert(entry.end(), d.begin(), d.end());
2394 }
2395 if (hasOutput)
2396 {
2397 PNExpectedSubmoduleDataDescription desc;
2398 desc.dataDescription = 2; // Output
2399 desc.submoduleDataLength = slot.outputLength;
2400 desc.lengthIocs = 1;
2401 desc.lengthIops = 1;
2402 Bytes d = desc.ToBytes();
2403 entry.insert(entry.end(), d.begin(), d.end());
2404 }
2405 if (!hasInput && !hasOutput)
2406 {
2407 PNExpectedSubmoduleDataDescription desc; // NO_IO: single zero-length descriptor
2408 desc.dataDescription = 1;
2409 desc.submoduleDataLength = 0;
2410 desc.lengthIocs = 0;
2411 desc.lengthIops = 1;
2412 Bytes d = desc.ToBytes();
2413 entry.insert(entry.end(), d.begin(), d.end());
2414 }
2415
2416 apiEntries.insert(apiEntries.end(), entry.begin(), entry.end());
2417 *
2418 }
2419 *
2420 Bytes body;
2421 wire::PutU16(body, 1); // NumberOfAPIs
2422 wire::PutU32(body, 0); // API
2423 wire::PutU16(body, static_cast<std::uint16_t>(setup.slots.size())); // NumberOfSubmodules (flattened)
2424 body.insert(body.end(), apiEntries.begin(), apiEntries.end());
2425
2426 PNBlockHeader header;
2427 header.blockType = BLOCK_EXPECTED_SUBMODULE_REQ; // ExpectedSubmoduleBlockReq, 0x0104
2428 header.blockLength = static_cast<std::uint16_t>(2 + body.size());
2429 header.blockVersionHigh = VERSION_HIGH;
2430 header.blockVersionLow = VERSION_LOW;
2431
2432 Bytes out = header.ToBytes();
2433 out.insert(out.end(), body.begin(), body.end());
2434 *
2435 Bytes out = builder.ToBytes();
2436 return out;
2437}
2438*/
2440{
2442
2443 for (const auto& slot : setup.slots)
2444 {
2445 const bool hasInput = slot.inputLength > 0;
2446 const bool hasOutput = slot.outputLength > 0;
2447
2448 const blocks::ModuleType submoduleType =
2449 [&]()
2450 {
2451 if (hasInput && hasOutput)
2452 {
2454 }
2455
2456 if (hasInput)
2457 {
2459 }
2460
2461 if (hasOutput)
2462 {
2464 }
2465
2467 }();
2468
2469 builder.AddSubmodule(
2470 0,
2471 slot.slot,
2472 slot.subslot,
2473 slot.moduleIdent,
2474 slot.submoduleIdent,
2475 static_cast<std::uint16_t>(submoduleType),
2476 slot.inputLength,
2477 slot.outputLength);
2478 }
2479
2480 return builder.ToBytes();
2481}
2483{
2485
2486 for (const auto& slot : setup.slots)
2487 {
2488 const bool hasInput = slot.inputLength > 0;
2489 const bool hasOutput = slot.outputLength > 0;
2490
2491 std::uint16_t submoduleType = 0; // none
2492 if (hasInput && hasOutput)
2493 {
2494 submoduleType = 3; // inputAndOutput
2495 }
2496 else if (hasInput)
2497 {
2498 submoduleType = 1; // inputOnly
2499 }
2500 else if (hasOutput)
2501 {
2502 submoduleType = 2; // outputOnly
2503 }
2504
2505 builder.AddSubmodule(
2506 0, // API
2507 slot.slot,
2508 slot.subslot,
2509 slot.moduleIdent,
2510 slot.submoduleIdent,
2511 submoduleType,
2512 slot.inputLength,
2513 slot.outputLength);
2514 }
2515
2516 return builder.ToBytes();
2517}
2518
2519std::uint16_t RPCCon::ParseIocrResponse(const Bytes& responseData, int iocrType)
2520{
2521 std::size_t offset = 0;
2522 while (offset + blockHeaderLenght <= responseData.size())
2523 {
2524 auto hdr = PeekBlockHeader(responseData, offset);
2525 if (hdr.blockType == PNIOCRBlockRes::BLOCK_TYPE)
2526 {
2527 const Bytes blockBytes(responseData.begin() + offset, responseData.end());
2528 try
2529 {
2530 auto res = PNIOCRBlockRes::Parse(blockBytes);
2531 if (res.iocrType == iocrType)
2532 {
2533 return res.frameId;
2534 }
2535 }
2536 // NOLINTNEXTLINE(bugprone-empty-catch)
2537 catch (const std::exception&)
2538 {
2539 // fall through and keep scanning
2540 }
2541 }
2542 offset += 4 + hdr.blockLength;
2543 }
2544 return 0;
2545}
2546
2548{
2549 Bytes rpcBytes = request.ToBytes();
2550 auto timeoutInMilliSeconds = std::chrono::floor<std::chrono::milliseconds>(std::chrono::duration<double>(timeout));
2551 PNRPCHeader resp;
2552 while (true)
2553 {
2554 const Bytes responseBytes =
2555 rpcTransport->SendReceive(
2556 rpcBytes,
2557 timeoutInMilliSeconds);
2558
2559 if (responseBytes.size() < PNRPCHeader::kFixedSize)
2560 {
2561 throw RPCError("Failed to parse RPC response: data too short (" + std::to_string(responseBytes.size()) +
2562 " bytes)");
2563 }
2564 try
2565 {
2566 resp = PNRPCHeader::Parse(responseBytes);
2567 }
2568 catch (const std::exception& e)
2569 {
2570 throw RPCError(std::string("Failed to parse RPC response: ") + e.what());
2571 }
2572
2573 if (resp.packetType == PNRPCHeader::REQUEST)
2574 {
2575 continue;
2576 }
2577 break;
2578 }
2579 if (resp.packetType == PNRPCHeader::FAULT)
2580 {
2581 throw RPCFaultError("RPC fault from " + info.name, resp.operationNumber);
2582 }
2583 if (resp.packetType == PNRPCHeader::REJECT)
2584 {
2585 throw RPCError("RPC request rejected by " + info.name);
2586 }
2588 {
2589 // Extract the complete formatted error message into a dedicated const variable
2590 const std::string errorMessage = std::format("Unexpected RPC packet type: 0x{:02X}", resp.packetType);
2591
2592 throw RPCError(errorMessage);
2593 }
2594
2595 live = true;
2596 liveMonotonic = std::chrono::steady_clock::now();
2597 return resp;
2598}
2599
2601{
2602 if (live)
2603 {
2604 auto elapsed = std::chrono::duration<double>(std::chrono::steady_clock::now() - liveMonotonic).count();
2605 if (elapsed >= CONNECTION_TIMEOUT)
2606 {
2607 Connect();
2608 }
2609 }
2610}
2611
2612std::optional<ConnectResult> RPCCon::Connect(std::optional<MacAddress> mac,
2613 bool withAlarmCr,
2614 std::optional<IOCRSetup> setup)
2615{
2616 if (!live)
2617 {
2618 if (!mac)
2619 {
2620 throw std::invalid_argument("srcMac required for initial connection");
2621 }
2622 this->srcMac = mac;
2623 }
2624 else
2625 {
2626 arUuid = RandomUuidBytes();
2627 activityUuid = RandomUuidBytes();
2628 sessionKey = RandomNonzeroU16();
2629 if (mac)
2630 {
2631 this->srcMac = mac;
2632 }
2633 }
2634 if (!this->srcMac)
2635 {
2636 throw std::invalid_argument("No source MAC address available");
2637 }
2638
2639 // NOLINTNEXTLINE(performance-unnecessary-value-param)
2640 this->iocrSetup = std::move(setup);
2641
2642 const std::uint32_t arProperties = this->iocrSetup ? 0x00000011U : 0x00000111U;
2643 std::string initiatorStationName = "rhodium-profinet-connector";
2644
2645 PNBlockHeader block;
2646 block.blockType = BlockType::ApplicationRelationRequest; // ARBlockReq, 0x0101
2647 block.blockLength = static_cast<std::uint16_t>(PNARBlockRequest::kFixedSize - 2 + initiatorStationName.length() - 2);
2650
2651 const std::uint16_t arType = this->iocrSetup ? 0x0001 : 0x0006;
2652
2654 std::memcpy(ar.blockHeader.data(), block.ToBytes().data(), blockHeaderLenght);
2655 ar.arType = arType;
2656 ar.arUuid = arUuid;
2658 ar.cmInitiatorMacAddress = *mac;
2660 ar.arProperties = arProperties;
2663 ar.stationNameLength = static_cast<std::uint16_t>(initiatorStationName.length());
2664 const Bytes initiatorStationNameInBytes(initiatorStationName.begin(), initiatorStationName.end());
2665 ar.cmInitiatorStationName = initiatorStationNameInBytes;
2666
2667 Bytes nrdPayload = ar.ToBytes();
2668
2669 if (this->iocrSetup)
2670 {
2672 Bytes inputIocr = BuildIocrBlock(1, inputIocrRef, *this->iocrSetup);
2673 nrdPayload.insert(nrdPayload.end(), inputIocr.begin(), inputIocr.end());
2674
2676 Bytes outputIocr = BuildIocrBlock(2, outputIocrRef, *this->iocrSetup);
2677 nrdPayload.insert(nrdPayload.end(), outputIocr.begin(), outputIocr.end());
2678
2679 Bytes alarmCrData = BuildAlarmCrBlock();
2680 nrdPayload.insert(nrdPayload.end(), alarmCrData.begin(), alarmCrData.end());
2681 withAlarmCr = true;
2682
2683 Bytes expectedSubmodule = BuildExpectedSubmoduleBlock(*this->iocrSetup);
2684 nrdPayload.insert(nrdPayload.end(), expectedSubmodule.begin(), expectedSubmodule.end());
2685 /*
2686 Bytes expectedInputSubmodule = BuildExpectedSubmoduleBlock(*this->iocrSetup);
2687 nrdPayload.insert(nrdPayload.end(), expectedInputSubmodule.begin(), expectedInputSubmodule.end());
2688 Bytes expectedOutputSubmodule = BuildExpectedSubmoduleBlock(*this->iocrSetup);
2689 nrdPayload.insert(nrdPayload.end(), expectedOutputSubmodule.begin(), expectedOutputSubmodule.end());
2690 */
2691 // Build one single ExpectedSubmoduleBlockReq containing all slots (0, 1, and 2)
2692 // Bytes expectedSubmodules = BuildUnifiedExpectedSubmoduleBlock(*this->iocrSetup);
2693 // nrdPayload.insert(nrdPayload.end(), expectedSubmodules.begin(), expectedSubmodules.end());
2694 }
2695 else if (withAlarmCr)
2696 {
2697 Bytes alarmCrData = BuildAlarmCrBlock();
2698 nrdPayload.insert(nrdPayload.end(), alarmCrData.begin(), alarmCrData.end());
2699 }
2700
2701 const PNNRDData nrd = CreateNrd(nrdPayload);
2703
2704 try
2705 {
2706 const PNRPCHeader resp = SendReceive(req);
2707 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
2708
2709 if (nrdResp.argsMaximumStatus != 0)
2710 {
2711 const PNIOError pnioErr = PNIOError::FromArgsStatus(nrdResp.argsMaximumStatus);
2712 throw RPCConnectionError(std::string("Connect rejected by device: ") + pnioErr.what());
2713 }
2714
2715 std::optional<ConnectResult> result;
2716 if (this->iocrSetup)
2717 {
2718 result = ConnectResult{};
2719 result->arUuid = arUuid;
2720 result->sessionKey = sessionKey;
2722 result->inputFrameId = inputFrameId;
2724 result->outputFrameId = outputFrameId;
2725 }
2726
2727 if (withAlarmCr)
2728 {
2730 if (deviceAlarmRef >= 0)
2731 {
2732 alarmCrEnabled = true;
2733 if (result)
2734 {
2735 result->deviceAlarmRef = deviceAlarmRef;
2736 }
2737 }
2738 }
2739
2740 return result;
2741 }
2742 catch (const RPCError& e)
2743 {
2744 throw RPCConnectionError(std::string("Failed to connect: ") + e.what());
2745 }
2746}
2747
2748PNIODHeader RPCCon::Read(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx)
2749{
2750 CheckTimeout();
2751
2752 PNBlockHeader block;
2757
2758 PNIODHeader iod;
2759 std::memcpy(iod.blockHeader.data(), block.ToBytes().data(), blockHeaderLenght);
2760 iod.sequenceNumber = 0;
2761 iod.arUuid = arUuid;
2762 iod.api = api;
2763 iod.slot = slot;
2764 iod.subslot = subslot;
2765 iod.padding1 = 0;
2766 iod.index = idx;
2767 iod.length = 4096;
2768 iod.targetArUuid = {};
2769 iod.padding2 = {};
2770
2771 const PNNRDData nrd = CreateNrd(iod.ToBytes());
2772 const PNRPCHeader req = CreateRpc(PNRPCHeader::READ, nrd.ToBytes());
2773
2774 const PNRPCHeader resp = SendReceive(req);
2775 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
2776 if (nrdResp.argsMaximumStatus != 0)
2777 {
2779 }
2780
2781 return PNIODHeader::Parse(nrdResp.payload);
2782}
2783
2784PNIODHeader RPCCon::ReadImplicit(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx)
2785{
2786 PNBlockHeader block;
2788 block.blockLength = 60;
2789 block.blockVersionHigh = 0x01;
2790 block.blockVersionLow = 0x00;
2791
2792 PNIODHeader iod;
2793 std::memcpy(iod.blockHeader.data(), block.ToBytes().data(), blockHeaderLenght);
2794 iod.sequenceNumber = 0;
2795 iod.arUuid = {}; // empty AR UUID for implicit read
2796 iod.api = api;
2797 iod.slot = slot;
2798 iod.subslot = subslot;
2799 iod.padding1 = 0;
2800 iod.index = idx;
2801 iod.length = 4096;
2802 iod.targetArUuid = {};
2803 iod.padding2 = {};
2804
2805 const PNNRDData nrd = CreateNrd(iod.ToBytes());
2807
2808 const PNRPCHeader resp = SendReceive(req);
2809 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
2810 if (nrdResp.argsMaximumStatus != 0)
2811 {
2813 }
2814
2815 return PNIODHeader::Parse(nrdResp.payload);
2816}
2817
2818void RPCCon::Write(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx,
2819 const Bytes& data)
2820{
2821 CheckTimeout();
2822
2823 PNBlockHeader block;
2824 block.blockType = BlockType::IoDataWriteRequest; // 0x0008; // IODWriteReqHeader
2825 block.blockLength = 60;
2826 block.blockVersionHigh = 0x01;
2827 block.blockVersionLow = 0x00;
2828
2829 PNIODHeader iod;
2830 std::memcpy(iod.blockHeader.data(), block.ToBytes().data(), blockHeaderLenght);
2831 iod.sequenceNumber = 0;
2832 iod.arUuid = arUuid;
2833 iod.api = api;
2834 iod.slot = slot;
2835 iod.subslot = subslot;
2836 iod.padding1 = 0;
2837 iod.index = idx;
2838 iod.length = static_cast<std::uint32_t>(data.size());
2839 iod.targetArUuid = {};
2840 iod.padding2 = {};
2841 iod.payload = data;
2842
2843 const PNNRDData nrd = CreateNrd(iod.ToBytes());
2844 const PNRPCHeader req = CreateRpc(PNRPCHeader::WRITE, nrd.ToBytes());
2845
2846 const PNRPCHeader resp = SendReceive(req);
2847 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
2848 if (nrdResp.argsMaximumStatus != 0)
2849 {
2851 }
2852}
2853
2854Bytes RPCCon::ReadRaw(std::uint16_t idx, std::uint16_t slot, std::uint16_t subslot)
2855{
2856 return Read(0, slot, subslot, idx).payload;
2857}
2858
2859PNInM0 RPCCon::ReadIm0(std::uint16_t slot, std::uint16_t subslot)
2860{
2861 return PNInM0::Parse(Read(0, slot, subslot, PNInM0::IDX).payload);
2862}
2863PNInM1 RPCCon::ReadIm1(std::uint16_t slot, std::uint16_t subslot)
2864{
2865 return PNInM1::Parse(Read(0, slot, subslot, PNInM1::IDX).payload);
2866}
2867PNInM2 RPCCon::ReadIm2(std::uint16_t slot, std::uint16_t subslot)
2868{
2869 return PNInM2::Parse(Read(0, slot, subslot, PNInM2::IDX).payload);
2870}
2871PNInM3 RPCCon::ReadIm3(std::uint16_t slot, std::uint16_t subslot)
2872{
2873 return PNInM3::Parse(Read(0, slot, subslot, PNInM3::IDX).payload);
2874}
2875PNInM4 RPCCon::ReadIm4(std::uint16_t slot, std::uint16_t subslot)
2876{
2877 return PNInM4::Parse(Read(0, slot, subslot, PNInM4::IDX).payload);
2878}
2879PNInM5 RPCCon::ReadIm5(std::uint16_t slot, std::uint16_t subslot)
2880{
2881 return PNInM5::Parse(Read(0, slot, subslot, PNInM5::IDX).payload);
2882}
2883PNInM6 RPCCon::ReadIm6(std::uint16_t slot, std::uint16_t subslot)
2884{
2885 return PNInM6::Parse(Read(0, slot, subslot, PNInM6::IDX).payload);
2886}
2887PNInM7 RPCCon::ReadIm7(std::uint16_t slot, std::uint16_t subslot)
2888{
2889 return PNInM7::Parse(Read(0, slot, subslot, PNInM7::IDX).payload);
2890}
2891PNInM8 RPCCon::ReadIm8(std::uint16_t slot, std::uint16_t subslot)
2892{
2893 return PNInM8::Parse(Read(0, slot, subslot, PNInM8::IDX).payload);
2894}
2895PNInM9 RPCCon::ReadIm9(std::uint16_t slot, std::uint16_t subslot)
2896{
2897 return PNInM9::Parse(Read(0, slot, subslot, PNInM9::IDX).payload);
2898}
2899PNInM10 RPCCon::ReadIm10(std::uint16_t slot, std::uint16_t subslot)
2900{
2901 return PNInM10::Parse(Read(0, slot, subslot, PNInM10::IDX).payload);
2902}
2903PNInM11 RPCCon::ReadIm11(std::uint16_t slot, std::uint16_t subslot)
2904{
2905 return PNInM11::Parse(Read(0, slot, subslot, PNInM11::IDX).payload);
2906}
2907PNInM12 RPCCon::ReadIm12(std::uint16_t slot, std::uint16_t subslot)
2908{
2909 return PNInM12::Parse(Read(0, slot, subslot, PNInM12::IDX).payload);
2910}
2911PNInM13 RPCCon::ReadIm13(std::uint16_t slot, std::uint16_t subslot)
2912{
2913 return PNInM13::Parse(Read(0, slot, subslot, PNInM13::IDX).payload);
2914}
2915PNInM14 RPCCon::ReadIm14(std::uint16_t slot, std::uint16_t subslot)
2916{
2917 return PNInM14::Parse(Read(0, slot, subslot, PNInM14::IDX).payload);
2918}
2919PNInM15 RPCCon::ReadIm15(std::uint16_t slot, std::uint16_t subslot)
2920{
2921 return PNInM15::Parse(Read(0, slot, subslot, PNInM15::IDX).payload);
2922}
2923
2924AllIM RPCCon::ReadAllIm(std::uint16_t slot, std::uint16_t subslot)
2925{
2926 AllIM result;
2927 result.im0 = ReadIm0(slot, subslot); // I&M0 is mandatory; propagate failure.
2928
2929 auto tryRead = [&](auto& field, auto memberFn)
2930 {
2931 try
2932 {
2933 field = (this->*memberFn)(slot, subslot);
2934 }
2935 // NOLINTNEXTLINE(bugprone-empty-catch)
2936 catch (const RPCError&)
2937 {
2938 // fall through and keep scanning
2939 }
2940 // NOLINTNEXTLINE(bugprone-empty-catch)
2941 catch (const std::exception&)
2942 {
2943 // fall through and keep scanning
2944 }
2945 };
2946 tryRead(result.im1, &RPCCon::ReadIm1);
2947 tryRead(result.im2, &RPCCon::ReadIm2);
2948 tryRead(result.im3, &RPCCon::ReadIm3);
2949 tryRead(result.im4, &RPCCon::ReadIm4);
2950 tryRead(result.im5, &RPCCon::ReadIm5);
2951 tryRead(result.im6, &RPCCon::ReadIm6);
2952 tryRead(result.im7, &RPCCon::ReadIm7);
2953 tryRead(result.im8, &RPCCon::ReadIm8);
2954 tryRead(result.im9, &RPCCon::ReadIm9);
2955 tryRead(result.im10, &RPCCon::ReadIm10);
2956 tryRead(result.im11, &RPCCon::ReadIm11);
2957 tryRead(result.im12, &RPCCon::ReadIm12);
2958 tryRead(result.im13, &RPCCon::ReadIm13);
2959 tryRead(result.im14, &RPCCon::ReadIm14);
2960 tryRead(result.im15, &RPCCon::ReadIm15);
2961 return result;
2962}
2963
2964// =============================================================================
2965// Configuration / diagnosis / topology
2966// =============================================================================
2967
2968std::vector<blocks::WriteMultipleResult> RPCCon::WriteMultiple(const std::vector<WriteItem>& writes)
2969{
2970 CheckTimeout();
2971 if (writes.empty())
2972 {
2973 return {};
2974 }
2975
2976 blocks::IODWriteMultipleBuilder builder(arUuid, /*SeqNum=*/0);
2977 for (const auto& w : writes)
2978 {
2979 builder.AddWrite(w.slot, w.subslot, w.index, w.data, w.api);
2980 }
2981 const Bytes payload = builder.Build();
2982
2983 PNBlockHeader block;
2984 block.blockType = BlockType::IoDataWriteRequest; // 0x0008; // IODWriteReqHeader
2985 block.blockLength = 60;
2986 block.blockVersionHigh = 0x01;
2987 block.blockVersionLow = 0x00;
2988
2989 PNIODHeader iod;
2990 std::memcpy(iod.blockHeader.data(), block.ToBytes().data(), blockHeaderLenght);
2991 iod.sequenceNumber = 0;
2992 iod.arUuid = arUuid;
2993 iod.api = 0xFFFFFFFF; // wildcard for multiple
2994 iod.slot = 0xFFFF; // wildcard
2995 iod.subslot = 0xFFFF; // wildcard
2996 iod.padding1 = 0;
2998 iod.length = static_cast<std::uint32_t>(payload.size());
2999 iod.targetArUuid = {};
3000 iod.padding2 = {};
3001 iod.payload = payload;
3002
3003 const PNNRDData nrd = CreateNrd(iod.ToBytes());
3004 const PNRPCHeader req = CreateRpc(PNRPCHeader::WRITE, nrd.ToBytes());
3005
3006 const PNRPCHeader resp = SendReceive(req);
3007 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
3008 if (nrdResp.argsMaximumStatus != 0)
3009 {
3011 }
3012
3014}
3015
3021
3022diagnosis::DiagnosisData RPCCon::ReadDiagnosis(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index)
3023{
3024 try
3025 {
3026 const PNIODHeader iod = Read(0, slot, subslot, index);
3027 const Bytes& data = iod.payload;
3028
3029 if (data.size() > 6)
3030 {
3031 diagnosis::DiagnosisData result = diagnosis::ParseDiagnosisBlock(data, 0, slot, subslot);
3032 if (result.entries.empty())
3033 {
3034 result = diagnosis::ParseDiagnosisSimple(data, 0, slot, subslot);
3035 }
3036 return result;
3037 }
3039 empty.api = 0;
3040 empty.slot = slot;
3041 empty.subslot = subslot;
3042 empty.rawData = data;
3043 return empty;
3044 }
3045 catch (const RPCError&)
3046 {
3048 empty.api = 0;
3049 empty.slot = slot;
3050 empty.subslot = subslot;
3051 return empty;
3052 }
3053}
3054
3055std::map<std::uint16_t, diagnosis::DiagnosisData> RPCCon::ReadAllDiagnosis()
3056{
3057 static const std::vector<std::tuple<std::uint16_t, std::uint16_t, std::uint16_t>> kDiagnosisProbes = {
3058 {0x800A, 0, 0}, // Channel diagnosis for slot 0
3059 {0x800B, 0, 0}, // All diagnosis for slot 0
3060 {0x800C, 0, 1}, // Channel diagnosis for subslot 1
3061 {0xF000, 0, 0}, // All diagnosis data (device level)
3062 {0xF00A, 0, 0}, // Channel diagnosis (API level)
3063 {0xF00B, 0, 0}, // All diagnosis (API level)
3064 };
3065
3066 std::map<std::uint16_t, diagnosis::DiagnosisData> results;
3067 for (const auto& [idx, Slot, Subslot] : kDiagnosisProbes)
3068 {
3069 try
3070 {
3071 const diagnosis::DiagnosisData diag = ReadDiagnosis(Slot, Subslot, idx);
3072 if (!diag.entries.empty())
3073 {
3074 results[idx] = diag;
3075 }
3076 }
3077 // NOLINTNEXTLINE(bugprone-empty-catch)
3078 catch (const RPCError&)
3079 {
3080 }
3081 }
3082 return results;
3083}
3084
3090
3096
3097std::vector<blocks::SlotInfo> RPCCon::DiscoverSlots()
3098{
3100}
3101
3102std::pair<blocks::PDRealData, blocks::RealIdentificationData> RPCCon::DiscoverTopology()
3103{
3105}
3106
3107std::map<std::uint16_t, rpc::IndexProbeResult> RPCCon::EnumerateIndices(
3108 std::uint16_t slot, std::uint16_t subslot, std::optional<std::vector<std::uint16_t>> customIndices)
3109{
3110 std::vector<std::pair<std::uint16_t, std::string>> probeList;
3111
3112 if (customIndices)
3113 {
3114 for (auto idx : *customIndices)
3115 {
3116 probeList.emplace_back(idx, GetIndexName(idx));
3117 }
3118 }
3119 else
3120 {
3121 auto extend = [&](const IndexNamePairs& src)
3122 {
3123 probeList.insert(probeList.end(), src.begin(), src.end());
3124 };
3125 extend(ImIndices());
3126 extend(DiagnosisIndices("subslot"));
3127 extend(DiagnosisIndices("device"));
3128 extend(PortIndices());
3129 extend(InterfaceIndices());
3130 extend(DeviceIndices());
3131 probeList.emplace_back(EXPECTED_ID_SUBSLOT, "ExpectedIdentificationData");
3132 probeList.emplace_back(REAL_ID_SUBSLOT, "RealIdentificationData");
3133 probeList.emplace_back(MODULE_DIFF_BLOCK, "ModuleDiffBlock");
3134 probeList.emplace_back(RECORD_INPUT_DATA, "RecordInputData");
3135 probeList.emplace_back(RECORD_OUTPUT_DATA, "RecordOutputData");
3136 }
3137
3138 std::map<std::uint16_t, IndexProbeResult> results;
3139 std::vector<std::uint16_t> seen;
3140 for (const auto& [idx, Name] : probeList)
3141 {
3142 if (std::ranges::find(seen, idx) != seen.end())
3143 {
3144 continue;
3145 }
3146 seen.push_back(idx);
3147
3149 r.name = Name;
3150 try
3151 {
3152 const PNIODHeader iod = Read(0, slot, subslot, idx);
3153 if (!iod.payload.empty())
3154 {
3155 r.status = "readable";
3156 r.size = iod.payload.size();
3157 }
3158 else
3159 {
3160 r.status = "empty";
3161 }
3162 }
3163 catch (const PNIOError& e)
3164 {
3165 r.status = "error";
3166 r.error = e.what();
3167 r.errorCode1 = e.errorCode1;
3168 r.errorCode2 = e.errorCode2;
3169 }
3170 catch (const RPCError& e)
3171 {
3172 r.status = "error";
3173 r.error = e.what();
3174 }
3175 results[idx] = r;
3176 }
3177
3178 return results;
3179}
3180
3181Bytes RPCCon::SendControl(BlockType blockType, ControlCommand controlCommand, bool waitResponse,
3182 const Bytes& subBlocks)
3183{
3184 if (!live)
3185 {
3186 throw RPCError("Not connected");
3187 }
3188
3189 PNBlockHeader block;
3190 block.blockType = blockType;
3191 block.blockLength = 28;
3192 block.blockVersionHigh = 0x01;
3193 block.blockVersionLow = 0x00;
3194
3195 PNIODReleaseBlock controlBlock;
3196 std::memcpy(controlBlock.blockHeader.data(), block.ToBytes().data(), blockHeaderLenght);
3197 controlBlock.padding1 = 0;
3198 controlBlock.arUuid = arUuid;
3199 controlBlock.sessionKey = sessionKey;
3200 controlBlock.padding2 = 0;
3201 controlBlock.controlCommand = controlCommand;
3202 controlBlock.controlBlockProperties = 0;
3203
3204 Bytes nrdPayload = controlBlock.ToBytes();
3205 nrdPayload.insert(nrdPayload.end(), subBlocks.begin(), subBlocks.end());
3206
3207 const PNNRDData nrd = CreateNrd(nrdPayload);
3209
3210 if (!waitResponse)
3211 {
3212 Bytes rpcBytes = req.ToBytes();
3213 rpcTransport->Send(rpcBytes);
3214 return {};
3215 }
3216
3217 const PNRPCHeader resp = SendReceive(req);
3218 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
3219 if (nrdResp.argsMaximumStatus != 0)
3220 {
3222 }
3223 return nrdResp.payload;
3224}
3225
3238
3239namespace
3240{
3241
3245struct RawRpcHeader
3246{
3247 std::uint8_t version{}, packetType{}, flags1{}, flags2{}, serialHigh{};
3248 std::array<std::uint8_t, 4> drep{};
3249 std::array<std::uint8_t, uuidLenght> objectUuid{}, interfaceUuid{}, activityUuid{};
3251 std::uint16_t operationNumber{};
3252 std::uint8_t serialLow{};
3255};
3256
3257std::optional<RawRpcHeader> ParseRawRpcHeader(const Bytes& data)
3258{
3259 if (data.size() < PNRPCHeader::kFixedSize)
3260 {
3261 return std::nullopt;
3262 }
3263 RawRpcHeader h;
3264 h.version = data[0]; // 0x04
3265 h.packetType = data[1]; // 0x00 = Request, 0x02 = Response
3266 h.flags1 = data[2];
3267 h.flags2 = data[3];
3268 // drep is 4 bytes (indices 4, 5, 6, 7), drep[0] = 0x10 = Little-Endian, drep[0] = 0x00 = Big-Endian
3269 h.drep = {data[4], data[5], data[6], data[7]};
3270
3271 std::memcpy(h.objectUuid.data(), &data[8], uuidLenght);
3272 std::memcpy(h.interfaceUuid.data(), &data[24], uuidLenght);
3273 std::memcpy(h.activityUuid.data(), &data[40], uuidLenght);
3274 h.isLittleEndian = (h.drep[0] & 0x10) != 0;
3275
3276 auto u32 = [&](std::size_t off)
3277 {
3278 return h.isLittleEndian ? ReadU32Le(&data[off])
3279 : static_cast<std::uint32_t>((data[off] << ThreeOctetsShift) | (data[off + 1] << uuidLenght) |
3280 (data[off + 2] << OneOctetShift) | data[off + 3]);
3281 };
3282 auto u16 = [&](std::size_t off)
3283 {
3284 return h.isLittleEndian ? ReadU16Le(&data[off])
3285 : static_cast<std::uint16_t>((data[off] << OneOctetShift) | data[off + 1]);
3286 };
3287
3288 // Field offsets per DCE/RPC v4 specification:
3289 // Bytes 56-59: server_boot_time
3290 h.interfaceVersion = u32(60);
3291 h.sequenceNumber = u32(64);
3292 h.operationNumber = u16(68);
3293 // Bytes 70-71: interface_hint
3294 // Bytes 72-73: activity_hint
3295 // Bytes 74-75: len (args_len)
3296 // Bytes 76-77: fragnum
3297 // Byte 78: auth_proto
3298 h.serialLow = data[79];
3299
3300 h.payload = Bytes(data.begin() + PNRPCHeader::kFixedSize, data.end());
3301 return h;
3302}
3303
3304} // namespace
3305
3307{
3308 if (!live)
3309 {
3310 throw RPCError("Not connected");
3311 }
3312
3313 auto timeoutInMilliSeconds = std::chrono::floor<std::chrono::milliseconds>(std::chrono::duration<double>(timeoutSec));
3314 Bytes result;
3315 try
3316 {
3317 while (true)
3318 {
3319 Bytes respBytes;
3320 // This maybe wrong... i am too tired to validate
3321 // Receive incoming UDP request (and capture sender endpoint if transport supports it)
3322
3323 asio::ip::udp::endpoint senderEndpoint;
3324 Bytes buf = ccontrolTransport->Receive(timeoutInMilliSeconds, senderEndpoint);
3325 auto hdr = ParseRawRpcHeader(buf);
3326 if (!hdr)
3327 {
3328 continue;
3329 }
3330 if (hdr->packetType != PNRPCHeader::REQUEST)
3331 {
3332 continue;
3333 }
3334 if (hdr->operationNumber != PNRPCHeader::CONTROL)
3335 {
3336 continue;
3337 }
3338
3339 const Bytes& nrdPayload = hdr->payload;
3340 if (nrdPayload.size() < 20)
3341 {
3342 continue;
3343 }
3344 bool le = hdr->isLittleEndian;
3345 const std::uint32_t nrdActual =
3346 le ? ReadU32Le(&nrdPayload[16])
3347 : static_cast<std::uint32_t>((nrdPayload[16] << ThreeOctetsShift) | (nrdPayload[17] << TwoOctetsShift) |
3348 (nrdPayload[18] << OneOctetShift) | nrdPayload[19]);
3349 (void)nrdActual;
3350 Bytes nrdBody(nrdPayload.begin() + 20, nrdPayload.end());
3351 if (nrdBody.size() < 32)
3352 {
3353 continue;
3354 }
3355 /*
3356 const std::uint16_t blockType =
3357
3358 le ? ReadU16Le(nrdBody.data()) : static_cast<std::uint16_t>((nrdBody[0] << OneOctetShift) | nrdBody[1]);
3359 auto controlCmd = static_cast<std::uint16_t>((nrdBody[28] << OneOctetShift) | nrdBody[29]);
3360
3361 if (blockType != BLOCK_IOD_CONTROL_APP_READY_REQ)
3362 {
3363 continue;
3364 }
3365 */
3366 // 1. BlockType is ALWAYS Big-Endian (0x0112 = IODControlReq / ApplicationReady)
3367 const std::uint16_t blockType = (static_cast<std::uint16_t>(nrdBody[0]) << 8) | nrdBody[1];
3368
3369 // 2. ControlCommand is located at offset 28 inside PNIODControlBlockReq
3370 // (Block Header: 6 bytes + ARUUID: 16 bytes + SessionKey: 2 bytes + Reserved: 4 bytes = 28 bytes)
3371 const std::uint16_t controlCmd = (static_cast<std::uint16_t>(nrdBody[28]) << 8) | nrdBody[29];
3372
3373 std::cout << "Parsed BlockType: 0x" << std::hex << blockType
3374 << ", ControlCmd: 0x" << controlCmd << std::dec << "\n";
3375
3376 if (blockType != BLOCK_IOD_CONTROL_APP_READY_REQ) // 0x0112
3377 {
3378 std::cerr << "Ignoring non-AppReady block: 0x" << std::hex << blockType << "\n";
3379 continue;
3380 }
3381 // (control_cmd expected to be CONTROL_CMD_APP_READY; devices that
3382 // deviate are still accepted, mirroring rpc.py's warn-and-continue.)
3383 //(void)controlCmd;
3384
3385 // Build CControl response: block 0x8112, cmd=DONE.
3386 PNBlockHeader respBlock;
3388 respBlock.blockLength = 28;
3389 respBlock.blockVersionHigh = 0x01;
3390 respBlock.blockVersionLow = 0x00;
3391
3392 PNIODReleaseBlock respControl;
3393 std::memcpy(respControl.blockHeader.data(), respBlock.ToBytes().data(), blockHeaderLenght);
3394 respControl.padding1 = 0;
3395 respControl.arUuid = arUuid;
3396 respControl.sessionKey = sessionKey;
3397 respControl.padding2 = 0;
3399 respControl.controlBlockProperties = 0;
3400
3401 Bytes respNrdPayload = respControl.ToBytes();
3402 auto respNrdLen = static_cast<std::uint32_t>(respNrdPayload.size());
3403
3404 Bytes respNrd;
3405 if (le)
3406 {
3407 WriteU32Le(respNrd, 0); // pnio_status = OK
3408 WriteU32Le(respNrd, respNrdLen); // args_length
3409 WriteU32Le(respNrd, respNrdLen); // maximum_count
3410 WriteU32Le(respNrd, 0); // offset
3411 WriteU32Le(respNrd, respNrdLen); // actual_count
3412 }
3413 else
3414 {
3415 wire::PutU32(respNrd, 0);
3416 wire::PutU32(respNrd, respNrdLen);
3417 wire::PutU32(respNrd, respNrdLen);
3418 wire::PutU32(respNrd, 0);
3419 wire::PutU32(respNrd, respNrdLen);
3420 }
3421 respNrd.insert(respNrd.end(), respNrdPayload.begin(), respNrdPayload.end());
3422
3423 respBytes.push_back(hdr->version);
3424 respBytes.push_back(PNRPCHeader::RESPONSE);
3425 respBytes.push_back(0x00);
3426 respBytes.push_back(0x00);
3427 respBytes.insert(respBytes.end(), hdr->drep.begin(), hdr->drep.end());
3428 // respBytes.push_back(hdr->serialHigh);
3429 respBytes.insert(respBytes.end(), hdr->objectUuid.begin(), hdr->objectUuid.end());
3430 respBytes.insert(respBytes.end(), hdr->interfaceUuid.begin(), hdr->interfaceUuid.end());
3431 respBytes.insert(respBytes.end(), hdr->activityUuid.begin(), hdr->activityUuid.end());
3432
3433 auto putFieldU32 = [&](std::uint32_t v)
3434 {
3435 if (le)
3436 {
3437 WriteU32Le(respBytes, v);
3438 }
3439 else
3440 {
3441 wire::PutU32(respBytes, v);
3442 }
3443 };
3444 auto putFieldU16 = [&](std::uint16_t v)
3445 {
3446 if (le)
3447 {
3448 WriteU16Le(respBytes, v);
3449 }
3450 else
3451 {
3452 wire::PutU16(respBytes, v);
3453 }
3454 };
3455
3456 putFieldU32(0); // server_boot_time
3457 putFieldU32(hdr->interfaceVersion);
3458 putFieldU32(hdr->sequenceNumber);
3459 putFieldU16(hdr->operationNumber);
3460 putFieldU16(0xFFFF); // interface_hint
3461 putFieldU16(0xFFFF); // activity_hint
3462 putFieldU16(static_cast<std::uint16_t>(respNrd.size()));
3463 putFieldU16(0); // fragment_number
3464 respBytes.push_back(0);
3465 respBytes.push_back(hdr->serialLow);
3466 respBytes.insert(respBytes.end(), respNrd.begin(), respNrd.end());
3467
3468 ccontrolTransport->SendTo(respBytes, senderEndpoint);
3469
3470 live = true;
3471 liveMonotonic = std::chrono::steady_clock::now();
3472 result = nrdBody;
3473 break;
3474 }
3475 }
3476 catch (...)
3477 {
3478 throw;
3479 }
3480 return result;
3481}
3482
3484{
3485 if (!live)
3486 {
3487 return;
3488 }
3489
3490 try
3491 {
3492 PNBlockHeader block;
3494 block.blockLength = 28;
3495 block.blockVersionHigh = 0x01;
3496 block.blockVersionLow = 0x00;
3497
3498 PNIODReleaseBlock release;
3499 std::memcpy(release.blockHeader.data(), block.ToBytes().data(), blockHeaderLenght);
3500 release.padding1 = 0;
3501 release.arUuid = arUuid;
3502 release.sessionKey = sessionKey;
3503 release.padding2 = 0;
3505 release.controlBlockProperties = 0;
3506
3507 const PNNRDData nrd = CreateNrd(release.ToBytes());
3509 Bytes rpcBytes = req.ToBytes();
3510 rpcTransport->Send(rpcBytes);
3511 }
3512 // NOLINTNEXTLINE(bugprone-empty-catch)
3513 catch (...)
3514 {
3515 // best-effort, matching rpc.py's disconnect()
3516 }
3517
3518 live = false;
3519}
3520
3521void RPCCon::Close() noexcept
3522{
3523 Disconnect();
3524 rpcTransport->Close();
3525 ccontrolTransport->Close();
3526}
3527
3528} // namespace profinet::rpc
Device not found via DCP.
Definition exceptions.h:563
A minimal RAII wrapper around a Linux AF_PACKET raw socket bound to an interface.
Definition util.h:196
PNIO application error with error codes.
Definition exceptions.h:692
std::uint8_t errorCode1
Raw ErrorCode1 byte (category / block type).
Definition exceptions.h:701
std::uint8_t errorCode2
Raw ErrorCode2 byte (specific error within the category).
Definition exceptions.h:704
static PNIOError FromArgsStatus(std::uint32_t argsStatus)
Create a PNIOError from the little-endian ArgsStatus field carried in a DCE/RPC PDU.
Failed to establish RPC connection.
Definition exceptions.h:615
DCE/RPC protocol errors.
Definition exceptions.h:575
RPC returned a fault response.
Definition exceptions.h:599
ExpectedSubmoduleBlockReq (0x0104) builder.
Definition blocks.h:675
Bytes ToBytes() const
Serialize the complete block to raw bytes.
Definition blocks.cpp:934
ExpectedSubmoduleBlockReq & AddSubmodule(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint32_t moduleIdent, std::uint32_t submoduleIdent, std::uint16_t submoduleType=0, std::uint16_t inputLength=0, std::uint16_t outputLength=0)
Add a single submodule.
Definition blocks.cpp:860
Builder for IODWriteMultipleReq packets (index 0xE040).
Definition blocks.h:488
Bytes Build() const
Build the complete IODWriteMultipleReq packet.
Definition blocks.cpp:717
IODWriteMultipleBuilder & AddWrite(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, Bytes data, std::uint32_t api=0)
Add a write operation.
Definition blocks.cpp:667
static constexpr std::uint16_t writeMultipleIndex
Record data index for WriteMultiple (0xE040).
Definition blocks.h:491
Parsed PROFINET device information from a DCP response.
Definition dcp.h:426
std::uint8_t vendorLow
Low byte of the PROFINET vendor ID.
Definition dcp.h:464
std::string name
Station name.
Definition dcp.h:440
std::uint8_t vendorHigh
High byte of the PROFINET vendor ID.
Definition dcp.h:461
std::uint8_t deviceLow
Low byte of the PROFINET device ID.
Definition dcp.h:470
std::uint8_t deviceHigh
High byte of the PROFINET device ID.
Definition dcp.h:467
std::string ip
IPv4 address, or "0.0.0.0" if unset.
Definition dcp.h:443
PNInM13 ReadIm13(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M13 (reserved).
Definition rpc.cpp:2911
PNInM0 ReadIm0(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M0 (mandatory identification data).
Definition rpc.cpp:2859
static std::uint16_t ParseIocrResponse(const Bytes &responseData, int iocrType)
Parse an IOCRBlockRes from a CONNECT response.
Definition rpc.cpp:2519
int iocrRefCounter
Counter used to allocate unique IOCR references.
Definition rpc.h:582
std::uint32_t sequenceNumber
Monotonically increasing RPC sequence number.
Definition rpc.h:579
blocks::PDRealData ReadPdRealData() override
Read and parse the device's PDRealData (physical topology).
Definition rpc.cpp:3085
static Bytes BuildUnifiedExpectedSubmoduleBlock(const IOCRSetup &setup)
Definition rpc.cpp:2482
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID for this connection.
Definition rpc.h:545
blocks::ModuleDiffBlock ReadModuleDiff() override
Read and parse the device's ModuleDiffBlock.
Definition rpc.cpp:3016
PNInM2 ReadIm2(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M2 (installation date).
Definition rpc.cpp:2867
RPCCon(RPCCon &&)=delete
Deleted move constructor to prevent moving instances of this class.
Bytes BuildAlarmCrBlock(int transport=0, int priority=0) const
Build an AlarmCRBlockReq for inclusion in the CONNECT request.
Definition rpc.cpp:714
static Bytes BuildIocrBlock(int iocrType, int iocrReference, const IOCRSetup &setup)
Build an IOCRBlockReq (header + API object list) for the CONNECT request.
Definition rpc.cpp:1690
double timeout
Default RPC response timeout in seconds.
Definition rpc.h:542
PNInM8 ReadIm8(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M8 (reserved).
Definition rpc.cpp:2891
std::array< std::uint8_t, uuidLenght > localObjectUuid
This controller's own object UUID.
Definition rpc.h:551
~RPCCon()
Disconnect (best-effort) and close the underlying sockets.
Definition rpc.cpp:671
static PNNRDData CreateNrd(const Bytes &payload)
Wrap a payload in an NRD (Network Representation Data) header.
Definition rpc.cpp:702
PNInM4 ReadIm4(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M4 (PROFIsafe signature).
Definition rpc.cpp:2875
PNInM15 ReadIm15(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M15 (reserved).
Definition rpc.cpp:2919
AllIM ReadAllIm(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read all I&M records a device supports.
Definition rpc.cpp:2924
std::vector< blocks::SlotInfo > DiscoverSlots() override
Convenience accessor for ReadRealIdentificationData().Slots.
Definition rpc.cpp:3097
static int ParseAlarmCrResponse(const Bytes &responseData)
Parse the AlarmCRBlockRes from a CONNECT response.
Definition rpc.cpp:739
Bytes PrmEnd() override
Send PrmEnd (end of the parameterization phase).
Definition rpc.cpp:3230
PNInM6 ReadIm6(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M6 (reserved).
Definition rpc.cpp:2883
static Bytes BuildExpectedSubmoduleBlock(const IOCRSetup &setup)
Build an ExpectedSubmoduleBlockReq for the CONNECT request.
Definition rpc.cpp:2439
Bytes SendControl(BlockType blockType, ControlCommand controlCommand, bool waitResponse=true, const Bytes &subBlocks={})
Send a CONTROL operation and optionally wait for its response.
Definition rpc.cpp:3181
void Disconnect() override
Send Release to terminate the AR.
Definition rpc.cpp:3483
int deviceAlarmRef
Device's local alarm reference, if an AlarmCR was established.
Definition rpc.h:573
PNInM11 ReadIm11(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M11 (reserved).
Definition rpc.cpp:2903
std::pair< blocks::PDRealData, blocks::RealIdentificationData > DiscoverTopology() override
Read both PDRealData and RealIdentificationData in one call.
Definition rpc.cpp:3102
std::array< std::uint8_t, uuidLenght > remoteObjectUuid
The target device's object UUID.
Definition rpc.h:554
dcp::DCPDeviceDescription info
DCP-discovered description of the target device.
Definition rpc.h:539
diagnosis::DiagnosisData ReadDiagnosis(std::uint16_t slot=0, std::uint16_t subslot=0, std::uint16_t index=0xF000) override
Read and parse diagnosis data at the given location/index.
Definition rpc.cpp:3022
PNInM14 ReadIm14(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M14 (reserved).
Definition rpc.cpp:2915
void Close() noexcept override
Disconnect() and close the underlying sockets.
Definition rpc.cpp:3521
std::unique_ptr< RpcTransport > rpcTransport
Definition rpc.h:602
PNInM12 ReadIm12(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M12 (reserved).
Definition rpc.cpp:2907
int inputIocrRef
IOCR reference for the input (device -> controller) IOCR.
Definition rpc.h:585
std::optional< MacAddress > srcMac
Source MAC address used to establish the AR.
Definition rpc.h:563
std::map< std::uint16_t, diagnosis::DiagnosisData > ReadAllDiagnosis() override
Read diagnosis from all standard diagnosis indices.
Definition rpc.cpp:3055
std::map< std::uint16_t, IndexProbeResult > EnumerateIndices(std::uint16_t slot=0, std::uint16_t subslot=1, std::optional< std::vector< std::uint16_t > > customIndices=std::nullopt) override
Probe a set of indices and report which are readable/empty/erroring.
Definition rpc.cpp:3107
bool alarmCrEnabled
Whether an AlarmCR was successfully established.
Definition rpc.h:576
std::chrono::steady_clock::time_point liveMonotonic
Timestamp of the last successful RPC exchange, for CONNECTION_TIMEOUT tracking.
Definition rpc.h:560
void CheckTimeout()
Reconnect if the AR has been idle longer than CONNECTION_TIMEOUT.
Definition rpc.cpp:2600
Bytes PrmBegin() override
Send PrmBegin (start of the parameterization phase).
Definition rpc.cpp:3226
int outputIocrRef
IOCR reference for the output (controller -> device) IOCR.
Definition rpc.h:588
std::uint16_t outputFrameId
Frame ID assigned to the output IOCR.
Definition rpc.h:594
Bytes ApplicationReady(double timeoutSec=30.0) override
Wait for the device's CControl/ApplicationReady request and confirm it.
Definition rpc.cpp:3306
PNInM5 ReadIm5(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M5 (free-text annotation).
Definition rpc.cpp:2879
PNInM9 ReadIm9(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M9 (reserved).
Definition rpc.cpp:2895
std::uint16_t alarmRef
Controller's own local alarm reference.
Definition rpc.h:570
PNInM10 ReadIm10(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M10 (reserved).
Definition rpc.cpp:2899
PNInM3 ReadIm3(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M3 (free-text descriptor).
Definition rpc.cpp:2871
std::uint16_t inputFrameId
Frame ID assigned to the input IOCR.
Definition rpc.h:591
std::unique_ptr< RpcTransport > ccontrolTransport
Definition rpc.h:603
blocks::RealIdentificationData ReadRealIdentificationData() override
Read and parse the device's RealIdentificationData (logical structure).
Definition rpc.cpp:3091
std::optional< ConnectResult > Connect(std::optional< MacAddress > srcMac=std::nullopt, bool withAlarmCr=false, std::optional< IOCRSetup > iocrSetup=std::nullopt) override
Establish (or re-establish) the AR with the device.
Definition rpc.cpp:2612
PNInM1 ReadIm1(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M1 (tag function/location).
Definition rpc.cpp:2863
PNIODHeader Read(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx) override
Read a data record via slot/subslot/index (AR must be connected).
Definition rpc.cpp:2748
void Write(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx, const Bytes &data) override
Write a data record via slot/subslot/index (AR must be connected).
Definition rpc.cpp:2818
std::uint16_t sessionKey
Session key for this connection.
Definition rpc.h:566
PNRPCHeader CreateRpc(std::uint16_t operation, const Bytes &nrd)
Build an RPC header for the given operation and NRD body.
Definition rpc.cpp:676
Bytes ReadRaw(std::uint16_t idx, std::uint16_t slot=0, std::uint16_t subslot=1)
Read a raw record payload by index (convenience wrapper over Read()).
Definition rpc.cpp:2854
PNRPCHeader SendReceive(const PNRPCHeader &request)
Send an RPC request and synchronously wait for its response.
Definition rpc.cpp:2547
PNInM7 ReadIm7(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M7 (reserved).
Definition rpc.cpp:2887
Bytes ReadyForRtClass3() override
Send ReadyForRTClass3 (isochronous real-time readiness).
Definition rpc.cpp:3234
std::vector< blocks::WriteMultipleResult > WriteMultiple(const std::vector< WriteItem > &writes) override
Write multiple records atomically via IODWriteMultipleReq (0xE040).
Definition rpc.cpp:2968
bool live
Whether the AR is currently established.
Definition rpc.h:557
PNIODHeader ReadImplicit(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx) override
Read a data record without an established AR.
Definition rpc.cpp:2784
std::optional< IOCRSetup > iocrSetup
Cyclic IO configuration used for the current AR, if any.
Definition rpc.h:597
std::array< std::uint8_t, uuidLenght > activityUuid
Activity UUID for the current RPC exchange.
Definition rpc.h:548
Synchronous UDP transport used by PROFINET DCE/RPC.
void Open(const std::string &address, std::uint16_t port, bool isListener=false)
Open the UDP socket and configure the remote endpoint.
Bytes SendReceive(const Bytes &data, std::chrono::milliseconds timeout) override
Send a UDP datagram and wait for one response.
Declares the PROFINET RTC1 cyclic IO controller and process-data exchange API.
ModuleDiffBlock ParseModuleDiffBlock(const Bytes &data)
Parse a ModuleDiffBlock (0x8104) from raw bytes.
Definition blocks.cpp:593
std::vector< WriteMultipleResult > ParseWriteMultipleResponse(const Bytes &data)
Parse an IODWriteMultipleRes into individual per-write results.
Definition blocks.cpp:740
PDRealData ParsePdRealData(const Bytes &data)
Parse a complete PDRealData (0xF841) response.
Definition blocks.cpp:313
RealIdentificationData ParseRealIdentificationData(const Bytes &data)
Parse a RealIdentificationData (0xF000 or 0x0013) response.
Definition blocks.cpp:407
constexpr int MIN_CYCLE_MS
Minimum cycle time (ms) considered reliable.
Definition cyclic.h:151
void SendRequest(const EthernetSocket &sock, const MacAddress &src, BlockKey blockType, const std::vector< std::uint8_t > &value)
Send a DCP Identify request filtered to a specific (option, suboption).
Definition dcp.cpp:1025
std::pair< std::uint8_t, std::uint8_t > BlockKey
(option, suboption) key identifying a DCP block.
Definition dcp.h:421
void SendDiscover(const EthernetSocket &sock, const MacAddress &src, std::uint16_t responseDelay=DEFAULT_RESPONSE_DELAY_FACTOR)
Send a DCP Identify multicast request to discover all devices on the segment.
Definition dcp.cpp:1003
ResponseMap ReadResponse(const EthernetSocket &sock, const MacAddress &myMac, int timeoutSec=DEFAULT_RESPONSE_TIMEOUT, bool once=false, std::optional< std::uint32_t > expectedXid=std::nullopt)
Read and parse DCP Identify responses.
Definition dcp.cpp:1048
DiagnosisData ParseDiagnosisSimple(const Bytes &data, std::uint32_t api=0, std::uint16_t slot=0, std::uint16_t subslot=0)
Parse a simple, fixed-format diagnosis block.
DiagnosisData ParseDiagnosisBlock(const Bytes &data, std::uint32_t api=0, std::uint16_t slot=0, std::uint16_t subslot=0)
Parse a full DiagnosisData block from raw bytes.
constexpr std::uint8_t VERSION_HIGH
Version high.
Definition rpc.cpp:34
constexpr std::uint16_t BLOCK_IOD_CONTROL_APP_READY_REQ
Block type: IODControlReqAppReady.
Definition rpcTypes.h:60
std::string UuidBytesToString(const std::array< std::uint8_t, uuidLenght > &data)
Format a 16-byte DCE/RPC UUID as a canonical string.
Definition rpc.cpp:122
const std::string UUID_PNIO_CONTROLLER
PROFINET IO-Controller interface UUID.
Definition rpcTypes.h:38
const std::string UUID_EPM_V4
Endpoint Mapper interface UUID.
Definition rpcTypes.h:34
dcp::DCPDeviceDescription GetStationInfo(const EthernetSocket &sock, const MacAddress &src, const std::string &name, int timeoutSec=static_cast< int >(DEFAULT_TIMEOUT *2))
Resolve a device by PROFINET station name via DCP.
Definition rpc.cpp:588
constexpr std::uint32_t EPM_INQUIRY_ALL
EPM inquiry type: all interfaces.
Definition rpcTypes.h:46
const std::string UUID_PNIO_DEVICE
PROFINET IO-Device interface UUID.
Definition rpcTypes.h:36
constexpr double CONNECTION_TIMEOUT
Maximum idle time in seconds before an AR is considered to need re-connection.
Definition rpcTypes.h:53
BlockHeaderView PeekBlockHeader(const Bytes &data, std::size_t offset)
Definition rpc.h:53
constexpr std::uint32_t EPM_INQUIRY_INTERFACE
EPM inquiry type: specific interface.
Definition rpcTypes.h:48
constexpr std::uint8_t VERSION_LOW
Version low.
Definition rpc.cpp:36
std::vector< EPMEndpoint > EpmLookup(asio::io_context &ioContext, const std::string &ip, std::uint16_t port=RPC_PORT, double timeoutSec=DEFAULT_TIMEOUT, std::optional< std::string > interfaceFilter=std::nullopt)
Query a device's Endpoint Mapper for available RPC endpoints.
Definition rpc.cpp:389
std::array< std::uint8_t, uuidLenght > StringToUuidBytes(const std::string &uuidStr)
Parse a canonical UUID string into 16 raw DCE/RPC UUID bytes.
Definition rpc.cpp:159
constexpr std::uint32_t EPM_LOOKUP
EPM operation: Lookup.
Definition rpcTypes.h:44
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t v)
Append a 16-bit value to a buffer in big-endian order.
Definition wire.h:27
void PutU32(std::vector< std::uint8_t > &out, std::uint32_t v)
Append a 32-bit value to a buffer in big-endian order.
Definition wire.h:36
BlockType
PROFINET block types.
Definition indices.h:91
@ ControlPrmEndRequest
IOD Control Prm End request.
@ ApplicationRelationRequest
AR block request.
@ ControlRtClass3Request
IOD Control RT Class 3 request.
@ ReleaseRequest
IOD Release request.
@ ParameterizationBeginRequest
Parameterization Begin request.
@ IoDataWriteRequest
IOD write request block.
@ ControlApplicationReadyResponse
IOD Control Application Ready response.
const IndexNamePairs & DiagnosisIndices(const std::string &scope)
Diagnosis indices for a given addressing scope.
Definition indices.cpp:532
constexpr int uuidLenght
Constant lenght of a UUID.
Definition util.h:44
constexpr std::uint16_t PD_REAL_DATA
PD Real Data.
Definition indices.h:738
constexpr std::uint16_t MODULE_DIFF_BLOCK
Module Diff Block.
Definition indices.h:726
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:67
constexpr std::uint16_t IP_ETHERTYPE
EtherType value identifying IP frames (0x8000).
Definition util.h:34
ControlCommand
PROFINET IOD control command values.
Definition indices.h:60
@ PrmEnd
Prm End command.
@ Release
Release command.
@ ReadyForRtClass3
Ready for RT Class 3 command.
@ PrmBegin
Prm Begin command.
std::vector< std::pair< std::uint16_t, std::string > > IndexNamePairs
(index, name) pairs, e.g. for building a "try each of these" scan.
Definition indices.h:924
constexpr std::uint16_t EXPECTED_ID_SUBSLOT
Expected ID Subslot.
Definition indices.h:717
static constexpr int OneOctetShift
The bit-shift distance required to move data across a single octet.
Definition util.h:50
static constexpr int ThreeOctetsShift
The bit-shift distance required to move data across three octets.
Definition util.h:56
constexpr std::uint16_t REAL_ID_API
Real ID API.
Definition indices.h:729
const IndexNamePairs & DeviceIndices()
Device-level indices.
Definition indices.cpp:604
constexpr std::uint16_t RECORD_INPUT_DATA
Record Input Data.
Definition indices.h:841
static constexpr int TwoOctetsShift
The bit-shift distance required to move data across two octets.
Definition util.h:53
std::string ToHex(const std::uint8_t *data, std::size_t len)
Hex-encode a byte buffer.
Definition util.cpp:112
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
Definition util.h:32
const IndexNamePairs & ImIndices()
I&M0-I&M15 indices.
Definition indices.cpp:509
static constexpr std::uint8_t LowByteMask
Bitmask used to isolate the lowest significant byte (8 bits) of a larger integer.
Definition util.h:59
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:52
constexpr int blockHeaderLenght
Constant lenght of a blockHeader.
Definition protocol.h:55
constexpr std::uint16_t REAL_ID_SUBSLOT
Real ID Subslot.
Definition indices.h:719
const IndexNamePairs & InterfaceIndices()
Interface-level indices.
Definition indices.cpp:594
std::string GetIndexName(std::uint16_t index)
Get the human-readable name for a record data index.
Definition indices.cpp:642
const IndexNamePairs & PortIndices()
Port-level indices.
Definition indices.cpp:582
constexpr std::uint16_t RECORD_OUTPUT_DATA
Record Output Data.
Definition indices.h:843
constexpr std::array< std::uint8_t, 12 > kPnUuidSuffix
PROFINET UUID suffix, shared by all interface/object UUIDs.
Definition protocol.h:460
std::array< std::uint8_t, uuidLenght > interfaceUuid
Definition rpc.cpp:3249
std::uint8_t flags2
Definition rpc.cpp:3247
std::array< std::uint8_t, 4 > drep
Definition rpc.cpp:3248
std::uint16_t operationNumber
Definition rpc.cpp:3251
std::uint8_t packetType
Definition rpc.cpp:3247
std::uint8_t version
Definition rpc.cpp:3247
bool isLittleEndian
Definition rpc.cpp:3253
Bytes payload
Definition rpc.cpp:3254
std::uint8_t flags1
Definition rpc.cpp:3247
std::uint8_t serialLow
Definition rpc.cpp:3252
std::uint32_t interfaceVersion
Definition rpc.cpp:3250
std::array< std::uint8_t, uuidLenght > objectUuid
Definition rpc.cpp:3249
std::uint32_t sequenceNumber
Definition rpc.cpp:3250
std::array< std::uint8_t, uuidLenght > activityUuid
Definition rpc.cpp:3249
std::uint8_t serialHigh
Definition rpc.cpp:3247
PROFINET DCE/RPC protocol structures and IO-Device connection API.
One slot/subslot's placement within an IOCR's cyclic data frame.
Definition protocol.h:1450
ARBlockReq: establishes an Application Relationship (sent in the CONNECT request).
Definition protocol.h:919
std::array< std::uint8_t, blockHeaderLenght > blockHeader
Nested 6-byte block header.
Definition protocol.h:921
std::uint32_t arProperties
AR property flags (supervisor takeover, device access, etc.).
Definition protocol.h:939
std::uint16_t cmInitiatorActivityTimeoutFactor
Activity timeout factor for the initiator.
Definition protocol.h:942
MacAddress cmInitiatorMacAddress
MAC address of the initiating controller.
Definition protocol.h:933
std::uint16_t sessionKey
Session key chosen by the initiator.
Definition protocol.h:930
std::array< std::uint8_t, uuidLenght > arUuid
Unique AR UUID chosen by the initiator (controller).
Definition protocol.h:927
Bytes cmInitiatorStationName
Station name of the initiating controller (variable length).
Definition protocol.h:951
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:987
std::uint16_t arType
AR type (e.g. IOCARSingle, IOSAR).
Definition protocol.h:924
std::uint16_t initiatorUdpRtport
UDP port the initiator uses for real-time data.
Definition protocol.h:945
static constexpr std::size_t kFixedSize
Size in bytes of the fixed portion of this block.
Definition protocol.h:957
std::array< std::uint8_t, uuidLenght > cmInitiatorObjectUuid
Object UUID of the initiating controller.
Definition protocol.h:936
std::uint16_t stationNameLength
Length in bytes of CmInitiatorStationName.
Definition protocol.h:948
AlarmCRBlockReq: requests establishment of the alarm connection (part of the CONNECT request).
Definition protocol.h:1669
static constexpr std::uint16_t DEFAULT_MAX_ALARM_DATA_LENGTH
Default maximum alarm data length in bytes.
Definition protocol.h:1681
std::array< std::uint8_t, blockHeaderLenght > blockHeader
Nested 6-byte block header.
Definition protocol.h:1691
static constexpr std::uint16_t DEFAULT_TAG_HEADER_LOW
Default low word of the alarm VLAN tag header.
Definition protocol.h:1687
static constexpr BlockType BLOCK_TYPE
Block type identifier for AlarmCRBlockReq.
Definition protocol.h:1672
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:1725
static constexpr std::uint16_t DEFAULT_TAG_HEADER_HIGH
Default high word of the alarm VLAN tag header.
Definition protocol.h:1684
std::uint16_t alarmCrType
Alarm CR type (always 1: Alarm).
Definition protocol.h:1694
std::uint16_t etherTypeLT
EtherType used for alarm frames (0x8892).
Definition protocol.h:1697
std::uint32_t alarmCrProperties
Alarm CR property flags (transport, priority).
Definition protocol.h:1700
static constexpr std::size_t kSize
Size in bytes of this block.
Definition protocol.h:1721
static constexpr std::uint16_t DEFAULT_RTA_RETRIES
Default number of RTA retries.
Definition protocol.h:1678
std::uint16_t localAlarmReference
Local alarm reference chosen by the controller.
Definition protocol.h:1709
std::uint16_t alarmCrTagHeaderHigh
High word of the alarm VLAN tag header.
Definition protocol.h:1715
std::uint16_t maxAlarmDataLength
Maximum alarm data length in bytes.
Definition protocol.h:1712
std::uint16_t alarmCrTagHeaderLow
Low word of the alarm VLAN tag header.
Definition protocol.h:1718
std::uint16_t rtaRetries
Number of RTA retransmission attempts.
Definition protocol.h:1706
std::uint16_t rtaTimeoutFactor
RTA (real-time acyclic) retransmission timeout factor.
Definition protocol.h:1703
static constexpr std::uint16_t DEFAULT_RTA_TIMEOUT_FACTOR
Default RTA timeout factor.
Definition protocol.h:1675
static constexpr std::uint16_t BLOCK_TYPE
Block type identifier for AlarmCRBlockRes.
Definition protocol.h:1747
static PNAlarmCRBlockRes Parse(const Bytes &data)
Parse an AlarmCRBlockRes from raw bytes.
Definition protocol.h:1768
Bytes ToBytes() const
Serialize this header back to raw bytes.
Definition protocol.h:906
BlockType blockType
Block type identifier.
Definition protocol.h:872
static constexpr std::uint16_t DEFAULT_BLOCK_LENGTH
Default block length.
Definition protocol.h:868
std::uint8_t blockVersionHigh
Block format major version.
Definition protocol.h:878
std::uint8_t blockVersionLow
Block format minor version.
Definition protocol.h:881
std::uint16_t blockLength
Length in bytes of the block body (excludes Type/Length fields).
Definition protocol.h:875
static constexpr BlockType IODReadRequestHeader
Generic 6-byte PROFINET block header (Type + Length + Version).
Definition protocol.h:850
static constexpr std::pair< std::uint8_t, std::uint8_t > NAME_OF_STATION
Block option/suboption: station name.
Definition protocol.h:372
IOCRBlockReq header: requests establishment of one IOCR (part of the CONNECT request).
Definition protocol.h:1538
static constexpr std::size_t kSize
Size in bytes of this header.
Definition protocol.h:1596
static constexpr BlockType BLOCK_TYPE
Block type identifier for IOCRBlockReq.
Definition protocol.h:1541
std::uint16_t frameId
Requested frame ID (device may reassign in its response).
Definition protocol.h:1563
MacAddress iocrMulticastMac
Multicast MAC address (used only for multicast IOCRs).
Definition protocol.h:1590
std::uint16_t dataLength
Total cyclic data length in bytes.
Definition protocol.h:1560
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
Definition protocol.h:1566
std::uint16_t dataHoldFactor
Data hold factor (how long to keep the last good frame's data).
Definition protocol.h:1584
std::uint16_t phase
Phase offset within the reduction ratio.
Definition protocol.h:1572
std::uint16_t iocrType
IOCR type (Input/Output/MulticastProvider/MulticastConsumer).
Definition protocol.h:1548
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
Definition protocol.h:1581
std::uint16_t sequence
Sequence value (reserved, usually 0).
Definition protocol.h:1575
std::uint16_t numberOfApis
Number of API entries that follow this header.
Definition protocol.h:1593
std::uint16_t iocrReference
IOCR reference chosen by the controller.
Definition protocol.h:1551
std::uint16_t iocrTagHeader
VLAN tag header used for cyclic frames.
Definition protocol.h:1587
std::uint32_t iocrProperties
IOCR property flags (RT class, redundancy, etc.).
Definition protocol.h:1557
std::uint16_t etherTypeLT
EtherType used for cyclic frames (0x8892).
Definition protocol.h:1554
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
Definition protocol.h:1569
std::array< std::uint8_t, blockHeaderLenght > blockHeader
Nested 6-byte block header.
Definition protocol.h:1545
Bytes ToBytes() const
Serialize this header back to raw bytes.
Definition protocol.h:1639
std::uint32_t frameSendOffset
Frame send offset within the cycle.
Definition protocol.h:1578
static PNIOCRBlockRes Parse(const Bytes &data)
Parse an IOCRBlockRes from raw bytes.
Definition protocol.h:1518
static constexpr std::uint16_t BLOCK_TYPE
Block type identifier for IOCRBlockRes.
Definition protocol.h:1497
IOD read/write request/response header (RPC body for READ/WRITE/IMPLICIT_READ).
Definition protocol.h:758
std::uint32_t api
API number.
Definition protocol.h:769
std::uint16_t slot
Slot number.
Definition protocol.h:772
Bytes ToBytes() const
Serialize this header and payload back to raw bytes.
Definition protocol.h:826
std::array< std::uint8_t, blockHeaderLenght > blockHeader
Nested 6-byte block header (type + length + version).
Definition protocol.h:760
static PNIODHeader Parse(const Bytes &data)
Parse an IOD header and payload from raw bytes.
Definition protocol.h:801
std::uint16_t sequenceNumber
Sequence number for tracking retries.
Definition protocol.h:763
std::array< std::uint8_t, 8 > padding2
Reserved/padding.
Definition protocol.h:790
std::uint16_t padding1
Reserved/padding.
Definition protocol.h:778
Bytes payload
Record data payload.
Definition protocol.h:793
std::uint16_t subslot
Subslot number.
Definition protocol.h:775
std::array< std::uint8_t, uuidLenght > targetArUuid
Target AR UUID for AR handover scenarios (usually zero).
Definition protocol.h:787
std::uint32_t length
Length in bytes of Payload.
Definition protocol.h:784
std::uint16_t index
Record data index being read or written.
Definition protocol.h:781
std::array< std::uint8_t, uuidLenght > arUuid
AR (Application Relationship) UUID this record belongs to.
Definition protocol.h:766
Control block used for Release/PrmBegin/PrmEnd/ApplicationReady/RTClass3 requests.
Definition protocol.h:1008
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID this control operation applies to.
Definition protocol.h:1016
std::uint16_t controlBlockProperties
Control block property flags.
Definition protocol.h:1028
std::uint16_t padding1
Reserved/padding.
Definition protocol.h:1013
std::uint16_t sessionKey
Session key matching the AR.
Definition protocol.h:1019
ControlCommand controlCommand
Control command bitmask (PrmEnd/AppReady/Release/Done/...).
Definition protocol.h:1025
std::uint16_t padding2
Reserved/padding.
Definition protocol.h:1022
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:1056
std::array< std::uint8_t, blockHeaderLenght > blockHeader
Nested 6-byte block header.
Definition protocol.h:1010
I&M0: mandatory identification data (vendor, order ID, serial number, revisions).
Definition protocol.h:1076
static PNInM0 Parse(const Bytes &data)
Parse an I&M0 record from raw bytes.
Definition protocol.h:1140
static constexpr std::uint16_t IDX
Record data index for I&M0.
Definition protocol.h:1079
I&M1: user-assigned tag function and location strings.
Definition protocol.h:1178
static PNInM1 Parse(const Bytes &data)
Parse an I&M1 record from raw bytes.
Definition protocol.h:1199
static constexpr std::uint16_t IDX
Record data index for I&M1.
Definition protocol.h:1181
I&M2: installation date.
Definition protocol.h:1222
static PNInM2 Parse(const Bytes &data)
Parse an I&M2 record from raw bytes.
Definition protocol.h:1240
static constexpr std::uint16_t IDX
Record data index for I&M2.
Definition protocol.h:1225
I&M3: free-text descriptor.
Definition protocol.h:1263
static constexpr std::uint16_t IDX
Record data index for I&M3.
Definition protocol.h:1266
static PNInM3 Parse(const Bytes &data)
Parse an I&M3 record from raw bytes.
Definition protocol.h:1281
I&M4: PROFIsafe signature (binary, not text).
Definition protocol.h:1303
static PNInM4 Parse(const Bytes &data)
Parse an I&M4 record from raw bytes.
Definition protocol.h:1321
static constexpr std::uint16_t IDX
Record data index for I&M4.
Definition protocol.h:1306
I&M5: free-text annotation.
Definition protocol.h:1343
static PNInM5 Parse(const Bytes &data)
Parse an I&M5 record from raw bytes.
Definition protocol.h:1361
static constexpr std::uint16_t IDX
Record data index for I&M5.
Definition protocol.h:1346
I&M6-I&M15: reserved for future use per IEC 61158-6-10.
Definition protocol.h:1387
static constexpr std::uint16_t IDX
Record data index for this reserved I&M slot.
Definition protocol.h:1390
static PNInMReserved Parse(const Bytes &data)
Parse a reserved I&M record from raw bytes.
Definition protocol.h:1402
NRD (Network Representation Data) wrapper carrying the actual IOD payload.
Definition protocol.h:695
Bytes payload
The wrapped IOD data.
Definition protocol.h:712
static PNNRDData Parse(const Bytes &data)
Parse an NRD wrapper and payload from raw bytes.
Definition protocol.h:720
Bytes ToBytes() const
Serialize this wrapper and payload back to raw bytes.
Definition protocol.h:743
std::uint32_t argsLength
Length in bytes of the arguments (usually equals ActualCount).
Definition protocol.h:700
std::uint32_t offset
Byte offset into the logical result (0 unless fragmented).
Definition protocol.h:706
std::uint32_t argsMaximumStatus
Maximum status/argument buffer size accepted by the caller.
Definition protocol.h:697
std::uint32_t maximumCount
Maximum number of bytes the caller can accept.
Definition protocol.h:703
std::uint32_t actualCount
Actual length in bytes of Payload.
Definition protocol.h:709
DCE/RPC PDU header used to carry PROFINET connect/read/write/control requests.
Definition protocol.h:465
Bytes ToBytes() const
Serialize this header and body back to raw bytes.
Definition protocol.h:647
std::array< std::uint8_t, 3 > dataRepresentation
Data representation format label.
Definition protocol.h:533
static constexpr std::uint16_t RELEASE
Operation number: RELEASE.
Definition protocol.h:504
std::uint16_t operationNumber
Operation number (CONNECT/RELEASE/READ/WRITE/CONTROL/IMPLICIT_READ).
Definition protocol.h:557
std::uint16_t fragmentNumber
Fragment number for fragmented PDUs.
Definition protocol.h:569
static constexpr std::size_t kFixedSize
Size in bytes of the fixed portion of this header.
Definition protocol.h:581
std::uint16_t interfaceHint
Interface hint (0xFFFF if unused).
Definition protocol.h:560
static constexpr std::uint16_t READ
Operation number: READ.
Definition protocol.h:507
std::uint8_t flags1
Packet flags, first byte.
Definition protocol.h:527
static PNRPCHeader Parse(const Bytes &data)
Parse an RPC header and body from raw bytes.
Definition protocol.h:614
std::uint8_t packetType
Packet type (REQUEST/RESPONSE/FAULT/...).
Definition protocol.h:524
static constexpr std::uint16_t WRITE
Operation number: WRITE.
Definition protocol.h:510
std::array< std::uint8_t, uuidLenght > interfaceUuid
Target interface UUID (identifies device/controller/supervisor role).
Definition protocol.h:542
std::uint8_t authenticationProtocol
Authentication protocol identifier (unused by PROFINET).
Definition protocol.h:572
std::uint8_t serialNrHigh
High byte of the serial number.
Definition protocol.h:536
static std::array< std::uint8_t, uuidLenght > IfaceUuidDevice()
Interface UUID identifying the IO-Device role.
Definition protocol.h:585
std::uint8_t serialNrLow
Low byte of the serial number.
Definition protocol.h:575
std::uint32_t sequenceNumber
Sequence number within the activity.
Definition protocol.h:554
static constexpr std::uint8_t REJECT
Packet type: REJECT.
Definition protocol.h:486
static constexpr std::uint16_t CONNECT
Operation number: CONNECT.
Definition protocol.h:501
std::uint8_t version
DCE/RPC version (always 4 for PROFINET).
Definition protocol.h:521
static constexpr std::uint8_t REQUEST
Packet type: REQUEST.
Definition protocol.h:468
static constexpr std::uint16_t CONTROL
Operation number: CONTROL.
Definition protocol.h:513
std::uint32_t serverBootTime
Server boot time counter.
Definition protocol.h:548
static constexpr std::uint8_t RESPONSE
Packet type: RESPONSE.
Definition protocol.h:474
std::uint8_t flags2
Packet flags, second byte.
Definition protocol.h:530
static constexpr std::uint16_t IMPLICIT_READ
Operation number: IMPLICIT_READ.
Definition protocol.h:516
Bytes payload
PDU body (NRD data for read/write/control, etc.).
Definition protocol.h:578
std::uint32_t interfaceVersion
Interface version.
Definition protocol.h:551
std::uint16_t activityHint
Activity hint (0xFFFF if unused).
Definition protocol.h:563
std::uint16_t lengthOfBody
Length in bytes of Payload.
Definition protocol.h:566
std::array< std::uint8_t, uuidLenght > objectUuid
Target object UUID.
Definition protocol.h:539
static constexpr std::uint8_t FAULT
Packet type: FAULT.
Definition protocol.h:477
std::array< std::uint8_t, uuidLenght > activityUuid
Activity (call) UUID correlating request/response pairs.
Definition protocol.h:545
Parsed ModuleDiffBlock (0x8104).
Definition blocks.h:415
Parsed PDRealData (0xF841) structure.
Definition blocks.h:221
Parsed RealIdentificationData (0xF000/0x0013) structure.
Definition blocks.h:244
std::vector< SlotInfo > slots
Populated slots/subslots.
Definition blocks.h:246
Complete diagnosis data for one slot/subslot.
Definition diagnosis.h:282
std::vector< AnyChannelDiagnosis > entries
Parsed diagnosis entries.
Definition diagnosis.h:293
Bytes rawData
Raw, undecoded source data.
Definition diagnosis.h:296
std::uint32_t api
API number.
Definition diagnosis.h:284
std::uint16_t subslot
Subslot number.
Definition diagnosis.h:290
std::uint16_t slot
Slot number.
Definition diagnosis.h:287
All I&M records a device supports, read in one call.
Definition rpcTypes.h:227
std::optional< PNInM7 > im7
I&M7 (reserved), if supported.
Definition rpcTypes.h:243
std::optional< PNInM15 > im15
I&M15 (reserved), if supported.
Definition rpcTypes.h:259
std::optional< PNInM9 > im9
I&M9 (reserved), if supported.
Definition rpcTypes.h:247
std::optional< PNInM14 > im14
I&M14 (reserved), if supported.
Definition rpcTypes.h:257
std::optional< PNInM1 > im1
I&M1 (tag function/location), if supported.
Definition rpcTypes.h:231
std::optional< PNInM5 > im5
I&M5 (free-text annotation), if supported.
Definition rpcTypes.h:239
std::optional< PNInM10 > im10
I&M10 (reserved), if supported.
Definition rpcTypes.h:249
std::optional< PNInM3 > im3
I&M3 (free-text descriptor), if supported.
Definition rpcTypes.h:235
std::optional< PNInM6 > im6
I&M6 (reserved), if supported.
Definition rpcTypes.h:241
std::optional< PNInM13 > im13
I&M13 (reserved), if supported.
Definition rpcTypes.h:255
std::optional< PNInM11 > im11
I&M11 (reserved), if supported.
Definition rpcTypes.h:251
std::optional< PNInM2 > im2
I&M2 (installation date), if supported.
Definition rpcTypes.h:233
std::optional< PNInM12 > im12
I&M12 (reserved), if supported.
Definition rpcTypes.h:253
std::optional< PNInM4 > im4
I&M4 (PROFIsafe signature), if supported.
Definition rpcTypes.h:237
std::optional< PNInM0 > im0
I&M0 (mandatory identification data).
Definition rpcTypes.h:229
std::optional< PNInM8 > im8
I&M8 (reserved), if supported.
Definition rpcTypes.h:245
Result of a successful RPCCon::Connect() that also established cyclic IO.
Definition rpcTypes.h:180
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID assigned to this connection.
Definition rpcTypes.h:182
std::string InterfaceName() const
Human-readable interface name.
Definition rpc.cpp:249
std::string interfaceUuid
Interface UUID this endpoint serves.
Definition rpc.h:82
Configuration for establishing cyclic IO alongside an AR.
Definition rpcTypes.h:134
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
Definition rpcTypes.h:145
std::uint16_t dataHoldFactor
Data hold factor (how long to keep the last good frame's data).
Definition rpcTypes.h:148
std::vector< IOSlot > slots
Slots to include in the cyclic data frames.
Definition rpcTypes.h:136
std::vector< std::string > Validate() const
Sanity-check this configuration.
Definition rpc.cpp:625
double CycleTimeMs() const
Compute the cycle time in milliseconds.
Definition rpcTypes.h:161
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
Definition rpcTypes.h:142
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
Definition rpcTypes.h:139
One slot/subslot's expected module configuration and IO data sizes.
Definition rpcTypes.h:91
std::uint16_t inputLength
Expected input data length in bytes (0 if no input data).
Definition rpcTypes.h:99
std::uint16_t slot
Slot number.
Definition rpcTypes.h:93
std::uint16_t outputLength
Expected output data length in bytes (0 if no output data).
Definition rpcTypes.h:102
std::uint16_t subslot
Subslot number.
Definition rpcTypes.h:96
Result of probing one index in EnumerateIndices().
Definition rpcTypes.h:270
std::uint8_t errorCode1
PNIOError ErrorCode1, if the error was a PNIOError.
Definition rpcTypes.h:280
std::string name
Human-readable name of the index.
Definition rpcTypes.h:274
std::size_t size
Payload size in bytes; valid when Status == "readable".
Definition rpcTypes.h:276
std::uint8_t errorCode2
PNIOError ErrorCode2, if the error was a PNIOError.
Definition rpcTypes.h:282
std::string error
Error message; valid when Status == "error".
Definition rpcTypes.h:278
std::string status
Probe outcome: "readable", "empty", or "error".
Definition rpcTypes.h:272
Configuration options for an RPC connection.
Definition rpcTypes.h:292
std::uint16_t rpcPort
UDP port used for RPC communication.
Definition rpcTypes.h:297