///////////////// Module // const Self = @This(); header: PacketHeader, address1: [ADDRESS_SIZE]u8, address2: [ADDRESS_SIZE]u8, context: PacketContext, data: []u8, ///////////////// Constants // const MAX_DATA_SIZE = 465; const ADDRESS_SIZE = 16; ///////////////// Enums // pub const IfacFlag = enum(u1) { open = 0, // Packet for publically accessible interface authenticated = 1, // Interface authentication is included in packet }; pub const HeaderType = enum(u1) { type1 = 0, // One address field type2 = 1, // Two address fields }; pub const ContextFlag = enum(u1) { // Meaning depends on packet context unset = 0, set = 1, }; pub const PropagationType = enum(u1) { broadcast = 0, transport = 1 }; pub const DestinationType = enum(u2) { single = 0b0, group = 0b01, plain = 0b10, link = 0b11, }; pub const PacketType = enum(u2) { data = 0b0, announce = 0b01, link_request = 0b10, proof = 0b11, }; pub const PacketContext = enum(u8) { none = 0x00, // Generic data packet resource = 0x01, // Packet is part of a resource resource_adv = 0x02, // Packet is a resource advertisement resource_req = 0x03, // Packet is a resource part request resource_hmu = 0x04, // Packet is a resource hashmap update resource_prf = 0x05, // Packet is a resource proof resource_icl = 0x06, // Packet is a resource initiator cancel message resource_rcl = 0x07, // Packet is a resource receiver cancel message cache_request = 0x08, // Packet is a cache request request = 0x09, // Packet is a request response = 0x0A, // Packet is a response to a request path_response = 0x0B, // Packet is a response to a path request command = 0x0C, // Packet is a command command_status = 0x0D, // Packet is a status of an executed command channel = 0x0E, // Packet contains link channel data keepalive = 0xFA, // Packet is a keepalive packet linkidentify = 0xFB, // Packet is a link peer identification proof linkclose = 0xFC, // Packet is a link close message linkproof = 0xFD, // Packet is a link packet proof lrrtt = 0xFE, // Packet is a link request round-trip time measurement lrproof = 0xFF, // Packet is a link request proof }; ///////////////// Structs // pub const PacketHeader = packed struct { ifac: IfacFlag, header: HeaderType, context: ContextFlag, propagation: PropagationType, destination: DestinationType, packet: PacketType, hops: u8, }; ///////////////// Functions // //////// Helper functions // // Copies serialized data (as a byte array) into any structure type fn copyToPacket(dst: anytype, src: []const u8, offset: usize, count: usize) !usize { if (count == 0) return 0; if (src.len < offset + count) return error.BufferTooShort; const dst_ptr: []u8 = @ptrCast(dst); if (dst_ptr.len != count) return error.MismatchedLengths; @memcpy(dst_ptr, src[offset .. offset + count]); return offset + count; } // Returns the size of the packet in bytes fn getPacketSize(data_size: usize, has_two_addresses: bool) usize { var res = @sizeOf(PacketHeader) + ADDRESS_SIZE + @sizeOf(PacketContext) + data_size; if (has_two_addresses) res += ADDRESS_SIZE; return res; } //////// Public functions // pub fn serialize(packet: *const Self, output_buffer: []u8) !usize { const has_two_addresses = packet.header.header == HeaderType.type2; const target_size = getPacketSize(packet.data.len, has_two_addresses); if (output_buffer.len < target_size) { return error.BufferTooShort; } // Write buffer var offset: usize = 0; @memcpy(output_buffer[offset .. offset + @sizeOf(PacketHeader)], asBytes(&packet.header)); offset += @sizeOf(PacketHeader); @memcpy(output_buffer[offset .. offset + ADDRESS_SIZE], asBytes(&packet.address1)); offset += ADDRESS_SIZE; if (has_two_addresses) { @memcpy(output_buffer[offset .. offset + ADDRESS_SIZE], asBytes(&packet.address2)); offset += ADDRESS_SIZE; } @memcpy(output_buffer[offset .. offset + @sizeOf(PacketContext)], asBytes(&packet.context)); offset += @sizeOf(PacketContext); @memcpy(output_buffer[offset .. offset + packet.data.len], packet.data); offset += packet.data.len; if (target_size != offset) { return error.InternalError; } return offset; } // Reads the message buffer and builds the corresponding packet struct inside dest_packet pub fn deserialize(packet: *Self, message_buffer: []u8) !void { var offset: usize = 0; offset = try copyToPacket(&packet.header, message_buffer, offset, @sizeOf(PacketHeader)); offset = try copyToPacket(&packet.address1, message_buffer, offset, ADDRESS_SIZE); const has_two_addresses = packet.header.header == HeaderType.type2; if (has_two_addresses) { offset = try copyToPacket(&packet.address2, message_buffer, offset, ADDRESS_SIZE); } offset = try copyToPacket(&packet.context, message_buffer, offset, @sizeOf(PacketContext)); offset = try copyToPacket(packet.data, message_buffer, offset, message_buffer.len - offset); } ///////////////// Tests // const std = @import("std"); const expect = std.testing.expect; const memeql = std.mem.eql; const asBytes = std.mem.asBytes; //////// Helper functions // // Serializes the given packet and makes sure the output is correct fn testPacketSerialization(packet: *const Self) !void { const buf_size = comptime @sizeOf(Self) + MAX_DATA_SIZE; var buf: [buf_size]u8 = undefined; const res: usize = try packet.serialize(&buf); const has_second_address = packet.header.header == HeaderType.type2; const expected_res: usize = getPacketSize(packet.data.len, has_second_address); try expect(res == expected_res); var offset: usize = 0; try expect(memeql(u8, buf[offset .. offset + @sizeOf(PacketHeader)], asBytes(&packet.header))); offset += @sizeOf(PacketHeader); try expect(memeql(u8, buf[offset .. offset + packet.address1.len], asBytes(&packet.address1))); offset += packet.address1.len; if (has_second_address) { try expect(memeql(u8, buf[offset .. offset + packet.address2.len], asBytes(&packet.address2))); offset += packet.address2.len; } try expect(memeql(u8, buf[offset .. offset + @sizeOf(PacketContext)], asBytes(&packet.context))); offset += @sizeOf(PacketContext); try expect(memeql(u8, buf[offset .. offset + packet.data.len], packet.data)); offset += packet.data.len; } fn headersEql(h1: *const PacketHeader, h2: *const PacketHeader) bool { return h1.ifac == h2.ifac and h1.header == h2.header and h1.context == h2.context and h1.propagation == h2.propagation and h1.destination == h2.destination and h1.packet == h2.packet and h1.hops == h2.hops; } fn packetsEql(p1: *const Self, p2: *const Self) bool { return headersEql(&p1.header, &p2.header) and memeql(u8, &p1.address1, &p2.address1) and memeql(u8, &p1.address2, &p2.address2) and p1.context == p2.context and p1.data.len == p2.data.len and memeql(u8, p1.data, p2.data); } //////// Tests // test "Structs size" { try expect(@sizeOf(PacketHeader) == 2); } test "Basic serialization: header type 1, max data size" { const header: PacketHeader = .{ .ifac = IfacFlag.open, .header = HeaderType.type1, .context = ContextFlag.set, .propagation = PropagationType.transport, .destination = DestinationType.single, .packet = PacketType.announce, .hops = 0, }; const data_size = MAX_DATA_SIZE; var data: [data_size]u8 = undefined; const packet: Self = .{ .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; try packet.testPacketSerialization(); } test "Basic serialization: header type 2, max data size" { const header: PacketHeader = .{ .ifac = IfacFlag.open, .header = HeaderType.type2, .context = ContextFlag.set, .propagation = PropagationType.transport, .destination = DestinationType.single, .packet = PacketType.announce, .hops = 0, }; const data_size = MAX_DATA_SIZE; var data: [data_size]u8 = undefined; const packet: Self = .{ .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; try packet.testPacketSerialization(); } test "Basic serialization: header type 1, medium data size" { const header: PacketHeader = .{ .ifac = IfacFlag.open, .header = HeaderType.type1, .context = ContextFlag.set, .propagation = PropagationType.transport, .destination = DestinationType.single, .packet = PacketType.announce, .hops = 0, }; const data_size = MAX_DATA_SIZE / 2 + 3; var data: [data_size]u8 = undefined; const packet: Self = .{ .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; try packet.testPacketSerialization(); } test "Basic serialization: header type 2, medium data size" { const header: PacketHeader = .{ .ifac = IfacFlag.open, .header = HeaderType.type2, .context = ContextFlag.set, .propagation = PropagationType.transport, .destination = DestinationType.single, .packet = PacketType.announce, .hops = 0, }; const data_size = MAX_DATA_SIZE / 2 + 3; var data: [data_size]u8 = undefined; const packet: Self = .{ .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; try packet.testPacketSerialization(); } test "Serialize / Deserialize Packet: Header type2, Medium data size" { const header: PacketHeader = .{ .ifac = IfacFlag.open, .header = HeaderType.type2, .context = ContextFlag.set, .propagation = PropagationType.transport, .destination = DestinationType.single, .packet = PacketType.announce, .hops = 0, }; const data_size = MAX_DATA_SIZE / 2 + 3; var data: [data_size]u8 = undefined; const packet: Self = .{ .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; const buf_size = comptime @sizeOf(PacketHeader) + 2*ADDRESS_SIZE + @sizeOf(PacketContext) + data_size; var buf: [buf_size]u8 = undefined; var res_packet: Self = undefined; var res_data: [data_size]u8 = undefined; res_packet.data = &res_data; _ = try packet.serialize(&buf); try res_packet.deserialize(&buf); try expect(packetsEql(&res_packet, &packet)); }