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