PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
blocks.cpp
Go to the documentation of this file.
1
8
9#include "profinet/blocks.h"
10
11#include <algorithm>
12#include <cstring>
13#include <map>
14#include <ranges>
15#include <stdexcept>
16
17#include "profinet/indices.h"
18#include "profinet/wire.h"
19
20namespace profinet::blocks
21{
22
23namespace
24{
25
26std::string Latin1Decode(const std::uint8_t* data, std::size_t len)
27{
28 // Python's .decode("latin-1", errors="replace") maps each byte 1:1 to
29 // the Unicode code point of the same value, which for byte values >
30 // 0x7F needs UTF-8 multi-byte encoding here to represent correctly in
31 // a UTF-8 std::string. ASCII-range bytes pass through unchanged.
32 std::string out;
33 out.reserve(len);
34 for (std::size_t i = 0; i < len; ++i)
35 {
36 const std::uint8_t b = data[i];
37 if (b < 0x80)
38 {
39 out.push_back(static_cast<char>(b));
40 }
41 else
42 {
43 out.push_back(static_cast<char>(0xC0 | (b >> 6)));
44 out.push_back(static_cast<char>(0x80 | (b & 0x3F)));
45 }
46 }
47 return out;
48}
49
50const std::map<std::uint16_t, std::string>& MauTypeNames()
51{
52 static const std::map<std::uint16_t, std::string> table = {
53 {0, "Unknown"},
54 {10, "10BASE-T HD"},
55 {11, "10BASE-T FD"},
56 {15, "100BASE-TX HD"},
57 {16, "100BASE-TX FD"},
58 {17, "100BASE-FX HD"},
59 {18, "100BASE-FX FD"},
60 {29, "1000BASE-T HD"},
61 {30, "1000BASE-T FD"},
62 {21, "1000BASE-X HD"},
63 {22, "1000BASE-X FD"},
64 {40, "10GBASE-T"},
65 };
66 return table;
67}
68
69} // namespace
70
71std::string BlockHeader::TypeName() const
72{
74}
75
76std::string SlotInfo::ToString() const
77{
78 // Formats directly and prints subslot as a 4-digit, zero-padded hex value
79 return std::format("SlotInfo(api={}, slot={}, subslot=0x{:04X})", api, slot, subslot);
80}
81
82std::string PortInfo::MauTypeName() const
83{
84 auto it = MauTypeNames().find(mauType);
85 if (it != MauTypeNames().end())
86 {
87 return it->second;
88 }
89 return "Unknown(" + std::to_string(mauType) + ")";
90}
91
92std::string PortInfo::LinkState() const
93{
94 switch (linkStateLink)
95 {
96 case 0:
97 return "Unknown";
98 case 1:
99 return "Up";
100 case 2:
101 return "Down";
102 case 3:
103 return "Testing";
104 default:
105 return "Unknown(" + std::to_string(linkStateLink) + ")";
106 }
107}
108
109std::pair<BlockHeader, std::size_t> ParseBlockHeader(const Bytes& data, std::size_t offset)
110{
111 if (data.size() < offset + blockHeaderLenght)
112 {
113 throw std::invalid_argument("Block header requires 6 bytes, got " +
114 std::to_string(data.size() >= offset ? data.size() - offset : 0));
115 }
116 wire::Reader r(data.data() + offset, blockHeaderLenght);
117 BlockHeader h;
118 h.blockType = static_cast<BlockType>(r.U16());
119 h.blockLength = r.U16();
120 h.versionHigh = r.U8();
121 h.versionLow = r.U8();
122 return {h, offset + blockHeaderLenght};
123}
124
125std::tuple<std::uint32_t, std::uint16_t, std::uint16_t, std::size_t> ParseMultipleBlockHeader(const Bytes& data,
126 std::size_t offset)
127{
128 if (data.size() < offset + 10)
129 {
130 throw std::invalid_argument("MultipleBlockHeader body requires 8 bytes after padding");
131 }
132 wire::Reader r(data.data() + offset, 10);
133 r.Skip(2); // padding
134 const std::uint32_t api = r.U32();
135 const std::uint16_t slot = r.U16();
136 const std::uint16_t subslot = r.U16();
137 return {api, slot, subslot, offset + 10};
138}
139
140InterfaceInfo ParsePdInterfaceDataReal(const Bytes& data, std::size_t offset, std::size_t blockHeaderSize)
141{
142 std::size_t start = offset;
143
144 auto alignFromBlock = [&](std::size_t bodyOffset) -> std::size_t
145 {
146 const std::size_t blockOffset = blockHeaderSize + (bodyOffset - start);
147 const std::size_t alignedBlock = Align4(blockOffset);
148 return start + (alignedBlock - blockHeaderSize);
149 };
150
151 if (data.size() <= offset)
152 {
153 throw std::invalid_argument("Truncated interface data (missing chassis length)");
154 }
155 const std::uint8_t chassisLen = data[offset];
156 offset += 1;
157
158 if (data.size() < offset + chassisLen)
159 {
160 throw std::invalid_argument("Truncated chassis ID");
161 }
162 const std::string chassisId = Latin1Decode(&data[offset], chassisLen);
163 offset += chassisLen;
164
165 offset = alignFromBlock(offset);
166
167 if (data.size() < offset + 6)
168 {
169 throw std::invalid_argument("Truncated MAC address");
170 }
171 InterfaceInfo info;
172 info.chassisId = chassisId;
173 std::memcpy(info.macAddress.data(), &data[offset], 6);
174 offset += 6;
175
176 offset = alignFromBlock(offset);
177
178 if (data.size() < offset + 12)
179 {
180 throw std::invalid_argument("Truncated IP configuration");
181 }
182 std::memcpy(info.ipAddress.data(), &data[offset], 4);
183 offset += 4;
184 std::memcpy(info.subnetMask.data(), &data[offset], 4);
185 offset += 4;
186 std::memcpy(info.gateway.data(), &data[offset], 4);
187
188 return info;
189}
190
191PortInfo ParsePdPortDataReal(const Bytes& data, std::size_t offset, std::uint16_t slot, std::uint16_t subslot)
192{
193 const std::size_t start = offset;
194 offset = Align4(offset);
195
196 if (data.size() >= offset + 4)
197 {
198 slot = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
199 subslot = static_cast<std::uint16_t>((data[offset + 2] << OneOctetShift) | data[offset + 3]);
200 offset += 4;
201 }
202
203 if (data.size() < offset + 1)
204 {
205 return PortInfo{.slot = slot, .subslot = subslot, .portId = "", .mauType = 0, .linkStatePort = 0, .linkStateLink = 0, .mediaType = 0, .peers = {}, .domainBoundary = 0, .multicastBoundary = 0};
206 }
207
208 const std::uint8_t portIdLen = data[offset];
209 offset += 1;
210
211 std::string portId;
212 if (data.size() >= offset + portIdLen)
213 {
214 portId = Latin1Decode(&data[offset], portIdLen);
215 offset += portIdLen;
216 }
217
218 std::uint8_t numPeers = 0;
219 std::vector<PeerInfo> peers;
220 if (data.size() > offset)
221 {
222 numPeers = data[offset];
223 offset += 1;
224 }
225
226 offset = start + Align4(offset - start);
227
228 for (std::uint8_t i = 0; i < numPeers; ++i)
229 {
230 if (data.size() < offset + 1)
231 {
232 break;
233 }
234
235 const std::uint8_t peerPortLen = data[offset];
236 offset += 1;
237 std::string peerPortId;
238 if (data.size() >= offset + peerPortLen)
239 {
240 peerPortId = Latin1Decode(&data[offset], peerPortLen);
241 offset += peerPortLen;
242 }
243
244 if (data.size() < offset + 1)
245 {
246 break;
247 }
248 const std::uint8_t peerChassisLen = data[offset];
249 offset += 1;
250 std::string peerChassisId;
251 if (data.size() >= offset + peerChassisLen)
252 {
253 peerChassisId = Latin1Decode(&data[offset], peerChassisLen);
254 offset += peerChassisLen;
255 }
256
257 offset = start + Align4(offset - start);
258
259 std::array<std::uint8_t, 6> peerMac{};
260 if (data.size() >= offset + 6)
261 {
262 std::memcpy(peerMac.data(), &data[offset], 6);
263 offset += 6;
264 }
265
266 offset = start + Align4(offset - start);
267
268 peers.push_back({peerPortId, peerChassisId, peerMac});
269 }
270
271 std::uint16_t mauType = 0;
272 if (data.size() >= offset + 2)
273 {
274 mauType = static_cast<std::uint16_t>((data[offset] << OneOctetShift) | data[offset + 1]);
275 offset += 2;
276 }
277
278 offset = start + Align4(offset - start);
279
280 std::uint32_t domainBoundary = 0;
281 std::uint32_t multicastBoundary = 0;
282 if (data.size() >= offset + 8)
283 {
284 domainBoundary = static_cast<std::uint32_t>(data[offset]) << ThreeOctetsShift | static_cast<std::uint32_t>(data[offset + 1]) << TwoOctetsShift |
285 static_cast<std::uint32_t>(data[offset + 2]) << OneOctetShift | data[offset + 3];
286 multicastBoundary = static_cast<std::uint32_t>(data[offset + 4]) << ThreeOctetsShift |
287 static_cast<std::uint32_t>(data[offset + 5]) << TwoOctetsShift |
288 static_cast<std::uint32_t>(data[offset + 6]) << OneOctetShift | data[offset + 7];
289 offset += 8;
290 }
291
292 std::uint8_t linkStatePort = 0;
293 std::uint8_t linkStateLink = 0;
294 if (data.size() >= offset + 2)
295 {
296 linkStatePort = data[offset];
297 linkStateLink = data[offset + 1];
298 offset += 2;
299 }
300
301 offset = start + Align4(offset - start);
302
303 std::uint32_t mediaType = 0;
304 if (data.size() >= offset + 4)
305 {
306 mediaType = static_cast<std::uint32_t>(data[offset]) << ThreeOctetsShift | static_cast<std::uint32_t>(data[offset + 1]) << TwoOctetsShift |
307 static_cast<std::uint32_t>(data[offset + 2]) << OneOctetShift | data[offset + 3];
308 }
309
310 return PortInfo{.slot = slot, .subslot = subslot, .portId = portId, .mauType = mauType, .linkStatePort = linkStatePort, .linkStateLink = linkStateLink, .mediaType = mediaType, .peers = peers, .domainBoundary = domainBoundary, .multicastBoundary = multicastBoundary};
311}
312
314{
315 PDRealData result;
316 std::size_t offset = 0;
317
318 while (offset + 6 <= data.size())
319 {
320 BlockHeader header;
321 std::size_t newOffset = 0;
322 try
323 {
324 std::tie(header, newOffset) = ParseBlockHeader(data, offset);
325 }
326 catch (const std::invalid_argument&)
327 {
328 break;
329 }
330
331 std::size_t blockEnd = newOffset + header.BodyLength();
332 if (blockEnd > data.size())
333 {
334 blockEnd = data.size();
335 }
336
338 {
339 try
340 {
341 auto [api, slotNr, subslotNr, nestedOffset] = ParseMultipleBlockHeader(data, newOffset);
342
343 SlotInfo slotInfo;
344 slotInfo.api = api;
345 slotInfo.slot = slotNr;
346 slotInfo.subslot = subslotNr;
347
348 while (nestedOffset + blockHeaderLenght <= blockEnd)
349 {
350 BlockHeader nestedHeader;
351 std::size_t nestedBody = 0;
352 try
353 {
354 std::tie(nestedHeader, nestedBody) = ParseBlockHeader(data, nestedOffset);
355 }
356 catch (const std::invalid_argument&)
357 {
358 break;
359 }
360
361 std::size_t nestedEnd = nestedBody + nestedHeader.BodyLength();
362 if (nestedEnd > blockEnd)
363 {
364 nestedEnd = blockEnd;
365 }
366 slotInfo.blocks.push_back(nestedHeader.TypeName());
367
369 {
370 try
371 {
372 result.interface = ParsePdInterfaceDataReal(data, nestedBody);
373 }
374 catch (const std::invalid_argument&)
375 {
376 }
377 }
378 else if (nestedHeader.blockType == BlockType::PortDataReal)
379 {
380 try
381 {
382 result.ports.push_back(ParsePdPortDataReal(data, nestedBody, slotNr, subslotNr));
383 }
384 catch (const std::invalid_argument&)
385 {
386 }
387 }
388
389 nestedOffset = nestedEnd;
390 }
391
392 result.slots.push_back(slotInfo);
393 result.rawBlocks.emplace_back(api, slotNr, subslotNr,
394 Bytes(data.begin() + newOffset, data.begin() + blockEnd));
395 }
396 catch (const std::invalid_argument&)
397 {
398 }
399 }
400
401 offset = blockEnd;
402 }
403
404 return result;
405}
406
408{
410 std::size_t offset = 0;
411
412 if (data.size() >= 6)
413 {
414 try
415 {
416 BlockHeader header;
417 std::tie(header, offset) = ParseBlockHeader(data, 0);
418 // Only accept it as a real block header if the type actually matches
419 // a RealIdentificationData block — otherwise the data is raw body
420 // content (no header), as callers parsing RPC read responses that
421 // have already stripped the outer NRD envelope may provide.
424 {
425 result.version = {header.versionHigh, header.versionLow};
426 }
427 else
428 {
429 offset = 0;
430 result.version = {1, 0};
431 }
432 }
433 catch (const std::invalid_argument&)
434 {
435 offset = 0;
436 result.version = {1, 0};
437 }
438 }
439
440 if (data.size() < offset + 2)
441 {
442 return result;
443 }
444
445 wire::Reader rOuter(data.data() + offset, data.size() - offset);
446
447 const bool v11 = result.version.first >= 1 && result.version.second >= 1;
448
449 if (v11)
450 {
451 const std::uint16_t numApis = rOuter.U16();
452 for (std::uint16_t i = 0; i < numApis; ++i)
453 {
454 if (rOuter.Remaining() < 6)
455 {
456 break;
457 }
458 const std::uint32_t api = rOuter.U32();
459 const std::uint16_t numSlots = rOuter.U16();
460
461 for (std::uint16_t s = 0; s < numSlots; ++s)
462 {
463 if (rOuter.Remaining() < 8)
464 {
465 break;
466 }
467 const std::uint16_t slotNr = rOuter.U16();
468 const std::uint32_t moduleIdent = rOuter.U32();
469 const std::uint16_t numSubslots = rOuter.U16();
470
471 for (std::uint16_t ss = 0; ss < numSubslots; ++ss)
472 {
473 if (rOuter.Remaining() < 6)
474 {
475 break;
476 }
477 const std::uint16_t subslotNr = rOuter.U16();
478 const std::uint32_t submoduleIdent = rOuter.U32();
479 // result.Slots.push_back({slot_nr, subslot_nr, Api, ModuleIdent, SubmoduleIdent, {}});
480 result.slots.push_back({slotNr, subslotNr, api, moduleIdent, submoduleIdent, {}});
481 }
482 }
483 }
484 }
485 else
486 {
487 const std::uint16_t numSlots = rOuter.U16();
488 for (std::uint16_t s = 0; s < numSlots; ++s)
489 {
490 if (rOuter.Remaining() < 8)
491 {
492 break;
493 }
494 const std::uint16_t slotNr = rOuter.U16();
495 const std::uint32_t moduleIdent = rOuter.U32();
496 const std::uint16_t numSubslots = rOuter.U16();
497
498 for (std::uint16_t ss = 0; ss < numSubslots; ++ss)
499 {
500 if (rOuter.Remaining() < 6)
501 {
502 break;
503 }
504 const std::uint16_t subslotNr = rOuter.U16();
505 const std::uint32_t submoduleIdent = rOuter.U32();
506 result.slots.push_back({slotNr, subslotNr, 0, moduleIdent, submoduleIdent, {}});
507 }
508 }
509 }
510
511 return result;
512}
513
514std::optional<PortStatistics> ParsePortStatistics(const Bytes& data, std::size_t offset)
515{
516 if (data.size() < offset + 26)
517 {
518 return std::nullopt;
519 }
520 wire::Reader r(data.data() + offset, 26);
522 s.counterStatus = r.U16();
523 s.inOctets = r.U32();
524 s.outOctets = r.U32();
525 s.inDiscards = r.U32();
526 s.outDiscards = r.U32();
527 s.inErrors = r.U32();
528 s.outErrors = r.U32();
529 return s;
530}
531
532// =============================================================================
533// ModuleDiffBlock
534// =============================================================================
535
537{
539}
541{
543}
544
546{
548}
553
555{
556 for (const auto& mod : modules)
557 {
558 if (!mod.IsProper())
559 {
560 return false;
561 }
562 for (const auto& sub : mod.submodules)
563 {
564 if (!sub.IsOk())
565 {
566 return false;
567 }
568 }
569 }
570 return true;
571}
572
573std::vector<std::tuple<std::uint16_t, std::uint16_t, std::string>> ModuleDiffBlock::GetMismatches() const
574{
575 std::vector<std::tuple<std::uint16_t, std::uint16_t, std::string>> mismatches;
576 for (const auto& mod : modules)
577 {
578 if (!mod.IsProper())
579 {
580 mismatches.emplace_back(mod.slotNumber, 0, mod.StateName());
581 }
582 for (const auto& sub : mod.submodules)
583 {
584 if (!sub.IsOk())
585 {
586 mismatches.emplace_back(mod.slotNumber, sub.subslotNumber, sub.StateName());
587 }
588 }
589 }
590 return mismatches;
591}
592
594{
595 if (data.size() < 6)
596 {
597 return ModuleDiffBlock{};
598 }
599
600 wire::Reader r(data);
601 const auto blockType = static_cast<BlockType>(r.U16());
602 r.Skip(4); // block_len, ver_hi, ver_lo
603
604 if (blockType != BlockType::ModuleDifference)
605 {
606 // Extract the formatted error message into a dedicated const variable
607 const std::string message = std::format("Expected block type {} (0x8104), got {} (0x{:04X})",
609 GetBlockTypeName(blockType),
610 static_cast<std::uint16_t>(blockType));
611
612 throw std::invalid_argument(message);
613 }
614
615 if (r.Remaining() < 2)
616 {
617 return ModuleDiffBlock{};
618 }
619 const std::uint16_t numApis = r.U16();
620
621 std::vector<ModuleDiffModule> modules;
622
623 for (std::uint16_t i = 0; i < numApis; ++i)
624 {
625 if (r.Remaining() < 6)
626 {
627 break;
628 }
629 const std::uint32_t api = r.U32();
630 const std::uint16_t numModules = r.U16();
631
632 for (std::uint16_t m = 0; m < numModules; ++m)
633 {
634 if (r.Remaining() < 10)
635 {
636 break;
637 }
638 const std::uint16_t slotNr = r.U16();
639 const std::uint32_t moduleIdent = r.U32();
640 const std::uint16_t moduleState = r.U16();
641 const std::uint16_t numSubmodules = r.U16();
642
643 std::vector<ModuleDiffSubmodule> submodules;
644 for (std::uint16_t s = 0; s < numSubmodules; ++s)
645 {
646 if (r.Remaining() < 8)
647 {
648 break;
649 }
650 const std::uint16_t subslotNr = r.U16();
651 const std::uint32_t submoduleIdent = r.U32();
652 const std::uint16_t submoduleState = r.U16();
653 submodules.push_back({subslotNr, submoduleIdent, submoduleState});
654 }
655
656 modules.push_back({api, slotNr, moduleIdent, moduleState, submodules});
657 }
658 }
659
660 return ModuleDiffBlock{modules};
661}
662
663// =============================================================================
664// IODWriteMultiple
665// =============================================================================
666
667IODWriteMultipleBuilder& IODWriteMultipleBuilder::AddWrite(std::uint16_t slot, std::uint16_t subslot,
668 std::uint16_t index, Bytes data, std::uint32_t api)
669{
670 writes.push_back({api, slot, subslot, index, std::move(data)});
671 return *this;
672}
673
674Bytes IODWriteMultipleBuilder::BuildWriteBlock(std::uint16_t seq, std::uint32_t api, std::uint16_t slot,
675 std::uint16_t subslot, std::uint16_t index,
676 const Bytes& data) const
677{
678 Bytes out;
680 wire::PutU16(out, 60);
681 wire::PutU8(out, 0x01);
682 wire::PutU8(out, 0x00);
683 wire::PutU16(out, seq);
685 wire::PutU32(out, api);
686 wire::PutU16(out, slot);
687 wire::PutU16(out, subslot);
688 wire::PutU16(out, 0);
689 wire::PutU16(out, index);
690 wire::PutU32(out, static_cast<std::uint32_t>(data.size()));
691 Bytes padding(24, 0);
692 out.insert(out.end(), padding.begin(), padding.end());
693 out.insert(out.end(), data.begin(), data.end());
694 return out;
695}
696
697Bytes IODWriteMultipleBuilder::BuildHeader(std::size_t blocksLen) const
698{
699 Bytes out;
701 wire::PutU16(out, 60);
702 wire::PutU8(out, 0x01);
703 wire::PutU8(out, 0x00);
704 wire::PutU16(out, seqNum);
706 wire::PutU32(out, 0xFFFFFFFF);
707 wire::PutU16(out, 0xFFFF);
708 wire::PutU16(out, 0xFFFF);
709 wire::PutU16(out, 0);
711 wire::PutU32(out, static_cast<std::uint32_t>(blocksLen));
712 Bytes padding(24, 0);
713 out.insert(out.end(), padding.begin(), padding.end());
714 return out;
715}
716
718{
719 Bytes blocksData;
720
721 for (std::size_t i = 0; i < writes.size(); ++i)
722 {
723 const auto& w = writes[i];
724 Bytes block = BuildWriteBlock(static_cast<std::uint16_t>(i), w.api, w.slot, w.subslot, w.index, w.data);
725 blocksData.insert(blocksData.end(), block.begin(), block.end());
726
727 if (i + 1 < writes.size())
728 {
729 std::size_t padLen = (4 - (block.size() % 4)) % 4;
730 blocksData.insert(blocksData.end(), padLen);
731 }
732 }
733
734 const Bytes header = BuildHeader(blocksData.size());
735 Bytes out = header;
736 out.insert(out.end(), blocksData.begin(), blocksData.end());
737 return out;
738}
739
740std::vector<WriteMultipleResult> ParseWriteMultipleResponse(const Bytes& data)
741{
742 std::vector<WriteMultipleResult> results;
743 if (data.size() < 64)
744 {
745 return results;
746 }
747
748 const std::uint32_t recordLen = static_cast<std::uint32_t>(data[36]) << ThreeOctetsShift | static_cast<std::uint32_t>(data[37]) << TwoOctetsShift |
749 static_cast<std::uint32_t>(data[38]) << OneOctetShift | data[39];
750
751 std::size_t offset = 64;
752 const std::size_t end = std::min<std::size_t>(offset + recordLen, data.size());
753
754 while (offset + 56 <= end)
755 {
756 wire::Reader r(data.data() + offset, 56);
757 const std::uint16_t blockType = r.U16();
758 const std::uint16_t blockLen = r.U16();
759 r.Skip(2); // ver_hi, ver_lo
760 const std::uint16_t seqNum = r.U16();
761 r.Skip(uuidLenght); // ar_uuid
762 const std::uint32_t api = r.U32();
763 const std::uint16_t slot = r.U16();
764 const std::uint16_t subslot = r.U16();
765 r.Skip(2); // padding
766 const std::uint16_t index = r.U16();
767 r.Skip(4); // record_data_length
768 const std::uint16_t addVal1 = r.U16();
769 const std::uint16_t addVal2 = r.U16();
770 const std::uint32_t status = r.U32();
771
772 if (blockType != 0x8008)
773 {
774 break;
775 }
776
777 results.push_back({seqNum, api, slot, subslot, index, status, addVal1, addVal2});
778
779 const std::size_t blockSize = 4 + blockLen;
780 const std::size_t pad = (4 - (blockSize % 4)) % 4;
781 offset += blockSize + pad;
782 }
783
784 return results;
785}
786
787// =============================================================================
788// ExpectedSubmodule Structures
789// =============================================================================
790
800
802{
803 Bytes out;
807 for (const auto& dd : dataDescriptions)
808 {
809 Bytes d = dd.ToBytes();
810 out.insert(out.end(), d.begin(), d.end());
811 }
812 return out;
813}
814
816{
817 Bytes out;
818 wire::PutU32(out, api);
822
823 if (submodules.size() > std::numeric_limits<std::uint16_t>::max())
824 {
825 throw std::invalid_argument("ExpectedSubmoduleAPI: too many submodules");
826 }
827 wire::PutU16(out, static_cast<std::uint16_t>(submodules.size()));
828
829 for (const auto& sm : submodules)
830 {
831 Bytes s = sm.ToBytes();
832 out.insert(out.end(), s.begin(), s.end());
833 }
834 return out;
835}
836
838{
839 Bytes out;
840
842 // wire::PutU16(out, 0); // reserved
843 // wire::PutU16(out, static_cast<std::uint16_t>(slots.size()));
844
845 if (slots.size() > std::numeric_limits<std::uint16_t>::max())
846 {
847 throw std::invalid_argument("ExpectedSubmoduleAPI: too many modules");
848 }
849
850 // wire::PutU16(out, static_cast<std::uint16_t>(slots.size()));
851
852 for (const auto& slot : slots)
853 {
854 Bytes s = slot.ToBytes();
855 out.insert(out.end(), s.begin(), s.end());
856 }
857 return out;
858}
859
861 std::uint16_t subslot,
862 std::uint32_t moduleIdent,
863 std::uint32_t submoduleIdent,
864 std::uint16_t submoduleType,
865 std::uint16_t inputLength,
866 std::uint16_t outputLength)
867{
868 ExpectedSubmoduleAPI* apiEntry = nullptr;
869 for (auto& a : apis)
870 {
871 if (a.api == api)
872 {
873 apiEntry = &a;
874 break;
875 }
876 }
877 if (apiEntry == nullptr)
878 {
880 newApi.api = api;
881 apis.push_back(newApi);
882 apiEntry = &apis.back();
883 }
884
885 // Find Slot inside API
886 auto it = std::ranges::find_if(apiEntry->slots, [slot](const auto& s)
887 {
888 return s.slotNumber == slot;
889 });
890
891 auto* slotEntry = (it != apiEntry->slots.end()) ? &(*it) : nullptr;
892 if (slotEntry == nullptr)
893 {
894 apiEntry->slots.push_back(ExpectedSubmoduleSlot{
895 .api = api,
896 .slotNumber = slot,
897 .moduleIdentNumber = moduleIdent,
898 .moduleProperties = 0,
899 .submodules = {}});
900 slotEntry = &apiEntry->slots.back();
901 }
902
903 std::vector<ExpectedSubmoduleDataDescription> dds;
904 switch (submoduleType)
905 {
906 case 0: // NO_IO
907 // NO DataDescription is encoded for a NO_IO submodule.
908 dds.push_back({1, 0, 1, 1});
909 break;
910 case 1: // INPUT
911 dds.push_back({1, inputLength, 1, 1});
912 break;
913 case 2: // OUTPUT
914 dds.push_back({2, outputLength, 1, 1});
915 break;
916 case 3: // INPUT_OUTPUT
917 dds.push_back({1, inputLength, 1, 1});
918 dds.push_back({2, outputLength, 1, 1});
919 break;
920 default:
921 break;
922 }
923
925 sm.subslotNumber = subslot;
926 sm.submoduleIdentNumber = submoduleIdent;
927 sm.submoduleProperties = submoduleType; // 0x0001; // Use 0x0001 instead of submoduleType
928 sm.dataDescriptions = std::move(dds);
929 slotEntry->submodules.push_back(std::move(sm));
930
931 return *this;
932}
933
935{
936 if (apis.size() > std::numeric_limits<std::uint16_t>::max())
937 {
938 throw std::invalid_argument("ExpectedSubmoduleBlockReq: too many APIs");
939 }
940 Bytes body;
941 // wire::PutU16(body, 0); // reserved
942 // wire::PutU16(body, static_cast<std::uint16_t>(apis.size()));
943
944 for (const auto& api : apis)
945 {
946 wire::PutU16(body, static_cast<std::uint16_t>(api.slots.size()));
947 Bytes a = api.ToBytes();
948 body.insert(body.end(), a.begin(), a.end());
949 }
950
951 auto blockLen = static_cast<std::uint16_t>(body.size() + 2); // +2 == BlockVersion length
952 if (blockLen > std::numeric_limits<std::uint16_t>::max())
953 {
954 throw std::invalid_argument("ExpectedSubmoduleBlockReq: block too large");
955 }
956
957 Bytes header;
959 wire::PutU16(header, blockLen);
960 wire::PutU8(header, 0x01);
961 wire::PutU8(header, 0x00);
962
963 Bytes out = header;
964 out.insert(out.end(), body.begin(), body.end());
965 return out;
966}
967
968} // namespace profinet::blocks
Declares PROFINET IO connection-management block structures and wire serialization.
ExpectedSubmoduleBlockReq (0x0104) builder.
Definition blocks.h:675
Bytes ToBytes() const
Serialize the complete block to raw bytes.
Definition blocks.cpp:934
ExpectedSubmoduleBlockReq & AddSubmodule(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint32_t moduleIdent, std::uint32_t submoduleIdent, std::uint16_t submoduleType=0, std::uint16_t inputLength=0, std::uint16_t outputLength=0)
Add a single submodule.
Definition blocks.cpp:860
std::vector< ExpectedSubmoduleAPI > apis
APIs added so far, each with its own module/submodule list.
Definition blocks.h:701
static constexpr std::uint16_t expectedSubmoduleBlockReqBlockType
Block type identifier for ExpectedSubmoduleBlockReq.
Definition blocks.h:678
Builder for IODWriteMultipleReq packets (index 0xE040).
Definition blocks.h:488
Bytes BuildHeader(std::size_t blocksLen) const
Serialize the outer IODWriteMultipleReq header.
Definition blocks.cpp:697
static constexpr std::uint16_t writeMultipleBlockType
Block type used for each nested write block.
Definition blocks.h:494
Bytes Build() const
Build the complete IODWriteMultipleReq packet.
Definition blocks.cpp:717
IODWriteMultipleBuilder & AddWrite(std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, Bytes data, std::uint32_t api=0)
Add a write operation.
Definition blocks.cpp:667
static constexpr std::uint16_t writeMultipleIndex
Record data index for WriteMultiple (0xE040).
Definition blocks.h:491
std::vector< Write > writes
Queued write operations, in the order they'll be serialized.
Definition blocks.h:557
Bytes BuildWriteBlock(std::uint16_t seq, std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t index, const Bytes &data) const
Serialize a single nested write block.
Definition blocks.cpp:674
std::uint16_t seqNum
Starting sequence number for the outer header.
Definition blocks.h:539
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID the writes belong to.
Definition blocks.h:536
Lightweight cursor for parsing a big-endian byte buffer without copies.
Definition wire.h:67
std::uint8_t U8()
Read and consume one byte.
Definition wire.h:125
std::uint32_t U32()
Read and consume a big-endian 32-bit value.
Definition wire.h:143
std::size_t Remaining() const
Number of bytes not yet consumed.
Definition wire.h:93
void Skip(std::size_t n)
Advance the read position without returning the skipped bytes.
Definition wire.h:180
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
Definition wire.h:133
PROFINET record-data indices, block types, and protocol enumerations.
ModuleDiffBlock ParseModuleDiffBlock(const Bytes &data)
Parse a ModuleDiffBlock (0x8104) from raw bytes.
Definition blocks.cpp:593
PortInfo ParsePdPortDataReal(const Bytes &data, std::size_t offset=0, std::uint16_t slot=0, std::uint16_t subslot=0)
Parse a PDPortDataReal (0x020F) block body.
Definition blocks.cpp:191
std::tuple< std::uint32_t, std::uint16_t, std::uint16_t, std::size_t > ParseMultipleBlockHeader(const Bytes &data, std::size_t offset=0)
Parse a MultipleBlockHeader (0x0400) body.
Definition blocks.cpp:125
InterfaceInfo ParsePdInterfaceDataReal(const Bytes &data, std::size_t offset=0, std::size_t blockHeaderSize=blockHeaderLenght)
Parse a PDInterfaceDataReal (0x0240) block body.
Definition blocks.cpp:140
std::vector< WriteMultipleResult > ParseWriteMultipleResponse(const Bytes &data)
Parse an IODWriteMultipleRes into individual per-write results.
Definition blocks.cpp:740
PDRealData ParsePdRealData(const Bytes &data)
Parse a complete PDRealData (0xF841) response.
Definition blocks.cpp:313
std::size_t Align4(std::size_t offset)
Round an offset up to the next 4-byte boundary.
Definition blocks.h:266
RealIdentificationData ParseRealIdentificationData(const Bytes &data)
Parse a RealIdentificationData (0xF000 or 0x0013) response.
Definition blocks.cpp:407
std::pair< BlockHeader, std::size_t > ParseBlockHeader(const Bytes &data, std::size_t offset=0)
Parse a 6-byte PROFINET block header at an offset.
Definition blocks.cpp:109
std::optional< PortStatistics > ParsePortStatistics(const Bytes &data, std::size_t offset=0)
Parse a PDPortStatistic (0x0251) block body.
Definition blocks.cpp:514
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t v)
Append a 16-bit value to a buffer in big-endian order.
Definition wire.h:27
void PutU8(std::vector< std::uint8_t > &out, std::uint8_t v)
Append a single byte to a buffer.
Definition wire.h:19
void PutU32(std::vector< std::uint8_t > &out, std::uint32_t v)
Append a 32-bit value to a buffer in big-endian order.
Definition wire.h:36
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:58
BlockType
PROFINET block types.
Definition indices.h:91
@ RealIdentificationDataApi
Real Identification Data API block.
@ ModuleDifference
Module difference block.
@ PortDataReal
PD Port Data Real block.
@ RealIdentificationData
Real identification data block.
@ PortDataInterfaceDataReal
PD Interface Data Real block.
@ MultipleHeader
Multiple Header block.
constexpr int uuidLenght
Constant lenght of a UUID.
Definition util.h:44
std::string GetSubmoduleStateName(std::uint16_t state)
Get the human-readable name for a submodule state.
Definition indices.cpp:457
std::string GetModuleStateName(std::uint16_t state)
Get the human-readable name for a module state.
Definition indices.cpp:445
std::string GetBlockTypeName(BlockType blockType)
Get the human-readable name for a block type.
Definition indices.cpp:38
static constexpr int OneOctetShift
The bit-shift distance required to move data across a single octet.
Definition util.h:50
static constexpr int ThreeOctetsShift
The bit-shift distance required to move data across three octets.
Definition util.h:56
constexpr std::uint16_t MODULE_STATE_PROPER_MODULE
Module State Proper Module.
Definition indices.h:568
static constexpr int TwoOctetsShift
The bit-shift distance required to move data across two octets.
Definition util.h:53
constexpr std::uint16_t SUBMODULE_STATE_OK
Submodule State Ok.
Definition indices.h:581
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:52
constexpr int blockHeaderLenght
Constant lenght of a blockHeader.
Definition protocol.h:55
PROFINET block header (6 bytes).
Definition blocks.h:33
std::uint16_t BodyLength() const
Length of the block body, excluding the version bytes.
Definition blocks.h:48
BlockType blockType
Block type identifier.
Definition blocks.h:35
std::uint16_t blockLength
Length in bytes of the block body, including the 2 version bytes.
Definition blocks.h:38
std::string TypeName() const
Human-readable name for BlockType.
Definition blocks.cpp:71
std::uint8_t versionHigh
Block format major version.
Definition blocks.h:41
std::uint8_t versionLow
Block format minor version.
Definition blocks.h:44
Expected submodules for a specific API/slot.
Definition blocks.h:648
std::vector< ExpectedSubmoduleSlot > slots
Expected slots within this module.
Definition blocks.h:653
Bytes ToBytes() const
Serialize this API entry to raw bytes.
Definition blocks.cpp:837
std::uint32_t api
API number.
Definition blocks.h:650
std::uint16_t submoduleDataLength
Length in bytes of this direction's cyclic data.
Definition blocks.h:584
std::uint8_t lengthIops
Length in bytes of the IOPS (provider status) trailer.
Definition blocks.h:590
std::uint8_t lengthIocs
Length in bytes of the IOCS (consumer status) trailer.
Definition blocks.h:587
std::uint16_t dataDescription
Data direction: 1 = Input, 2 = Output.
Definition blocks.h:581
Bytes ToBytes() const
Serialize this description to raw bytes.
Definition blocks.cpp:791
One slot with in an api submodule.
Definition blocks.h:625
std::uint16_t slotNumber
Slot number.
Definition blocks.h:630
std::uint32_t moduleIdentNumber
Expected module ident number.
Definition blocks.h:633
std::vector< ExpectedSubmodule > submodules
Expected submodules within this module.
Definition blocks.h:639
std::uint32_t api
API number.
Definition blocks.h:627
Bytes ToBytes() const
Serialize this api submodule slot.
Definition blocks.cpp:815
std::uint16_t moduleProperties
Module property flags.
Definition blocks.h:636
Expected submodule within a slot.
Definition blocks.h:599
std::vector< ExpectedSubmoduleDataDescription > dataDescriptions
Data descriptions for this submodule's cyclic data (one per active direction).
Definition blocks.h:610
Bytes ToBytes() const
Serialize this submodule entry to raw bytes.
Definition blocks.cpp:801
std::uint16_t subslotNumber
Subslot number.
Definition blocks.h:601
std::uint16_t submoduleProperties
Submodule property flags (encodes the I/O direction type).
Definition blocks.h:607
std::uint32_t submoduleIdentNumber
Expected submodule ident number.
Definition blocks.h:604
Interface information from PDInterfaceDataReal (0x0240).
Definition blocks.h:168
std::array< std::uint8_t, 4 > gateway
Interface IPv4 default gateway.
Definition blocks.h:182
std::string chassisId
LLDP chassis ID.
Definition blocks.h:170
std::array< std::uint8_t, 4 > subnetMask
Interface IPv4 subnet mask.
Definition blocks.h:179
std::array< std::uint8_t, macAddressLength > macAddress
Interface MAC address.
Definition blocks.h:173
std::array< std::uint8_t, 4 > ipAddress
Interface IPv4 address.
Definition blocks.h:176
Parsed ModuleDiffBlock (0x8104).
Definition blocks.h:415
std::vector< ModuleDiffModule > modules
Modules described by this block.
Definition blocks.h:417
bool AllOk() const
Whether every module and submodule matches the expected configuration.
Definition blocks.cpp:554
std::vector< std::tuple< std::uint16_t, std::uint16_t, std::string > > GetMismatches() const
List every non-OK module/submodule.
Definition blocks.cpp:573
std::uint16_t moduleState
Module state (see MODULE_STATE_* constants).
Definition blocks.h:392
bool IsProper() const
Whether this module matches the expected configuration.
Definition blocks.cpp:549
std::string StateName() const
Human-readable name for ModuleState.
Definition blocks.cpp:545
std::string StateName() const
Human-readable name for SubmoduleState.
Definition blocks.cpp:536
bool IsOk() const
Whether this submodule matches the expected configuration.
Definition blocks.cpp:540
std::uint16_t submoduleState
Submodule state (see SUBMODULE_STATE_* constants).
Definition blocks.h:361
Parsed PDRealData (0xF841) structure.
Definition blocks.h:221
std::optional< InterfaceInfo > interface
Interface information, if a PDInterfaceDataReal block was found.
Definition blocks.h:226
std::vector< SlotInfo > slots
Slots discovered in the response.
Definition blocks.h:223
std::vector< PortInfo > ports
Port information for each port found.
Definition blocks.h:229
std::vector< std::tuple< std::uint32_t, std::uint16_t, std::uint16_t, Bytes > > rawBlocks
Raw (api, slot, subslot, MultipleBlockHeader payload) tuples, one per slot.
Definition blocks.h:232
Port information from PDPortDataReal (0x020F).
Definition blocks.h:119
std::string MauTypeName() const
Human-readable name for MauType.
Definition blocks.cpp:82
std::uint16_t mauType
MAU type (media attachment unit) code.
Definition blocks.h:130
std::uint16_t slot
Slot number.
Definition blocks.h:121
std::string LinkState() const
Human-readable name for LinkStateLink.
Definition blocks.cpp:92
std::uint8_t linkStateLink
Link-level link state.
Definition blocks.h:136
Parsed PDPortStatistic (0x0251) counters.
Definition blocks.h:318
std::uint32_t outDiscards
Transmitted discarded frame count.
Definition blocks.h:332
std::uint32_t outOctets
Transmitted octet count.
Definition blocks.h:326
std::uint32_t inErrors
Received error frame count.
Definition blocks.h:335
std::uint32_t inDiscards
Received discarded frame count.
Definition blocks.h:329
std::uint32_t inOctets
Received octet count.
Definition blocks.h:323
std::uint32_t outErrors
Transmitted error frame count.
Definition blocks.h:338
std::uint16_t counterStatus
Counter validity status.
Definition blocks.h:320
Parsed RealIdentificationData (0xF000/0x0013) structure.
Definition blocks.h:244
std::vector< SlotInfo > slots
Populated slots/subslots.
Definition blocks.h:246
std::pair< std::uint8_t, std::uint8_t > version
Block format version (determines v1.0 vs v1.1 layout).
Definition blocks.h:249
Slot/subslot discovered from a device.
Definition blocks.h:60
std::vector< std::string > blocks
Names of nested blocks found for this slot (e.g. "PDPortDataReal").
Definition blocks.h:77
std::string ToString() const
Human-readable summary of this slot.
Definition blocks.cpp:76
std::uint32_t api
API number.
Definition blocks.h:68
std::uint16_t slot
Slot number.
Definition blocks.h:62
std::uint16_t subslot
Subslot number.
Definition blocks.h:65
Small helpers for reading/writing big-endian ("network order") integers to/from byte buffers....