srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_checksum.cpp
blob: 12954999aa7b44a173135b7d151d43c8c64fa2bd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <doctest/doctest.h>

#include <vector>

#include "wireframe/net/checksum.hpp"

using namespace wireframe::net;

namespace {

// Mirrors checksum.hpp's own detail::build_ipv4_pseudo_header, kept
// separate here deliberately: constructing expected test vectors using
// the exact same private helper the code under test uses would make
// these tests circular. A few duplicated lines of test-only setup is
// the honest cost of testing independently.
std::vector<unsigned char> pseudo_header(const std::array<unsigned char, 4>& src,
                                          const std::array<unsigned char, 4>& dst,
                                          unsigned char protocol,
                                          std::span<const unsigned char> segment) {
    std::vector<unsigned char> buf;
    buf.insert(buf.end(), src.begin(), src.end());
    buf.insert(buf.end(), dst.begin(), dst.end());
    buf.push_back(0);
    buf.push_back(protocol);
    std::uint16_t len = static_cast<std::uint16_t>(segment.size());
    buf.push_back(static_cast<unsigned char>(len >> 8));
    buf.push_back(static_cast<unsigned char>(len & 0xFF));
    buf.insert(buf.end(), segment.begin(), segment.end());
    return buf;
}

}  // namespace

TEST_CASE("internet_checksum matches RFC 1071's own worked example") {
    // The RFC's example data (0001 f203 f4f5 f6f7) computes to checksum
    // 220d - an external reference, not derived from this code.
    std::vector<unsigned char> data = {0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7};
    CHECK(internet_checksum(data) == 0x220d);
}

TEST_CASE("internet_checksum of data with its own valid checksum appended is zero") {
    // Direct consequence of the RFC 1071 example: appending that
    // checksum as one more word should sum to all-ones, complementing
    // to exactly zero - this is the actual verification technique
    // verify_ipv4_checksum() etc. rely on.
    std::vector<unsigned char> data = {0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7, 0x22, 0x0d};
    CHECK(internet_checksum(data) == 0);
}

TEST_CASE("internet_checksum handles an odd-length buffer (trailing byte padded high)") {
    std::vector<unsigned char> data = {0x00, 0x01, 0xf2};  // 3 bytes: one word + one odd byte
    // 0x0001 + 0xf200 (odd byte in the high half) = 0xf201; ~0xf201 = 0x0dfe
    CHECK(internet_checksum(data) == 0x0dfe);
}

TEST_CASE("verify_ipv4_checksum accepts a header with a correctly computed checksum") {
    std::vector<unsigned char> header(20, 0);
    header[0] = 0x45;
    header[8] = 64;  // ttl
    header[9] = kProtoTcp;
    header[12] = 10; header[13] = 0; header[14] = 0; header[15] = 1;
    header[16] = 10; header[17] = 0; header[18] = 0; header[19] = 2;
    // checksum field (bytes 10-11) computed with itself still zeroed
    std::uint16_t csum = internet_checksum(header);
    header[10] = static_cast<unsigned char>(csum >> 8);
    header[11] = static_cast<unsigned char>(csum & 0xFF);

    CHECK(verify_ipv4_checksum(header));
}

TEST_CASE("verify_ipv4_checksum rejects a header corrupted after the checksum was computed") {
    std::vector<unsigned char> header(20, 0);
    header[0] = 0x45;
    header[9] = kProtoTcp;
    std::uint16_t csum = internet_checksum(header);
    header[10] = static_cast<unsigned char>(csum >> 8);
    header[11] = static_cast<unsigned char>(csum & 0xFF);

    header[15] ^= 0xFF;  // flip a source-address byte after the fact
    CHECK_FALSE(verify_ipv4_checksum(header));
}

TEST_CASE("verify_tcp_checksum_ipv4 accepts a segment with a correctly computed checksum") {
    std::array<unsigned char, 4> src = {10, 0, 0, 1};
    std::array<unsigned char, 4> dst = {10, 0, 0, 2};

    std::vector<unsigned char> tcp(20, 0);
    tcp[0] = 0; tcp[1] = 80;      // src port
    tcp[2] = 0x01; tcp[3] = 0xbb;  // dst port 443
    tcp[12] = 5 << 4;             // data_offset = 5

    auto buf = pseudo_header(src, dst, kProtoTcp, tcp);
    std::uint16_t csum = internet_checksum(buf);
    tcp[16] = static_cast<unsigned char>(csum >> 8);
    tcp[17] = static_cast<unsigned char>(csum & 0xFF);

    CHECK(verify_tcp_checksum_ipv4({src}, {dst}, tcp) == ChecksumResult::kValid);
}

TEST_CASE("verify_tcp_checksum_ipv4 rejects a segment corrupted after the checksum was computed") {
    std::array<unsigned char, 4> src = {10, 0, 0, 1};
    std::array<unsigned char, 4> dst = {10, 0, 0, 2};

    std::vector<unsigned char> tcp(20, 0);
    tcp[12] = 5 << 4;
    auto buf = pseudo_header(src, dst, kProtoTcp, tcp);
    std::uint16_t csum = internet_checksum(buf);
    tcp[16] = static_cast<unsigned char>(csum >> 8);
    tcp[17] = static_cast<unsigned char>(csum & 0xFF);

    tcp[0] ^= 0xFF;  // corrupt the source port after the fact
    CHECK(verify_tcp_checksum_ipv4({src}, {dst}, tcp) == ChecksumResult::kInvalid);
}

TEST_CASE("verify_udp_checksum_ipv4 treats a transmitted checksum of 0x0000 as not present") {
    std::array<unsigned char, 4> src = {10, 0, 0, 1};
    std::array<unsigned char, 4> dst = {10, 0, 0, 2};
    std::vector<unsigned char> udp = {0x00, 0x35, 0x00, 0x35, 0x00, 0x08, 0x00, 0x00};  // csum=0
    CHECK(verify_udp_checksum_ipv4({src}, {dst}, udp) == ChecksumResult::kNotPresent);
}

TEST_CASE("verify_udp_checksum_ipv4 accepts a datagram with a correctly computed checksum") {
    std::array<unsigned char, 4> src = {10, 0, 0, 1};
    std::array<unsigned char, 4> dst = {10, 0, 0, 2};

    std::vector<unsigned char> udp = {0x00, 0x35, 0x00, 0x35, 0x00, 0x08, 0x00, 0x00};
    auto buf = pseudo_header(src, dst, kProtoUdp, udp);
    std::uint16_t csum = internet_checksum(buf);
    // A computed checksum of exactly 0 is itself sent as 0xFFFF per
    // RFC 768, to keep it distinguishable from "no checksum" - not
    // exercised by this test's specific values, but worth the note.
    udp[6] = static_cast<unsigned char>(csum >> 8);
    udp[7] = static_cast<unsigned char>(csum & 0xFF);

    CHECK(verify_udp_checksum_ipv4({src}, {dst}, udp) == ChecksumResult::kValid);
}