36#include <netinet/in.h>
38#include <sys/socket.h>
49#include <linux/if_packet.h>
63std::string
Hex4(std::uint16_t value)
65 return std::format(
"0x{:04X}", value);
68std::string
Hex2(std::uint8_t value)
70 return std::format(
"0x{:02X}", value);
73std::vector<std::uint8_t>
ToVec(
const std::string& input)
75 return {input.begin(), input.end()};
78std::string
HexFallback(
const char* widthPrefix, std::uint32_t value,
int digitWidth)
82 return std::format(
"{}(0x{:0{}X})", widthPrefix, value, digitWidth);
89std::size_t
SkipVlanTags(std::span<const std::uint8_t> frame)
noexcept
91 std::size_t offset = 12;
93 while (frame.size() >= offset + 4)
96 const std::uint16_t tpid = (
static_cast<std::uint16_t
>(frame[offset]) << 8) | frame[offset + 1];
99 if (tpid == 0x8100 || tpid == 0x88A8 || tpid == 0x9100)
112std::string
ToHex(
const std::uint8_t* data, std::size_t len)
115 out.reserve(len * 2);
116 static const char* digits =
"0123456789abcdef";
117 for (std::size_t i = 0; i < len; ++i)
119 out.push_back(digits[data[i] >> 4]);
120 out.push_back(digits[data[i] & 0x0F]);
125std::string
ToHex(
const std::vector<std::uint8_t>& data)
127 return ToHex(data.data(), data.size());
133 static constexpr std::size_t expectedStringLength = 17;
134 static constexpr std::size_t singleByteHexLength = 2;
135 static constexpr int hexBase = 16;
138 static constexpr std::array<std::size_t, 5> delimiterIndices = {2, 5, 8, 11, 14};
141 if (macStr.size() != expectedStringLength)
143 throw InvalidMACError(std::format(
"Invalid MAC address length: '{}'", macStr));
148 bool isValidStructure =
true;
149 for (std::size_t i = 0; i < expectedStringLength; ++i)
152 const bool isDelimiterIdx = std::ranges::any_of(delimiterIndices,
158 if (macStr[i] !=
':')
160 isValidStructure =
false;
166 if (std::isxdigit(
static_cast<unsigned char>(macStr[i])) == 0)
168 isValidStructure =
false;
174 if (!isValidStructure)
176 throw InvalidMACError(std::format(
"Invalid MAC address characters or format: '{}'", macStr));
180 std::string_view macView{macStr};
183 static constexpr std::size_t hexPairStride = 3;
186 const std::size_t offset = i * hexPairStride;
187 const std::string_view hexPair = macView.substr(offset, singleByteHexLength);
190 mac.at(i) =
static_cast<std::uint8_t
>(std::stoul(std::string(hexPair),
nullptr, hexBase));
201std::string
Mac2String(
const std::uint8_t* macBytes, std::size_t len)
205 throw InvalidMACError(
"MAC address must be 6 bytes, got " + std::to_string(len));
208 const std::string macStr = std::format(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
209 macBytes[0], macBytes[1], macBytes[2],
210 macBytes[3], macBytes[4], macBytes[5]);
214std::string
String2Ip(std::span<const std::uint8_t> ipBytes)
216 if (ipBytes.size() < 4)
218 throw InvalidIPError(
"IP address must be at least 4 bytes, got " + std::to_string(ipBytes.size()));
221 return std::to_string(ipBytes[0]) +
"." + std::to_string(ipBytes[1]) +
"." +
222 std::to_string(ipBytes[2]) +
"." + std::to_string(ipBytes[3]);
225std::string
String2Ip(
const std::uint8_t* ipBytes, std::size_t len)
227 return String2Ip(std::span<const std::uint8_t>(ipBytes, len));
230std::array<std::uint8_t, 4>
Ip2String(
const std::string& ipStr)
238 if (::inet_pton(AF_INET, ipStr.c_str(), &addr) != 1)
242 std::array<std::uint8_t, 4> out{};
243 std::memcpy(out.data(), &addr.s_addr, 4);
249 std::size_t end = len;
250 while (end > 0 && data[end - 1] == 0)
255 return std::string(
reinterpret_cast<const char*
>(data), end);
271 throw SocketError(
"Interface name cannot be empty");
274 const int s = ::socket(AF_INET, SOCK_DGRAM, 0);
277 throw SocketError(
"Failed to get MAC address for '" + ifname +
"': " + std::strerror(errno));
281 std::strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ - 1);
282 if (::ioctl(s, SIOCGIFHWADDR, &ifr) < 0)
284 const int err = errno;
286 throw SocketError(
"Failed to get MAC address for '" + ifname +
"': " + std::strerror(err));
291 std::memcpy(mac.data(), ifr.ifr_hwaddr.sa_data, 6);
297 if (interface.empty())
299 throw SocketError(
"Interface name cannot be empty");
302 const std::uint16_t proto = ethertype != 0 ? ethertype :
static_cast<std::uint16_t
>(
ETH_P_ALL);
304 fd = ::socket(AF_PACKET, SOCK_RAW, htons(proto));
307 if (errno == EPERM || errno == EACCES)
310 std::string(std::strerror(errno)));
312 throw SocketError(
"Failed to create socket on '" + interface +
"': " + std::strerror(errno));
315 const unsigned int ifindex = if_nametoindex(interface.c_str());
318 const int err = errno;
321 throw SocketError(
"Failed to create socket on '" + interface +
"': " + std::strerror(err));
325 addr.sll_family = AF_PACKET;
326 addr.sll_protocol = htons(proto);
327 addr.sll_ifindex =
static_cast<int>(ifindex);
329 if (::bind(
fd,
reinterpret_cast<sockaddr*
>(&addr),
sizeof(addr)) < 0)
331 const int err = errno;
334 throw SocketError(
"Failed to create socket on '" + interface +
"': " + std::strerror(err));
368 const ssize_t n = ::send(
fd, frame.data(), frame.size(), 0);
369 if (n < 0 ||
static_cast<std::size_t
>(n) != frame.size())
371 throw SocketError(std::string(
"Failed to send frame: ") + std::strerror(errno));
378 tv.tv_sec =
static_cast<time_t
>(timeout.count() / 1000);
379 tv.tv_usec =
static_cast<suseconds_t
>((timeout.count() % 1000) * 1000);
380 if (::setsockopt(
fd, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv)) < 0)
382 throw SocketError(std::string(
"Failed to set socket timeout: ") + std::strerror(errno));
389 const ssize_t n = ::recv(
fd, buf.data(), buf.size(), 0);
392 if (errno == EAGAIN || errno == EWOULDBLOCK)
396 throw SocketError(std::string(
"Socket error during receive: ") + std::strerror(errno));
398 buf.resize(
static_cast<std::size_t
>(n));
403 std::chrono::duration<double> timeout)
406 hints.ai_family = AF_INET;
407 hints.ai_socktype = SOCK_DGRAM;
408 addrinfo* result =
nullptr;
410 const std::string portStr = std::to_string(port);
411 if (::getaddrinfo(host.c_str(), portStr.c_str(), &hints, &result) != 0 || result ==
nullptr)
413 throw SocketError(
"Failed to create UDP socket to " + host +
":" + portStr);
416 fd = ::socket(result->ai_family, result->ai_socktype, result->ai_protocol);
419 ::freeaddrinfo(result);
420 throw SocketError(
"Failed to create UDP socket to " + host +
":" + portStr +
": " +
421 std::strerror(errno));
425 tv.tv_sec =
static_cast<time_t
>(timeout.count());
426 static constexpr int microsecondsPerSecond = 1'000'000;
427 tv.tv_usec =
static_cast<suseconds_t
>((timeout.count() -
static_cast<double>(tv.tv_sec)) * microsecondsPerSecond);
428 ::setsockopt(
fd, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv));
430 if (::connect(
fd, result->ai_addr, result->ai_addrlen) < 0)
432 const int err = errno;
433 ::freeaddrinfo(result);
436 throw SocketError(
"Failed to create UDP socket to " + host +
":" + portStr +
": " +
439 ::freeaddrinfo(result);
472 const ssize_t n = ::send(
fd, data.data(), data.size(), 0);
473 if (n < 0 ||
static_cast<std::size_t
>(n) != data.size())
475 throw SocketError(std::string(
"Failed to send UDP datagram: ") + std::strerror(errno));
481 std::vector<std::uint8_t> buf(maxLen);
482 const ssize_t n = ::recv(
fd, buf.data(), buf.size(), 0);
485 if (errno == EAGAIN || errno == EWOULDBLOCK)
489 throw SocketError(std::string(
"Socket error during UDP receive: ") + std::strerror(errno));
491 buf.resize(
static_cast<std::size_t
>(n));
496 constexpr std::string_view processLink =
"/proc/self/exe";
501 if (std::filesystem::is_symlink(processLink) || std::filesystem::exists(processLink))
503 return std::filesystem::read_symlink(processLink).parent_path();
506 catch (
const std::filesystem::filesystem_error& e)
509 throw std::runtime_error(
"Filesystem error: " + std::string(e.what()));
511 throw std::runtime_error(
"Critical error: /proc/self/exe not available on this system");
A minimal RAII wrapper around a Linux AF_PACKET raw socket bound to an interface.
int fd
Underlying socket file descriptor, or -1 if closed/moved-from.
EthernetSocket & operator=(const EthernetSocket &)=delete
Not copyable (owns a raw socket file descriptor).
void SetTimeout(std::chrono::milliseconds timeout) const override
Set the receive timeout.
EthernetSocket(const std::string &interface, std::uint16_t ethertype=0)
Bind a raw socket to an interface.
void Send(const std::vector< std::uint8_t > &frame) const override
Send a raw Ethernet frame.
std::vector< std::uint8_t > Recv() const override
Receive up to MAX_ETHERNET_FRAME bytes.
~EthernetSocket()
Close the underlying socket.
Invalid IP address format.
Invalid MAC address format.
Insufficient permissions for raw socket.
A connected UDP socket with a timeout.
~UdpSocket()
Close the underlying socket.
std::vector< std::uint8_t > Recv(std::size_t maxLen=defaultReceiveBufferSize) const
Receive a UDP datagram from the connected peer.
UdpSocket(const std::string &host, std::uint16_t port, std::chrono::duration< double > timeout=std::chrono::duration< double >(defaultTimeout))
Resolve a host and connect a UDP socket to it.
void Send(const std::vector< std::uint8_t > &data) const
Send a UDP datagram to the connected peer.
int fd
Underlying socket file descriptor, or -1 if closed/moved-from.
UdpSocket & operator=(const UdpSocket &)=delete
Not copyable (owns a socket file descriptor).
Declares exceptions and error types used by the PROFINET IO controller stack.
std::string Hex2(std::uint8_t value)
Format an 8-bit unsigned integer as a 2-digit hexadecimal string.
std::vector< std::uint8_t > ToVec(const std::string &input)
Convert a string into a vector of raw bytes.
std::string String2Ip(std::span< const std::uint8_t > ipBytes)
Format raw IPv4 address bytes as a dotted string.
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
std::array< std::uint8_t, 4 > Ip2String(const std::string &ipStr)
Parse a dotted-decimal IPv4 address string.
std::filesystem::path GetExecutableDirectory()
Retrieves the absolute directory path of the currently running executable.
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
constexpr std::size_t MAX_ETHERNET_FRAME
Maximum PROFINET Ethernet frame size in bytes (including VLAN tag headroom).
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.
MacAddress GetMac(const std::string &ifname)
Get the MAC address of a network interface.
constexpr int macAddressLength
Constant lenght of a mac address.
std::string ToHex(const std::uint8_t *data, std::size_t len)
Hex-encode a byte buffer.
std::string Hex4(std::uint16_t value)
Format a 16-bit unsigned integer as a 4-digit hexadecimal string.
std::string HexFallback(const char *widthPrefix, std::uint32_t value, int digitWidth)
Format a hexadecimal string with a custom prefix and dynamic width.
constexpr int ETH_P_ALL
Linux AF_PACKET protocol value matching every EtherType.
std::string Mac2String(const MacAddress &mac)
Format a MAC address as a colon-separated string.
std::string DecodeBytes(const std::uint8_t *data, std::size_t len)
Decode bytes to a UTF-8 string, stripping trailing NUL bytes.
Declares Linux Ethernet, addressing, timing, and utility helpers used by the PROFINET stack.