///////////////// Imports // const std = @import("std"); ///////////////// Constants // const expect = std.testing.expect; const memeql = std.mem.eql; const asBytes = std.mem.asBytes; const MAX_DATA_SIZE = 465; const ADDRESS_SIZE = 16; ///////////////// Enums // const IfacFlag = enum(u1) { open = 0, // Packet for publically accessible interface authenticated = 1, // Interface authentication is included in packet }; const HeaderType = enum(u1) { type1 = 0, // One address field type2 = 1, // Two address fields }; const ContextFlag = enum(u1) { // Meaning depends on packet context unset = 0, set = 1, }; const PropagationType = enum(u1) { broadcast = 0, transport = 1 }; const DestinationType = enum(u2) { // single = 0b0, group = 0b01, plain = 0b10, link = 0b11, }; const PacketType = enum(u2) { // data = 0b0, announce = 0b01, link_request = 0b10, proof = 0b11, }; 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 // const PacketHeader = packed struct { ifac: IfacFlag, header: HeaderType, context: ContextFlag, propagation: PropagationType, destination: DestinationType, packet: PacketType, hops: u8, // }; const Packet = struct { header: PacketHeader, address1: [ADDRESS_SIZE]u8, address2: [ADDRESS_SIZE]u8, context: PacketContext, data: []const u8, // }; ///////////////// Functions // pub fn serializePacket(packet: Packet, output_buffer: []u8) !usize { const has_two_addresses = packet.header.header == HeaderType.type2; // Compute size of final packet (in bytes) var target_size: u16 = @sizeOf(Packet); if (!has_two_addresses) { target_size -= packet.address2.len; } // TODO check data len 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; } fn copyToPacket(dst: anytype, src: []const u8, offset: usize, count: usize) !usize { if (src.len > offset + count) { return error.BufferTooShort; } @memcpy(@as(*u8, &dst), src[offset .. offset + count]); return offset + count; } // Reads the message buffer and builds the corresponding packet struct inside dest_packet pub fn deserializePacket(message_buffer: []u8, dest_packet: *Packet) !void { var offset: usize = 0; offset = try copyToPacket(&dest_packet.header, message_buffer, offset, @sizeOf(PacketHeader)); offset = try copyToPacket(&dest_packet.address1, message_buffer, offset, ADDRESS_SIZE); const has_two_addresses = dest_packet.header.header == HeaderType.type2; if (has_two_addresses) { offset = try copyToPacket(&dest_packet.address2, message_buffer, offset, ADDRESS_SIZE); } offset = try copyToPacket(&dest_packet.context, message_buffer, offset, @sizeOf(PacketContext)); // TODO compute data length const data_len = 0; offset = try copyToPacket(&dest_packet.data, message_buffer, offset, data_len); } ///////////////// Tests // // Serializes the given packet and makes sure the output is correct fn testPacketSerialization(packet: Packet) !void { const buf_size = comptime @sizeOf(Packet) + MAX_DATA_SIZE; var buf: [buf_size]u8 = undefined; const res: usize = try serializePacket(packet, &buf); var expected_res: usize = @sizeOf(PacketHeader) + packet.address1.len + @sizeOf(PacketContext) + packet.data.len; const has_second_address = packet.header.header == HeaderType.type2; if (has_second_address) { expected_res += packet.address2.len; } 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; 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; } 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; const data: [data_size]u8 = undefined; const packet: Packet = .{ // .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; try testPacketSerialization(packet); } 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; const data: [data_size]u8 = undefined; const packet: Packet = .{ // .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; try testPacketSerialization(packet); } 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; const data: [data_size]u8 = undefined; const packet: Packet = .{ // .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; try testPacketSerialization(packet); } 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; const data: [data_size]u8 = undefined; const packet: Packet = .{ // .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; try testPacketSerialization(packet); } 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; const data: [data_size]u8 = undefined; const packet: Packet = .{ // .header = header, .address1 = undefined, .address2 = undefined, .context = PacketContext.none, .data = &data, }; const buf_size = comptime @sizeOf(Packet) + MAX_DATA_SIZE; var buf: [buf_size]u8 = undefined; var res_packet: Packet = undefined; _ = try serializePacket(packet, &buf); try deserializePacket(&buf, &res_packet); try expect(res_packet == packet); }