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 <algorithm>
13#include <cstdio>
14#include <cstring>
15#include <iostream>
16#include <random>
17#include <ranges>
18#include <sstream>
19
20#include "profinet/cyclic.h"
21
22namespace profinet::rpc
23{
24
25namespace
26{
27
28std::array<std::uint8_t, uuidLenght> RandomUuidBytes()
29{
30 static thread_local std::mt19937 rng{std::random_device{}()};
31 static constexpr int maxByteValue = 255;
32 static thread_local std::uniform_int_distribution<int> dist(0, maxByteValue);
33 std::array<std::uint8_t, uuidLenght> out{};
34 for (auto& b : out)
35 {
36 b = static_cast<std::uint8_t>(dist(rng));
37 }
38 return out;
39}
40
41std::uint16_t RandomNonzeroU16()
42{
43 static thread_local std::mt19937 rng{std::random_device{}()};
44
45 static constexpr int maxU16Value = 0xFFFF;
46 static thread_local std::uniform_int_distribution<int> dist(0, maxU16Value);
47 const int v = dist(rng);
48 return static_cast<std::uint16_t>(v == 0 ? 1 : v);
49}
50
51} // namespace
52
53// =============================================================================
54// UUID helpers
55// =============================================================================
56std::array<std::uint8_t, uuidLenght> BuildObjectUuid(std::uint8_t b10, std::uint8_t b11, std::uint8_t b12, std::uint8_t b13,
57 std::uint8_t b14, std::uint8_t b15)
58{
59 // OBJECT_UUID_PREFIX (10 bytes, per protocol.h): DE A0 00 00 + first 6
60 // bytes of the shared PROFINET UUID suffix (6C 97 11 D1 82 71).
61
62 // Protocol constant hex values for the header
63 static constexpr std::uint8_t pnPrefixByte0 = 0xDE;
64 static constexpr std::uint8_t pnPrefixByte1 = 0xA0;
65 static constexpr std::uint8_t pnPrefixByte2 = 0x00;
66 static constexpr std::uint8_t pnPrefixByte3 = 0x00;
67
68 // UUID field indices mapped to official UUID structure
69 static constexpr std::size_t uuidIdxTimeLow0 = 0;
70 static constexpr std::size_t uuidIdxTimeLow1 = 1;
71 static constexpr std::size_t uuidIdxTimeLow2 = 2;
72 static constexpr std::size_t uuidIdxTimeLow3 = 3;
73
74 static constexpr std::size_t uuidIdxTimeMid0 = 4;
75 static constexpr std::size_t suffixBytesToCopy = 6;
76
77 static constexpr std::size_t uuidIdxNode0 = 10;
78 static constexpr std::size_t uuidIdxNode1 = 11;
79 static constexpr std::size_t uuidIdxNode2 = 12;
80 static constexpr std::size_t uuidIdxNode3 = 13;
81 static constexpr std::size_t uuidIdxNode4 = 14;
82 static constexpr std::size_t uuidIdxNode5 = 15;
83
84 std::array<std::uint8_t, uuidLenght> objectUuid{};
85
86 // Write the fixed 4-byte PROFINET protocol prefix (into time_low)
87 objectUuid[uuidIdxTimeLow0] = pnPrefixByte0;
88 objectUuid[uuidIdxTimeLow1] = pnPrefixByte1;
89 objectUuid[uuidIdxTimeLow2] = pnPrefixByte2;
90 objectUuid[uuidIdxTimeLow3] = pnPrefixByte3;
91
92 // Copy the 6-byte shared suffix (populates time_mid, time_hi, and clock_seq)
93 std::ranges::copy(kPnUuidSuffix | std::views::take(suffixBytesToCopy),
94 (objectUuid | std::views::drop(uuidIdxTimeMid0)).begin());
95
96 // Populate the trailing 6 unique bytes (populates node ID)
97 objectUuid[uuidIdxNode0] = b10;
98 objectUuid[uuidIdxNode1] = b11;
99 objectUuid[uuidIdxNode2] = b12;
100 objectUuid[uuidIdxNode3] = b13;
101 objectUuid[uuidIdxNode4] = b14;
102 objectUuid[uuidIdxNode5] = b15;
103
104 return objectUuid;
105}
106std::string UuidBytesToString(const std::array<std::uint8_t, uuidLenght>& data)
107{
108 wire::Reader r(data);
109 // DCE/RPC UUIDs use mixed byte order:
110 // time_low, time_mid and time_hi_and_version are little-endian
111 // clock sequence and node are big-endian.
112 const std::uint32_t timeLow = r.U32LittleEndian();
113 const std::uint16_t timeMid = r.U16LittleEndian();
114 const std::uint16_t timeHi = r.U16LittleEndian();
115 const std::uint16_t clockSeq = r.U16();
116
117 const auto node = r.Fixed<6U>();
118
119 // Format the first tracking segments and cleanly append the remaining hex data
120 const std::string out = std::format("{:08x}-{:04x}-{:04x}-{:04x}-{}",
121 timeLow, timeMid, timeHi, clockSeq,
122 ToHex(node.data(), node.size()));
123 return out;
124}
125
126std::array<std::uint8_t, uuidLenght> StringToUuidBytes(const std::string& uuidStr)
127{
128 // UUID Structural Constants
129 static constexpr std::size_t expectedHexLength = 32;
130 static constexpr int hexBase = 16;
131 static constexpr std::size_t singleByteHexLength = 2;
132
133 // Component Substring Offsets and Lengths
134 static constexpr std::size_t timeLowOffset = 0U;
135 static constexpr std::size_t timeLowLength = 8U;
136
137 static constexpr std::size_t timeMidOffset = 8U;
138 static constexpr std::size_t timeMidLength = 4U;
139
140 static constexpr std::size_t timeHiOffset = 12U;
141 static constexpr std::size_t timeHiLength = 4U;
142
143 static constexpr std::size_t clockSeqOffset = 16U;
144 static constexpr std::size_t clockSeqLength = 4U;
145
146 static constexpr std::size_t nodeDataStartHexOffset = 20;
147
148 std::string hex;
149 hex.reserve(expectedHexLength);
150 for (const char c : uuidStr)
151 {
152 if (c != '-')
153 {
154 hex.push_back(c);
155 }
156 }
157
158 if (hex.size() != expectedHexLength)
159 {
160 throw std::invalid_argument("Invalid UUID string: " + uuidStr);
161 }
162
163 // Helper lambda to parse character hex into a bytes
164 auto parseValue = [&hex](std::size_t offset, std::size_t length) -> std::uint32_t
165 {
166 auto hexString = hex.substr(offset, length);
167 auto hexLong = std::stoul(hexString, nullptr, hexBase);
168 return static_cast<std::uint32_t>(hexLong);
169 };
170
171 const std::uint32_t timeLow = parseValue(timeLowOffset, timeLowLength);
172 const std::uint16_t timeMid = static_cast<std::uint16_t>(parseValue(timeMidOffset, timeMidLength));
173 const std::uint16_t timeHi = static_cast<std::uint16_t>(parseValue(timeHiOffset, timeHiLength));
174 const std::uint16_t clockSeq = static_cast<std::uint16_t>(parseValue(clockSeqOffset, clockSeqLength));
175
176 Bytes out{};
177 out.reserve(uuidLenght);
178
179 // DCE/RPC UUID: first three fields are little-endian.
180 wire::PutLittleEndian(out, timeLow);
181 wire::PutLittleEndian(out, timeMid);
182 wire::PutLittleEndian(out, timeHi);
183
184 // DCE/RPC UUID: clock sequence is big-endian.
185 wire::PutBigEndian(out, clockSeq);
186
187 // Node ID is six raw bytes and has no integer byte-order conversion.
188 for (std::size_t i = 0U; i < 6U; ++i)
189 {
190 auto hexString = hex.substr(nodeDataStartHexOffset + i * singleByteHexLength, singleByteHexLength);
191 auto hexLong = std::stoul(hexString, nullptr, hexBase);
192 const auto nodeByte = static_cast<std::uint8_t>(hexLong);
193 out.push_back(nodeByte);
194 }
195
196 if (out.size() != uuidLenght)
197 {
198 throw std::logic_error("UUID serialization produced an invalid size");
199 }
200
201 std::array<std::uint8_t, uuidLenght> result{};
202 std::ranges::copy(out, result.begin());
203
204 return result;
205}
206
207// =============================================================================
208// EPM
209// =============================================================================
210
211std::string EPMEndpoint::InterfaceName() const
212{
213 std::string lower = interfaceUuid;
214 std::ranges::transform(lower, lower.begin(), ::tolower);
215
216 if (lower == UUID_PNIO_DEVICE)
217 {
218 return "PNIO-Device";
219 }
220 if (lower == UUID_PNIO_CONTROLLER)
221 {
222 return "PNIO-Controller";
223 }
224 if (lower == UUID_PNIO_SUPERVISOR)
225 {
226 return "PNIO-Supervisor";
227 }
228 if (lower == UUID_PNIO_PARAMETER_SERVER)
229 {
230 return "PNIO-ParameterServer";
231 }
232 if (lower == UUID_EPM_V4)
233 {
234 return "EPM";
235 }
236 return "Unknown(" + interfaceUuid + ")";
237}
238
239std::optional<EPMEndpoint> ParseEpmTower(const Bytes& towerData)
240{
241 if (towerData.size() < sizeof(std::uint16_t))
242 {
243 return std::nullopt;
244 }
245
246 wire::Reader r(towerData);
247 const auto floorCount = r.U16LittleEndian();
248
249 EPMEndpoint endpoint;
250
251 for (std::uint16_t floorIdx = 0; floorIdx < floorCount; ++floorIdx)
252 {
253 if (r.Remaining() < 2U * sizeof(std::uint16_t))
254 {
255 break;
256 }
257
258 const auto lhsLen = r.U16LittleEndian();
259
260 if (r.Remaining() < lhsLen + sizeof(std::uint16_t))
261 {
262 break;
263 }
264
265 const Bytes lhsData = r.ReadBytes(lhsLen);
266
267 const auto rhsLen = r.U16LittleEndian();
268
269 if (r.Remaining() < rhsLen)
270 {
271 break;
272 }
273
274 const Bytes rhsData = r.ReadBytes(rhsLen);
275
276 if (lhsLen < 1U)
277 {
278 continue;
279 }
280
281 const std::uint8_t protocolId = lhsData[0];
282
283 if (protocolId == 0x0D && lhsLen >= 19U)
284 {
285 if (floorIdx == 0U)
286 {
287 wire::Reader lhsReader(lhsData);
288 lhsReader.SkipTo(1U);
289
290 const auto uuidBytes = lhsReader.Fixed<uuidLenght>();
291 endpoint.interfaceUuid = UuidBytesToString(uuidBytes);
292
293 lhsReader.SkipTo(1U + uuidLenght);
294 endpoint.interfaceVersionMajor = lhsReader.U16LittleEndian();
295
296 if (rhsLen >= sizeof(std::uint16_t))
297 {
298 wire::Reader rhsReader(rhsData);
299 endpoint.interfaceVersionMinor = rhsReader.U16LittleEndian();
300 }
301 }
302 }
303 else if (protocolId == 0x0A)
304 {
305 endpoint.protocol = "ncadg_ip_udp";
306 }
307 else if (protocolId == 0x08 && rhsLen >= sizeof(std::uint16_t))
308 {
309 wire::Reader rhsReader(rhsData);
310 endpoint.port = rhsReader.U16();
311 }
312 else if (protocolId == 0x09 && rhsLen >= 4U)
313 {
314 endpoint.ipAddress =
315 std::to_string(rhsData[0]) + "." +
316 std::to_string(rhsData[1]) + "." +
317 std::to_string(rhsData[2]) + "." +
318 std::to_string(rhsData[3]);
319 }
320 }
321
322 if (endpoint.interfaceUuid.empty())
323 {
324 return std::nullopt;
325 }
326
327 return endpoint;
328}
329
330std::vector<EPMEndpoint> RPCCon::EpmLookup(const std::string& ip, std::uint16_t port, double timeoutSec,
331 std::optional<std::string> interfaceFilter)
332{
333 std::vector<EPMEndpoint> results;
334
335 RpcTransport transport(ioContext);
336
337 transport.Open(ip, port);
338
339 // Build little-endian RPC header (kFixedSize, 80 bytes).
340 auto activityUuidArr = RandomUuidBytes();
341 Bytes activityUuidBytes(activityUuidArr.begin(), activityUuidArr.end());
342 auto objectUuidBytes = std::array<std::uint8_t, uuidLenght>{}; // NULL object UUID
343 auto interfaceUuidBytes = StringToUuidBytes(UUID_EPM_V4);
344
345 Bytes header;
346 header.push_back(4); // version
347 header.push_back(PNRPCHeader::REQUEST); // packet_type = REQUEST, 0x00
348 auto flags1 = static_cast<std::uint8_t>(raw::Flags1::Idempotent);
349 header.push_back(flags1); // flags1
350 header.push_back(0x00); // flags2
352 header.push_back(0x00);
353 header.push_back(0x00); // drep (little-endian)
354 header.push_back(0); // serial_high
355 wire::PutFixed(header, objectUuidBytes);
356 wire::PutFixed(header, interfaceUuidBytes);
357 wire::PutFixed(header, activityUuidBytes);
358
359 wire::PutLittleEndian<std::uint32_t>(header, 0); // server boot time
360 wire::PutLittleEndian<std::uint32_t>(header, 3); // interface version
361 wire::PutLittleEndian<std::uint32_t>(header, 0); // sequence number
362
363 wire::PutLittleEndian<std::uint16_t>(header, EPM_LOOKUP);
364
367
368 const std::size_t lengthOfBodyOffset = header.size();
369 wire::PutLittleEndian<std::uint16_t>(header, 0); // length_of_body placeholder, patched below
370 wire::PutLittleEndian<std::uint16_t>(header, 0); // fragment_number
371
372 header.push_back(0); // authentication_protocol
373 header.push_back(0); // serial_low
374
375 const std::uint32_t inquiryType = interfaceFilter ? EPM_INQUIRY_INTERFACE : EPM_INQUIRY_ALL;
376 Bytes body;
377 wire::PutLittleEndian(body, inquiryType);
378 if (interfaceFilter)
379 {
380 auto ifaceBytes = StringToUuidBytes(*interfaceFilter);
381 body.insert(body.end(), ifaceBytes.begin(), ifaceBytes.end());
382 wire::PutLittleEndian<std::uint16_t>(body, 1);
383 wire::PutLittleEndian<std::uint16_t>(body, 0);
384 }
385 else
386 {
387 body.insert(body.end(), uuidLenght, 0);
388 wire::PutLittleEndian<std::uint16_t>(body, 0);
389 wire::PutLittleEndian<std::uint16_t>(body, 0);
390 }
391 wire::PutLittleEndian<std::uint32_t>(body, 0); // vers option
392 wire::PutLittleEndian<std::uint32_t>(body, 0); // entry handle
393 constexpr std::uint32_t maxEnts = 100U;
394 wire::PutLittleEndian(body, maxEnts); // max ents
395
396 wire::PutLittleEndian<std::uint16_t>(header.data() + lengthOfBodyOffset, body.size());
397 // header was built with a 2-byte placeholder at lengthOfBodyOffset and
398 // is already exactly 80 bytes (the fixed RPC header size); no resize needed.
399
400 Bytes request = header;
401 request.insert(request.end(), body.begin(), body.end());
402
403 auto timeoutInMilliSeconds = std::chrono::floor<std::chrono::milliseconds>(std::chrono::duration<double>(timeoutSec));
404 Bytes data = transport.SendReceive(request, timeoutInMilliSeconds);
405
406 if (data.size() < PNRPCHeader::kFixedSize)
407 {
408 return results;
409 }
410 // data.resize(static_cast<std::size_t>(n));
411
412 const std::uint8_t respType = data[1];
413 if (respType == 0x03 || respType != 0x02)
414 {
415 return results; // FAULT or not RESPONSE
416 }
417 wire::Reader r(data);
418 constexpr std::uint16_t kBodyLenIndex = 74U;
419 r.SkipTo(kBodyLenIndex);
420
421 std::uint16_t bodyLen = r.U16LittleEndian();
422 if (data.size() < PNRPCHeader::kFixedSize + static_cast<std::size_t>(bodyLen))
423 {
424 bodyLen = static_cast<std::uint16_t>(data.size() - PNRPCHeader::kFixedSize);
425 }
426 Bytes bodyData(data.begin() + PNRPCHeader::kFixedSize, data.begin() + PNRPCHeader::kFixedSize + bodyLen);
427 if (bodyData.size() < 12)
428 {
429 return results;
430 }
431
432 std::size_t offset = sizeof(std::uint32_t); // skip entry_handle
433 wire::Reader b(bodyData);
434
435 b.SkipTo(offset);
436 const std::uint32_t numEnts = b.U32LittleEndian();
437 offset += sizeof(std::uint32_t);
438
439 offset += 3U * sizeof(std::uint32_t); // max_count, offset, actual_count
440
441 for (std::uint32_t i = 0; i < numEnts; ++i)
442 {
443 if (offset + uuidLenght > bodyData.size())
444 {
445 break;
446 }
447 b.SkipTo(offset);
448 const auto entryUuidBytes = b.Fixed<uuidLenght>();
449
450 const std::string entryObjectUuid = UuidBytesToString(entryUuidBytes);
451 offset += uuidLenght;
452
453 if (bodyData.size() < offset + sizeof(std::uint32_t))
454 {
455 break;
456 }
457 offset += sizeof(std::uint32_t); // tower p pointer
458
459 if (bodyData.size() < offset + sizeof(std::uint32_t))
460 {
461 break;
462 }
463 b.SkipTo(offset);
464 const std::uint32_t annotationLen = b.U32LittleEndian();
465 offset += sizeof(std::uint32_t);
466
467 std::string annotation;
468 if (annotationLen > 0 && offset + annotationLen <= bodyData.size())
469 {
470 const std::size_t end = offset + annotationLen;
471 std::size_t trim = end;
472 while (trim > offset && bodyData[trim - 1] == 0)
473 {
474 --trim;
475 }
476 annotation = std::string(bodyData.begin() + offset, bodyData.begin() + trim);
477 }
478 offset += annotationLen;
479 offset = (offset + 3) & ~static_cast<std::size_t>(3);
480
481 if (offset + 4 > bodyData.size())
482 {
483 break;
484 }
485 b.SkipTo(offset);
486 const auto towerLen = b.U32LittleEndian();
487 offset += sizeof(std::uint32_t);
488
489 if (bodyData.size() < offset + towerLen)
490 {
491 break;
492 }
493 const Bytes towerData(bodyData.begin() + offset, bodyData.begin() + offset + towerLen);
494 offset += towerLen;
495 offset = (offset + 3) & ~static_cast<std::size_t>(3);
496
497 auto endpoint = ParseEpmTower(towerData);
498 if (endpoint)
499 {
500 endpoint->objectUuid = entryObjectUuid;
501 endpoint->annotation = annotation;
502 results.push_back(*endpoint);
503 }
504 }
505
506 return results;
507}
508
510 const MacAddress& src,
511 const std::string& name,
512 double timeoutSec)
513{
515 Bytes(name.begin(), name.end()));
516 auto responses = dcp::ReadResponse(sock, src, timeoutSec, /*once=*/true);
517
518 if (responses.empty())
519 {
520 dcp::SendDiscover(sock, src);
521 responses = dcp::ReadResponse(sock, src, timeoutSec);
522 for (const auto& [Mac, Blocks] : responses)
523 {
524 dcp::DCPDeviceDescription desc(Mac, Blocks);
525 std::string lowerDescName = desc.name;
526 std::string lowerTarget = name;
527 std::ranges::transform(lowerDescName, lowerDescName.begin(), ::tolower);
528 std::ranges::transform(lowerTarget, lowerTarget.begin(), ::tolower);
529 if (lowerDescName == lowerTarget)
530 {
531 return desc;
532 }
533 }
534 throw DCPDeviceNotFoundError("Device with name '" + name + "' not found");
535 }
536
537 const auto& [Mac, Blocks] = *responses.begin();
538 // NOLINTNEXTLINE(modernize-return-braced-init-list)
539 return dcp::DCPDeviceDescription(Mac, Blocks);
540}
541
542// =============================================================================
543// IOCRSetup
544// =============================================================================
545
546std::vector<std::string> IOCRSetup::Validate() const
547{
548 std::vector<std::string> warnings;
549 const double cycleMs = CycleTimeMs();
550 if (cycleMs < 1)
551 {
552 warnings.emplace_back("Cycle time too fast - sub-1ms is impractical without hardware support");
553 }
554 else if (cycleMs < cyclic::MIN_CYCLE_MS)
555 {
556 warnings.emplace_back("Cycle time may cause jitter (8ms+ recommended)");
557 }
558 if (watchdogFactor < 3)
559 {
560 warnings.emplace_back("Watchdog factor too low (use 6+)");
561 }
562 if (slots.empty())
563 {
564 warnings.emplace_back("No IO slots configured");
565 }
566 return warnings;
567}
568
569// =============================================================================
570// RPCCon
571// =============================================================================
572
574 std::unique_ptr<IRpcTransport> rpcTransport,
575 std::unique_ptr<IRpcTransport> ccontrolTransport,
576 asio::io_context* context)
577 : info(info),
578 timeout(options.timeoutSec),
579 sessionKey(RandomNonzeroU16()),
580 rpcContext(context ? nullptr : std::make_unique<asio::io_context>()),
581 rpcContextRunner(context ? nullptr : std::make_unique<AsioIoContextRunner>(*rpcContext)), // use this only when not called
582 ioContext(context ? *context : *rpcContext),
583 rpcTransport(std::move(rpcTransport)),
584 ccontrolTransport(std::move(ccontrolTransport))
585{
586 if (this->rpcTransport == nullptr)
587 {
588 this->rpcTransport = std::make_unique<RpcTransport>(ioContext);
589 }
590 if (this->ccontrolTransport == nullptr)
591 {
592 this->ccontrolTransport = std::make_unique<RpcTransport>(ioContext);
593 }
594
595 arUuid = RandomUuidBytes();
596 activityUuid = RandomUuidBytes();
598
599 localObjectUuid = BuildObjectUuid(0x00, 0x01, 0x76, 0x54, 0x32, 0x10);
602
603 this->rpcTransport->Open(info.ip, options.rpcPort);
604 const std::string anyIp = asio::ip::address_v4::any().to_string();
605
606 // Fixed listener: 34964 -> 0x8894.
607 this->ccontrolTransport->Open(anyIp, RPC_PORT, true);
608}
609
611{
612 Close();
613}
614
615PNRPCHeader RPCCon::CreateRpc(std::uint16_t operation, const Bytes& nrd)
616{
617 PNRPCHeader h;
618 h.version = 4;
620 h.flags1 = static_cast<std::uint8_t>(raw::Flags1::Idempotent);
621 h.flags2 = 0x00;
622 h.dataRepresentation = {0x00, 0x00, 0x00};
623 h.serialNrHigh = 0;
627 h.serverBootTime = 0;
628 h.interfaceVersion = 1;
629 h.sequenceNumber = sequenceNumber++; // drep?
630 h.operationNumber = operation; // drep?
633 h.lengthOfBody = static_cast<std::uint16_t>(nrd.size()); // drep?
634 h.fragmentNumber = 0; // drep?
635 h.authenticationProtocol = 0; // drep?
636 h.serialNrLow = 0; // drep?
637 h.payload = nrd;
638 return h;
639}
640
642{
643 PNNRDData nrd;
644 nrd.argsMaximumStatus = 1500;
645 nrd.argsLength = static_cast<std::uint32_t>(payload.size());
646 nrd.maximumCount = 1500;
647 nrd.offset = 0;
648 nrd.actualCount = static_cast<std::uint32_t>(payload.size());
649 nrd.payload = payload;
650 return nrd;
651}
652
653Bytes RPCCon::BuildAlarmCrBlock(int transport, int priority) const
654{
655 auto properties = static_cast<std::uint32_t>((transport << 1) | priority);
656
657 PNBlockHeader header;
659 header.blockLength = static_cast<std::uint16_t>(PNAlarmCRBlockReq::kSize - 4);
662
663 PNAlarmCRBlockReq alarmCr;
664 std::ranges::copy(header.ToBytes() | std::views::take(blockHeaderLength), alarmCr.blockHeader.begin());
665
666 alarmCr.alarmCrType = 0x0001;
667 alarmCr.etherTypeLT = transport == 0 ? PROFINET_ETHERTYPE : IP_ETHERTYPE;
668 alarmCr.alarmCrProperties = properties;
675
676 return alarmCr.ToBytes();
677}
678
679int RPCCon::ParseAlarmCrResponse(const Bytes& responseData)
680{
681 std::size_t offset = 0;
682 while (offset + blockHeaderLength <= responseData.size())
683 {
684 auto hdr = PeekBlockHeader(responseData, offset);
685 const std::size_t blockSize = 4 + hdr.blockLength;
686
687 if (offset + blockSize > responseData.size())
688 {
689 break;
690 }
691 if (hdr.blockType == PNAlarmCRBlockRes::BLOCK_TYPE)
692 {
693 const Bytes blockBytes(
694 responseData.begin() + offset,
695 responseData.begin() + offset + blockSize);
696 auto res = PNAlarmCRBlockRes::Parse(blockBytes);
697 return res.localAlarmReference;
698 }
699 offset += 4 + hdr.blockLength;
700 }
701 return -1;
702}
704{
705 // ===========================================================================
706 // Build the IOCR from the resolved IO configuration.
707 //
708 // IOCRSetup::slots is the canonical resolved configuration produced by
709 // BuildIoSlots().
710 //
711 // The two IOSlot flags determine whether a slot participates in the
712 // corresponding IOCR:
713 //
714 // inputIocrObject -> Input CR
715 // outputIocrObject -> Output CR
716 //
717 // Within an IOCR:
718 //
719 // INPUT CR:
720 // inputIocrObject -> IODataObject
721 // outputIocrObject -> IOCSObject
722 //
723 // OUTPUT CR:
724 // outputIocrObject -> IODataObject
725 // inputIocrObject -> IOCSObject
726 //
727 // This deliberately contains no knowledge of AUMA, Phoenix, slot numbers,
728 // subslot numbers, module identifiers, or application data sizes.
729 // ===========================================================================
730
731 if (iocrType != IocrType::Input && iocrType != IocrType::Output)
732 {
733 throw std::invalid_argument("BuildIocrBlock: unsupported IOCR type " + std::string(magic_enum::enum_name(iocrType)));
734 }
735
736 // ---------------------------------------------------------------------------
737 // Collect IOData and IOCS objects.
738 //
739 // We keep the objects in the same order as setup.slots. This is important:
740 // BuildIoSlots() defines the resolved configuration order, and therefore
741 // also defines the deterministic wire representation.
742 // ---------------------------------------------------------------------------
743
744 std::vector<IOCRAPIObject> ioDataObjects;
745 std::vector<IOCRAPIObject> iocsObjects;
746
747 std::uint16_t frameOffset = 0;
748 std::uint16_t dapOffset = 0;
749 std::uint16_t processByteLength = 0;
750 for (const IOSlot& slot : setup.slots)
751 {
752 std::cerr
753 << "IO slot "
754 << slot.slot << "/"
755 << std::hex << slot.subslot << std::dec
756 << " in=" << slot.inputLength
757 << " out=" << slot.outputLength
758 << '\n';
759
760 if (iocrType == IocrType::Input)
761 {
762 if ((slot.inputLength >= 0 && slot.outputLength == 0) || (slot.inputLength > 0 && slot.outputLength > 0))
763 {
764 ioDataObjects.push_back({.slotNumber = slot.slot, .subslotNumber = slot.subslot, .frameOffset = frameOffset});
765 processByteLength = slot.inputLength;
766 frameOffset++;
767 frameOffset += slot.inputLength;
768 }
769
770 if (slot.outputLength > 0)
771 {
772 iocsObjects.push_back({.slotNumber = slot.slot, .subslotNumber = slot.subslot, .frameOffset = frameOffset});
773 frameOffset++;
774 }
775 }
776 else
777 {
778 if ((slot.inputLength >= 0 && slot.outputLength == 0) || (slot.inputLength > 0 && slot.outputLength > 0))
779 {
780 iocsObjects.push_back({.slotNumber = slot.slot, .subslotNumber = slot.subslot, .frameOffset = frameOffset});
781 processByteLength = slot.outputLength;
782 frameOffset++;
783 }
784 if (slot.outputLength > 0)
785 {
786 ioDataObjects.push_back({.slotNumber = slot.slot, .subslotNumber = slot.subslot, .frameOffset = frameOffset});
787 frameOffset++;
788 frameOffset += slot.outputLength;
789 }
790 }
791 }
792
793 // ---------------------------------------------------------------------------
794 // Correct the frameOffset
795 // ---------------------------------------------------------------------------
796 const std::uint16_t numberOfIocsBytes = iocsObjects.size();
797 const std::uint16_t numberOfIopsBytes = ioDataObjects.size();
798
799 const std::uint16_t dataLength = numberOfIocsBytes + processByteLength + numberOfIopsBytes;
800 /*
801 if (iocrType == IocrType::Input)
802 {
803 std::uint16_t dapModuleOffset = dataLength - dapOffset;
804 std::uint16_t ioModuleOffset = 0;
805
806 for (const IOSlot& slot : setup.slots)
807 {
808 if (slot.inputLength == 0 && slot.outputLength == 0)
809 {
810 auto it = std::ranges::find_if(ioDataObjects,
811 [&slot](const IOCRAPIObject& s)
812 {
813 return slot.slot == s.slotNumber && slot.subslot == s.subslotNumber;
814 });
815 if (it == ioDataObjects.end())
816 {
817 continue;
818 }
819 it->frameOffset = dapModuleOffset;
820 dapModuleOffset++;
821 }
822 if (slot.inputLength > 0)
823 {
824 auto it = std::ranges::find_if(ioDataObjects,
825 [&slot](const IOCRAPIObject& s)
826 {
827 return slot.slot == s.slotNumber && slot.subslot == s.subslotNumber;
828 });
829 if (it == ioDataObjects.end())
830 {
831 continue;
832 }
833 if (frameOffset != 0)
834 {
835 frameOffset--;
836 }
837 it->frameOffset = frameOffset;
838 }
839 if (slot.outputLength > 0)
840 {
841 auto it = std::ranges::find_if(iocsObjects,
842 [&slot](const IOCRAPIObject& s)
843 {
844 return slot.slot == s.slotNumber && slot.subslot == s.subslotNumber;
845 });
846 if (it == iocsObjects.end())
847 {
848 continue;
849 }
850 if (frameOffset != 0)
851 {
852 frameOffset--;
853 }
854 it->frameOffset = frameOffset;
855 }
856 }
857 }
858 else
859 {
860 std::uint16_t dapModuleOffset = dataLength - dapOffset;
861 std::uint16_t ioModuleOffset = 0;
862
863 for (const IOSlot& slot : setup.slots)
864 {
865 if (slot.inputLength == 0 && slot.outputLength == 0)
866 {
867 auto it = std::ranges::find_if(iocsObjects,
868 [&slot](const IOCRAPIObject& s)
869 {
870 return slot.slot == s.slotNumber && slot.subslot == s.subslotNumber;
871 });
872 if (it == iocsObjects.end())
873 {
874 continue;
875 }
876 it->frameOffset = dapModuleOffset;
877 dapModuleOffset++;
878 }
879 if (slot.inputLength > 0)
880 {
881 auto it = std::ranges::find_if(iocsObjects,
882 [&slot](const IOCRAPIObject& s)
883 {
884 return slot.slot == s.slotNumber && slot.subslot == s.subslotNumber;
885 });
886 if (it == iocsObjects.end())
887 {
888 continue;
889 }
890 if (frameOffset != 0)
891 {
892 frameOffset--;
893 }
894 it->frameOffset = frameOffset;
895 }
896 if (slot.outputLength > 0)
897 {
898 auto it = std::ranges::find_if(ioDataObjects,
899 [&slot](const IOCRAPIObject& s)
900 {
901 return slot.slot == s.slotNumber && slot.subslot == s.subslotNumber;
902 });
903 if (it == ioDataObjects.end())
904 {
905 continue;
906 }
907 if (frameOffset != 0)
908 {
909 frameOffset--;
910 }
911 it->frameOffset = frameOffset;
912 }
913 }
914 *
915 std::uint16_t ioModuleOffset = 0;
916
917 for (const IOSlot& slot : setup.slots)
918 {
919 if ((slot.inputLength >= 0 && slot.outputLength == 0) || (slot.inputLength > 0 && slot.outputLength > 0))
920 {
921 auto it = std::ranges::find_if(iocsObjects,
922 [&slot](const IOCRAPIObject& s)
923 {
924 return slot.slot == s.slotNumber && slot.subslot == s.subslotNumber;
925 });
926 if (it == ioDataObjects.end())
927 {
928 continue;
929 }
930 it->frameOffset = ioModuleOffset;
931 ioModuleOffset++;
932 }
933 else if (slot.outputLength > 0)
934 {
935 auto it = std::ranges::find_if(ioDataObjects,
936 [&slot](const IOCRAPIObject& s)
937 {
938 return slot.slot == s.slotNumber && slot.subslot == s.subslotNumber;
939 });
940 if (it == ioDataObjects.end())
941 {
942 continue;
943 }
944 it->frameOffset = ioModuleOffset;
945 ioModuleOffset++;
946 }
947 }
948*
949}
950*/
951 // ---------------------------------------------------------------------------
952 // Validate object counts before converting to the wire uint16_t fields.
953 // ---------------------------------------------------------------------------
954 if (ioDataObjects.size() > std::numeric_limits<std::uint16_t>::max())
955 {
956 throw std::invalid_argument("BuildIocrBlock: too many IODataObjects");
957 }
958
959 if (iocsObjects.size() > std::numeric_limits<std::uint16_t>::max())
960 {
961 throw std::invalid_argument("BuildIocrBlock: too many IOCSObjects");
962 }
963
964 Bytes apiBlock;
965
966 // API = 0
967 wire::PutU32(apiBlock, 0);
968
969 wire::PutU16(apiBlock, static_cast<std::uint16_t>(ioDataObjects.size()));
970
971 for (const IOCRAPIObject& object : ioDataObjects)
972 {
973 const Bytes bytes = object.ToBytes();
974 apiBlock.insert(apiBlock.end(), bytes.begin(), bytes.end());
975 }
976
977 wire::PutU16(apiBlock, static_cast<std::uint16_t>(iocsObjects.size()));
978
979 for (const IOCRAPIObject& object : iocsObjects)
980 {
981 const Bytes bytes = object.ToBytes();
982 apiBlock.insert(apiBlock.end(), bytes.begin(), bytes.end());
983 }
984
985 // ---------------------------------------------------------------------------
986 // Build IOCR block header.
987 // ---------------------------------------------------------------------------
988
989 PNBlockHeader blockHeader;
990
991 blockHeader.blockType = PNIOCRBlockReqHeader::BLOCK_TYPE; // 0x0102
992
993 // The IOCR fixed header is followed by the API block.
994 //
995 // blockLength excludes:
996 // - BlockType 2 bytes
997 // - BlockLength 2 bytes
998 //
999 const std::size_t totalSize = PNIOCRBlockReqHeader::kSize + apiBlock.size();
1000
1001 if (totalSize < 4 || totalSize - 4 > std::numeric_limits<std::uint16_t>::max())
1002 {
1003 throw std::invalid_argument("BuildIocrBlock: IOCR block is too large");
1004 }
1005
1006 blockHeader.blockLength = static_cast<std::uint16_t>(totalSize - 4);
1007
1008 blockHeader.blockVersionHigh = VERSION_HIGH;
1009 blockHeader.blockVersionLow = VERSION_LOW;
1010
1011 PNIOCRBlockReqHeader iocrHeader;
1012
1013 const Bytes blockHeaderBytes = blockHeader.ToBytes();
1014
1015 std::ranges::copy(blockHeaderBytes, iocrHeader.blockHeader.begin());
1016
1017 iocrHeader.iocrType = static_cast<std::uint16_t>(iocrType);
1018 iocrHeader.iocrReference = static_cast<std::uint16_t>(iocrType);
1019 iocrHeader.etherTypeLT = PROFINET_ETHERTYPE; // 0x8892
1021 ? 0x00000002
1022 : 0x00000001; // RT_CLASS_1, RT_CLASS_3 maybe later
1023 // iocrHeader.iocrProperties = 0x00000002; /// class2?
1024 iocrHeader.dataLength = static_cast<std::uint16_t>(std::max<std::uint16_t>(40, dataLength));
1025
1026 /*
1027 if (setup.applicationRelations.startupMode == StartupMode::Advanced)
1028 {
1029 // Input CR -> 0x8000
1030 // Output CR -> 0x8001
1031
1032 iocrHeader.frameId = static_cast<std::uint16_t>(0x8000 + static_cast<std::uint16_t>(iocrType) - 1);
1033 }
1034 else
1035 {
1036 // Input CR -> 0xC000
1037 // Output CR -> 0xC001
1038
1039 iocrHeader.frameId = static_cast<std::uint16_t>(0xC000 + static_cast<std::uint16_t>(iocrType) - 1);
1040 }
1041*/
1043 ? iocrType == IocrType::Input
1044 ? 0x9000
1045 : 0xFFFF
1046 : iocrType == IocrType::Input
1047 ? 0xC000
1048 : 0xC001;
1049
1050 // iocrHeader.frameId = iocrType == IocrType::Input ? 0xC000 : 0xC001;
1051
1052 iocrHeader.sendClockFactor = setup.sendClockFactor;
1053 iocrHeader.reductionRatio = setup.reductionRatio;
1054
1055 // These are currently controller/protocol configuration values rather
1056 // than GSDML-derived slot properties.
1057 iocrHeader.phase = 8;
1058 iocrHeader.sequence = 0;
1059 iocrHeader.frameSendOffset = 0xFFFFFFFF;
1060 iocrHeader.watchdogFactor = setup.watchdogFactor;
1061 iocrHeader.dataHoldFactor = setup.dataHoldFactor;
1062 iocrHeader.iocrTagHeader = 0xC000;
1063 iocrHeader.iocrMulticastMac = {};
1064
1065 // One API: API 0.
1066 iocrHeader.numberOfApis = 1;
1067
1068 // ---------------------------------------------------------------------------
1069 // Serialize complete block.
1070 // ---------------------------------------------------------------------------
1071
1072 Bytes out = iocrHeader.ToBytes();
1073
1074 out.insert(out.end(), apiBlock.begin(), apiBlock.end());
1075
1076 return out;
1077}
1078
1080{
1081 Bytes expectedSlotModuleBlocks;
1083 std::uint16_t previousSlotnr = 0;
1084 for (const auto& slot : setup.slots)
1085 {
1086 if (previousSlotnr != slot.slot)
1087 {
1088 previousSlotnr = slot.slot;
1089 const auto slotBytes = builder.ToBytes();
1090 expectedSlotModuleBlocks.insert(expectedSlotModuleBlocks.end(), slotBytes.begin(), slotBytes.end());
1091 builder.Clear();
1092 }
1093 const bool hasInput = slot.inputLength > 0;
1094 const bool hasOutput = slot.outputLength > 0;
1095
1096 const blocks::ModuleType submoduleType =
1097 [&]()
1098 {
1099 if (hasInput && hasOutput)
1100 {
1102 }
1103
1104 if (hasInput)
1105 {
1107 }
1108
1109 if (hasOutput)
1110 {
1112 }
1113
1115 }();
1116
1117 builder.AddSubmodule(
1118 0,
1119 slot.slot,
1120 slot.subslot,
1121 slot.moduleIdent,
1122 slot.submoduleIdent,
1123 static_cast<std::uint16_t>(submoduleType),
1124 slot.inputLength,
1125 slot.outputLength);
1126 }
1127 const auto slotBytes = builder.ToBytes();
1128 expectedSlotModuleBlocks.insert(expectedSlotModuleBlocks.end(), slotBytes.begin(), slotBytes.end());
1129 return expectedSlotModuleBlocks;
1130}
1131
1132std::uint16_t RPCCon::ParseIocrResponse(const Bytes& responseData, int iocrType)
1133{
1134 std::size_t offset = 0;
1135 while (offset + blockHeaderLength <= responseData.size())
1136 {
1137 auto hdr = PeekBlockHeader(responseData, offset);
1138 if (hdr.blockType == PNIOCRBlockRes::BLOCK_TYPE)
1139 {
1140 const Bytes blockBytes(responseData.begin() + offset, responseData.end());
1141 try
1142 {
1143 auto res = PNIOCRBlockRes::Parse(blockBytes);
1144 if (res.iocrType == iocrType)
1145 {
1146 return res.frameId;
1147 }
1148 }
1149 // NOLINTNEXTLINE(bugprone-empty-catch)
1150 catch (const std::exception&)
1151 {
1152 // fall through and keep scanning
1153 }
1154 }
1155 offset += 4 + hdr.blockLength;
1156 }
1157 return 0;
1158}
1159
1161{
1162 Bytes rpcBytes = request.ToBytes();
1163 auto timeoutInMilliSeconds = std::chrono::floor<std::chrono::milliseconds>(std::chrono::duration<double>(timeout));
1164 PNRPCHeader resp;
1165 while (true)
1166 {
1167 CheckTimeout();
1168 const Bytes responseBytes = rpcTransport->SendReceive(rpcBytes, timeoutInMilliSeconds);
1169
1170 if (responseBytes.size() < PNRPCHeader::kFixedSize)
1171 {
1172 throw RPCError("Failed to parse RPC response: data too short (" + std::to_string(responseBytes.size()) + " bytes)");
1173 }
1174 try
1175 {
1176 resp = PNRPCHeader::Parse(responseBytes);
1177 }
1178 catch (const std::exception& e)
1179 {
1180 throw RPCError(std::string("Failed to parse RPC response: ") + e.what());
1181 }
1182
1183 if (resp.packetType == PNRPCHeader::REQUEST)
1184 {
1185 continue;
1186 }
1187 break;
1188 }
1189 if (resp.packetType == PNRPCHeader::FAULT)
1190 {
1191 throw RPCFaultError("RPC fault from " + info.name, resp.operationNumber);
1192 }
1193 if (resp.packetType == PNRPCHeader::REJECT)
1194 {
1195 throw RPCError("RPC request rejected by " + info.name);
1196 }
1198 {
1199 // Extract the complete formatted error message into a dedicated const variable
1200 const std::string errorMessage = std::format("Unexpected RPC packet type: 0x{:02X}", resp.packetType);
1201
1202 throw RPCError(errorMessage);
1203 }
1204
1205 live = true;
1206 liveMonotonic = std::chrono::steady_clock::now();
1207 return resp;
1208}
1209
1211{
1212 if (live && !reconnecting)
1213 {
1214 auto elapsed = std::chrono::duration<double>(std::chrono::steady_clock::now() - liveMonotonic).count();
1215 if (elapsed >= CONNECTION_TIMEOUT)
1216 {
1217 reconnecting = true;
1218 Connect();
1219 }
1220 }
1221}
1222
1223std::optional<ConnectResult> RPCCon::Connect(std::optional<MacAddress> mac,
1224 bool withAlarmCr,
1225 std::optional<IOCRSetup> setup)
1226{
1227 if (!live)
1228 {
1229 if (!mac)
1230 {
1231 throw std::invalid_argument("srcMac required for initial connection");
1232 }
1233 this->srcMac = mac;
1234 }
1235 else
1236 {
1237 arUuid = RandomUuidBytes();
1238 activityUuid = RandomUuidBytes();
1239 sessionKey = RandomNonzeroU16();
1240 if (mac)
1241 {
1242 this->srcMac = mac;
1243 }
1244 }
1245 if (!this->srcMac)
1246 {
1247 throw std::invalid_argument("No source MAC address available");
1248 }
1249
1250 // NOLINTNEXTLINE(performance-unnecessary-value-param)
1251 this->iocrSetup = std::move(setup);
1252
1253 const std::uint32_t arProperties = this->iocrSetup
1254 ? this->iocrSetup->applicationRelations.startupMode == StartupMode::Advanced
1255 ? 0x40000011U
1256 : 0x00000011U
1257 : 0x00000111U;
1258 std::string initiatorStationName = "rhodium-profinet-connector";
1259
1260 PNBlockHeader block;
1261 block.blockType = BlockType::ApplicationRelationRequest; // ARBlockReq, 0x0101
1262 block.blockLength = static_cast<std::uint16_t>(PNARBlockRequest::kFixedSize - 2 + initiatorStationName.length() - 2);
1265
1266 const std::uint16_t arType = this->iocrSetup ? 0x0001 : 0x0006;
1267
1269 std::ranges::copy(block.ToBytes() | std::views::take(blockHeaderLength), ar.blockHeader.begin());
1270 ar.arType = arType;
1271 ar.arUuid = arUuid;
1273 ar.cmInitiatorMacAddress = *mac;
1275 ar.arProperties = arProperties; // this->iocrSetup ? 0x00000011U : 0x00000111U; // arProperties;
1278 ar.stationNameLength = static_cast<std::uint16_t>(initiatorStationName.length());
1279 const Bytes initiatorStationNameInBytes(initiatorStationName.begin(), initiatorStationName.end());
1280 ar.cmInitiatorStationName = initiatorStationNameInBytes;
1281
1282 Bytes nrdPayload = ar.ToBytes();
1283
1284 if (this->iocrSetup)
1285 {
1286 Bytes inputIocr = BuildIocrBlock(IocrType::Input, *this->iocrSetup);
1287 nrdPayload.insert(nrdPayload.end(), inputIocr.begin(), inputIocr.end());
1288
1289 Bytes outputIocr = BuildIocrBlock(IocrType::Output, *this->iocrSetup);
1290 nrdPayload.insert(nrdPayload.end(), outputIocr.begin(), outputIocr.end());
1291
1292 Bytes expectedSubmodule = BuildExpectedSubmoduleBlock(*this->iocrSetup);
1293 nrdPayload.insert(nrdPayload.end(), expectedSubmodule.begin(), expectedSubmodule.end());
1294
1295 Bytes alarmCrData = BuildAlarmCrBlock();
1296 nrdPayload.insert(nrdPayload.end(), alarmCrData.begin(), alarmCrData.end());
1297 withAlarmCr = true;
1298 }
1299 else if (withAlarmCr)
1300 {
1301 Bytes alarmCrData = BuildAlarmCrBlock();
1302 nrdPayload.insert(nrdPayload.end(), alarmCrData.begin(), alarmCrData.end());
1303 }
1304
1305 const PNNRDData nrd = CreateNrd(nrdPayload);
1307
1308 try
1309 {
1310 const PNRPCHeader resp = SendReceive(req);
1311 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
1312
1313 if (nrdResp.argsMaximumStatus != 0)
1314 {
1315 const PNIOError pnioErr = PNIOError::FromArgsStatus(nrdResp.argsMaximumStatus);
1316 throw RPCConnectionError(std::string("Connect rejected by device: ") + pnioErr.what());
1317 }
1318
1319 reconnecting = false;
1320
1321 std::optional<ConnectResult> result;
1322 if (this->iocrSetup)
1323 {
1324 result = ConnectResult{};
1325 result->arUuid = arUuid;
1326 result->sessionKey = sessionKey;
1328 result->inputFrameId = inputFrameId;
1330 result->outputFrameId = outputFrameId;
1331 result->header = raw::ParseRawRpcHeader(nrdResp.ToBytes());
1332 if (result.has_value())
1333 {
1334 dataRepresentation = result->header->drep;
1335 /*
1336 remoteObjectUuid = result->header->objectUuid;
1337 interfaceUuid = result->header->interfaceUuid;
1338 activityUuid = result->header->activityUuid;
1339*/
1340 }
1341 }
1342
1343 if (withAlarmCr)
1344 {
1346 if (deviceAlarmRef >= 0)
1347 {
1348 alarmCrEnabled = true;
1349 if (result)
1350 {
1351 result->deviceAlarmRef = deviceAlarmRef;
1352 }
1353 }
1354 }
1355
1356 return result;
1357 }
1358 catch (const RPCError& e)
1359 {
1360 throw RPCConnectionError(std::string("Failed to connect: ") + e.what());
1361 }
1362}
1363
1364PNIODHeader RPCCon::Read(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx)
1365{
1366 CheckTimeout();
1367
1368 PNBlockHeader block;
1373
1374 PNIODHeader iod;
1375 std::ranges::copy(block.ToBytes() | std::views::take(blockHeaderLength), iod.blockHeader.begin());
1376 iod.sequenceNumber = 0;
1377 iod.arUuid = arUuid;
1378 iod.api = api;
1379 iod.slot = slot;
1380 iod.subslot = subslot;
1381 iod.padding1 = 0;
1382 iod.index = idx;
1383 iod.length = 4096;
1384 iod.targetArUuid = {};
1385 iod.padding2 = {};
1386
1387 const PNNRDData nrd = CreateNrd(iod.ToBytes());
1388 const PNRPCHeader req = CreateRpc(PNRPCHeader::READ, nrd.ToBytes());
1389
1390 const PNRPCHeader resp = SendReceive(req);
1391 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
1392 if (nrdResp.argsMaximumStatus != 0)
1393 {
1395 }
1396
1397 return PNIODHeader::Parse(nrdResp.payload);
1398}
1399
1400PNIODHeader RPCCon::ReadImplicit(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx)
1401{
1402 PNBlockHeader block;
1404 block.blockLength = 60;
1405 block.blockVersionHigh = 0x01;
1406 block.blockVersionLow = 0x00;
1407
1408 PNIODHeader iod;
1409 std::ranges::copy(block.ToBytes() | std::views::take(blockHeaderLength), iod.blockHeader.begin());
1410 iod.sequenceNumber = 0;
1411 iod.arUuid = {}; // empty AR UUID for implicit read
1412 iod.api = api;
1413 iod.slot = slot;
1414 iod.subslot = subslot;
1415 iod.padding1 = 0;
1416 iod.index = idx;
1417 iod.length = 4096;
1418 iod.targetArUuid = {};
1419 iod.padding2 = {};
1420
1421 const PNNRDData nrd = CreateNrd(iod.ToBytes());
1423
1424 const PNRPCHeader resp = SendReceive(req);
1425 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
1426 if (nrdResp.argsMaximumStatus != 0)
1427 {
1429 }
1430
1431 return PNIODHeader::Parse(nrdResp.payload);
1432}
1433
1434void RPCCon::Write(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx,
1435 const Bytes& data)
1436{
1437 CheckTimeout();
1438
1439 PNBlockHeader block;
1440 block.blockType = BlockType::IoDataWriteRequest; // 0x0008; // IODWriteReqHeader
1441 block.blockLength = 60;
1442 block.blockVersionHigh = 0x01;
1443 block.blockVersionLow = 0x00;
1444
1445 PNIODHeader iod;
1446 std::ranges::copy(block.ToBytes() | std::views::take(blockHeaderLength), iod.blockHeader.begin());
1447 iod.sequenceNumber = 0;
1448 iod.arUuid = arUuid;
1449 iod.api = api;
1450 iod.slot = slot;
1451 iod.subslot = subslot;
1452 iod.padding1 = 0;
1453 iod.index = idx;
1454 iod.length = static_cast<std::uint32_t>(data.size());
1455 iod.targetArUuid = {};
1456 iod.padding2 = {};
1457 iod.payload = data;
1458
1459 const PNNRDData nrd = CreateNrd(iod.ToBytes());
1460 const PNRPCHeader req = CreateRpc(PNRPCHeader::WRITE, nrd.ToBytes());
1461
1462 const PNRPCHeader resp = SendReceive(req);
1463 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
1464 if (nrdResp.argsMaximumStatus != 0)
1465 {
1467 }
1468}
1469
1470Bytes RPCCon::ReadRaw(std::uint16_t idx, std::uint16_t slot, std::uint16_t subslot)
1471{
1472 return Read(0, slot, subslot, idx).payload;
1473}
1474
1475PNInM0 RPCCon::ReadIm0(std::uint16_t slot, std::uint16_t subslot)
1476{
1477 return PNInM0::Parse(Read(0, slot, subslot, PNInM0::IDX).payload);
1478}
1479PNInM1 RPCCon::ReadIm1(std::uint16_t slot, std::uint16_t subslot)
1480{
1481 return PNInM1::Parse(Read(0, slot, subslot, PNInM1::IDX).payload);
1482}
1483PNInM2 RPCCon::ReadIm2(std::uint16_t slot, std::uint16_t subslot)
1484{
1485 return PNInM2::Parse(Read(0, slot, subslot, PNInM2::IDX).payload);
1486}
1487PNInM3 RPCCon::ReadIm3(std::uint16_t slot, std::uint16_t subslot)
1488{
1489 return PNInM3::Parse(Read(0, slot, subslot, PNInM3::IDX).payload);
1490}
1491PNInM4 RPCCon::ReadIm4(std::uint16_t slot, std::uint16_t subslot)
1492{
1493 return PNInM4::Parse(Read(0, slot, subslot, PNInM4::IDX).payload);
1494}
1495PNInM5 RPCCon::ReadIm5(std::uint16_t slot, std::uint16_t subslot)
1496{
1497 return PNInM5::Parse(Read(0, slot, subslot, PNInM5::IDX).payload);
1498}
1499PNInM6 RPCCon::ReadIm6(std::uint16_t slot, std::uint16_t subslot)
1500{
1501 return PNInM6::Parse(Read(0, slot, subslot, PNInM6::IDX).payload);
1502}
1503PNInM7 RPCCon::ReadIm7(std::uint16_t slot, std::uint16_t subslot)
1504{
1505 return PNInM7::Parse(Read(0, slot, subslot, PNInM7::IDX).payload);
1506}
1507PNInM8 RPCCon::ReadIm8(std::uint16_t slot, std::uint16_t subslot)
1508{
1509 return PNInM8::Parse(Read(0, slot, subslot, PNInM8::IDX).payload);
1510}
1511PNInM9 RPCCon::ReadIm9(std::uint16_t slot, std::uint16_t subslot)
1512{
1513 return PNInM9::Parse(Read(0, slot, subslot, PNInM9::IDX).payload);
1514}
1515PNInM10 RPCCon::ReadIm10(std::uint16_t slot, std::uint16_t subslot)
1516{
1517 return PNInM10::Parse(Read(0, slot, subslot, PNInM10::IDX).payload);
1518}
1519PNInM11 RPCCon::ReadIm11(std::uint16_t slot, std::uint16_t subslot)
1520{
1521 return PNInM11::Parse(Read(0, slot, subslot, PNInM11::IDX).payload);
1522}
1523PNInM12 RPCCon::ReadIm12(std::uint16_t slot, std::uint16_t subslot)
1524{
1525 return PNInM12::Parse(Read(0, slot, subslot, PNInM12::IDX).payload);
1526}
1527PNInM13 RPCCon::ReadIm13(std::uint16_t slot, std::uint16_t subslot)
1528{
1529 return PNInM13::Parse(Read(0, slot, subslot, PNInM13::IDX).payload);
1530}
1531PNInM14 RPCCon::ReadIm14(std::uint16_t slot, std::uint16_t subslot)
1532{
1533 return PNInM14::Parse(Read(0, slot, subslot, PNInM14::IDX).payload);
1534}
1535PNInM15 RPCCon::ReadIm15(std::uint16_t slot, std::uint16_t subslot)
1536{
1537 return PNInM15::Parse(Read(0, slot, subslot, PNInM15::IDX).payload);
1538}
1539
1540AllIM RPCCon::ReadAllIm(std::uint16_t slot, std::uint16_t subslot)
1541{
1542 AllIM result;
1543 result.im0 = ReadIm0(slot, subslot); // I&M0 is mandatory; propagate failure.
1544
1545 auto tryRead = [&](auto& field, auto memberFn)
1546 {
1547 try
1548 {
1549 field = (this->*memberFn)(slot, subslot);
1550 }
1551 // NOLINTNEXTLINE(bugprone-empty-catch)
1552 catch (const RPCError&)
1553 {
1554 // fall through and keep scanning
1555 }
1556 // NOLINTNEXTLINE(bugprone-empty-catch)
1557 catch (const std::exception&)
1558 {
1559 // fall through and keep scanning
1560 }
1561 };
1562 tryRead(result.im1, &RPCCon::ReadIm1);
1563 tryRead(result.im2, &RPCCon::ReadIm2);
1564 tryRead(result.im3, &RPCCon::ReadIm3);
1565 tryRead(result.im4, &RPCCon::ReadIm4);
1566 tryRead(result.im5, &RPCCon::ReadIm5);
1567 tryRead(result.im6, &RPCCon::ReadIm6);
1568 tryRead(result.im7, &RPCCon::ReadIm7);
1569 tryRead(result.im8, &RPCCon::ReadIm8);
1570 tryRead(result.im9, &RPCCon::ReadIm9);
1571 tryRead(result.im10, &RPCCon::ReadIm10);
1572 tryRead(result.im11, &RPCCon::ReadIm11);
1573 tryRead(result.im12, &RPCCon::ReadIm12);
1574 tryRead(result.im13, &RPCCon::ReadIm13);
1575 tryRead(result.im14, &RPCCon::ReadIm14);
1576 tryRead(result.im15, &RPCCon::ReadIm15);
1577 return result;
1578}
1579
1580// =============================================================================
1581// Configuration / diagnosis / topology
1582// =============================================================================
1583
1584std::vector<blocks::WriteMultipleResult> RPCCon::WriteMultiple(const std::vector<WriteItem>& writes)
1585{
1586 CheckTimeout();
1587 if (writes.empty())
1588 {
1589 return {};
1590 }
1591
1592 blocks::IODWriteMultipleBuilder builder(arUuid, /*SeqNum=*/0);
1593 for (const auto& w : writes)
1594 {
1595 builder.AddWrite(w.slot, w.subslot, w.index, w.data, w.api);
1596 }
1597 const Bytes payload = builder.Build();
1598
1599 PNBlockHeader block;
1600 block.blockType = BlockType::IoDataWriteRequest; // 0x0008; // IODWriteReqHeader
1601 block.blockLength = 60;
1602 block.blockVersionHigh = 0x01;
1603 block.blockVersionLow = 0x00;
1604
1605 PNIODHeader iod;
1606 std::ranges::copy(block.ToBytes() | std::views::take(blockHeaderLength), iod.blockHeader.begin());
1607 iod.sequenceNumber = 0;
1608 iod.arUuid = arUuid;
1609 iod.api = 0xFFFFFFFF; // wildcard for multiple
1610 iod.slot = 0xFFFF; // wildcard
1611 iod.subslot = 0xFFFF; // wildcard
1612 iod.padding1 = 0;
1614 iod.length = static_cast<std::uint32_t>(payload.size());
1615 iod.targetArUuid = {};
1616 iod.padding2 = {};
1617 iod.payload = payload;
1618
1619 const PNNRDData nrd = CreateNrd(iod.ToBytes());
1620 const PNRPCHeader req = CreateRpc(PNRPCHeader::WRITE, nrd.ToBytes());
1621
1622 const PNRPCHeader resp = SendReceive(req);
1623 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
1624 if (nrdResp.argsMaximumStatus != 0)
1625 {
1627 }
1628
1630}
1631
1637
1638diagnosis::DiagnosisData RPCCon::ReadDiagnosis(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index)
1639{
1640 try
1641 {
1642 const PNIODHeader iod = Read(0, slot, subslot, index);
1643 const Bytes& data = iod.payload;
1644
1645 if (data.size() > 6)
1646 {
1647 diagnosis::DiagnosisData result = diagnosis::ParseDiagnosisBlock(data, 0, slot, subslot);
1648 if (result.entries.empty())
1649 {
1650 result = diagnosis::ParseDiagnosisSimple(data, 0, slot, subslot);
1651 }
1652 return result;
1653 }
1655 empty.api = 0;
1656 empty.slot = slot;
1657 empty.subslot = subslot;
1658 empty.rawData = data;
1659 return empty;
1660 }
1661 catch (const RPCError&)
1662 {
1664 empty.api = 0;
1665 empty.slot = slot;
1666 empty.subslot = subslot;
1667 return empty;
1668 }
1669}
1670
1671std::map<std::uint16_t, diagnosis::DiagnosisData> RPCCon::ReadAllDiagnosis()
1672{
1673 static const std::vector<std::tuple<std::uint16_t, std::uint16_t, std::uint16_t>> kDiagnosisProbes = {
1674 {0x800A, 0, 0}, // Channel diagnosis for slot 0
1675 {0x800B, 0, 0}, // All diagnosis for slot 0
1676 {0x800C, 0, 1}, // Channel diagnosis for subslot 1
1677 {0xF000, 0, 0}, // All diagnosis data (device level)
1678 {0xF00A, 0, 0}, // Channel diagnosis (API level)
1679 {0xF00B, 0, 0}, // All diagnosis (API level)
1680 };
1681
1682 std::map<std::uint16_t, diagnosis::DiagnosisData> results;
1683 for (const auto& [idx, Slot, Subslot] : kDiagnosisProbes)
1684 {
1685 try
1686 {
1687 const diagnosis::DiagnosisData diag = ReadDiagnosis(Slot, Subslot, idx);
1688 if (!diag.entries.empty())
1689 {
1690 results[idx] = diag;
1691 }
1692 }
1693 // NOLINTNEXTLINE(bugprone-empty-catch)
1694 catch (const RPCError&)
1695 {
1696 }
1697 }
1698 return results;
1699}
1700
1706
1712
1713std::vector<blocks::SlotInfo> RPCCon::DiscoverSlots()
1714{
1716}
1717
1718std::pair<blocks::PDRealData, blocks::RealIdentificationData> RPCCon::DiscoverTopology()
1719{
1721}
1722
1723std::map<std::uint16_t, rpc::IndexProbeResult> RPCCon::EnumerateIndices(
1724 std::uint16_t slot, std::uint16_t subslot, std::optional<std::vector<std::uint16_t>> customIndices)
1725{
1726 std::vector<std::pair<std::uint16_t, std::string>> probeList;
1727
1728 if (customIndices)
1729 {
1730 for (auto idx : *customIndices)
1731 {
1732 probeList.emplace_back(idx, GetIndexName(idx));
1733 }
1734 }
1735 else
1736 {
1737 auto extend = [&](const IndexNamePairs& src)
1738 {
1739 probeList.insert(probeList.end(), src.begin(), src.end());
1740 };
1741 extend(ImIndices());
1742 extend(DiagnosisIndices("subslot"));
1743 extend(DiagnosisIndices("device"));
1744 extend(PortIndices());
1745 extend(InterfaceIndices());
1746 extend(DeviceIndices());
1747 probeList.emplace_back(EXPECTED_ID_SUBSLOT, "ExpectedIdentificationData");
1748 probeList.emplace_back(REAL_ID_SUBSLOT, "RealIdentificationData");
1749 probeList.emplace_back(MODULE_DIFF_BLOCK, "ModuleDiffBlock");
1750 probeList.emplace_back(RECORD_INPUT_DATA, "RecordInputData");
1751 probeList.emplace_back(RECORD_OUTPUT_DATA, "RecordOutputData");
1752 }
1753
1754 std::map<std::uint16_t, IndexProbeResult> results;
1755 std::vector<std::uint16_t> seen;
1756 for (const auto& [idx, Name] : probeList)
1757 {
1758 if (std::ranges::find(seen, idx) != seen.end())
1759 {
1760 continue;
1761 }
1762 seen.push_back(idx);
1763
1765 r.name = Name;
1766 try
1767 {
1768 const PNIODHeader iod = Read(0, slot, subslot, idx);
1769 if (!iod.payload.empty())
1770 {
1771 r.status = "readable";
1772 r.size = iod.payload.size();
1773 }
1774 else
1775 {
1776 r.status = "empty";
1777 }
1778 }
1779 catch (const PNIOError& e)
1780 {
1781 r.status = "error";
1782 r.error = e.what();
1783 r.errorCode1 = e.errorCode1;
1784 r.errorCode2 = e.errorCode2;
1785 }
1786 catch (const RPCError& e)
1787 {
1788 r.status = "error";
1789 r.error = e.what();
1790 }
1791 results[idx] = r;
1792 }
1793
1794 return results;
1795}
1796
1798 bool waitResponse, const Bytes& subBlocks)
1799{
1800 if (!live)
1801 {
1802 throw RPCError("Not connected");
1803 }
1804
1805 PNBlockHeader block;
1806 block.blockType = blockType;
1807 block.blockLength = 28;
1808 block.blockVersionHigh = 0x01;
1809 block.blockVersionLow = 0x00;
1810
1811 PNIODReleaseBlock controlBlock;
1812
1813 std::ranges::copy(block.ToBytes() | std::views::take(blockHeaderLength), controlBlock.blockHeader.begin());
1814 controlBlock.padding1 = 0;
1815 controlBlock.arUuid = arUuid;
1816 controlBlock.sessionKey = sessionKey;
1817 controlBlock.padding2 = 0;
1818 controlBlock.controlCommand = controlCommand;
1819 controlBlock.controlBlockProperties = 0;
1820
1821 Bytes nrdPayload = controlBlock.ToBytes();
1822 nrdPayload.insert(nrdPayload.end(), subBlocks.begin(), subBlocks.end());
1823
1824 const PNNRDData nrd = CreateNrd(nrdPayload);
1826
1827 if (!waitResponse)
1828 {
1829 Bytes rpcBytes = req.ToBytes();
1830 rpcTransport->Send(rpcBytes);
1831 return {};
1832 }
1833
1834 const PNRPCHeader resp = SendReceive(req);
1835 const PNNRDData nrdResp = PNNRDData::Parse(resp.payload);
1836 if (nrdResp.argsMaximumStatus != 0)
1837 {
1839 }
1840 return nrdResp.payload;
1841}
1842
1855
1856namespace raw
1857{
1858std::optional<RawRpcHeader> ParseRawRpcHeader(const Bytes& data)
1859{
1860 if (data.size() < PNRPCHeader::kFixedSize)
1861 {
1862 return std::nullopt;
1863 }
1864 wire::Reader r(data);
1865 RawRpcHeader h;
1866 h.version = r.U8(); // 0x04
1867 h.packetType = r.U8(); // 0x00 = Request, 0x02 = Response
1868 h.flags1 = r.U8();
1869 h.flags2 = r.U8();
1870
1871 // drep is 3 bytes (indices 4, 5, 6), drep[0] = 0x10 = Little-Endian, drep[0] = 0x00 = Big-Endian
1872 h.drep = r.Fixed<3U>();
1873
1874 h.serialHigh = r.U8();
1875
1876 h.objectUuid = r.Fixed<uuidLenght>();
1878 h.activityUuid = r.Fixed<uuidLenght>();
1879
1881
1882 // Field offsets per DCE/RPC v4 specification:
1883 // Bytes 56-59: server_boot_time
1884 r.SkipTo(60U);
1888 // Bytes 70-71: interface_hint
1889 // Bytes 72-73: activity_hint
1890 // Bytes 74-75: len (args_len)
1891 // Bytes 76-77: fragnum
1892 // Byte 78: auth_proto
1893 r.SkipTo(79U);
1894 h.serialLow = r.U8();
1895
1896 h.payload = Bytes(data.begin() + PNRPCHeader::kFixedSize, data.end());
1897
1898 return h;
1899}
1900
1901} // namespace raw
1902
1904{
1905 if (!live)
1906 {
1907 throw RPCError("Not connected");
1908 }
1909
1910 auto timeoutInMilliSeconds = std::chrono::floor<std::chrono::milliseconds>(std::chrono::duration<double>(timeoutSec));
1911 Bytes result;
1912 while (true)
1913 {
1914 CheckTimeout();
1915 Bytes respBytes;
1916 // This maybe wrong... i am too tired to validate
1917 // Receive incoming UDP request (and capture sender endpoint if transport supports it)
1918
1919 asio::ip::udp::endpoint senderEndpoint;
1920 Bytes buf = ccontrolTransport->Receive(timeoutInMilliSeconds, senderEndpoint);
1921 auto hdr = raw::ParseRawRpcHeader(buf);
1922 if (!hdr)
1923 {
1924 continue;
1925 }
1926 if (hdr->packetType != PNRPCHeader::REQUEST)
1927 {
1928 continue;
1929 }
1930 if (hdr->operationNumber != PNRPCHeader::CONTROL)
1931 {
1932 continue;
1933 }
1934
1935 const Bytes& nrdPayload = hdr->payload;
1936 if (nrdPayload.size() < 20)
1937 {
1938 continue;
1939 }
1940
1941 wire::Reader r1(nrdPayload);
1942 const std::uint32_t nrdArgsMaximum = hdr->isLittleEndian ? r1.U32LittleEndian()
1943 : r1.U32();
1944 // r.SkipTo(16);
1945 // const std::uint32_t nrdActual = le ? r.U32LittleEndian()
1946 // : r.U32();
1947 // (void)nrdActual; // do I need nrdActual?
1948
1949 Bytes nrdBody(nrdPayload.begin() + 20, nrdPayload.end());
1950 if (nrdBody.size() < 32)
1951 {
1952 continue;
1953 }
1954 wire::Reader r(nrdBody);
1955 // BlockType is ALWAYS Big-Endian (0x0112 = IODControlReq / ApplicationReady)
1956 const std::uint16_t blockType = r.U16();
1957
1958 // ControlCommand is located at offset 28 inside PNIODControlBlockReq
1959 // (Block Header: 6 bytes + ARUUID: 16 bytes + SessionKey: 2 bytes + Reserved: 4 bytes = 28 bytes)
1961 const std::uint16_t controlCmd = r.U16();
1962
1963 std::cout << "Parsed BlockType: 0x" << std::hex << blockType
1964 << ", ControlCmd: 0x" << controlCmd << std::dec << "\n";
1965
1966 if (blockType != BLOCK_IOD_CONTROL_APP_READY_REQ) // 0x0112
1967 {
1968 std::cerr << "Ignoring non-AppReady block: 0x" << std::hex << blockType << "\n";
1969 continue;
1970 }
1971
1972 // (control_cmd expected to be CONTROL_CMD_APP_READY; devices that
1973 // deviate are still accepted)
1974 //(void)controlCmd;
1975
1976 // Build CControl response: block 0x8112, cmd=DONE.
1977 PNBlockHeader respBlock;
1979
1980 // version(2) + padding1(2) + arUuid(16) + sessionKey(2) + padding2(2) + controlcmd (2) + blockproperties(2)
1981 // = 2 + 2 + 16 + 2 + 2 + 2 + 2 = 28 or 0x001C
1982 constexpr std::uint16_t kApplicationReadyBlockLength = 28;
1983
1984 respBlock.blockLength = kApplicationReadyBlockLength;
1985
1986 respBlock.blockVersionHigh = 0x01;
1987 respBlock.blockVersionLow = 0x00;
1988
1989 PNIODReleaseBlock respControl;
1990 std::ranges::copy(respBlock.ToBytes() | std::views::take(blockHeaderLength), respControl.blockHeader.begin());
1991 respControl.padding1 = 0;
1992 respControl.arUuid = arUuid;
1993 respControl.sessionKey = sessionKey;
1994 respControl.padding2 = 0;
1996 respControl.controlBlockProperties = 0;
1997
1998 Bytes respNrdPayload = respControl.ToBytes();
1999 const auto respNrdLen = static_cast<std::uint32_t>(respNrdPayload.size());
2000
2001 Bytes respNrd;
2002 if (hdr->isLittleEndian)
2003 {
2004 wire::PutLittleEndian<std::uint32_t>(respNrd, 0); // pnio status = OK
2005 wire::PutLittleEndian(respNrd, respNrdLen); // args length
2006 wire::PutLittleEndian(respNrd, nrdArgsMaximum); // maximum count
2007 wire::PutLittleEndian<std::uint32_t>(respNrd, 0); // offset
2008 wire::PutLittleEndian(respNrd, respNrdLen); // actual count
2009 }
2010 else
2011 {
2012 wire::PutU32(respNrd, 0);
2013 wire::PutU32(respNrd, respNrdLen);
2014 wire::PutU32(respNrd, nrdArgsMaximum);
2015 wire::PutU32(respNrd, 0);
2016 wire::PutU32(respNrd, respNrdLen);
2017 }
2018 wire::PutFixed(respNrd, respNrdPayload);
2019
2039
2040 respBytes.push_back(hdr->version); // 0x00 : Protocol Version (`0x04`)
2041 respBytes.push_back(PNRPCHeader::RESPONSE); // 0x01 : Packet Type (0x02` = Resp)
2042 constexpr auto flag1 = static_cast<std::uint8_t>(raw::Flags1::NoFack | raw::Flags1::LastFragment);
2043 constexpr std::uint8_t flag2 = 0x00; // all special reserved flags, leave empty
2044 respBytes.push_back(flag1); // 0x02 : First flags group
2045 respBytes.push_back(flag2); // 0x03 : Second flags group
2046 // respBytes.push_back(0x00); // flags1
2047 // respBytes.push_back(0x00); // flags2
2048
2049 // respBytes.push_back(0x00); // data representation / [0] defines Endianness
2050 // respBytes.push_back(0x00); // data representation - drep[1]
2051 // respBytes.push_back(0x00); // data representation - drep[2]
2052 // respBytes.push_back(0x00); // serial high
2053
2054 wire::PutFixed(respBytes, hdr->drep); // 0x04 - 0x06 Data Representation (`drep[0]` defines Endianness)
2055 respBytes.push_back(hdr->serialHigh);
2056 wire::PutFixed(respBytes, hdr->objectUuid);
2057 wire::PutFixed(respBytes, hdr->interfaceUuid);
2058 wire::PutFixed(respBytes, hdr->activityUuid);
2059 // respBytes.insert(respBytes.end(), localObjectUuid.begin(), localObjectUuid.end());
2060 // respBytes.insert(respBytes.end(), interfaceUuid.begin(), interfaceUuid.end());
2061 // respBytes.insert(respBytes.end(), activityUuid.begin(), activityUuid.end());
2062
2063 if (hdr->isLittleEndian)
2064 {
2065 wire::PutLittleEndian<std::uint32_t>(respBytes, 0); // server_boot_time
2066 wire::PutLittleEndian(respBytes, hdr->interfaceVersion);
2067 wire::PutLittleEndian(respBytes, hdr->sequenceNumber);
2068 wire::PutLittleEndian(respBytes, hdr->operationNumber);
2069 wire::PutLittleEndian(respBytes, PNRPCHeader::defaultInterfaceHint); // interface_hint
2070 wire::PutLittleEndian(respBytes, PNRPCHeader::defaultActivityHint); // activity_hint
2071 wire::PutLittleEndian(respBytes, static_cast<std::uint16_t>(respNrd.size()));
2072 wire::PutLittleEndian<std::uint16_t>(respBytes, 0); // fragment_number
2073 }
2074 else
2075 {
2076 wire::PutU32(respBytes, 0); // server_boot_time
2077 wire::PutU32(respBytes, hdr->interfaceVersion);
2078 wire::PutU32(respBytes, hdr->sequenceNumber);
2079 wire::PutU16(respBytes, hdr->operationNumber);
2080 wire::PutU16(respBytes, PNRPCHeader::defaultInterfaceHint); // interface_hint
2081 wire::PutU16(respBytes, PNRPCHeader::defaultActivityHint); // activity_hint
2082 wire::PutU16(respBytes, static_cast<std::uint16_t>(respNrd.size()));
2083 wire::PutU16(respBytes, 0); // fragment_number
2084 }
2085
2086 respBytes.push_back(0);
2087 respBytes.push_back(hdr->serialLow);
2088
2089 wire::PutFixed(respBytes, respNrd);
2090
2091 ccontrolTransport->SendTo(respBytes, senderEndpoint);
2092
2093 live = true;
2094 liveMonotonic = std::chrono::steady_clock::now();
2095 result = nrdBody;
2096 break;
2097 }
2098 return result;
2099}
2100
2102{
2103 if (!live)
2104 {
2105 return;
2106 }
2107
2108 try
2109 {
2110 PNBlockHeader block;
2112 block.blockLength = 28;
2113 block.blockVersionHigh = 0x01;
2114 block.blockVersionLow = 0x00;
2115
2116 PNIODReleaseBlock release;
2117 std::ranges::copy(block.ToBytes() | std::views::take(blockHeaderLength), release.blockHeader.begin());
2118 release.padding1 = 0;
2119 release.arUuid = arUuid;
2120 release.sessionKey = sessionKey;
2121 release.padding2 = 0;
2123 release.controlBlockProperties = 0;
2124
2125 const PNNRDData nrd = CreateNrd(release.ToBytes());
2127 Bytes rpcBytes = req.ToBytes();
2128 rpcTransport->Send(rpcBytes);
2129 }
2130 // NOLINTNEXTLINE(bugprone-empty-catch)
2131 catch (...)
2132 {
2133 // best-effort
2134 }
2135
2136 live = false;
2137}
2138
2139void RPCCon::Close() noexcept
2140{
2141 Disconnect();
2142 rpcTransport->Close();
2143 ccontrolTransport->Close();
2144}
2145
2146} // namespace profinet::rpc
Keeps an Asio I/O context running on a dedicated worker thread.
Device not found via DCP.
Definition exceptions.h:563
Abstract interface for raw Ethernet frame transport.
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:673
Bytes ToBytes() const
Serialize the complete block to raw bytes.
Definition blocks.cpp:958
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:878
Builder for IODWriteMultipleReq packets (index 0xE040).
Definition blocks.h:487
Bytes Build() const
Build the complete IODWriteMultipleReq packet.
Definition blocks.cpp:733
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:683
static constexpr std::uint16_t writeMultipleIndex
Record data index for WriteMultiple (0xE040).
Definition blocks.h:490
Parsed PROFINET device information from a DCP response.
Definition dcp.h:268
std::uint8_t vendorLow
Low byte of the PROFINET vendor ID.
Definition dcp.h:309
std::string name
Station name.
Definition dcp.h:282
std::uint8_t vendorHigh
High byte of the PROFINET vendor ID.
Definition dcp.h:306
std::uint8_t deviceLow
Low byte of the PROFINET device ID.
Definition dcp.h:318
std::uint8_t deviceHigh
High byte of the PROFINET device ID.
Definition dcp.h:315
std::string ip
IPv4 address, or "0.0.0.0" if unset.
Definition dcp.h:285
PNInM13 ReadIm13(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M13 (reserved).
Definition rpc.cpp:1527
PNInM0 ReadIm0(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M0 (mandatory identification data).
Definition rpc.cpp:1475
static std::uint16_t ParseIocrResponse(const Bytes &responseData, int iocrType)
Parse an IOCRBlockRes from a CONNECT response.
Definition rpc.cpp:1132
std::uint32_t sequenceNumber
Monotonically increasing RPC sequence number.
Definition rpc.h:583
blocks::PDRealData ReadPdRealData() override
Read and parse the device's PDRealData (physical topology).
Definition rpc.cpp:1701
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID for this connection.
Definition rpc.h:543
blocks::ModuleDiffBlock ReadModuleDiff() override
Read and parse the device's ModuleDiffBlock.
Definition rpc.cpp:1632
PNInM2 ReadIm2(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M2 (installation date).
Definition rpc.cpp:1483
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:653
double timeout
Default RPC response timeout in seconds.
Definition rpc.h:537
PNInM8 ReadIm8(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M8 (reserved).
Definition rpc.cpp:1507
std::array< std::uint8_t, uuidLenght > localObjectUuid
This controller's own object UUID.
Definition rpc.h:552
~RPCCon()
Disconnect (best-effort) and close the underlying sockets.
Definition rpc.cpp:610
static PNNRDData CreateNrd(const Bytes &payload)
Wrap a payload in an NRD (Network Representation Data) header.
Definition rpc.cpp:641
PNInM4 ReadIm4(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M4 (PROFIsafe signature).
Definition rpc.cpp:1491
PNInM15 ReadIm15(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M15 (reserved).
Definition rpc.cpp:1535
AllIM ReadAllIm(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read all I&M records a device supports.
Definition rpc.cpp:1540
std::vector< blocks::SlotInfo > DiscoverSlots() override
Convenience accessor for ReadRealIdentificationData().Slots.
Definition rpc.cpp:1713
static int ParseAlarmCrResponse(const Bytes &responseData)
Parse the AlarmCRBlockRes from a CONNECT response.
Definition rpc.cpp:679
Bytes PrmEnd() override
Send PrmEnd (end of the parameterization phase).
Definition rpc.cpp:1847
PNInM6 ReadIm6(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M6 (reserved).
Definition rpc.cpp:1499
static Bytes BuildExpectedSubmoduleBlock(const IOCRSetup &setup)
Build an ExpectedSubmoduleBlockReq for the CONNECT request.
Definition rpc.cpp:1079
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:1797
void Disconnect() override
Send Release to terminate the AR.
Definition rpc.cpp:2101
int deviceAlarmRef
Device's local alarm reference, if an AlarmCR was established.
Definition rpc.h:577
std::vector< EPMEndpoint > EpmLookup(const std::string &ip, std::uint16_t port=RPC_PORT, double timeoutSec=DEFAULT_TIMEOUT, std::optional< std::string > interfaceFilter=std::nullopt) override
Query a device's Endpoint Mapper for available RPC endpoints.
Definition rpc.cpp:330
PNInM11 ReadIm11(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M11 (reserved).
Definition rpc.cpp:1519
std::pair< blocks::PDRealData, blocks::RealIdentificationData > DiscoverTopology() override
Read both PDRealData and RealIdentificationData in one call.
Definition rpc.cpp:1718
std::array< std::uint8_t, 3 > dataRepresentation
AR DREP for this connection.
Definition rpc.h:540
std::array< std::uint8_t, uuidLenght > remoteObjectUuid
The target device's object UUID.
Definition rpc.h:555
dcp::DCPDeviceDescription info
DCP-discovered description of the target device.
Definition rpc.h:534
bool reconnecting
Whether timeout has occured and reconnection is needed.
Definition rpc.h:561
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:1638
PNInM14 ReadIm14(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M14 (reserved).
Definition rpc.cpp:1531
void Close() noexcept override
Disconnect() and close the underlying sockets.
Definition rpc.cpp:2139
PNInM12 ReadIm12(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M12 (reserved).
Definition rpc.cpp:1523
std::optional< MacAddress > srcMac
Source MAC address used to establish the AR.
Definition rpc.h:567
std::map< std::uint16_t, diagnosis::DiagnosisData > ReadAllDiagnosis() override
Read diagnosis from all standard diagnosis indices.
Definition rpc.cpp:1671
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:1723
static Bytes BuildIocrBlock(IocrType iocrType, const IOCRSetup &setup)
Build an IOCRBlockReq (header + API object list) for the CONNECT request.
Definition rpc.cpp:703
bool alarmCrEnabled
Whether an AlarmCR was successfully established.
Definition rpc.h:580
std::chrono::steady_clock::time_point liveMonotonic
Timestamp of the last successful RPC exchange, for CONNECTION_TIMEOUT tracking.
Definition rpc.h:564
void CheckTimeout()
Reconnect if the AR has been idle longer than CONNECTION_TIMEOUT.
Definition rpc.cpp:1210
Bytes PrmBegin() override
Send PrmBegin (start of the parameterization phase).
Definition rpc.cpp:1843
std::uint16_t outputFrameId
Frame ID assigned to the output IOCR.
Definition rpc.h:595
Bytes ApplicationReady(double timeoutSec=30.0) override
Wait for the device's CControl/ApplicationReady request and confirm it.
Definition rpc.cpp:1903
PNInM5 ReadIm5(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M5 (free-text annotation).
Definition rpc.cpp:1495
PNInM9 ReadIm9(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M9 (reserved).
Definition rpc.cpp:1511
std::unique_ptr< IRpcTransport > rpcTransport
Definition rpc.h:605
std::uint16_t alarmRef
Controller's own local alarm reference.
Definition rpc.h:574
std::array< std::uint8_t, uuidLenght > interfaceUuid
Interface UUID for the current RPC exchange.
Definition rpc.h:549
PNInM10 ReadIm10(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M10 (reserved).
Definition rpc.cpp:1515
PNInM3 ReadIm3(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M3 (free-text descriptor).
Definition rpc.cpp:1487
asio::io_context & ioContext
Asio execution context used by the RPC transport.
Definition rpc.h:603
std::uint16_t inputFrameId
Frame ID assigned to the input IOCR.
Definition rpc.h:592
blocks::RealIdentificationData ReadRealIdentificationData() override
Read and parse the device's RealIdentificationData (logical structure).
Definition rpc.cpp:1707
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:1223
PNInM1 ReadIm1(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M1 (tag function/location).
Definition rpc.cpp:1479
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:1364
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:1434
std::uint16_t sessionKey
Session key for this connection.
Definition rpc.h:570
PNRPCHeader CreateRpc(std::uint16_t operation, const Bytes &nrd)
Build an RPC header for the given operation and NRD body.
Definition rpc.cpp:615
std::unique_ptr< IRpcTransport > ccontrolTransport
Definition rpc.h:606
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:1470
PNRPCHeader SendReceive(const PNRPCHeader &request)
Send an RPC request and synchronously wait for its response.
Definition rpc.cpp:1160
PNInM7 ReadIm7(std::uint16_t slot=0, std::uint16_t subslot=1) override
Read I&M7 (reserved).
Definition rpc.cpp:1503
Bytes ReadyForRtClass3() override
Send ReadyForRTClass3 (isochronous real-time readiness).
Definition rpc.cpp:1851
std::vector< blocks::WriteMultipleResult > WriteMultiple(const std::vector< WriteItem > &writes) override
Write multiple records atomically via IODWriteMultipleReq (0xE040).
Definition rpc.cpp:1584
bool live
Whether the AR is currently established.
Definition rpc.h:558
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:1400
std::optional< IOCRSetup > iocrSetup
Cyclic IO configuration used for the current AR, if any.
Definition rpc.h:598
std::array< std::uint8_t, uuidLenght > activityUuid
Activity UUID for the current RPC exchange.
Definition rpc.h:546
Synchronous UDP transport used by PROFINET DCE/RPC.
void Open(const std::string &address, std::uint16_t port, bool isListener=false) override
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.
Lightweight cursor for parsing a big-endian byte buffer without copies.
Definition wire.h:192
std::uint8_t U8()
Read and consume one byte.
Definition wire.h:250
std::uint32_t U32()
Read and consume a big-endian 32-bit value.
Definition wire.h:268
std::size_t Remaining() const
Number of bytes not yet consumed.
Definition wire.h:218
std::array< std::uint8_t, N > Fixed()
Read and consume a fixed-size byte array.
Definition wire.h:316
void SkipTo(std::size_t offset)
Move the read position to an absolute byte offset.
Definition wire.h:343
std::uint32_t U32LittleEndian()
Read and consume a little-endian 32-bit value.
Definition wire.h:291
std::vector< std::uint8_t > ReadBytes(std::size_t n)
Read and consume a variable-length byte range.
Definition wire.h:304
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
Definition wire.h:258
std::uint16_t U16LittleEndian()
Read and consume a little-endian 16-bit value.
Definition wire.h:279
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:609
std::vector< WriteMultipleResult > ParseWriteMultipleResponse(const Bytes &data)
Parse an IODWriteMultipleRes into individual per-write results.
Definition blocks.cpp:756
PDRealData ParsePdRealData(const Bytes &data)
Parse a complete PDRealData (0xF841) response.
Definition blocks.cpp:328
RealIdentificationData ParseRealIdentificationData(const Bytes &data)
Parse a RealIdentificationData (0xF000 or 0x0013) response.
Definition blocks.cpp:422
constexpr int MIN_CYCLE_MS
Minimum cycle time (ms) considered reliable.
Definition cyclic.h:153
void SendDiscover(IRawEthernetSocket &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:1173
constexpr DcpBlockKey MakeBlockKey(std::uint16_t option, std::uint8_t suboption)
Definition dcp.h:231
void SendRequest(IRawEthernetSocket &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:1195
ResponseMap ReadResponse(IRawEthernetSocket &sock, const MacAddress &myMac, double timeoutSec=DEFAULT_RESPONSE_TIMEOUT, bool once=false, std::optional< std::uint32_t > expectedXid=std::nullopt)
Read and parse DCP Identify responses.
Definition dcp.cpp:1218
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.
std::optional< RawRpcHeader > ParseRawRpcHeader(const Bytes &data)
Parses a raw network byte vector into a structured DCE/RPC v4 Connectionless Header.
Definition rpc.cpp:1858
@ LastFragment
Bit 1 (0x02): Final fragment in the sequence.
@ Idempotent
Bit 5 (0x20): Activity call is idempotent (safe to retry).
@ NoFack
Bit 3 (0x08): Do not send FACK (fragment acknowledgment) frames.
const std::string UUID_PNIO_PARAMETER_SERVER
PROFINET IO-ParameterServer interface UUID.
Definition rpcTypes.h:43
std::array< std::uint8_t, uuidLenght > BuildObjectUuid(std::uint8_t b10, std::uint8_t b11, std::uint8_t b12, std::uint8_t b13, std::uint8_t b14, std::uint8_t b15)
Builds a PROFINET object UUID.
Definition rpc.cpp:56
constexpr std::uint8_t VERSION_HIGH
Version high.
Definition rpc.h:42
constexpr std::uint16_t BLOCK_IOD_CONTROL_APP_READY_REQ
Block type: IODControlReqAppReady.
Definition rpcTypes.h:65
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:106
const std::string UUID_PNIO_SUPERVISOR
PROFINET IO-Supervisor interface UUID.
Definition rpcTypes.h:41
dcp::DCPDeviceDescription GetStationInfo(IRawEthernetSocket &sock, const MacAddress &src, const std::string &name, double timeoutSec=DEFAULT_TIMEOUT *2)
Resolve a device by PROFINET station name via DCP.
Definition rpc.cpp:509
const std::string UUID_PNIO_CONTROLLER
PROFINET IO-Controller interface UUID.
Definition rpcTypes.h:39
const std::string UUID_EPM_V4
Endpoint Mapper interface UUID.
Definition rpcTypes.h:35
constexpr std::uint16_t RPC_PORT
PROFINET IO RPC port (34964).
Definition rpcTypes.h:28
std::optional< EPMEndpoint > ParseEpmTower(const Bytes &towerData)
Parses a DCE/RPC EPM tower into a PROFINET endpoint.
Definition rpc.cpp:239
constexpr std::uint32_t EPM_INQUIRY_ALL
EPM inquiry type: all interfaces.
Definition rpcTypes.h:51
const std::string UUID_PNIO_DEVICE
PROFINET IO-Device interface UUID.
Definition rpcTypes.h:37
constexpr double CONNECTION_TIMEOUT
Maximum idle time in seconds before an AR is considered to need re-connection.
Definition rpcTypes.h:58
BlockHeaderView PeekBlockHeader(const Bytes &data, std::size_t offset)
Definition rpc.h:54
constexpr std::uint32_t EPM_INQUIRY_INTERFACE
EPM inquiry type: specific interface.
Definition rpcTypes.h:53
constexpr std::uint8_t VERSION_LOW
Version low.
Definition rpc.h:44
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:126
constexpr std::uint32_t EPM_LOOKUP
EPM operation: Lookup.
Definition rpcTypes.h:49
void PutFixed(std::vector< std::uint8_t > &out, const std::array< std::uint8_t, N > &data)
Append a fixed-size byte array to a buffer.
Definition wire.h:124
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t value)
Append a 16-bit value to a buffer in big-endian order.
Definition wire.h:97
void PutLittleEndian(std::vector< std::uint8_t > &out, T value)
Append an unsigned integral value to a buffer in little-endian order.
Definition wire.h:64
void PutU32(std::vector< std::uint8_t > &out, std::uint32_t value)
Append a 32-bit value to a buffer in big-endian order.
Definition wire.h:105
void PutBigEndian(std::vector< std::uint8_t > &out, T value)
Append an unsigned integral value to a buffer in big-endian order.
Definition wire.h:45
BlockType
PROFINET block types.
Definition indices.h:89
@ 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:40
constexpr int blockHeaderLength
Constant lenght of a blockHeader.
Definition protocol.h:58
constexpr std::uint16_t PD_REAL_DATA
PD Real Data.
Definition indices.h:815
constexpr std::uint16_t MODULE_DIFF_BLOCK
Module Diff Block.
Definition indices.h:803
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
constexpr std::uint16_t IP_ETHERTYPE
EtherType value identifying IP frames (0x8000).
Definition util.h:32
IocrType
PROFINET IOCR types.
Definition indices.h:463
@ Output
Output IOCR.
@ Input
Input IOCR.
ControlCommand
PROFINET IOD control command values.
Definition indices.h:61
@ 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:1001
constexpr std::uint16_t EXPECTED_ID_SUBSLOT
Expected ID Subslot.
Definition indices.h:794
constexpr std::uint16_t REAL_ID_API
Real ID API.
Definition indices.h:806
const IndexNamePairs & DeviceIndices()
Device-level indices.
Definition indices.cpp:604
constexpr std::uint16_t RECORD_INPUT_DATA
Record Input Data.
Definition indices.h:918
std::string ToHex(const std::uint8_t *data, std::size_t len)
Hex-encode a byte buffer.
Definition util.cpp:104
DcpOption
DCP options.
Definition protocol.h:116
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
Definition util.h:30
const IndexNamePairs & ImIndices()
I&M0-I&M15 indices.
Definition indices.cpp:509
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
constexpr std::uint16_t REAL_ID_SUBSLOT
Real ID Subslot.
Definition indices.h:796
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:920
constexpr std::array< std::uint8_t, 12 > kPnUuidSuffix
PROFINET UUID suffix, shared by all interface/object UUIDs.
Definition protocol.h:855
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:1896
ARBlockReq: establishes an Application Relationship (sent in the CONNECT request).
Definition protocol.h:1360
std::uint32_t arProperties
AR property flags (supervisor takeover, device access, etc.).
Definition protocol.h:1380
std::uint16_t cmInitiatorActivityTimeoutFactor
Activity timeout factor for the initiator.
Definition protocol.h:1383
MacAddress cmInitiatorMacAddress
MAC address of the initiating controller.
Definition protocol.h:1374
std::uint16_t sessionKey
Session key chosen by the initiator.
Definition protocol.h:1371
std::array< std::uint8_t, uuidLenght > arUuid
Unique AR UUID chosen by the initiator (controller).
Definition protocol.h:1368
Bytes cmInitiatorStationName
Station name of the initiating controller (variable length).
Definition protocol.h:1392
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:1428
std::uint16_t arType
AR type (e.g. IOCARSingle, IOSAR).
Definition protocol.h:1365
std::uint16_t initiatorUdpRtport
UDP port the initiator uses for real-time data.
Definition protocol.h:1386
static constexpr std::size_t kFixedSize
Size in bytes of the fixed portion of this block.
Definition protocol.h:1398
std::array< std::uint8_t, blockHeaderLength > blockHeader
Nested 6-byte block header.
Definition protocol.h:1362
std::array< std::uint8_t, uuidLenght > cmInitiatorObjectUuid
Object UUID of the initiating controller.
Definition protocol.h:1377
std::uint16_t stationNameLength
Length in bytes of CmInitiatorStationName.
Definition protocol.h:1389
AlarmCRBlockReq: requests establishment of the alarm connection (part of the CONNECT request).
Definition protocol.h:2115
static constexpr std::uint16_t DEFAULT_MAX_ALARM_DATA_LENGTH
Default maximum alarm data length in bytes.
Definition protocol.h:2127
static constexpr std::uint16_t DEFAULT_TAG_HEADER_LOW
Default low word of the alarm VLAN tag header.
Definition protocol.h:2133
static constexpr BlockType BLOCK_TYPE
Block type identifier for AlarmCRBlockReq.
Definition protocol.h:2118
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:2197
static constexpr std::uint16_t DEFAULT_TAG_HEADER_HIGH
Default high word of the alarm VLAN tag header.
Definition protocol.h:2130
std::uint16_t alarmCrType
Alarm CR type (always 1: Alarm).
Definition protocol.h:2140
std::uint16_t etherTypeLT
EtherType used for alarm frames (0x8892).
Definition protocol.h:2143
std::uint32_t alarmCrProperties
Alarm CR property flags (transport, priority).
Definition protocol.h:2146
static constexpr std::size_t kSize
Size in bytes of this block.
Definition protocol.h:2167
static constexpr std::uint16_t DEFAULT_RTA_RETRIES
Default number of RTA retries.
Definition protocol.h:2124
std::uint16_t localAlarmReference
Local alarm reference chosen by the controller.
Definition protocol.h:2155
std::uint16_t alarmCrTagHeaderHigh
High word of the alarm VLAN tag header.
Definition protocol.h:2161
std::array< std::uint8_t, blockHeaderLength > blockHeader
Nested 6-byte block header.
Definition protocol.h:2137
std::uint16_t maxAlarmDataLength
Maximum alarm data length in bytes.
Definition protocol.h:2158
std::uint16_t alarmCrTagHeaderLow
Low word of the alarm VLAN tag header.
Definition protocol.h:2164
std::uint16_t rtaRetries
Number of RTA retransmission attempts.
Definition protocol.h:2152
std::uint16_t rtaTimeoutFactor
RTA (real-time acyclic) retransmission timeout factor.
Definition protocol.h:2149
static constexpr std::uint16_t DEFAULT_RTA_TIMEOUT_FACTOR
Default RTA timeout factor.
Definition protocol.h:2121
static constexpr std::uint16_t BLOCK_TYPE
Block type identifier for AlarmCRBlockRes.
Definition protocol.h:2219
static PNAlarmCRBlockRes Parse(const Bytes &data)
Parse an AlarmCRBlockRes from raw bytes.
Definition protocol.h:2240
Bytes ToBytes() const
Serialize this header back to raw bytes.
Definition protocol.h:1347
BlockType blockType
Block type identifier.
Definition protocol.h:1308
static constexpr std::uint16_t DEFAULT_BLOCK_LENGTH
Default block length.
Definition protocol.h:1304
std::uint8_t blockVersionHigh
Block format major version.
Definition protocol.h:1317
std::uint8_t blockVersionLow
Block format minor version.
Definition protocol.h:1320
std::uint16_t blockLength
Length in bytes of the block body (excludes Type/Length fields).
Definition protocol.h:1311
static constexpr BlockType IODReadRequestHeader
Generic 6-byte PROFINET block header (Type + Length + Version).
Definition protocol.h:1286
static constexpr std::pair< std::uint8_t, std::uint8_t > NAME_OF_STATION
Block option/suboption: station name.
Definition protocol.h:767
IOCRBlockReq header: requests establishment of one IOCR (part of the CONNECT request).
Definition protocol.h:1984
static constexpr std::size_t kSize
Size in bytes of this header.
Definition protocol.h:2042
static constexpr BlockType BLOCK_TYPE
Block type identifier for IOCRBlockReq.
Definition protocol.h:1987
std::uint16_t frameId
Requested frame ID (device may reassign in its response).
Definition protocol.h:2009
MacAddress iocrMulticastMac
Multicast MAC address (used only for multicast IOCRs).
Definition protocol.h:2036
std::uint16_t dataLength
Total cyclic data length in bytes.
Definition protocol.h:2006
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
Definition protocol.h:2012
std::uint16_t dataHoldFactor
Data hold factor (how long to keep the last good frame's data).
Definition protocol.h:2030
std::uint16_t phase
Phase offset within the reduction ratio.
Definition protocol.h:2018
std::uint16_t iocrType
IOCR type (Input/Output/MulticastProvider/MulticastConsumer).
Definition protocol.h:1994
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
Definition protocol.h:2027
std::uint16_t sequence
Sequence value (reserved, usually 0).
Definition protocol.h:2021
std::uint16_t numberOfApis
Number of API entries that follow this header.
Definition protocol.h:2039
std::uint16_t iocrReference
IOCR reference chosen by the controller.
Definition protocol.h:1997
std::uint16_t iocrTagHeader
VLAN tag header used for cyclic frames.
Definition protocol.h:2033
std::uint32_t iocrProperties
IOCR property flags (RT class, redundancy, etc.).
Definition protocol.h:2003
std::uint16_t etherTypeLT
EtherType used for cyclic frames (0x8892).
Definition protocol.h:2000
std::array< std::uint8_t, blockHeaderLength > blockHeader
Nested 6-byte block header.
Definition protocol.h:1991
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
Definition protocol.h:2015
Bytes ToBytes() const
Serialize this header back to raw bytes.
Definition protocol.h:2085
std::uint32_t frameSendOffset
Frame send offset within the cycle.
Definition protocol.h:2024
static PNIOCRBlockRes Parse(const Bytes &data)
Parse an IOCRBlockRes from raw bytes.
Definition protocol.h:1964
static constexpr std::uint16_t BLOCK_TYPE
Block type identifier for IOCRBlockRes.
Definition protocol.h:1943
IOD read/write request/response header (RPC body for READ/WRITE/IMPLICIT_READ).
Definition protocol.h:1190
std::uint32_t api
API number.
Definition protocol.h:1205
std::uint16_t slot
Slot number.
Definition protocol.h:1208
std::array< std::uint8_t, blockHeaderLength > blockHeader
Nested 6-byte block header (type + length + version).
Definition protocol.h:1196
Bytes ToBytes() const
Serialize this header and payload back to raw bytes.
Definition protocol.h:1262
static PNIODHeader Parse(const Bytes &data)
Parse an IOD header and payload from raw bytes.
Definition protocol.h:1237
std::uint16_t sequenceNumber
Sequence number for tracking retries.
Definition protocol.h:1199
std::array< std::uint8_t, 8 > padding2
Reserved/padding.
Definition protocol.h:1226
std::uint16_t padding1
Reserved/padding.
Definition protocol.h:1214
static constexpr std::uint16_t kContolCommandOffset
ControlCommand is located at offset 28 inside PNIODControlBlockReq (Block Header: 6 bytes + ARUUID: 1...
Definition protocol.h:1193
Bytes payload
Record data payload.
Definition protocol.h:1229
std::uint16_t subslot
Subslot number.
Definition protocol.h:1211
std::array< std::uint8_t, uuidLenght > targetArUuid
Target AR UUID for AR handover scenarios (usually zero).
Definition protocol.h:1223
std::uint32_t length
Length in bytes of Payload.
Definition protocol.h:1220
std::uint16_t index
Record data index being read or written.
Definition protocol.h:1217
std::array< std::uint8_t, uuidLenght > arUuid
AR (Application Relationship) UUID this record belongs to.
Definition protocol.h:1202
Control block used for Release/PrmBegin/PrmEnd/ApplicationReady/RTClass3 requests.
Definition protocol.h:1449
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID this control operation applies to.
Definition protocol.h:1457
std::uint16_t controlBlockProperties
Control block property flags.
Definition protocol.h:1469
std::uint16_t padding1
Reserved/padding.
Definition protocol.h:1454
std::uint16_t sessionKey
Session key matching the AR.
Definition protocol.h:1460
std::array< std::uint8_t, blockHeaderLength > blockHeader
Nested 6-byte block header.
Definition protocol.h:1451
ControlCommand controlCommand
Control command bitmask (PrmEnd/AppReady/Release/Done/...).
Definition protocol.h:1466
std::uint16_t padding2
Reserved/padding.
Definition protocol.h:1463
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:1497
I&M0: mandatory identification data (vendor, order ID, serial number, revisions).
Definition protocol.h:1517
static PNInM0 Parse(const Bytes &data)
Parse an I&M0 record from raw bytes.
Definition protocol.h:1584
static constexpr std::uint16_t IDX
Record data index for I&M0.
Definition protocol.h:1520
I&M1: user-assigned tag function and location strings.
Definition protocol.h:1624
static PNInM1 Parse(const Bytes &data)
Parse an I&M1 record from raw bytes.
Definition protocol.h:1645
static constexpr std::uint16_t IDX
Record data index for I&M1.
Definition protocol.h:1627
I&M2: installation date.
Definition protocol.h:1668
static PNInM2 Parse(const Bytes &data)
Parse an I&M2 record from raw bytes.
Definition protocol.h:1686
static constexpr std::uint16_t IDX
Record data index for I&M2.
Definition protocol.h:1671
I&M3: free-text descriptor.
Definition protocol.h:1709
static constexpr std::uint16_t IDX
Record data index for I&M3.
Definition protocol.h:1712
static PNInM3 Parse(const Bytes &data)
Parse an I&M3 record from raw bytes.
Definition protocol.h:1727
I&M4: PROFIsafe signature (binary, not text).
Definition protocol.h:1749
static PNInM4 Parse(const Bytes &data)
Parse an I&M4 record from raw bytes.
Definition protocol.h:1767
static constexpr std::uint16_t IDX
Record data index for I&M4.
Definition protocol.h:1752
I&M5: free-text annotation.
Definition protocol.h:1789
static PNInM5 Parse(const Bytes &data)
Parse an I&M5 record from raw bytes.
Definition protocol.h:1807
static constexpr std::uint16_t IDX
Record data index for I&M5.
Definition protocol.h:1792
I&M6-I&M15: reserved for future use per IEC 61158-6-10.
Definition protocol.h:1833
static constexpr std::uint16_t IDX
Record data index for this reserved I&M slot.
Definition protocol.h:1836
static PNInMReserved Parse(const Bytes &data)
Parse a reserved I&M record from raw bytes.
Definition protocol.h:1848
NRD (Network Representation Data) wrapper carrying the actual IOD payload.
Definition protocol.h:1128
Bytes payload
The wrapped IOD data.
Definition protocol.h:1145
static PNNRDData Parse(const Bytes &data)
Parse an NRD wrapper and payload from raw bytes.
Definition protocol.h:1153
Bytes ToBytes() const
Serialize this wrapper and payload back to raw bytes.
Definition protocol.h:1175
std::uint32_t argsLength
Length in bytes of the arguments (usually equals ActualCount).
Definition protocol.h:1133
std::uint32_t offset
Byte offset into the logical result (0 unless fragmented).
Definition protocol.h:1139
std::uint32_t argsMaximumStatus
Maximum status/argument buffer size accepted by the caller.
Definition protocol.h:1130
std::uint32_t maximumCount
Maximum number of bytes the caller can accept.
Definition protocol.h:1136
std::uint32_t actualCount
Actual length in bytes of Payload.
Definition protocol.h:1142
DCE/RPC PDU header used to carry PROFINET connect/read/write/control requests.
Definition protocol.h:860
Bytes ToBytes() const
Serialize this header and body back to raw bytes.
Definition protocol.h:1045
static constexpr std::uint16_t defaultInterfaceHint
Definition protocol.h:915
std::array< std::uint8_t, 3 > dataRepresentation
Data representation format label.
Definition protocol.h:931
static constexpr std::uint16_t RELEASE
Operation number: RELEASE.
Definition protocol.h:899
std::uint16_t operationNumber
Operation number (CONNECT/RELEASE/READ/WRITE/CONTROL/IMPLICIT_READ).
Definition protocol.h:955
std::uint16_t fragmentNumber
Fragment number for fragmented PDUs.
Definition protocol.h:967
static constexpr std::size_t kFixedSize
Size in bytes of the fixed portion of this header.
Definition protocol.h:979
std::uint16_t interfaceHint
Interface hint (0xFFFF if unused).
Definition protocol.h:958
static constexpr std::uint16_t READ
Operation number: READ.
Definition protocol.h:902
std::uint8_t flags1
Packet flags, first byte.
Definition protocol.h:925
static constexpr std::uint16_t defaultActivityHint
Definition protocol.h:916
static PNRPCHeader Parse(const Bytes &data)
Parse an RPC header and body from raw bytes.
Definition protocol.h:1012
std::uint8_t packetType
Packet type (REQUEST/RESPONSE/FAULT/...).
Definition protocol.h:922
static constexpr std::uint16_t WRITE
Operation number: WRITE.
Definition protocol.h:905
std::array< std::uint8_t, uuidLenght > interfaceUuid
Target interface UUID (identifies device/controller/supervisor role).
Definition protocol.h:940
std::uint8_t authenticationProtocol
Authentication protocol identifier (unused by PROFINET).
Definition protocol.h:970
std::uint8_t serialNrHigh
High byte of the serial number.
Definition protocol.h:934
static std::array< std::uint8_t, uuidLenght > IfaceUuidDevice()
Interface UUID identifying the IO-Device role.
Definition protocol.h:983
std::uint8_t serialNrLow
Low byte of the serial number.
Definition protocol.h:973
std::uint32_t sequenceNumber
Sequence number within the activity.
Definition protocol.h:952
static constexpr std::uint8_t REJECT
Packet type: REJECT.
Definition protocol.h:881
static constexpr std::uint16_t CONNECT
Operation number: CONNECT.
Definition protocol.h:896
std::uint8_t version
DCE/RPC version (always 4 for PROFINET).
Definition protocol.h:919
static constexpr std::uint8_t REQUEST
Packet type: REQUEST.
Definition protocol.h:863
static constexpr std::uint16_t CONTROL
Operation number: CONTROL.
Definition protocol.h:908
std::uint32_t serverBootTime
Server boot time counter.
Definition protocol.h:946
static constexpr std::uint8_t RESPONSE
Packet type: RESPONSE.
Definition protocol.h:869
std::uint8_t flags2
Packet flags, second byte.
Definition protocol.h:928
static constexpr std::uint16_t IMPLICIT_READ
Operation number: IMPLICIT_READ.
Definition protocol.h:911
Bytes payload
PDU body (NRD data for read/write/control, etc.).
Definition protocol.h:976
std::uint32_t interfaceVersion
Interface version.
Definition protocol.h:949
std::uint16_t activityHint
Activity hint (0xFFFF if unused).
Definition protocol.h:961
std::uint16_t lengthOfBody
Length in bytes of Payload.
Definition protocol.h:964
std::array< std::uint8_t, uuidLenght > objectUuid
Target object UUID.
Definition protocol.h:937
static constexpr std::uint8_t FAULT
Packet type: FAULT.
Definition protocol.h:872
std::array< std::uint8_t, uuidLenght > activityUuid
Activity (call) UUID correlating request/response pairs.
Definition protocol.h:943
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:275
std::vector< AnyChannelDiagnosis > entries
Parsed diagnosis entries.
Definition diagnosis.h:286
Bytes rawData
Raw, undecoded source data.
Definition diagnosis.h:289
std::uint32_t api
API number.
Definition diagnosis.h:277
std::uint16_t subslot
Subslot number.
Definition diagnosis.h:283
std::uint16_t slot
Slot number.
Definition diagnosis.h:280
All I&M records a device supports, read in one call.
Definition rpcTypes.h:451
std::optional< PNInM7 > im7
I&M7 (reserved), if supported.
Definition rpcTypes.h:467
std::optional< PNInM15 > im15
I&M15 (reserved), if supported.
Definition rpcTypes.h:483
std::optional< PNInM9 > im9
I&M9 (reserved), if supported.
Definition rpcTypes.h:471
std::optional< PNInM14 > im14
I&M14 (reserved), if supported.
Definition rpcTypes.h:481
std::optional< PNInM1 > im1
I&M1 (tag function/location), if supported.
Definition rpcTypes.h:455
std::optional< PNInM5 > im5
I&M5 (free-text annotation), if supported.
Definition rpcTypes.h:463
std::optional< PNInM10 > im10
I&M10 (reserved), if supported.
Definition rpcTypes.h:473
std::optional< PNInM3 > im3
I&M3 (free-text descriptor), if supported.
Definition rpcTypes.h:459
std::optional< PNInM6 > im6
I&M6 (reserved), if supported.
Definition rpcTypes.h:465
std::optional< PNInM13 > im13
I&M13 (reserved), if supported.
Definition rpcTypes.h:479
std::optional< PNInM11 > im11
I&M11 (reserved), if supported.
Definition rpcTypes.h:475
std::optional< PNInM2 > im2
I&M2 (installation date), if supported.
Definition rpcTypes.h:457
std::optional< PNInM12 > im12
I&M12 (reserved), if supported.
Definition rpcTypes.h:477
std::optional< PNInM4 > im4
I&M4 (PROFIsafe signature), if supported.
Definition rpcTypes.h:461
std::optional< PNInM0 > im0
I&M0 (mandatory identification data).
Definition rpcTypes.h:453
std::optional< PNInM8 > im8
I&M8 (reserved), if supported.
Definition rpcTypes.h:469
Result of a successful RPCCon::Connect() that also established cyclic IO.
Definition rpcTypes.h:402
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID assigned to this connection.
Definition rpcTypes.h:404
One RPC endpoint advertised by a device's Endpoint Mapper.
Definition IRPCCon.h:34
std::string protocol
Transport protocol, e.g. "ncadg_ip_udp".
Definition IRPCCon.h:48
std::uint16_t interfaceVersionMinor
Minor version of the interface.
Definition IRPCCon.h:42
std::string ipAddress
IPv4 address the endpoint listens on.
Definition IRPCCon.h:54
std::uint16_t port
UDP/TCP port the endpoint listens on.
Definition IRPCCon.h:51
std::uint16_t interfaceVersionMajor
Major version of the interface.
Definition IRPCCon.h:39
std::string InterfaceName() const
Human-readable interface name.
Definition rpc.cpp:211
std::string interfaceUuid
Interface UUID this endpoint serves.
Definition IRPCCon.h:36
Configuration for establishing cyclic IO alongside an AR.
Definition rpcTypes.h:149
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
Definition rpcTypes.h:160
std::uint16_t dataHoldFactor
Data hold factor (how long to keep the last good frame's data).
Definition rpcTypes.h:163
std::vector< IOSlot > slots
Slots to include in the cyclic data frames.
Definition rpcTypes.h:151
std::vector< std::string > Validate() const
Sanity-check this configuration.
Definition rpc.cpp:546
double CycleTimeMs() const
Compute the cycle time in milliseconds.
Definition rpcTypes.h:176
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
Definition rpcTypes.h:157
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
Definition rpcTypes.h:154
ApplicationRelations applicationRelations
Definition rpcTypes.h:165
One slot/subslot's expected module configuration and IO data sizes.
Definition rpcTypes.h:96
Result of probing one index in EnumerateIndices().
Definition rpcTypes.h:494
std::uint8_t errorCode1
PNIOError ErrorCode1, if the error was a PNIOError.
Definition rpcTypes.h:504
std::string name
Human-readable name of the index.
Definition rpcTypes.h:498
std::size_t size
Payload size in bytes; valid when Status == "readable".
Definition rpcTypes.h:500
std::uint8_t errorCode2
PNIOError ErrorCode2, if the error was a PNIOError.
Definition rpcTypes.h:506
std::string error
Error message; valid when Status == "error".
Definition rpcTypes.h:502
std::string status
Probe outcome: "readable", "empty", or "error".
Definition rpcTypes.h:496
Configuration options for an RPC connection.
Definition rpcTypes.h:516
std::uint16_t rpcPort
UDP port used for RPC communication.
Definition rpcTypes.h:521
DREP-aware fields we need out of a raw incoming RPC request; PROFINET block payloads inside the NRD b...
Definition rpcTypes.h:311
std::array< std::uint8_t, 3 > drep
Data Representation (DREP) bytes.
Definition rpcTypes.h:333
std::uint8_t packetType
RPC packet type, such as request or response.
Definition rpcTypes.h:319
std::uint8_t serialLow
Low byte of the RPC serial number.
Definition rpcTypes.h:354
static constexpr std::uint8_t kLittleEndianDrepFlag
DREP flag indicating little-endian byte order.
Definition rpcTypes.h:313
std::uint8_t serialHigh
High byte of the RPC serial number.
Definition rpcTypes.h:328
std::uint32_t interfaceVersion
RPC interface version.
Definition rpcTypes.h:345
std::uint16_t operationNumber
RPC operation number.
Definition rpcTypes.h:351
std::uint8_t flags2
Second RPC flags byte.
Definition rpcTypes.h:325
std::array< std::uint8_t, uuidLenght > interfaceUuid
Raw interface UUID bytes.
Definition rpcTypes.h:339
std::array< std::uint8_t, uuidLenght > objectUuid
Raw object UUID bytes.
Definition rpcTypes.h:336
std::uint8_t version
RPC protocol version.
Definition rpcTypes.h:316
std::uint32_t sequenceNumber
RPC sequence number.
Definition rpcTypes.h:348
bool isLittleEndian
True when RPC scalar fields use little-endian byte order.
Definition rpcTypes.h:357
std::array< std::uint8_t, uuidLenght > activityUuid
Raw activity UUID bytes.
Definition rpcTypes.h:342
std::uint8_t flags1
First RPC flags byte.
Definition rpcTypes.h:322
Bytes payload
Remaining bytes after the fixed RPC header.
Definition rpcTypes.h:363
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Bytes payload
Definition test_dcp.cpp:392
std::uint16_t length
Definition test_dcp.cpp:391