aboutsummaryrefslogtreecommitdiff
path: root/src/EthIpUdp.zig
blob: 251ed64ba8fde2e30aba1725845708148b2cbd26 (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
// Copyright 2026 Robby Zambito
//
// This file is part of zaprus.
//
// Zaprus is free software: you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// Zaprus is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// Zaprus. If not, see <https://www.gnu.org/licenses/>.

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;