26std::string Latin1Decode(
const std::uint8_t* data, std::size_t len)
34 for (std::size_t i = 0; i < len; ++i)
36 const std::uint8_t b = data[i];
39 out.push_back(
static_cast<char>(b));
43 out.push_back(
static_cast<char>(0xC0 | (b >> 6)));
44 out.push_back(
static_cast<char>(0x80 | (b & 0x3F)));
50const std::map<std::uint16_t, std::string>& MauTypeNames()
52 static const std::map<std::uint16_t, std::string> table = {
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"},
79 return std::format(
"SlotInfo(api={}, slot={}, subslot=0x{:04X})",
api,
slot,
subslot);
84 auto it = MauTypeNames().find(
mauType);
85 if (it != MauTypeNames().end())
89 return "Unknown(" + std::to_string(
mauType) +
")";
113 throw std::invalid_argument(
"Block header requires 6 bytes, got " +
114 std::to_string(data.size() >= offset ? data.size() - offset : 0));
128 if (data.size() < offset + 10)
130 throw std::invalid_argument(
"MultipleBlockHeader body requires 8 bytes after 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};
142 std::size_t start = offset;
144 auto alignFromBlock = [&](std::size_t bodyOffset) -> std::size_t
146 const std::size_t blockOffset = blockHeaderSize + (bodyOffset - start);
147 const std::size_t alignedBlock =
Align4(blockOffset);
148 return start + (alignedBlock - blockHeaderSize);
151 if (data.size() <= offset)
153 throw std::invalid_argument(
"Truncated interface data (missing chassis length)");
155 const std::uint8_t chassisLen = data[offset];
158 if (data.size() < offset + chassisLen)
160 throw std::invalid_argument(
"Truncated chassis ID");
162 const std::string chassisId = Latin1Decode(&data[offset], chassisLen);
163 offset += chassisLen;
165 offset = alignFromBlock(offset);
167 if (data.size() < offset + 6)
169 throw std::invalid_argument(
"Truncated MAC address");
173 std::memcpy(info.
macAddress.data(), &data[offset], 6);
176 offset = alignFromBlock(offset);
178 if (data.size() < offset + 12)
180 throw std::invalid_argument(
"Truncated IP configuration");
182 std::memcpy(info.
ipAddress.data(), &data[offset], 4);
184 std::memcpy(info.
subnetMask.data(), &data[offset], 4);
186 std::memcpy(info.
gateway.data(), &data[offset], 4);
193 const std::size_t start = offset;
196 if (data.size() >= offset + 4)
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]);
203 if (data.size() < offset + 1)
205 return PortInfo{.
slot = slot, .subslot = subslot, .portId =
"", .mauType = 0, .linkStatePort = 0, .linkStateLink = 0, .mediaType = 0, .peers = {}, .domainBoundary = 0, .multicastBoundary = 0};
208 const std::uint8_t portIdLen = data[offset];
212 if (data.size() >= offset + portIdLen)
214 portId = Latin1Decode(&data[offset], portIdLen);
218 std::uint8_t numPeers = 0;
219 std::vector<PeerInfo> peers;
220 if (data.size() > offset)
222 numPeers = data[offset];
226 offset = start +
Align4(offset - start);
228 for (std::uint8_t i = 0; i < numPeers; ++i)
230 if (data.size() < offset + 1)
235 const std::uint8_t peerPortLen = data[offset];
237 std::string peerPortId;
238 if (data.size() >= offset + peerPortLen)
240 peerPortId = Latin1Decode(&data[offset], peerPortLen);
241 offset += peerPortLen;
244 if (data.size() < offset + 1)
248 const std::uint8_t peerChassisLen = data[offset];
250 std::string peerChassisId;
251 if (data.size() >= offset + peerChassisLen)
253 peerChassisId = Latin1Decode(&data[offset], peerChassisLen);
254 offset += peerChassisLen;
257 offset = start +
Align4(offset - start);
259 std::array<std::uint8_t, 6> peerMac{};
260 if (data.size() >= offset + 6)
262 std::memcpy(peerMac.data(), &data[offset], 6);
266 offset = start +
Align4(offset - start);
268 peers.push_back({peerPortId, peerChassisId, peerMac});
271 std::uint16_t mauType = 0;
272 if (data.size() >= offset + 2)
274 mauType =
static_cast<std::uint16_t
>((data[offset] <<
OneOctetShift) | data[offset + 1]);
278 offset = start +
Align4(offset - start);
280 std::uint32_t domainBoundary = 0;
281 std::uint32_t multicastBoundary = 0;
282 if (data.size() >= offset + 8)
285 static_cast<std::uint32_t
>(data[offset + 2]) <<
OneOctetShift | data[offset + 3];
286 multicastBoundary =
static_cast<std::uint32_t
>(data[offset + 4]) <<
ThreeOctetsShift |
288 static_cast<std::uint32_t
>(data[offset + 6]) <<
OneOctetShift | data[offset + 7];
292 std::uint8_t linkStatePort = 0;
293 std::uint8_t linkStateLink = 0;
294 if (data.size() >= offset + 2)
296 linkStatePort = data[offset];
297 linkStateLink = data[offset + 1];
301 offset = start +
Align4(offset - start);
303 std::uint32_t mediaType = 0;
304 if (data.size() >= offset + 4)
307 static_cast<std::uint32_t
>(data[offset + 2]) <<
OneOctetShift | data[offset + 3];
310 return PortInfo{.
slot = slot, .subslot = subslot, .portId = portId, .mauType = mauType, .linkStatePort = linkStatePort, .linkStateLink = linkStateLink, .mediaType = mediaType, .peers = peers, .domainBoundary = domainBoundary, .multicastBoundary = multicastBoundary};
316 std::size_t offset = 0;
318 while (offset + 6 <= data.size())
321 std::size_t newOffset = 0;
326 catch (
const std::invalid_argument&)
331 std::size_t blockEnd = newOffset + header.
BodyLength();
332 if (blockEnd > data.size())
334 blockEnd = data.size();
345 slotInfo.
slot = slotNr;
351 std::size_t nestedBody = 0;
356 catch (
const std::invalid_argument&)
361 std::size_t nestedEnd = nestedBody + nestedHeader.
BodyLength();
362 if (nestedEnd > blockEnd)
364 nestedEnd = blockEnd;
374 catch (
const std::invalid_argument&)
384 catch (
const std::invalid_argument&)
389 nestedOffset = nestedEnd;
392 result.
slots.push_back(slotInfo);
393 result.
rawBlocks.emplace_back(api, slotNr, subslotNr,
394 Bytes(data.begin() + newOffset, data.begin() + blockEnd));
396 catch (
const std::invalid_argument&)
410 std::size_t offset = 0;
412 if (data.size() >= 6)
433 catch (
const std::invalid_argument&)
440 if (data.size() < offset + 2)
445 wire::Reader rOuter(data.data() + offset, data.size() - offset);
447 const bool v11 = result.
version.first >= 1 && result.
version.second >= 1;
451 const std::uint16_t numApis = rOuter.
U16();
452 for (std::uint16_t i = 0; i < numApis; ++i)
458 const std::uint32_t api = rOuter.
U32();
459 const std::uint16_t numSlots = rOuter.
U16();
461 for (std::uint16_t s = 0; s < numSlots; ++s)
467 const std::uint16_t slotNr = rOuter.
U16();
468 const std::uint32_t moduleIdent = rOuter.
U32();
469 const std::uint16_t numSubslots = rOuter.
U16();
471 for (std::uint16_t ss = 0; ss < numSubslots; ++ss)
477 const std::uint16_t subslotNr = rOuter.
U16();
478 const std::uint32_t submoduleIdent = rOuter.
U32();
480 result.
slots.push_back({slotNr, subslotNr, api, moduleIdent, submoduleIdent, {}});
487 const std::uint16_t numSlots = rOuter.
U16();
488 for (std::uint16_t s = 0; s < numSlots; ++s)
494 const std::uint16_t slotNr = rOuter.
U16();
495 const std::uint32_t moduleIdent = rOuter.
U32();
496 const std::uint16_t numSubslots = rOuter.
U16();
498 for (std::uint16_t ss = 0; ss < numSubslots; ++ss)
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, {}});
516 if (data.size() < offset + 26)
556 for (
const auto& mod :
modules)
562 for (
const auto& sub : mod.submodules)
575 std::vector<std::tuple<std::uint16_t, std::uint16_t, std::string>> mismatches;
576 for (
const auto& mod :
modules)
580 mismatches.emplace_back(mod.slotNumber, 0, mod.StateName());
582 for (
const auto& sub : mod.submodules)
586 mismatches.emplace_back(mod.slotNumber, sub.subslotNumber, sub.StateName());
607 const std::string message = std::format(
"Expected block type {} (0x8104), got {} (0x{:04X})",
610 static_cast<std::uint16_t
>(blockType));
612 throw std::invalid_argument(message);
619 const std::uint16_t numApis = r.
U16();
621 std::vector<ModuleDiffModule> modules;
623 for (std::uint16_t i = 0; i < numApis; ++i)
629 const std::uint32_t api = r.
U32();
630 const std::uint16_t numModules = r.
U16();
632 for (std::uint16_t m = 0; m < numModules; ++m)
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();
643 std::vector<ModuleDiffSubmodule> submodules;
644 for (std::uint16_t s = 0; s < numSubmodules; ++s)
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});
656 modules.push_back({api, slotNr, moduleIdent, moduleState, submodules});
668 std::uint16_t index,
Bytes data, std::uint32_t api)
670 writes.push_back({api, slot, subslot, index, std::move(data)});
675 std::uint16_t subslot, std::uint16_t index,
676 const Bytes& data)
const
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());
711 wire::PutU32(out,
static_cast<std::uint32_t
>(blocksLen));
712 Bytes padding(24, 0);
713 out.insert(out.end(), padding.begin(), padding.end());
721 for (std::size_t i = 0; i <
writes.size(); ++i)
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());
727 if (i + 1 <
writes.size())
729 std::size_t padLen = (4 - (block.size() % 4)) % 4;
730 blocksData.insert(blocksData.end(), padLen);
736 out.insert(out.end(), blocksData.begin(), blocksData.end());
742 std::vector<WriteMultipleResult> results;
743 if (data.size() < 64)
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];
751 std::size_t offset = 64;
752 const std::size_t end = std::min<std::size_t>(offset + recordLen, data.size());
754 while (offset + 56 <= end)
757 const std::uint16_t blockType = r.
U16();
758 const std::uint16_t blockLen = r.
U16();
760 const std::uint16_t seqNum = r.
U16();
762 const std::uint32_t api = r.
U32();
763 const std::uint16_t slot = r.
U16();
764 const std::uint16_t subslot = r.
U16();
766 const std::uint16_t index = r.
U16();
768 const std::uint16_t addVal1 = r.
U16();
769 const std::uint16_t addVal2 = r.
U16();
770 const std::uint32_t status = r.
U32();
772 if (blockType != 0x8008)
777 results.push_back({seqNum, api, slot, subslot, index, status, addVal1, addVal2});
779 const std::size_t blockSize = 4 + blockLen;
780 const std::size_t pad = (4 - (blockSize % 4)) % 4;
781 offset += blockSize + pad;
809 Bytes d = dd.ToBytes();
810 out.insert(out.end(), d.begin(), d.end());
823 if (
submodules.size() > std::numeric_limits<std::uint16_t>::max())
825 throw std::invalid_argument(
"ExpectedSubmoduleAPI: too many submodules");
831 Bytes s = sm.ToBytes();
832 out.insert(out.end(), s.begin(), s.end());
845 if (
slots.size() > std::numeric_limits<std::uint16_t>::max())
847 throw std::invalid_argument(
"ExpectedSubmoduleAPI: too many modules");
852 for (
const auto& slot :
slots)
854 Bytes s = slot.ToBytes();
855 out.insert(out.end(), s.begin(), s.end());
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)
877 if (apiEntry ==
nullptr)
881 apis.push_back(newApi);
882 apiEntry = &
apis.back();
886 auto it = std::ranges::find_if(apiEntry->
slots, [slot](
const auto& s)
888 return s.slotNumber == slot;
891 auto* slotEntry = (it != apiEntry->
slots.end()) ? &(*it) :
nullptr;
892 if (slotEntry ==
nullptr)
897 .moduleIdentNumber = moduleIdent,
898 .moduleProperties = 0,
900 slotEntry = &apiEntry->
slots.back();
903 std::vector<ExpectedSubmoduleDataDescription> dds;
904 switch (submoduleType)
908 dds.push_back({1, 0, 1, 1});
911 dds.push_back({1, inputLength, 1, 1});
914 dds.push_back({2, outputLength, 1, 1});
917 dds.push_back({1, inputLength, 1, 1});
918 dds.push_back({2, outputLength, 1, 1});
929 slotEntry->submodules.push_back(std::move(sm));
936 if (
apis.size() > std::numeric_limits<std::uint16_t>::max())
938 throw std::invalid_argument(
"ExpectedSubmoduleBlockReq: too many APIs");
944 for (
const auto& api :
apis)
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());
951 auto blockLen =
static_cast<std::uint16_t
>(body.size() + 2);
952 if (blockLen > std::numeric_limits<std::uint16_t>::max())
954 throw std::invalid_argument(
"ExpectedSubmoduleBlockReq: block too large");
964 out.insert(out.end(), body.begin(), body.end());
Declares PROFINET IO connection-management block structures and wire serialization.
ExpectedSubmoduleBlockReq (0x0104) builder.
Bytes ToBytes() const
Serialize the complete block to raw bytes.
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.
std::vector< ExpectedSubmoduleAPI > apis
APIs added so far, each with its own module/submodule list.
static constexpr std::uint16_t expectedSubmoduleBlockReqBlockType
Block type identifier for ExpectedSubmoduleBlockReq.
Builder for IODWriteMultipleReq packets (index 0xE040).
Bytes BuildHeader(std::size_t blocksLen) const
Serialize the outer IODWriteMultipleReq header.
static constexpr std::uint16_t writeMultipleBlockType
Block type used for each nested write block.
Bytes Build() const
Build the complete IODWriteMultipleReq packet.
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.
static constexpr std::uint16_t writeMultipleIndex
Record data index for WriteMultiple (0xE040).
std::vector< Write > writes
Queued write operations, in the order they'll be serialized.
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.
std::uint16_t seqNum
Starting sequence number for the outer header.
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID the writes belong to.
Lightweight cursor for parsing a big-endian byte buffer without copies.
std::uint8_t U8()
Read and consume one byte.
std::uint32_t U32()
Read and consume a big-endian 32-bit value.
std::size_t Remaining() const
Number of bytes not yet consumed.
void Skip(std::size_t n)
Advance the read position without returning the skipped bytes.
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
PROFINET record-data indices, block types, and protocol enumerations.
ModuleDiffBlock ParseModuleDiffBlock(const Bytes &data)
Parse a ModuleDiffBlock (0x8104) from raw bytes.
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.
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.
InterfaceInfo ParsePdInterfaceDataReal(const Bytes &data, std::size_t offset=0, std::size_t blockHeaderSize=blockHeaderLenght)
Parse a PDInterfaceDataReal (0x0240) block body.
std::vector< WriteMultipleResult > ParseWriteMultipleResponse(const Bytes &data)
Parse an IODWriteMultipleRes into individual per-write results.
PDRealData ParsePdRealData(const Bytes &data)
Parse a complete PDRealData (0xF841) response.
std::size_t Align4(std::size_t offset)
Round an offset up to the next 4-byte boundary.
RealIdentificationData ParseRealIdentificationData(const Bytes &data)
Parse a RealIdentificationData (0xF000 or 0x0013) response.
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.
std::optional< PortStatistics > ParsePortStatistics(const Bytes &data, std::size_t offset=0)
Parse a PDPortStatistic (0x0251) block body.
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t v)
Append a 16-bit value to a buffer in big-endian order.
void PutU8(std::vector< std::uint8_t > &out, std::uint8_t v)
Append a single byte to a buffer.
void PutU32(std::vector< std::uint8_t > &out, std::uint32_t v)
Append a 32-bit value to a buffer in big-endian order.
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.
BlockType
PROFINET block types.
@ 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.
std::string GetSubmoduleStateName(std::uint16_t state)
Get the human-readable name for a submodule state.
std::string GetModuleStateName(std::uint16_t state)
Get the human-readable name for a module state.
std::string GetBlockTypeName(BlockType blockType)
Get the human-readable name for a block type.
static constexpr int OneOctetShift
The bit-shift distance required to move data across a single octet.
static constexpr int ThreeOctetsShift
The bit-shift distance required to move data across three octets.
constexpr std::uint16_t MODULE_STATE_PROPER_MODULE
Module State Proper Module.
static constexpr int TwoOctetsShift
The bit-shift distance required to move data across two octets.
constexpr std::uint16_t SUBMODULE_STATE_OK
Submodule State Ok.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
constexpr int blockHeaderLenght
Constant lenght of a blockHeader.
Expected submodules for a specific API/slot.
std::vector< ExpectedSubmoduleSlot > slots
Expected slots within this module.
Bytes ToBytes() const
Serialize this API entry to raw bytes.
std::uint32_t api
API number.
std::uint16_t submoduleDataLength
Length in bytes of this direction's cyclic data.
std::uint8_t lengthIops
Length in bytes of the IOPS (provider status) trailer.
std::uint8_t lengthIocs
Length in bytes of the IOCS (consumer status) trailer.
std::uint16_t dataDescription
Data direction: 1 = Input, 2 = Output.
Bytes ToBytes() const
Serialize this description to raw bytes.
One slot with in an api submodule.
std::uint16_t slotNumber
Slot number.
std::uint32_t moduleIdentNumber
Expected module ident number.
std::vector< ExpectedSubmodule > submodules
Expected submodules within this module.
std::uint32_t api
API number.
Bytes ToBytes() const
Serialize this api submodule slot.
std::uint16_t moduleProperties
Module property flags.
Expected submodule within a slot.
std::vector< ExpectedSubmoduleDataDescription > dataDescriptions
Data descriptions for this submodule's cyclic data (one per active direction).
Bytes ToBytes() const
Serialize this submodule entry to raw bytes.
std::uint16_t subslotNumber
Subslot number.
std::uint16_t submoduleProperties
Submodule property flags (encodes the I/O direction type).
std::uint32_t submoduleIdentNumber
Expected submodule ident number.
Interface information from PDInterfaceDataReal (0x0240).
std::array< std::uint8_t, 4 > gateway
Interface IPv4 default gateway.
std::string chassisId
LLDP chassis ID.
std::array< std::uint8_t, 4 > subnetMask
Interface IPv4 subnet mask.
std::array< std::uint8_t, macAddressLength > macAddress
Interface MAC address.
std::array< std::uint8_t, 4 > ipAddress
Interface IPv4 address.
Parsed ModuleDiffBlock (0x8104).
std::vector< ModuleDiffModule > modules
Modules described by this block.
bool AllOk() const
Whether every module and submodule matches the expected configuration.
std::vector< std::tuple< std::uint16_t, std::uint16_t, std::string > > GetMismatches() const
List every non-OK module/submodule.
std::uint16_t moduleState
Module state (see MODULE_STATE_* constants).
bool IsProper() const
Whether this module matches the expected configuration.
std::string StateName() const
Human-readable name for ModuleState.
std::string StateName() const
Human-readable name for SubmoduleState.
bool IsOk() const
Whether this submodule matches the expected configuration.
std::uint16_t submoduleState
Submodule state (see SUBMODULE_STATE_* constants).
Parsed PDRealData (0xF841) structure.
std::optional< InterfaceInfo > interface
Interface information, if a PDInterfaceDataReal block was found.
std::vector< SlotInfo > slots
Slots discovered in the response.
std::vector< PortInfo > ports
Port information for each port found.
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.
Port information from PDPortDataReal (0x020F).
std::string MauTypeName() const
Human-readable name for MauType.
std::uint16_t mauType
MAU type (media attachment unit) code.
std::uint16_t slot
Slot number.
std::string LinkState() const
Human-readable name for LinkStateLink.
std::uint8_t linkStateLink
Link-level link state.
Parsed PDPortStatistic (0x0251) counters.
std::uint32_t outDiscards
Transmitted discarded frame count.
std::uint32_t outOctets
Transmitted octet count.
std::uint32_t inErrors
Received error frame count.
std::uint32_t inDiscards
Received discarded frame count.
std::uint32_t inOctets
Received octet count.
std::uint32_t outErrors
Transmitted error frame count.
std::uint16_t counterStatus
Counter validity status.
Parsed RealIdentificationData (0xF000/0x0013) structure.
std::vector< SlotInfo > slots
Populated slots/subslots.
std::pair< std::uint8_t, std::uint8_t > version
Block format version (determines v1.0 vs v1.1 layout).
Slot/subslot discovered from a device.
std::vector< std::string > blocks
Names of nested blocks found for this slot (e.g. "PDPortDataReal").
std::string ToString() const
Human-readable summary of this slot.
std::uint32_t api
API number.
std::uint16_t slot
Slot number.
std::uint16_t subslot
Subslot number.
Small helpers for reading/writing big-endian ("network order") integers to/from byte buffers....