diff --git a/src/lib/Reader.zig b/src/lib/Reader.zig deleted file mode 100644 index 4cf7c85..0000000 --- a/src/lib/Reader.zig +++ /dev/null @@ -1,134 +0,0 @@ -const Self = @This(); -const std = @import("std"); -const assert = std.debug.assert; - -const log = std.log.scoped(.reader); - -pub const Errors = ReadErrors || SeekErrors; -pub const ReadErrors = error{Underflow}; -pub const SeekErrors = error{Overflow}; - -original: []const u8, -current: []const u8, - -pub fn init(input: []const u8) Self { - return .{ - .original = input, - .current = input, - }; -} - -fn Return(comptime T: type) type { - comptime assert(check(T)); - const info = @typeInfo(T); - switch (info) { - .int => { - comptime assert(info.int.signedness == .unsigned); - comptime assert(info.int.bits % 8 == 0); - return T; - }, - .array => { - comptime assert(info.array.child == u8); - return []const u8; - }, - else => unreachable, - } -} - -fn check(comptime T: type) bool { - return switch (comptime style(T)) { - .int => true, - .slice => true, - .other => false, - }; -} - -fn style(comptime T: type) enum { int, slice, other } { - const info = @typeInfo(T); - switch (info) { - .int => { - comptime assert(info.int.signedness == .unsigned); - comptime assert(info.int.bits % 8 == 0); - return .int; - }, - .array => { - comptime assert(info.array.child == u8); - return .slice; - }, - else => return .other, - } -} - -fn length(comptime T: type) usize { - comptime assert(check(T)); - const info = @typeInfo(T); - switch (info) { - .int => { - comptime assert(info.int.signedness == .unsigned); - comptime assert(info.int.bits % 8 == 0); - return @divExact(info.int.bits, 8); - }, - .array => { - comptime assert(info.array.child == u8); - return info.array.len; - }, - else => unreachable, - } -} - -pub fn remaining(self: *const Self) usize { - return self.current.len; -} - -pub fn peek(self: *const Self, comptime T: type) ReadErrors!Return(T) { - comptime assert(check(T)); - switch (comptime style(T)) { - .int => return try self.peekInt(T), - .slice => return try self.peekSlice(T), - .other => unreachable, - } -} - -pub fn peekInt(self: *const Self, comptime T: type) ReadErrors!Return(T) { - comptime assert(check(T)); - comptime assert(style(T) == .int); - const len = comptime length(T); - if (self.current.len < len) return error.Underflow; - return std.mem.readInt(T, self.current[0..len], .big); -} - -pub fn peekSlice(self: *const Self, comptime T: type) ReadErrors!Return(T) { - comptime assert(check(T)); - comptime assert(style(T) == .slice); - const len = comptime length(T); - if (self.current.len < len) return error.Underflow; - return self.current[0..len]; -} - -pub fn consume(self: *Self, comptime T: type) ReadErrors!void { - comptime assert(check(T)); - const len = comptime length(T); - if (self.current.len < len) return error.Underflow; - self.current = self.current[comptime length(T)..]; -} - -pub fn read(self: *Self, comptime T: type) ReadErrors!Return(T) { - comptime assert(check(T)); - const result = try self.peek(T); - try self.consume(T); - return result; -} - -pub fn seek(self: *Self, position: usize) SeekErrors!Self { - if (position >= self.original.len) return error.Overflow; - return .{ - .original = self.original, - .current = self.original[position..], - }; -} - -pub fn rest(self: *Self) []const u8 { - const result = self.current; - self.current = self.original[self.original.len..]; - return result; -} diff --git a/src/lib/netascii.zig b/src/lib/netascii.zig index 845e19f..2c2041b 100644 --- a/src/lib/netascii.zig +++ b/src/lib/netascii.zig @@ -3,22 +3,16 @@ const builtin = @import("builtin"); const testing = std.testing; const Allocator = std.mem.Allocator; -const Reader = @import("Reader.zig"); -const Writer = std.io.AnyWriter; - pub const DecodeErrors = error{ UnescapedCR, - MissingSentinel, -} || Reader.Errors || Writer.Error; +} || std.Io.Reader.Error || std.Io.Writer.Error; pub const DecodeSentinelErrors = error{ UnescapedCR, MissingSentinel, -} || Reader.Errors || Writer.Error; +} || std.Io.Reader.Error || std.Io.Writer.Error; -pub const EncodeErrors = error{ - UnescapedCR, -} || Writer.Error; +pub const EncodeErrors = error{} || std.Io.Reader.Error || std.Io.Writer.Error; pub const LineSeparator = enum { cr, @@ -26,7 +20,7 @@ pub const LineSeparator = enum { crlf, }; -pub fn decode(input: *Reader, output: Writer) DecodeErrors!void { +pub fn decode(input: *std.io.Reader, output: *std.Io.Writer) DecodeErrors!void { switch (builtin.os.tag) { .windows => _decode(input, output, null, .crlf) catch |err| { switch (err) { @@ -44,7 +38,7 @@ pub fn decode(input: *Reader, output: Writer) DecodeErrors!void { } } -pub fn decodeZ(input: *Reader, output: Writer) DecodeSentinelErrors!void { +pub fn decodeZ(input: *std.Io.Reader, output: *std.Io.Writer) DecodeSentinelErrors!void { switch (builtin.os.tag) { .windows => try _decode(input, output, std.ascii.control_code.nul, .crlf), else => try _decode(input, output, std.ascii.control_code.nul, .lf), @@ -52,7 +46,7 @@ pub fn decodeZ(input: *Reader, output: Writer) DecodeSentinelErrors!void { } } -fn _decode(input: *Reader, output: Writer, sentinel_: ?u8, comptime linesep: LineSeparator) DecodeSentinelErrors!void { +fn _decode(input: *std.Io.Reader, output: *std.Io.Writer, sentinel_: ?u8, comptime linesep: LineSeparator) DecodeSentinelErrors!void { const replacements = .{ .{ [_]u8{ std.ascii.control_code.cr, std.ascii.control_code.nul }, @@ -68,40 +62,43 @@ fn _decode(input: *Reader, output: Writer, sentinel_: ?u8, comptime linesep: Lin }, }; - loop: while (input.remaining() > 0) { - inline for (replacements) |a| { - const needle, const replacement = a; - if (needle.len <= input.remaining()) { - if (std.mem.eql(u8, try input.peek(@TypeOf(needle)), &needle)) { - try input.consume(@TypeOf(needle)); - try output.writeAll(&replacement); - continue :loop; - } + while (true) { + inline for (replacements) |item| replacement: { + const needle, const replacement = item; + const data = input.peek(needle.len) catch |err| switch (err) { + error.EndOfStream => break :replacement, + error.ReadFailed => |e| return e, + }; + if (std.mem.eql(u8, data, &needle)) { + input.toss(needle.len); + try output.writeAll(&replacement); } } - const byte = try input.peek(u8); + const byte = input.takeByte() catch |err| switch (err) { + error.EndOfStream => { + if (sentinel_) |_| return error.MissingSentinel; + return; + }, + error.ReadFailed => |e| return e, + }; switch (byte) { std.ascii.control_code.cr => return error.UnescapedCR, else => { - try input.consume(u8); if (sentinel_) |sentinel| if (byte == sentinel) return; try output.writeByte(byte); - continue :loop; }, } } - - if (sentinel_) |_| return error.MissingSentinel; } -pub fn encode(input: []const u8, writer: Writer) EncodeErrors!void { +pub fn encode(input: *std.Io.Reader, writer: *std.Io.Writer) EncodeErrors!void { switch (builtin.os.tag) { .windows => try _encode(input, writer, .crlf), else => try _encode(input, writer, .lf), // macOS before OS X would be .cr, but that is not supported by Zig } } -pub fn encodeZ(input: []const u8, writer: Writer) EncodeErrors!void { +pub fn encodeZ(input: *std.Io.Reader, writer: *std.Io.Writer) EncodeErrors!void { switch (builtin.os.tag) { .windows => try _encode(input, writer, .crlf), else => try _encode(input, writer, .lf), @@ -110,9 +107,7 @@ pub fn encodeZ(input: []const u8, writer: Writer) EncodeErrors!void { try writer.writeByte(std.ascii.control_code.nul); } -fn _encode(input: []const u8, writer: Writer, comptime linesep: LineSeparator) EncodeErrors!void { - var slide: usize = 0; - +fn _encode(input: *std.Io.Reader, output: *std.Io.Writer, comptime linesep: LineSeparator) EncodeErrors!void { const replacements = .{ .{ [_]u8{ std.ascii.control_code.cr, std.ascii.control_code.lf }, @@ -140,215 +135,228 @@ fn _encode(input: []const u8, writer: Writer, comptime linesep: LineSeparator) E }, }; - outer: while (slide < input.len) { - inline for (replacements) |item| { + while (true) { + inline for (replacements) |item| replacement: { const needle, const replacement = item; - if (needle.len <= input.len - slide) { - if (std.mem.startsWith(u8, input[slide..], &needle)) { - try writer.writeAll(&replacement); - slide += needle.len; - continue :outer; - } + const data = input.peek(needle.len) catch |err| switch (err) { + error.EndOfStream => break :replacement, + error.ReadFailed => |e| return e, + }; + if (std.mem.eql(u8, data, &needle)) { + input.toss(needle.len); + try output.writeAll(&replacement); } } - switch (input[slide]) { + const byte = input.takeByte() catch |err| switch (err) { + error.EndOfStream => return, + error.ReadFailed => |e| return e, + }; + switch (byte) { std.ascii.control_code.cr => unreachable, - else => |byte| { - try writer.writeByte(byte); - slide += 1; - continue :outer; - }, + else => try output.writeByte(byte), } } } test "netascii cr 1" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("\x0d\x00"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("\x0d\x00"); try _decode(&input, output, null, .cr); - try testing.expectEqualStrings("\x0d", buf.items); + try testing.expectEqualStrings("\x0d", buf.written()); } test "netascii cr 2" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("\x0d\x0a"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("\x0d\x0a"); try _decode(&input, output, null, .cr); - try testing.expectEqualStrings("\x0d", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("\x0d", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii cr 3" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("foo\x0d\x0a\x0abar"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("foo\x0d\x0a\x0abar"); try _decode(&input, output, null, .cr); - try testing.expectEqualStrings("foo\x0d\x0abar", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("foo\x0d\x0abar", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii cr 4" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("foo\x0d\x00\x0abar"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("foo\x0d\x00\x0abar"); try _decode(&input, output, null, .cr); - try testing.expectEqualStrings("foo\x0d\x0abar", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("foo\x0d\x0abar", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii cr 5" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("foo\x0d\x0a\x0dbar"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("foo\x0d\x0a\x0dbar"); try testing.expectError(error.UnescapedCR, _decode(&input, output, null, .cr)); } test "netascii lf 1" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("\x0d\x00"); + + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + + var input: std.Io.Reader = .fixed("\x0d\x00"); + try _decode(&input, output, null, .lf); - try testing.expectEqualStrings("\x0d", buf.items); - try testing.expectEqual(0, input.remaining()); + + try testing.expectEqualStrings("\x0d", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii lf 2" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("\x0d\x0a"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("\x0d\x0a"); try _decode(&input, output, null, .lf); - try testing.expectEqualStrings("\x0a", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("\x0a", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii lf 3" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("foo\x0d\x0a\x0abar"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("foo\x0d\x0a\x0abar"); try _decode(&input, output, null, .lf); - try testing.expectEqualStrings("foo\x0a\x0abar", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("foo\x0a\x0abar", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii lf 4" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("foo\x0d\x00\x0abar"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("foo\x0d\x00\x0abar"); try _decode(&input, output, null, .lf); - try testing.expectEqualStrings("foo\x0d\x0abar", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("foo\x0d\x0abar", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii lf 5" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("foo\x0d\x0a\x0dbar"); + + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + + var input: std.Io.Reader = .fixed("foo\x0d\x0a\x0dbar"); + try testing.expectError(error.UnescapedCR, _decode(&input, output, null, .lf)); } test "netascii crlf 1" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("\x0d\x00"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("\x0d\x00"); try _decode(&input, output, null, .crlf); - try testing.expectEqualStrings("\x0d", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("\x0d", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii crlf 2" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("\x0d\x0a"); + + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + + var input: std.Io.Reader = .fixed("\x0d\x0a"); + try _decode(&input, output, null, .crlf); - try testing.expectEqualStrings("\x0d\x0a", buf.items); - try testing.expectEqual(0, input.remaining()); + + try testing.expectEqualStrings("\x0d\x0a", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii crlf 3" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("foo\x0d\x0a\x0abar"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("foo\x0d\x0a\x0abar"); try _decode(&input, output, null, .crlf); - try testing.expectEqualStrings("foo\x0d\x0a\x0abar", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("foo\x0d\x0a\x0abar", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii crlf 4" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("foo\x0d\x00\x0abar"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("foo\x0d\x00\x0abar"); try _decode(&input, output, null, .crlf); - try testing.expectEqualStrings("foo\x0d\x0abar", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("foo\x0d\x0abar", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } test "netascii crlf 5" { const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); - var input: Reader = .init("foo\x0d\x0a\x0dbar"); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + var input: std.Io.Reader = .fixed("foo\x0d\x0a\x0dbar"); try testing.expectError(error.UnescapedCR, _decode(&input, output, null, .crlf)); } test "netascii decodeZ lf 1" { - var input: Reader = .init("test.txt\x00netascii\x00"); + var input: std.Io.Reader = .fixed("test.txt\x00netascii\x00"); const alloc = std.testing.allocator; { - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; try _decode(&input, output, std.ascii.control_code.nul, .lf); - try testing.expectEqualStrings("test.txt", buf.items); - try testing.expectEqual(9, input.remaining()); + try testing.expectEqualStrings("test.txt", buf.written()); + try testing.expectEqual(9, input.bufferedLen()); } { - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; try _decode(&input, output, std.ascii.control_code.nul, .lf); - try testing.expectEqualStrings("netascii", buf.items); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqualStrings("netascii", buf.written()); + try testing.expectEqual(0, input.bufferedLen()); } } test "netascii decodeZ lf 2" { - var input: Reader = .init("test.txt"); + var input: std.Io.Reader = .fixed("test.txt"); const alloc = std.testing.allocator; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const output = buf.writer(alloc).any(); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + try testing.expectError(error.MissingSentinel, _decode(&input, output, std.ascii.control_code.nul, .lf)); } diff --git a/src/lib/packet.zig b/src/lib/packet.zig index d6ce90d..ea57c62 100644 --- a/src/lib/packet.zig +++ b/src/lib/packet.zig @@ -4,7 +4,7 @@ const Allocator = std.mem.Allocator; const testing = std.testing; const netascii = @import("netascii.zig"); -const Reader = @import("Reader.zig"); +// const Reader = @import("Reader.zig"); pub const Option = enum { pub const DecodeErrors = error{ @@ -50,43 +50,42 @@ pub const Option = enum { pub const Mode = enum { pub const DecodeErrors = error{ - IllegalMode, + InvalidMode, } || netascii.DecodeSentinelErrors; mail, netascii, octet, - pub fn decodeZ(alloc: Allocator, input: *Reader) DecodeErrors!Mode { - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - const writer = buf.writer(alloc).any(); - - try netascii.decodeZ(input, writer); + pub fn decodeZ(alloc: Allocator, input: *std.Io.Reader) DecodeErrors!Mode { + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + const output = &buf.writer; + try netascii.decodeZ(input, output); inline for (@typeInfo(Mode).@"enum".fields) |field| - if (std.ascii.eqlIgnoreCase(field.name, buf.items)) + if (std.ascii.eqlIgnoreCase(field.name, buf.written())) return @field(Mode, field.name); - return error.IllegalMode; + return error.InvalidMode; } test "mode 1" { const alloc = testing.allocator; { - var input: Reader = .init("mail\x00"); + var input: std.io.Reader = .fixed("mail\x00"); try testing.expectEqual(.mail, decodeZ(alloc, &input)); } { - var input: Reader = .init("MAIL\x00"); + var input: std.Io.Reader = .fixed("MAIL\x00"); try testing.expectEqual(.mail, decodeZ(alloc, &input)); } { - var input: Reader = .init("Mail\x00"); + var input: std.io.Reader = .fixed("Mail\x00"); try testing.expectEqual(.mail, decodeZ(alloc, &input)); } { - var input: Reader = .init("MaIl\x00"); + var input: std.io.Reader = .fixed("MaIl\x00"); try testing.expectEqual(.mail, decodeZ(alloc, &input)); } } @@ -94,7 +93,7 @@ pub const Mode = enum { test "mode 2" { const alloc = testing.allocator; { - var input: Reader = .init("netascii\x00"); + var input: std.io.Reader = .fixed("netascii\x00"); try testing.expectEqual(.netascii, decodeZ(alloc, &input)); } } @@ -102,7 +101,7 @@ pub const Mode = enum { test "mode 3" { const alloc = testing.allocator; { - var input: Reader = .init("octet\x00"); + var input: std.io.Reader = .fixed("octet\x00"); try testing.expectEqual(.octet, decodeZ(alloc, &input)); } } @@ -110,16 +109,17 @@ pub const Mode = enum { test "mode 4" { const alloc = testing.allocator; { - var input: Reader = .init("apple\x00"); - try testing.expectError(error.IllegalMode, decodeZ(alloc, &input)); + var input: std.io.Reader = .fixed("apple\x00"); + try testing.expectError(error.InvalidMode, decodeZ(alloc, &input)); } } }; pub const Opcode = enum(u16) { pub const DecodeErrors = error{ - IllegalOpcode, - } || Reader.Errors || netascii.DecodeSentinelErrors || Allocator.Error; + InvalidOpcode, + PacketTooLong, + } || std.Io.Reader.Error; rrq = 1, wrq = 2, @@ -128,51 +128,53 @@ pub const Opcode = enum(u16) { @"error" = 5, oack = 6, - pub fn decode(input: *Reader) DecodeErrors!Opcode { - const raw = try input.read(u16); - return std.meta.intToEnum(Opcode, raw) catch return error.IllegalOpcode; + pub fn decode(input: *std.Io.Reader) DecodeErrors!Opcode { + return input.takeEnum(Opcode, .big) catch |err| switch (err) { + error.InvalidEnumTag => return error.InvalidOpcode, + else => |e| return e, + }; } test "opcode" { { - var input: Reader = .init(&[_]u8{ 0x00, 0x00 }); - try testing.expectError(error.IllegalOpcode, decode(&input)); - try testing.expectEqual(0, input.remaining()); + var input: std.Io.Reader = .fixed(&[_]u8{ 0x00, 0x00 }); + try testing.expectError(error.InvalidOpcode, decode(&input)); + try testing.expectEqual(0, input.bufferedLen()); } { - var input: Reader = .init(&[_]u8{ 0x00, 0x01 }); + var input: std.Io.Reader = .fixed(&[_]u8{ 0x00, 0x01 }); try testing.expectEqual(.rrq, try decode(&input)); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqual(0, input.bufferedLen()); } { - var input: Reader = .init(&[_]u8{ 0x00, 0x02 }); + var input: std.Io.Reader = .fixed(&[_]u8{ 0x00, 0x02 }); try testing.expectEqual(.wrq, try decode(&input)); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqual(0, input.bufferedLen()); } { - var input: Reader = .init(&[_]u8{ 0x00, 0x03 }); + var input: std.Io.Reader = .fixed(&[_]u8{ 0x00, 0x03 }); try testing.expectEqual(.data, try decode(&input)); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqual(0, input.bufferedLen()); } { - var input: Reader = .init(&[_]u8{ 0x00, 0x04 }); + var input: std.Io.Reader = .fixed(&[_]u8{ 0x00, 0x04 }); try testing.expectEqual(.ack, try decode(&input)); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqual(0, input.bufferedLen()); } { - var input: Reader = .init(&[_]u8{ 0x00, 0x05 }); + var input: std.Io.Reader = .fixed(&[_]u8{ 0x00, 0x05 }); try testing.expectEqual(.@"error", try decode(&input)); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqual(0, input.bufferedLen()); } { - var input: Reader = .init(&[_]u8{ 0x00, 0x06 }); + var input: std.Io.Reader = .fixed(&[_]u8{ 0x00, 0x06 }); try testing.expectEqual(.oack, try decode(&input)); - try testing.expectEqual(0, input.remaining()); + try testing.expectEqual(0, input.bufferedLen()); } { - var input: Reader = .init(&[_]u8{ 0x01, 0x01 }); - try testing.expectError(error.IllegalOpcode, decode(&input)); - try testing.expectEqual(0, input.remaining()); + var input: std.Io.Reader = .fixed(&[_]u8{ 0x01, 0x01 }); + try testing.expectError(error.InvalidOpcode, decode(&input)); + try testing.expectEqual(0, input.bufferedLen()); } } }; @@ -203,24 +205,26 @@ pub const Error = enum(u8) { } }; -fn decodeFilename(alloc: Allocator, input: *Reader) (Allocator.Error || netascii.DecodeSentinelErrors)![]const u8 { - var buf: std.ArrayListUnmanaged(u8) = .empty; - errdefer buf.deinit(alloc); - const writer = buf.writer(alloc).any(); - try netascii.decodeZ(input, writer); - return buf.toOwnedSlice(alloc); +fn decodeFilename(alloc: Allocator, input: *std.Io.Reader) (Allocator.Error || netascii.DecodeSentinelErrors)![]const u8 { + var buf: std.Io.Writer.Allocating = .init(alloc); + errdefer buf.deinit(); + const output = &buf.writer; + try netascii.decodeZ(input, output); + return buf.toOwnedSlice(); } pub const Packet = union(Opcode) { pub const DecodeErrors = error{ UnterminatedFilename, UnterminatedMode, - } || Opcode.DecodeErrors || Mode.DecodeErrors; + ModeRequired, + } || Opcode.DecodeErrors || Mode.DecodeErrors || std.Io.Reader.LimitedAllocError; pub const DecodeOptions = struct { mode: ?Mode = null, + blocksize: ?u16 = null, - pub const defaults: @This() = .{}; + pub const default: @This() = .{}; pub const netascii: @This() = .{ .mode = .netascii }; pub const mail: @This() = .{ .mode = .mail }; pub const octet: @This() = .{ .mode = .octet }; @@ -229,6 +233,7 @@ pub const Packet = union(Opcode) { rrq: struct { filename: []const u8, mode: Mode, + blocksize: ?u16 = null, }, wrq: struct { filename: []const u8, @@ -238,6 +243,7 @@ pub const Packet = union(Opcode) { block: u16, mode: Mode, data: []const u8, + last: bool = false, }, ack: struct { block: u16, @@ -246,7 +252,8 @@ pub const Packet = union(Opcode) { oack: struct {}, pub fn decode(alloc: Allocator, packet: []const u8, options: DecodeOptions) DecodeErrors!Packet { - var input: Reader = .init(packet); + // var input: Reader = .init(packet); + var input = std.Io.Reader.fixed(packet); const opcode: Opcode = try .decode(&input); switch (opcode) { .rrq, .wrq => { @@ -274,28 +281,39 @@ pub const Packet = union(Opcode) { }; }, .data => { - const sequence = try input.read(u16); + const sequence = try input.takeInt(u16, .big); const mode = options.mode orelse return error.ModeRequired; + const blocksize: u16 = options.blocksize orelse 512; switch (mode) { .mail, .netascii => { - var buf: std.ArrayListUnmanaged(u8) = .empty; - errdefer buf.deinit(alloc); - const output = buf.writer(alloc).any(); + var buf: std.Io.Writer.Allocating = .init(alloc); + errdefer buf.deinit(); + const output = &buf.writer; try netascii.decode(&input, output); + if (buf.written().len > blocksize) return error.PacketTooLong; + const data = try buf.toOwnedSlice(); return .{ .data = .{ .block = sequence, .mode = mode, - .data = try buf.toOwnedSlice(alloc), + .data = data, + .last = data.len < blocksize, }, }; }, - .octet => return .{ - .data = .{ - .block = sequence, - .mode = mode, - .data = input.rest(), - }, + .octet => { + const data = input.allocRemaining(alloc, .limited(blocksize)) catch |err| switch (err) { + error.StreamTooLong => return error.PacketTooLong, + else => |e| return e, + }; + return .{ + .data = .{ + .block = sequence, + .mode = mode, + .data = data, + .last = data.len < blocksize, + }, + }; }, } }, @@ -316,8 +334,7 @@ pub const Packet = union(Opcode) { }, .data => |v| { switch (v.mode) { - .mail, .netascii => alloc.free(v.data), - .octet => {}, + inline else => alloc.free(v.data), } }, .ack => {}, @@ -329,7 +346,7 @@ pub const Packet = union(Opcode) { test "datagram decode 1" { const alloc = testing.allocator; const input = &[_]u8{ 0x0, 0x1, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78, 0x74, 0x0, 0x6e, 0x65, 0x74, 0x61, 0x73, 0x63, 0x69, 0x69, 0x0 }; - const packet: Packet = try .decode(alloc, input, .defaults); + const packet: Packet = try .decode(alloc, input, .default); defer packet.deinit(alloc); try testing.expect(packet == .rrq); try testing.expectEqualStrings("test.txt", packet.rrq.filename);