diff options
| -rw-r--r-- | src/message.zig | 458 | ||||
| -rw-r--r-- | src/root.zig | 1 |
2 files changed, 137 insertions, 322 deletions
diff --git a/src/message.zig b/src/message.zig index c705a0f..c847fc9 100644 --- a/src/message.zig +++ b/src/message.zig @@ -2,24 +2,10 @@ /// This is the first value in the actual packet sent over the network. pub const PacketType = enum(u16) { relay = 0x003C, - file_transfer = 0x8888, connection = 0x00E9, _, }; -/// Reserved option values. -/// Currently unused. -pub const ConnectionOptions = packed struct(u8) { - opt1: bool = false, - opt2: bool = false, - opt3: bool = false, - opt4: bool = false, - opt5: bool = false, - opt6: bool = false, - opt7: bool = false, - opt8: bool = false, -}; - pub const MessageTypeError = error{ NotImplementedSaprusType, UnknownSaprusType, @@ -28,13 +14,91 @@ pub const MessageParseError = MessageTypeError || error{ InvalidMessage, }; -pub const RelayMessage = struct { +const message = @This(); + +pub const Message = union(PacketType) { + relay: Message.Relay, + connection: Message.Connection, + + pub const Relay = message.Relay; + pub const Connection = message.Connection; +}; + +pub const relay_dest_len = 4; + +pub fn parse(bytes: []const u8) MessageParseError!Message { + var in: Reader = .fixed(bytes); + const @"type" = in.takeEnum(PacketType, .big) catch |err| switch (err) { + error.InvalidEnumTag => return error.UnknownSaprusType, + else => return error.InvalidMessage, + }; + const checksum = in.takeArray(2) catch return error.InvalidMessage; + switch (@"type") { + .relay => { + const dest: Relay.Dest = .fromBytes( + in.takeArray(relay_dest_len) catch return error.InvalidMessage, + ); + const payload = in.buffered(); + return .{ + .relay = .{ + .dest = dest, + .checksum = checksum.*, + .payload = payload, + }, + }; + }, + .connection => { + const src = in.takeInt(u16, .big) catch return error.InvalidMessage; + const dest = in.takeInt(u16, .big) catch return error.InvalidMessage; + const seq = in.takeInt(u32, .big) catch return error.InvalidMessage; + const id = in.takeInt(u32, .big) catch return error.InvalidMessage; + const reserved = in.takeByte() catch return error.InvalidMessage; + const options = in.takeStruct(Connection.Options, .big) catch return error.InvalidMessage; + const payload = in.buffered(); + return .{ + .connection = .{ + .src = src, + .dest = dest, + .seq = seq, + .id = id, + .reserved = reserved, + .options = options, + .payload = payload, + }, + }; + }, + else => return error.NotImplementedSaprusType, + } +} + +test parse { + _ = try parse(&[_]u8{ 0x00, 0x3c, 0x00, 0x17, 0xac, 0x12, 0x01, 0x1e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64 }); + + { + const expected: Message = .{ + .connection = .{ + .src = 12416, + .dest = 61680, + .seq = 0, + .id = 0, + .reserved = 0, + .options = @bitCast(@as(u8, 100)), + .payload = &[_]u8{ 0x69, 0x61, 0x6d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74 }, + }, + }; + const actual = try parse(&[_]u8{ 0x00, 0xe9, 0x00, 0x18, 0x30, 0x80, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x69, 0x61, 0x6d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74 }); + + try std.testing.expectEqualDeep(expected, actual); + } +} + +const Relay = struct { dest: Dest, + checksum: [2]u8 = undefined, payload: []const u8, - pub const @"type": PacketType = .relay; pub const Dest = struct { - bytes: [4]u8, + bytes: [relay_dest_len]u8, /// Asserts bytes is less than or equal to 4 bytes pub fn fromBytes(bytes: []const u8) Dest { @@ -45,27 +109,23 @@ pub const RelayMessage = struct { } }; - pub fn init(dest: Dest, payload: []const u8) RelayMessage { + pub fn init(dest: Dest, payload: []const u8) Relay { return .{ .dest = dest, .payload = payload }; } /// Asserts that buf is large enough to fit the relay message. - pub fn toBytes(self: RelayMessage, buf: []u8) []u8 { + pub fn toBytes(self: Relay, buf: []u8) []u8 { var out: Writer = .fixed(buf); - out.writeInt(u16, @intFromEnum(RelayMessage.type), .big) catch unreachable; - out.writeInt(u16, @intCast(self.payload.len + self.dest.bytes.len), .big) catch unreachable; + out.writeInt(u16, @intFromEnum(PacketType.relay), .big) catch unreachable; + out.writeInt(u16, undefined, .big) catch unreachable; // Length field, but unread. Will switch to checksum out.writeAll(&self.dest.bytes) catch unreachable; out.writeAll(self.payload) catch unreachable; return out.buffered(); } - pub fn fromBytes(buf: []const u8) RelayMessage { - var in: Reader = .fixed(buf); - } - test toBytes { var buf: [1024]u8 = undefined; - const relay: RelayMessage = .init( + const relay: Relay = .init( .fromBytes(&.{ 172, 18, 1, 30 }), // zig fmt: off &[_]u8{ @@ -81,312 +141,68 @@ pub const RelayMessage = struct { 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64 }; // zig fmt: on - try std.testing.expectEqualSlices(u8, &expected, relay.toBytes(&buf)); + try expectEqualMessageBuffers(&expected, relay.toBytes(&buf)); } }; -// pub const ConnectionMessage = struct { -// length: u16, -// src_port: u16, -// dest_port: u16, -// seq_num: u32 = 0, -// msg_id: u32 = 0, -// // _reserved: u8 = 0, -// options: ConnectionOptions = .{}, -// payload: []u8, - -// pub const type: PacketType = .connection; - -// /// Asserts that buf is large enough to fit the connection message. -// fn toBytes(self: ConnectionMessage, buf: []u8) []u8 { -// var out: Writer = .fixed(buf); -// out.writeInt(u16, ConnectionMessage.type, .big) catch unreachable; -// return w.buffered(); -// } -// }; - -// // ZERO COPY STUFF -// // &payload could be a void value that is treated as a pointer to a [*]u8 -// /// All Saprus messages -// pub const Message = packed struct { -// pub const Relay = packed struct { -// dest: @Vector(4, u8), -// payload: void, - -// pub fn getPayload(self: *align(1) Relay) []u8 { -// const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16)); -// return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @bitSizeOf(Relay) / 8]; -// } -// }; -// pub const Connection = packed struct { -// src_port: u16, // random number > 1024 -// dest_port: u16, // random number > 1024 -// seq_num: u32 = 0, -// msg_id: u32 = 0, -// reserved: u8 = 0, -// options: ConnectionOptions = .{}, -// payload: void, - -// pub fn getPayload(self: *align(1) Connection) []u8 { -// const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16)); -// return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @bitSizeOf(Connection) / 8]; -// } - -// fn nativeFromNetworkEndian(self: *align(1) Connection) void { -// self.src_port = bigToNative(@TypeOf(self.src_port), self.src_port); -// self.dest_port = bigToNative(@TypeOf(self.dest_port), self.dest_port); -// self.seq_num = bigToNative(@TypeOf(self.seq_num), self.seq_num); -// self.msg_id = bigToNative(@TypeOf(self.msg_id), self.msg_id); -// } - -// fn networkFromNativeEndian(self: *align(1) Connection) void { -// self.src_port = nativeToBig(@TypeOf(self.src_port), self.src_port); -// self.dest_port = nativeToBig(@TypeOf(self.dest_port), self.dest_port); -// self.seq_num = nativeToBig(@TypeOf(self.seq_num), self.seq_num); -// self.msg_id = nativeToBig(@TypeOf(self.msg_id), self.msg_id); -// } -// }; - -// const Self = @This(); -// const SelfBytes = []align(1) u8; - -// type: PacketType, -// length: u16, -// bytes: void = {}, - -// /// Takes a byte slice, and returns a Message struct backed by the slice. -// /// This properly initializes the top level headers within the slice. -// /// This is used for creating new messages. For reading messages from the network, -// /// see: networkBytesAsValue. -// pub fn init(@"type": PacketType, bytes: []u8) *align(1) Self { -// std.debug.assert(bytes.len >= @sizeOf(Self)); -// const res: *align(1) Self = @ptrCast(bytes.ptr); -// res.type = @"type"; -// res.length = @intCast(bytes.len - @sizeOf(Self)); -// return res; -// } - -// /// Compute the number of bytes required to store a given payload size for a given message type. -// pub fn calcSize(comptime @"type": PacketType, payload_len: usize) MessageTypeError!u16 { -// const header_size = @bitSizeOf(switch (@"type") { -// .relay => Relay, -// .connection => Connection, -// .file_transfer => return MessageTypeError.NotImplementedSaprusType, -// else => return MessageTypeError.UnknownSaprusType, -// }) / 8; -// return @intCast(payload_len + @sizeOf(Self) + header_size); -// } - -// fn getRelay(self: *align(1) Self) *align(1) Relay { -// return std.mem.bytesAsValue(Relay, &self.bytes); -// } -// fn getConnection(self: *align(1) Self) *align(1) Connection { -// return std.mem.bytesAsValue(Connection, &self.bytes); -// } - -// /// Access the message Saprus payload. -// pub fn getSaprusTypePayload(self: *align(1) Self) MessageTypeError!(union(PacketType) { -// relay: *align(1) Relay, -// file_transfer: void, -// connection: *align(1) Connection, -// }) { -// return switch (self.type) { -// .relay => .{ .relay = self.getRelay() }, -// .connection => .{ .connection = self.getConnection() }, -// .file_transfer => MessageTypeError.NotImplementedSaprusType, -// else => MessageTypeError.UnknownSaprusType, -// }; -// } - -// /// Convert the message to native endianness from network endianness in-place. -// pub fn nativeFromNetworkEndian(self: *align(1) Self) MessageTypeError!void { -// self.type = @enumFromInt(bigToNative( -// @typeInfo(@TypeOf(self.type)).@"enum".tag_type, -// @intFromEnum(self.type), -// )); -// self.length = bigToNative(@TypeOf(self.length), self.length); -// errdefer { -// // If the payload specific headers fail, revert the top level header values -// self.type = @enumFromInt(nativeToBig( -// @typeInfo(@TypeOf(self.type)).@"enum".tag_type, -// @intFromEnum(self.type), -// )); -// self.length = nativeToBig(@TypeOf(self.length), self.length); -// } -// switch (try self.getSaprusTypePayload()) { -// .relay => {}, -// .connection => |*con| con.*.nativeFromNetworkEndian(), -// // We know other values are unreachable, -// // because they would have returned an error from the switch condition. -// else => unreachable, -// } -// } - -// /// Convert the message to network endianness from native endianness in-place. -// pub fn networkFromNativeEndian(self: *align(1) Self) MessageTypeError!void { -// try switch (try self.getSaprusTypePayload()) { -// .relay => {}, -// .connection => |*con| con.*.networkFromNativeEndian(), -// .file_transfer => MessageTypeError.NotImplementedSaprusType, -// else => MessageTypeError.UnknownSaprusType, -// }; -// self.type = @enumFromInt(nativeToBig( -// @typeInfo(@TypeOf(self.type)).@"enum".tag_type, -// @intFromEnum(self.type), -// )); -// self.length = nativeToBig(@TypeOf(self.length), self.length); -// } - -// /// Convert network endian bytes to a native endian value in-place. -// pub fn networkBytesAsValue(bytes: SelfBytes) MessageParseError!*align(1) Self { -// const res = std.mem.bytesAsValue(Self, bytes); -// try res.nativeFromNetworkEndian(); -// return .bytesAsValue(bytes); -// } - -// /// Create a structured view of the bytes without initializing the length or type, -// /// and without converting the endianness. -// pub fn bytesAsValue(bytes: SelfBytes) MessageParseError!*align(1) Self { -// const res = std.mem.bytesAsValue(Self, bytes); -// return switch (res.type) { -// .relay, .connection => if (bytes.len == res.length + @sizeOf(Self)) -// res -// else -// MessageParseError.InvalidMessage, -// .file_transfer => MessageParseError.NotImplementedSaprusType, -// else => MessageParseError.UnknownSaprusType, -// }; -// } - -// /// Deprecated. -// /// If I need the bytes, I should just pass around the slice that is backing this to begin with. -// pub fn asBytes(self: *align(1) Self) SelfBytes { -// const size = @sizeOf(Self) + self.length; -// return @as([*]align(1) u8, @ptrCast(self))[0..size]; -// } -// }; - -// test "testing variable length zero copy struct" { -// { -// // Relay test -// const payload = "Hello darkness my old friend"; -// var msg_bytes: [try Message.calcSize(.relay, payload.len)]u8 align(@alignOf(Message)) = undefined; - -// // Create a view of the byte slice as a Message -// const msg: *align(1) Message = .init(.relay, &msg_bytes); - -// { -// // Set the message values -// { -// // These are both set by the init call. -// // msg.type = .relay; -// // msg.length = payload_len; -// } -// const relay = (try msg.getSaprusTypePayload()).relay; -// relay.dest = .{ 1, 2, 3, 4 }; -// @memcpy(relay.getPayload(), payload); -// } - -// { -// // Print the message as hex using the network byte order -// try msg.networkFromNativeEndian(); -// // We know the error from nativeFromNetworkEndian is unreachable because -// // it would have returned an error from networkFromNativeEndian. -// defer msg.nativeFromNetworkEndian() catch unreachable; -// std.debug.print("relay network bytes: {x}\n", .{msg_bytes}); -// std.debug.print("bytes len: {d}\n", .{msg_bytes.len}); -// } - -// if (false) { -// // Illegal behavior -// std.debug.print("{any}\n", .{(try msg.getSaprusTypePayload()).connection}); -// } - -// try std.testing.expectEqualDeep(msg, try Message.bytesAsValue(msg.asBytes())); -// } +const Connection = struct { + src: u16, + dest: u16, + seq: u32, + id: u32, + reserved: u8 = undefined, + options: Options = undefined, + payload: []const u8, -// { -// // Connection test -// const payload = "Hello darkness my old friend"; -// var msg_bytes: [try Message.calcSize(.connection, payload.len)]u8 align(@alignOf(Message)) = undefined; + /// Reserved option values. + /// Currently unused. + pub const Options = packed struct(u8) { + opt1: bool = false, + opt2: bool = false, + opt3: bool = false, + opt4: bool = false, + opt5: bool = false, + opt6: bool = false, + opt7: bool = false, + opt8: bool = false, + }; -// // Create a view of the byte slice as a Message -// const msg: *align(1) Message = .init(.connection, &msg_bytes); + /// Asserts that buf is large enough to fit the connection message. + pub fn toBytes(self: Connection, buf: []u8) []u8 { + var out: Writer = .fixed(buf); + out.writeInt(u16, @intFromEnum(PacketType.connection), .big) catch unreachable; + out.writeInt(u16, undefined, .big) catch unreachable; // Saprus length field, unread. + out.writeInt(u16, self.src, .big) catch unreachable; + out.writeInt(u16, self.dest, .big) catch unreachable; + out.writeInt(u32, self.seq, .big) catch unreachable; + out.writeInt(u32, self.id, .big) catch unreachable; + out.writeByte(self.reserved) catch unreachable; + out.writeStruct(self.options, .big) catch unreachable; + out.writeAll(self.payload) catch unreachable; + return out.buffered(); + } +}; -// { -// // Initializing connection header values -// const connection = (try msg.getSaprusTypePayload()).connection; -// connection.src_port = 1; -// connection.dest_port = 2; -// connection.seq_num = 3; -// connection.msg_id = 4; -// connection.reserved = 5; -// connection.options = @bitCast(@as(u8, 6)); -// @memcpy(connection.getPayload(), payload); -// } +test "Round trip" { + { + const expected = [_]u8{ 0x0, 0xe9, 0x0, 0x15, 0x30, 0x80, 0xf0, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x64, 0x36, 0x3a, 0x3a, 0x64, 0x61, 0x74, 0x61 }; + const msg = (try parse(&expected)).connection; + var res_buf: [expected.len + 1]u8 = undefined; // + 1 to test subslice result. + const res = msg.toBytes(&res_buf); + try expectEqualMessageBuffers(&expected, res); + } +} -// { -// // Print the message as hex using the network byte order -// try msg.networkFromNativeEndian(); -// // We know the error from nativeFromNetworkEndian is unreachable because -// // it would have returned an error from networkFromNativeEndian. -// defer msg.nativeFromNetworkEndian() catch unreachable; -// std.debug.print("connection network bytes: {x}\n", .{msg_bytes}); -// std.debug.print("bytes len: {d}\n", .{msg_bytes.len}); -// } -// } -// } +// Skip checking the length / checksum, because that is undefined. +fn expectEqualMessageBuffers(expected: []const u8, actual: []const u8) !void { + try std.testing.expectEqualSlices(u8, expected[0..2], actual[0..2]); + try std.testing.expectEqualSlices(u8, expected[4..], actual[4..]); +} const std = @import("std"); const Allocator = std.mem.Allocator; const Writer = std.Io.Writer; - -// const asBytes = std.mem.asBytes; -// const nativeToBig = std.mem.nativeToBig; -// const bigToNative = std.mem.bigToNative; - -// test "Round trip Relay toBytes and fromBytes" { -// if (true) return error.SkipZigTest; -// const gpa = std.testing.allocator; -// const msg = Message{ -// .relay = .{ -// .header = .{ .dest = .{ 255, 255, 255, 255 } }, -// .payload = "Hello darkness my old friend", -// }, -// }; - -// const to_bytes = try msg.toBytes(gpa); -// defer gpa.free(to_bytes); - -// const from_bytes = try Message.fromBytes(to_bytes, gpa); -// defer from_bytes.deinit(gpa); - -// try std.testing.expectEqualDeep(msg, from_bytes); -// } - -// test "Round trip Connection toBytes and fromBytes" { -// if (false) { -// const gpa = std.testing.allocator; -// const msg = Message{ -// .connection = .{ -// .header = .{ -// .src_port = 0, -// .dest_port = 0, -// }, -// .payload = "Hello darkness my old friend", -// }, -// }; - -// const to_bytes = try msg.toBytes(gpa); -// defer gpa.free(to_bytes); - -// const from_bytes = try Message.fromBytes(to_bytes, gpa); -// defer from_bytes.deinit(gpa); - -// try std.testing.expectEqualDeep(msg, from_bytes); -// } -// return error.SkipZigTest; -// } +const Reader = std.Io.Reader; test { std.testing.refAllDeclsRecursive(@This()); diff --git a/src/root.zig b/src/root.zig index 5aa9d9d..f7efb7b 100644 --- a/src/root.zig +++ b/src/root.zig @@ -4,7 +4,6 @@ pub const Connection = @import("Connection.zig"); const msg = @import("message.zig"); pub const PacketType = msg.PacketType; -pub const ConnectionOptions = msg.ConnectionOptions; pub const MessageTypeError = msg.MessageTypeError; pub const MessageParseError = msg.MessageParseError; pub const Message = msg.Message; |
