PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
util.cpp
Go to the documentation of this file.
1
26
27#include "profinet/util.h"
28
29#include <algorithm>
30#include <asio/ip/address_v4.hpp>
31#include <cerrno>
32#include <cstdio>
33#include <cstring>
34
35#include "profinet/exceptions.h"
36#include "profinet/rt.h"
37#include "profinet/wire.h"
38
39namespace profinet
40{
41// =============================================================================
42// Byte Conversion Utilities
43// =============================================================================
44std::string Hex4(std::uint16_t value)
45{
46 return std::format("0x{:04X}", value);
47}
48
49std::string Hex2(std::uint8_t value)
50{
51 return std::format("0x{:02X}", value);
52}
53
54std::vector<std::uint8_t> ToVec(const std::string& input)
55{
56 return {input.begin(), input.end()};
57}
58
59std::string HexFallback(const char* widthPrefix, std::uint32_t value, int digitWidth)
60{
61 // {} -> The prefix string
62 // {:0{}X} -> Hexadecimal (X), zero-padded (0), with a dynamic width ({})
63 return std::format("{}(0x{:0{}X})", widthPrefix, value, digitWidth);
64}
65
66// =============================================================================
67// Address Conversion Utilities
68// =============================================================================
69
70std::size_t SkipVlanTags(std::span<const std::uint8_t> frame) noexcept
71{
72 const std::size_t ethernetHeaderSize = 2U * macAddressLength;
73
74 if (frame.size() < ethernetHeaderSize)
75 {
76 return ethernetHeaderSize;
77 }
78
80 r.SkipTo(ethernetHeaderSize);
81
82 while (r.Remaining() >= 2U * sizeof(std::uint16_t))
83 {
84 const std::size_t tpidOffset = r.Position();
85
86 // Read 16-bit TPID in network byte order (big-endian)
87 const std::uint16_t tpid = r.U16();
88
89 // 802.1Q (0x8100), 802.1ad (0x88A8), 802.1Q double-tagging legacy (0x9100)
91 {
92 // Skip the 16-bit VLAN TCI.
93 r.Skip(sizeof(std::uint16_t));
94 }
95 else
96 {
97 // TPID was not a VLAN tag, so return the offset where it starts.
98 return tpidOffset;
99 }
100 }
101
102 return r.Position();
103}
104std::string ToHex(const std::uint8_t* data, std::size_t len)
105{
106 std::string out;
107 out.reserve(len * 2);
108 static const char* digits = "0123456789abcdef";
109 for (std::size_t i = 0; i < len; ++i)
110 {
111 out.push_back(digits[data[i] >> 4]);
112 out.push_back(digits[data[i] & 0x0F]);
113 }
114 return out;
115}
116
117std::string ToHex(const std::vector<std::uint8_t>& data)
118{
119 return ToHex(data.data(), data.size());
120}
121
122MacAddress String2Mac(const std::string& macStr)
123{
124 // Strict structural constants for a standard MAC address
125 static constexpr std::size_t expectedStringLength = 17;
126 static constexpr std::size_t singleByteHexLength = 2;
127 static constexpr int hexBase = 16;
128
129 // Explicit array index positions for delimiters
130 static constexpr std::array<std::size_t, 5> delimiterIndices = {2, 5, 8, 11, 14};
131
132 // Enforce strict total length check
133 if (macStr.size() != expectedStringLength)
134 {
135 throw InvalidMACError(std::format("Invalid MAC address length: '{}'", macStr));
136 }
137
138 // Validate format
139 // We loop through indices from 0 to 16
140 bool isValidStructure = true;
141 for (std::size_t i = 0; i < expectedStringLength; ++i)
142 {
143 // Check if current index 'i' matches one of our delimiter positions
144 const bool isDelimiterIdx = std::ranges::any_of(delimiterIndices,
145 [i](std::size_t d)
146 { return d == i; });
147
148 if (isDelimiterIdx)
149 {
150 if (macStr[i] != ':')
151 {
152 isValidStructure = false;
153 break;
154 }
155 }
156 else
157 {
158 if (std::isxdigit(static_cast<unsigned char>(macStr[i])) == 0)
159 {
160 isValidStructure = false;
161 break;
162 }
163 }
164 }
165
166 if (!isValidStructure)
167 {
168 throw InvalidMACError(std::format("Invalid MAC address characters or format: '{}'", macStr));
169 }
170
171 MacAddress mac{};
172 std::string_view macView{macStr};
173
174 // 3. Parse the hex pairs directly into the destination array using string_view
175 static constexpr std::size_t hexPairStride = 3; // Length of "xx:" segment
176 for (std::size_t i = 0; i < macAddressLength; ++i)
177 {
178 const std::size_t offset = i * hexPairStride;
179 const std::string_view hexPair = macView.substr(offset, singleByteHexLength);
180
181 // std::stoul is fully safe here because we pre-validated all digits above
182 mac.at(i) = static_cast<std::uint8_t>(std::stoul(std::string(hexPair), nullptr, hexBase));
183 }
184
185 return mac;
186}
187
188std::string Mac2String(const MacAddress& mac)
189{
190 return Mac2String(mac.data(), mac.size());
191}
192
193std::string Mac2String(const std::uint8_t* macBytes, std::size_t len)
194{
195 if (len != 6)
196 {
197 throw InvalidMACError("MAC address must be 6 bytes, got " + std::to_string(len));
198 }
199 // Extract the formatted MAC address into a dedicated string variable
200 const std::string macStr = std::format("{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
201 macBytes[0], macBytes[1], macBytes[2],
202 macBytes[3], macBytes[4], macBytes[5]);
203 return macStr;
204}
205
206std::string String2Ip(std::span<const std::uint8_t> ipBytes)
207{
208 if (ipBytes.size() < 4)
209 {
210 throw InvalidIPError("IP address must be at least 4 bytes, got " + std::to_string(ipBytes.size()));
211 }
212
213 return std::to_string(ipBytes[0]) + "." + std::to_string(ipBytes[1]) + "." +
214 std::to_string(ipBytes[2]) + "." + std::to_string(ipBytes[3]);
215}
216
217std::string String2Ip(const std::uint8_t* ipBytes, std::size_t len)
218{
219 return String2Ip(std::span<const std::uint8_t>(ipBytes, len));
220}
221
222std::array<std::uint8_t, 4> Ip2String(const std::string& ipStr)
223{
224 if (ipStr.empty())
225 {
226 throw InvalidIPError("IP address cannot be empty");
227 }
228
229 asio::error_code ec;
230 const auto address = asio::ip::make_address_v4(ipStr, ec);
231
232 if (ec)
233 {
234 throw InvalidIPError("Invalid IP address: '" + ipStr + "'");
235 }
236
237 const auto bytes = address.to_bytes();
238
239 return {
240 bytes[0],
241 bytes[1],
242 bytes[2],
243 bytes[3]};
244}
245
246std::string DecodeBytes(const std::uint8_t* data, std::size_t len)
247{
248 std::size_t end = len;
249 while (end > 0 && data[end - 1] == 0)
250 {
251 --end;
252 }
253 // NOLINTNEXTLINE(modernize-return-braced-init-list)
254 return std::string(reinterpret_cast<const char*>(data), end);
255}
256
257std::string DecodeBytes(const std::vector<std::uint8_t>& data)
258{
259 return DecodeBytes(data.data(), data.size());
260}
261
262// =============================================================================
263// Platform Network Utilities
264// =============================================================================
265
266MacAddress MacAddressProvider::GetMac(const std::string& interface)
267{
268 return GetInterfaceMac(interface);
269}
270} // namespace profinet
Invalid IP address format.
Definition exceptions.h:651
Invalid MAC address format.
Definition exceptions.h:639
MacAddress GetMac(const std::string &interface) override
Get the MAC address of a network interface.
Definition util.cpp:266
Lightweight cursor for parsing a big-endian byte buffer without copies.
Definition wire.h:192
std::size_t Position() const
Current read offset from the start of the buffer.
Definition wire.h:225
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
Declares exceptions and error types used by the PROFINET IO controller stack.
constexpr std::uint16_t ETHERTYPE_VLAN_AD
EtherType used by VLAN frames, 802.1ad VLAN TPID (0x88A8).
Definition rt.h:38
constexpr std::uint16_t ETHERTYPE_VLAN
EtherType used by VLAN frames, 802.1Q VLAN TPID (0x8100).
Definition rt.h:36
constexpr std::uint16_t ETHERTYPE_VLAN_LEGACY
EtherType used by VLAN frames, 802.1Q double-tagging legacy VLAN TPID (0x9100).
Definition rt.h:40
std::string Hex2(std::uint8_t value)
Format an 8-bit unsigned integer as a 2-digit hexadecimal string.
Definition util.cpp:49
MacAddress GetInterfaceMac(const std::string &interface)
Get the MAC address assigned to a network interface.
std::vector< std::uint8_t > ToVec(const std::string &input)
Convert a string into a vector of raw bytes.
Definition util.cpp:54
std::string String2Ip(std::span< const std::uint8_t > ipBytes)
Format raw IPv4 address bytes as a dotted string.
Definition util.cpp:206
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
std::array< std::uint8_t, 4 > Ip2String(const std::string &ipStr)
Parse a dotted-decimal IPv4 address string.
Definition util.cpp:222
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
Definition util.cpp:122
std::size_t SkipVlanTags(std::span< const std::uint8_t > frame) noexcept
Return the byte offset of the real EtherType in a raw Ethernet frame.
Definition util.cpp:70
constexpr int macAddressLength
Constant lenght of a mac address.
Definition util.h:37
std::string ToHex(const std::uint8_t *data, std::size_t len)
Hex-encode a byte buffer.
Definition util.cpp:104
std::string Hex4(std::uint16_t value)
Format a 16-bit unsigned integer as a 4-digit hexadecimal string.
Definition util.cpp:44
std::string HexFallback(const char *widthPrefix, std::uint32_t value, int digitWidth)
Format a hexadecimal string with a custom prefix and dynamic width.
Definition util.cpp:59
std::string Mac2String(const MacAddress &mac)
Format a MAC address as a colon-separated string.
Definition util.cpp:188
std::string DecodeBytes(const std::uint8_t *data, std::size_t len)
Decode bytes to a UTF-8 string, stripping trailing NUL bytes.
Definition util.cpp:246
Declares PROFINET RT_CLASS_1 cyclic frame and IOCR data structures.
Bytes frame
Declares Linux Ethernet, addressing, timing, and utility helpers used by the PROFINET stack.
Generic helpers for reading and writing integer and byte data in big- and little-endian wire formats.