aboutsummaryrefslogtreecommitdiff
path: root/src/EthIpUdp.zig
blob: a65a96f66b4c04e045ab323069e16138acb763c6 (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
// 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 IpAddr = packed struct {
    int: I,

    const V = @Vector(4, u8);
    const I = u32;

    pub fn fromBytes(s: V) IpAddr {
        return .{ .int = @bitCast(s) };
    }
};

pub const MacAddr = packed struct {
    int: I,

    const V = @Vector(6, u8);
    const I = @Int(.unsigned, @bitSizeOf(V));

    pub fn fromBytes(s: V) MacAddr {
        return .{ .int = @bitCast(s) };
    }
};

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: IpAddr,
        src_addr: IpAddr,
        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: MacAddr,
    dst_mac: MacAddr = .fromBytes(@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;