From 326d319fb839aaf8ed5dfba3af3a6c13108388ee Mon Sep 17 00:00:00 2001 From: Altagos Date: Sat, 07 Jun 2025 15:25:43 +0000 Subject: [PATCH] BytePacketBuffer read api --- src/main.zig | 1 - src/root.zig | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 2 file(s) changed, 149 insertion(s)(+), 11 deletion(s)(-) diff --git a/src/main.zig b/src/main.zig --- a/src/main.zig +++ b/src/main.zig @@ -3,7 +3,6 @@ pub fn main() !void { std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); - try aether.advancedPrint(); } test "simple test" { diff --git a/src/root.zig b/src/root.zig --- a/src/root.zig +++ b/src/root.zig @@ -1,19 +1,158 @@ const std = @import("std"); -pub fn advancedPrint() !void { - const stdout_file = std.io.getStdOut().writer(); - var bw = std.io.bufferedWriter(stdout_file); - const stdout = bw.writer(); +pub const BytePacketBuffer = struct { + buf: [512]u8 = undefined, + pos: usize = 0, - try stdout.print("Run `zig build test` to run the tests.\n", .{}); + /// Change the buffer position forward a specific number of steps + pub fn step(self: *BytePacketBuffer, pos: usize) void { + self.pos += pos; + } - try bw.flush(); // Don't forget to flush! + /// Chanke the buffer position + pub fn seek(self: *BytePacketBuffer, pos: usize) void { + self.pos = pos; + } + + /// Read a single byte and move the position one step forward + pub fn read(self: *BytePacketBuffer) error{EndOfBuffer}!u8 { + if (self.pos >= comptime self.buf.len) return error.EndOfBuffer; + const res = self.buf[self.pos]; + self.pos += 1; + return res; + } + + /// Get a single byte without changing the buffer position + pub fn get(self: *const BytePacketBuffer, pos: usize) error{EndOfBuffer}!u8 { + if (pos >= comptime self.buf.len) return error.EndOfBuffer; + return self.buf[pos]; + } + + /// Get a range of bytes + pub fn get_range(self: *const BytePacketBuffer, start: usize, len: usize) error{EndOfBuffer}![]const u8 { + if (start + len >= comptime self.buf.len) return error.EndOfBuffer; + return self.buf[start .. start + len]; + } + + /// Read two bytes, stepping two steps forward + pub fn read_u16(self: *BytePacketBuffer) error{EndOfBuffer}!u16 { + return (@as(u16, try self.read()) << 8) | + @as(u16, try self.read()); + } + + /// Read two bytes, stepping two steps forward + pub fn read_u32(self: *BytePacketBuffer) error{EndOfBuffer}!u32 { + return @as(u32, try self.read()) << 24 | + (@as(u32, try self.read()) << 16) | + (@as(u32, try self.read()) << 8) | + (@as(u32, try self.read())); + } + + /// Read a qname + /// + /// The tricky part: Reading domain names, taking labels into consideration. + /// Will take something like [3]www[6]google[3]com and append + /// www.google.com to outstr. + pub fn read_qname(self: *BytePacketBuffer, outstr: []u8) !void { + // We might encounter jumps, therefore we need to keep thrack of our position locally + var pos = self.pos; + var out_pos: usize = 0; + + // track whether or nor we've jumped + var jumped = false; + const max_jumps: usize = 5; + var jumps_performed: usize = 0; + + var delim: ?[]const u8 = null; + while (true) { + if (jumps_performed > max_jumps) return error.JumpLimitExceeded; + + // Each label starts with a length byte + const len = try self.get(pos); + + // If len has the two most signigicant bit set, it represents a jump to some other + // offset in the packet: + if ((len & 0xC0) == 0xC0) { + // Update the buffer position to a point past the current label + if (!jumped) self.seek(2); + + // Read another byte, calculate offset and performe the jump by updating our + // local position variable + const b2 = @as(u16, try self.get(pos + 1)); + const offset = ((@as(u16, len) ^ 0xC0) << 8) | b2; + pos = @as(usize, offset); + + // Indicate that a jump was performed + jumped = true; + jumps_performed += 1; + + continue; + } else { + // Move a single byte forward to move path the length + pos += 1; + + // Domain names are terminated by an empty label of length 0, so if the length + // is zero we're done + if (len == 0) break; + + if (delim) |del| { + @memcpy(outstr[out_pos .. out_pos + del.len], del); + out_pos += del.len; + } + + @memcpy(outstr[out_pos .. out_pos + len], try self.get_range(pos, len)); + delim = "."; + + pos += len; + out_pos += len; + } + } + + if (!jumped) self.seek(1); + } +}; + +test "BytePacketBuffer.read" { + const testing = std.testing; + var buf = BytePacketBuffer{}; + buf.buf[0] = 0x1; + try testing.expectEqual(0x1, try buf.read()); } -pub fn add(a: i32, b: i32) i32 { - return a + b; +test "BytePacketBuffer.read_u16" { + const testing = std.testing; + var buf = BytePacketBuffer{}; + buf.buf[0] = 0x1; + buf.buf[1] = 0x1; + try testing.expectEqual(0x101, try buf.read_u16()); } -test "basic add functionality" { - try std.testing.expect(add(3, 7) == 10); +test "BytePacketBuffer.read_u32" { + const testing = std.testing; + var buf = BytePacketBuffer{}; + buf.buf[0] = 0x1; + buf.buf[1] = 0x1; + buf.buf[2] = 0x1; + buf.buf[3] = 0x1; + try testing.expectEqual(0x1010101, try buf.read_u32()); +} + +test "BytePacketBuffer.read_qname" { + const testing = std.testing; + const allocator = testing.allocator; + + const input = [_]u8{ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00 }; + var buf = BytePacketBuffer{}; + + for (input, 0..) |char, idx| { + buf.buf[idx] = char; + } + + const expected = "google.com"; + const outstr = try allocator.alloc(u8, expected.len); + defer allocator.free(outstr); + + try buf.read_qname(outstr); + + try testing.expectEqualStrings(expected, outstr); } -- tangled.sh