diff --git a/tests/test_arp.cxx b/tests/test_arp.cxx index 91ba87b..fc7a2cb 100644 --- a/tests/test_arp.cxx +++ b/tests/test_arp.cxx @@ -1,8 +1,7 @@ #include #include "vbvx/header_view.hxx" -#include "vbvx/auto_swap.hxx" + #include "vbvx/arp.hxx" -#include "vbvx/ether.hxx" #include #include diff --git a/tests/test_buffer_view.cxx b/tests/test_buffer_view.cxx index 6fd1b43..1691406 100644 --- a/tests/test_buffer_view.cxx +++ b/tests/test_buffer_view.cxx @@ -1,6 +1,6 @@ #include #include "buffer_view.hxx" -#include "auto_swap.hxx" +#include "utils.hxx" #include #include @@ -192,7 +192,8 @@ TEST(BufferViewTest, IPv4UdpHeaderPresent) { } TEST(BufferViewTest, IPv6Icmpv6) { - const auto total = sizeof(vbvx::EtherHeader) + 40 + sizeof(vbvx::ICMPHeader); + const auto total = + sizeof(vbvx::EtherHeader) + 40 + sizeof(vbvx::ICMPv4Header); std::vector buf_bytes(total); std::memset(buf_bytes.data(), 0, buf_bytes.size()); @@ -206,7 +207,7 @@ TEST(BufferViewTest, IPv6Icmpv6) { // next_header field is at offset 6 within the IPv6 header buf_bytes[ip_off + 6] = static_cast(IpProtocol::ICMPv6); - ICMPHeader icmp{}; + ICMPv4Header icmp{}; icmp.type = static_cast(ICMPv4Type::EchoRequest); icmp.code = 0; icmp.set_checksum(0x1234); diff --git a/tests/test_header_view.cxx b/tests/test_header_view.cxx index aa28672..d0215a0 100644 --- a/tests/test_header_view.cxx +++ b/tests/test_header_view.cxx @@ -1,6 +1,6 @@ #include #include "header_view.hxx" -#include "auto_swap.hxx" +#include "utils.hxx" #include "vbvx/ether.hxx" #include "vbvx/arp.hxx" diff --git a/tests/test_icmp4.cxx b/tests/test_icmp4.cxx index 5372de9..d90791d 100644 --- a/tests/test_icmp4.cxx +++ b/tests/test_icmp4.cxx @@ -8,8 +8,8 @@ using enum ICMPv4Type; class IcmpHeaderBytesFixture : public ::testing::Test { protected: - ICMPHeader tmp{}; - std::array raw{}; + ICMPv4Header tmp{}; + std::array raw{}; void SetUp() override { tmp.type = static_cast(EchoRequest); @@ -18,13 +18,13 @@ class IcmpHeaderBytesFixture : public ::testing::Test { std::memcpy(raw.data(), &tmp, sizeof(tmp)); } - HeaderView hv_view() const { - return HeaderView(raw.data()); + HeaderView hv_view() const { + return HeaderView(raw.data()); } }; TEST(IcmpTypeTest, KnownTypes) { - ICMPHeader h{}; + ICMPv4Header h{}; h.type = 0; ASSERT_TRUE(h.type_known().has_value()); @@ -64,7 +64,7 @@ TEST(IcmpTypeTest, KnownTypes) { } TEST(IcmpTypeTest, UnassignedReturnsNullopt) { - ICMPHeader h{}; + ICMPv4Header h{}; h.type = 7; // unassigned EXPECT_FALSE(h.type_known().has_value()); @@ -73,9 +73,9 @@ TEST(IcmpTypeTest, UnassignedReturnsNullopt) { } TEST(IcmpHeaderTest, LayoutAndAlignment) { - static_assert(sizeof(ICMPHeader) == 4, "Wrong ICMP header size"); - EXPECT_EQ(sizeof(ICMPHeader), 4u); - EXPECT_EQ(alignof(ICMPHeader), 1u); + static_assert(sizeof(ICMPv4Header) == 4, "Wrong ICMP header size"); + EXPECT_EQ(sizeof(ICMPv4Header), 4u); + EXPECT_EQ(alignof(ICMPv4Header), 1u); } TEST_F(IcmpHeaderBytesFixture, HeaderFieldsParsed) { diff --git a/tests/test_ip4_header.cxx b/tests/test_ip4_header.cxx index 041bbd7..8436bf2 100644 --- a/tests/test_ip4_header.cxx +++ b/tests/test_ip4_header.cxx @@ -1,6 +1,6 @@ #include #include "header_view.hxx" -#include "auto_swap.hxx" +#include "utils.hxx" #include "vbvx/ip4_header.hxx" #include diff --git a/tests/test_ip6_header.cxx b/tests/test_ip6_header.cxx index 32bcc53..6b7b7f2 100644 --- a/tests/test_ip6_header.cxx +++ b/tests/test_ip6_header.cxx @@ -1,7 +1,7 @@ #include #include "header_view.hxx" -#include "auto_swap.hxx" +#include "utils.hxx" #include "buffer_view.hxx" #include "vbvx/ether.hxx" #include "vbvx/ip_protocol.hxx" diff --git a/tests/test_tcp_header.cxx b/tests/test_tcp_header.cxx index 1ee9c81..4b444ba 100644 --- a/tests/test_tcp_header.cxx +++ b/tests/test_tcp_header.cxx @@ -1,6 +1,6 @@ #include #include "buffer_view.hxx" -#include "auto_swap.hxx" +#include "utils.hxx" #include "vbvx/ether.hxx" #include "vbvx/ip4_header.hxx" #include "vbvx/tcp_header.hxx" diff --git a/tests/test_udp_header.cxx b/tests/test_udp_header.cxx index bf7d036..a18e426 100644 --- a/tests/test_udp_header.cxx +++ b/tests/test_udp_header.cxx @@ -1,7 +1,7 @@ #include #include "vbvx/udp_header.hxx" #include "buffer_view.hxx" -#include "auto_swap.hxx" +#include "utils.hxx" #include "vbvx/ether.hxx" #include "vbvx/ip4_header.hxx" diff --git a/vbvx/arp.hxx b/vbvx/arp.hxx index 4672951..145a8d1 100644 --- a/vbvx/arp.hxx +++ b/vbvx/arp.hxx @@ -5,7 +5,7 @@ #include #include -#include "auto_swap.hxx" +#include "utils.hxx" #include "ether.hxx" namespace vbvx { @@ -64,13 +64,11 @@ struct [[gnu::packed]] ArpHeader { } constexpr auto sender_ipv4_host() const noexcept -> uint32_t { - std::array a{spa[0], spa[1], spa[2], spa[3]}; - return autoswap(std::bit_cast(a)); + return autoswap(read_from_bytes(spa)); } constexpr auto target_ipv4_host() const noexcept -> uint32_t { - std::array a{tpa[0], tpa[1], tpa[2], tpa[3]}; - return autoswap(std::bit_cast(a)); + return autoswap(read_from_bytes(tpa)); } constexpr void set_opcode(ArpOpCode code) noexcept { diff --git a/vbvx/auto_swap.hxx b/vbvx/auto_swap.hxx deleted file mode 100644 index d7c2061..0000000 --- a/vbvx/auto_swap.hxx +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -namespace vbvx { - -template constexpr _Tp autoswap(_Tp tp) { - if constexpr (std::endian::native == std::endian::little) { - return std::byteswap(tp); - } else { - return tp; - } -} - -} // namespace vbvx diff --git a/vbvx/buffer_view.hxx b/vbvx/buffer_view.hxx index 9856ff0..fdd94db 100644 --- a/vbvx/buffer_view.hxx +++ b/vbvx/buffer_view.hxx @@ -11,6 +11,7 @@ #include "arp.hxx" #include "ether.hxx" #include "icmp4.hxx" +#include "icmp6.hxx" #include "ip_protocol.hxx" #include "ip4_header.hxx" #include "ip6_header.hxx" @@ -31,6 +32,7 @@ public: constexpr BufferView(const void* data, uint16_t length) noexcept : data_{static_cast(data)}, length_{length} {} + /** @brief Get the underlying buffer data as a span. */ constexpr auto data() const noexcept -> std::span { if (!data_) { return {}; @@ -38,12 +40,15 @@ public: return {data_, length_}; } + /** @brief Get the length of the buffer. */ constexpr auto length() const noexcept -> uint16_t { return length_; } + /** @brief Get Ethernet header view. */ constexpr auto ether_header() const noexcept -> HeaderView { return header_at(0); } + /** @brief Get the EtherType of the packet for possible VLAN tag. */ constexpr auto ether_type() const noexcept -> std::optional { auto eth = ether_header(); if (!eth) { @@ -62,6 +67,7 @@ public: return static_cast(type); } + /** @brief Get VLAN header view. */ constexpr auto vlan_header() const noexcept -> HeaderView { auto eth = ether_header(); if (!eth) { @@ -76,6 +82,7 @@ public: return header_at(sizeof(EtherHeader)); } + /** @brief Get VLAN ID if VLAN tag is present. */ constexpr auto vlan_id() const noexcept -> std::optional { auto vlan = vlan_header(); if (!vlan) { @@ -84,6 +91,7 @@ public: return static_cast(vlan->tci() & 0x0FFFu); } + /** @brief Get the offset of the Layer 3 header. */ constexpr auto l3_offset() const noexcept -> uint16_t { auto eth = ether_header(); if (!eth) { @@ -95,6 +103,7 @@ public: : static_cast(sizeof(EtherHeader)); } + /** @brief Get ARP header view. */ constexpr auto arp_header() const noexcept -> HeaderView { auto et = ether_type(); if (!et || *et != EtherType::ARP) { @@ -103,6 +112,7 @@ public: return header_at(l3_offset()); } + /** @brief Get IPv4 header view. */ constexpr auto ip4_header() const noexcept -> HeaderView { auto et = ether_type(); if (!et || *et != EtherType::IPv4) { @@ -111,6 +121,7 @@ public: return header_at(l3_offset()); } + /** @brief Get IPv6 header view. */ constexpr auto ip6_header() const noexcept -> HeaderView { auto et = ether_type(); if (!et || *et != EtherType::IPv6) { @@ -119,6 +130,7 @@ public: return header_at(l3_offset()); } + /** @brief Get the number of bytes in the IPv4 header. */ constexpr auto ip4_ihl_bytes() const noexcept -> std::optional { auto ip = ip4_header(); if (!ip) { @@ -132,6 +144,7 @@ public: return bytes; } + /** @brief Get the IP protocol (IPv4 or IPv6). */ constexpr auto ip_protocol() const noexcept -> std::optional { if (auto ip4 = ip4_header()) { return static_cast(ip4->protocol); @@ -142,6 +155,7 @@ public: return std::nullopt; } + /** @brief Get the offset of the Layer 4 header. */ constexpr auto l4_offset() const noexcept -> std::optional { if (ip4_header()) { auto ihl = ip4_ihl_bytes(); @@ -156,6 +170,7 @@ public: return std::nullopt; } + /** @brief Get TCP header view. */ constexpr auto tcp_header() const noexcept -> HeaderView { auto proto = ip_protocol(); auto off = l4_offset(); @@ -168,6 +183,7 @@ public: return header_at(*off); } + /** @brief Get UDP header view. */ constexpr auto udp_header() const noexcept -> HeaderView { auto proto = ip_protocol(); auto off = l4_offset(); @@ -180,33 +196,29 @@ public: return header_at(*off); } - constexpr auto icmp4_header() const noexcept -> HeaderView { + /** @brief Get ICMPv4 header view. */ + constexpr auto icmp4_header() const noexcept -> HeaderView { auto proto = ip_protocol(); auto off = l4_offset(); - if (!proto || !off) { - return {}; - } - if (*proto != IpProtocol::ICMP && *proto != IpProtocol::ICMPv6) { + if (!proto || !off || *proto != IpProtocol::ICMPv4) { return {}; } - return header_at(*off); + + return header_at(*off); } - constexpr auto icmp6_header() const noexcept -> HeaderView { + /** @brief Get ICMPv6 header view. */ + constexpr auto icmp6_header() const noexcept -> HeaderView { auto proto = ip_protocol(); auto off = l4_offset(); if (!proto || !off || *proto != IpProtocol::ICMPv6) { return {}; } - return header_at(*off); + + return header_at(*off); } - /** - * @brief Get SRv6 Header view if present. - * - * @return HeaderView if SRv6 header is present, empty optional - * otherwise. - */ + /** @brief Get SRv6 Header view if present. */ constexpr auto srv6_header() const noexcept -> HeaderView { auto ip6 = ip6_header(); if (!ip6) { diff --git a/vbvx/ether.hxx b/vbvx/ether.hxx index 1bf1025..69025ce 100644 --- a/vbvx/ether.hxx +++ b/vbvx/ether.hxx @@ -4,7 +4,7 @@ #include #include -#include "auto_swap.hxx" +#include "utils.hxx" namespace vbvx { diff --git a/vbvx/flags_view.hxx b/vbvx/flags_view.hxx index 173ba89..713f2f6 100644 --- a/vbvx/flags_view.hxx +++ b/vbvx/flags_view.hxx @@ -11,7 +11,8 @@ namespace vbvx { * Specialize this template for your enum to enable the bitwise operators. * * Example: - * enum class MyFlags : uint8_t { + * @code + * enum class MyFlags : uint8_t { * FlagA = 0x01, * FlagB = 0x02, * FlagC = 0x04 @@ -19,11 +20,13 @@ namespace vbvx { * * template <> * struct enable_bitmask_operators : std::true_type {}; + * @endcode */ -template struct enable_bitmask_operators : std::false_type {}; +template struct enable_bitmask_operators : std::false_type {}; -template -constexpr bool enable_bitmask_operators_v = enable_bitmask_operators::value; +template +constexpr bool enable_bitmask_operators_v = + enable_bitmask_operators<_Tp>::value; template requires enable_bitmask_operators_v diff --git a/vbvx/header_view.hxx b/vbvx/header_view.hxx index 4a4d2a6..b8c1749 100644 --- a/vbvx/header_view.hxx +++ b/vbvx/header_view.hxx @@ -5,9 +5,9 @@ namespace vbvx { -template -concept WireHeader = std::is_trivially_copyable_v && - std::is_standard_layout_v && (alignof(T) == 1); +template +concept WireHeader = std::is_trivially_copyable_v<_Tp> && + std::is_standard_layout_v<_Tp> && (alignof(_Tp) == 1); /** * @brief A lightweight view over a header inside a packet buffer. diff --git a/vbvx/icmp4.hxx b/vbvx/icmp4.hxx index 8d3c075..c228570 100644 --- a/vbvx/icmp4.hxx +++ b/vbvx/icmp4.hxx @@ -4,7 +4,7 @@ #include #include -#include "auto_swap.hxx" +#include "utils.hxx" namespace vbvx { @@ -53,12 +53,15 @@ enum class ICMPv4Type : uint8_t { Reserved = 255 }; -/** @brief ICMP header (type, code, checksum) (4 bytes). - * IANA: ICMP Parameters - * https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml IETF - * RFC: RFC792 https://datatracker.ietf.org/doc/html/rfc792 +/** + * @brief ICMP header (type, code, checksum) (4 bytes). + * + * @see IANA ICMP Parameters: + * https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml IETF + * @see RFC 792: + * https://datatracker.ietf.org/doc/html/rfc792 */ -struct [[gnu::packed]] ICMPHeader { +struct [[gnu::packed]] ICMPv4Header { uint8_t type; uint8_t code; uint16_t checksum_be; @@ -117,7 +120,7 @@ struct [[gnu::packed]] ICMPHeader { } }; -static_assert(sizeof(ICMPHeader) == 4, "Wrong ICMP header size"); -static_assert(alignof(ICMPHeader) == 1, "Wrong ICMP header alignment"); +static_assert(sizeof(ICMPv4Header) == 4, "Wrong ICMP header size"); +static_assert(alignof(ICMPv4Header) == 1, "Wrong ICMP header alignment"); } // namespace vbvx diff --git a/vbvx/icmp6.hxx b/vbvx/icmp6.hxx index cecb500..80d96cd 100644 --- a/vbvx/icmp6.hxx +++ b/vbvx/icmp6.hxx @@ -4,7 +4,7 @@ #include #include -#include "auto_swap.hxx" +#include "utils.hxx" namespace vbvx { @@ -66,10 +66,13 @@ enum class ICMPv6Type : uint8_t { Reserved = 255 }; -/** @brief ICMPv6 header (type, code, checksum) (4 bytes). - * IANA: ICMPv6 Parameters - * https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml IETF - * RFC: RFC4443 https://datatracker.ietf.org/doc/html/rfc4443 +/** + * @brief ICMPv6 header (type, code, checksum) (4 bytes). + * + * @see IANA ICMPv6 Parameters: + * https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml IETF + * @see RFC 4443: + * https://datatracker.ietf.org/doc/html/rfc4443 */ struct [[gnu::packed]] ICMPv6Header { uint8_t type; diff --git a/vbvx/ip4_header.hxx b/vbvx/ip4_header.hxx index 168bfd0..715a23a 100644 --- a/vbvx/ip4_header.hxx +++ b/vbvx/ip4_header.hxx @@ -5,7 +5,7 @@ #include #include -#include "auto_swap.hxx" +#include "utils.hxx" #include "ip_protocol.hxx" #include "flags_view.hxx" @@ -47,9 +47,11 @@ struct [[gnu::packed]] IPv4Header { constexpr auto version() const noexcept -> uint8_t { return (version_ihl >> 4) & 0x0Fu; } + constexpr auto ihl_words() const noexcept -> uint16_t { return version_ihl & 0x0Fu; } + constexpr auto ihl_bytes() const noexcept -> uint16_t { return ihl_words() * 4u; } @@ -134,9 +136,7 @@ struct [[gnu::packed]] IPv4Header { } constexpr void set_src(uint32_t v) noexcept { src_addr_be = autoswap(v); } - constexpr void set_dst(uint32_t v) noexcept { dst_addr_be = autoswap(v); } - constexpr bool valid_min_size() const noexcept { return ihl_bytes() >= 20; } private: diff --git a/vbvx/ip6_header.hxx b/vbvx/ip6_header.hxx index 4bd8a50..fe12f32 100644 --- a/vbvx/ip6_header.hxx +++ b/vbvx/ip6_header.hxx @@ -2,7 +2,7 @@ #include -#include "auto_swap.hxx" +#include "utils.hxx" #include "ip_protocol.hxx" namespace vbvx { diff --git a/vbvx/ip_protocol.hxx b/vbvx/ip_protocol.hxx index 3b1601c..346ba5f 100644 --- a/vbvx/ip_protocol.hxx +++ b/vbvx/ip_protocol.hxx @@ -12,7 +12,7 @@ namespace vbvx { */ enum class IpProtocol : uint8_t { HOPOPT = 0, - ICMP = 1, + ICMPv4 = 1, IGMP = 2, GGP = 3, IPv4 = 4, diff --git a/vbvx/srv6_header.hxx b/vbvx/srv6_header.hxx index 135be0f..0a96baf 100644 --- a/vbvx/srv6_header.hxx +++ b/vbvx/srv6_header.hxx @@ -3,7 +3,7 @@ #include #include -#include "auto_swap.hxx" +#include "utils.hxx" namespace vbvx { @@ -141,16 +141,16 @@ struct SRv6HmacTlvView { constexpr bool d_bit() const noexcept { if (!valid()) return false; - uint16_t b; - std::memcpy(&b, value, sizeof(b)); + + uint16_t b = read_from_bytes(value); return ((autoswap(b) >> 15) & 0x1u) != 0u; } constexpr auto key_id() const noexcept -> uint32_t { if (!valid()) return 0u; - uint32_t v; - std::memcpy(&v, value + 2u, sizeof(v)); + + uint32_t v = read_from_bytes(value + 2u); return autoswap(v); } diff --git a/vbvx/tcp_header.hxx b/vbvx/tcp_header.hxx index 29d6e88..5abbffe 100644 --- a/vbvx/tcp_header.hxx +++ b/vbvx/tcp_header.hxx @@ -2,7 +2,7 @@ #include -#include "auto_swap.hxx" +#include "utils.hxx" #include "flags_view.hxx" namespace vbvx { diff --git a/vbvx/udp_header.hxx b/vbvx/udp_header.hxx index 9362170..58a2344 100644 --- a/vbvx/udp_header.hxx +++ b/vbvx/udp_header.hxx @@ -2,7 +2,7 @@ #include -#include "auto_swap.hxx" +#include "utils.hxx" namespace vbvx { diff --git a/vbvx/utils.hxx b/vbvx/utils.hxx new file mode 100644 index 0000000..cec44af --- /dev/null +++ b/vbvx/utils.hxx @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +namespace vbvx { + +/** @brief Byte-swap a value if the host is little-endian. */ +template + requires std::integral<_Tp> +constexpr _Tp autoswap(_Tp tp) { + if constexpr (std::endian::native == std::endian::little) { + return std::byteswap(tp); + } else { + return tp; + } +} + +/** @brief Read a trivially copyable type from a byte array. */ +template + requires std::is_trivially_copyable_v<_Tp> +constexpr _Tp read_from_bytes(const uint8_t* src) { + _Tp tp; + std::memcpy(&tp, src, sizeof(_Tp)); + return tp; +} + +} // namespace vbvx