summaryrefslogtreecommitdiff
path: root/src/EthIpUdp.zig
blob: 27fc6111fc0573227973cae3fe89e5c75dae1141 (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
pub const EthIpUdp = packed struct(u336) { // 42 bytes * 8 bits = 336
    // --- UDP (Last in memory, defined first for LSB->MSB) ---
    udp: packed struct {
        checksum: u16 = 0,
        len: u16,
        dst_port: u16,
        src_port: u16,
    },

    // --- IP ---
    ip: packed struct {
        dst_addr: u32,
        src_addr: u32,
        header_checksum: u16 = 0,
        protocol: u8 = 17, // udp
        ttl: u8 = 0x40,

        // fragment_offset (13 bits) + flags (3 bits) = 16 bits
        // In Big Endian, flags are the high bits of the first byte.
        // To have flags appear first in the stream, define them last here.
        fragment_offset: u13 = 0,
        flags: packed struct(u3) {
            reserved: u1 = 0,
            dont_fragment: u1 = 1,
            more_fragments: u1 = 0,
        } = .{},

        id: u16,
        len: u16,
        tos: u8 = undefined,

        // ip_version (4 bits) + ihl (4 bits) = 8 bits
        // To have version appear first (high nibble), define it last.
        ihl: u4 = 5,
        ip_version: u4 = 4,
    },

    // --- Ethernet ---
    eth_type: u16 = std.os.linux.ETH.P.IP,
    src_mac: @Vector(6, u8),
    dst_mac: @Vector(6, u8) = @splat(0xff),

    pub fn toBytes(self: @This()) [336 / 8]u8 {
        var res: [336 / 8]u8 = undefined;
        var w: Writer = .fixed(&res);
        w.writeStruct(self, .big) catch unreachable;
        return res;
    }

    pub fn setPayloadLen(self: *@This(), len: usize) void {
        self.ip.len = @intCast(len + (@bitSizeOf(@TypeOf(self.udp)) / 8) + (@bitSizeOf(@TypeOf(self.ip)) / 8));

        // Zero the checksum field before calculation
        self.ip.header_checksum = 0;

        // Serialize IP header to big-endian bytes
        var ip_bytes: [@bitSizeOf(@TypeOf(self.ip)) / 8]u8 = undefined;
        var w: Writer = .fixed(&ip_bytes);
        w.writeStruct(self.ip, .big) catch unreachable;

        // Calculate checksum over serialized bytes
        self.ip.header_checksum = onesComplement16(&ip_bytes);

        self.udp.len = @intCast(len + (@bitSizeOf(@TypeOf(self.udp)) / 8));
    }
};

fn onesComplement16(data: []const u8) u16 {
    var sum: u32 = 0;

    // Process pairs of bytes as 16-bit words
    var i: usize = 0;
    while (i + 1 < data.len) : (i += 2) {
        const word: u16 = (@as(u16, data[i]) << 8) | data[i + 1];
        sum += word;
    }

    // Handle odd byte if present
    if (data.len % 2 == 1) {
        sum += @as(u32, data[data.len - 1]) << 8;
    }

    // Fold 32-bit sum to 16 bits
    while (sum >> 16 != 0) {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }

    // Return ones' complement
    return ~@as(u16, @truncate(sum));
}

const std = @import("std");
const Writer = std.Io.Writer;