380 lines
11 KiB
Zig
380 lines
11 KiB
Zig
///////////////// Imports
|
|
//
|
|
|
|
const CryptoEngine = @import("crypto.zig").Engine;
|
|
const Destination = @import("destination.zig");
|
|
|
|
|
|
///////////////// 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 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: Destination.Type,
|
|
packet: PacketType,
|
|
hops: u8,
|
|
};
|
|
|
|
|
|
///////////////// Structs
|
|
//
|
|
|
|
const Packet = struct {
|
|
|
|
const Self = @This();
|
|
|
|
///////////////// Fields
|
|
//
|
|
|
|
header: PacketHeader,
|
|
address1: [ADDRESS_SIZE]u8,
|
|
address2: [ADDRESS_SIZE]u8,
|
|
context: PacketContext,
|
|
data: []u8,
|
|
|
|
|
|
///////////////// 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);
|
|
}
|
|
|
|
};
|
|
|
|
// pub fn Packet(comptime crypto_engine: CryptoEngine) type {
|
|
// return struct {};
|
|
// }
|
|
|
|
|
|
///////////////// 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;
|
|
}
|
|
|
|
///////////////// 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 Packet) !void {
|
|
const buf_size = comptime @sizeOf(Packet) + 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 Packet, p2: *const Packet) 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 = Destination.Type.single,
|
|
.packet = PacketType.announce,
|
|
.hops = 0,
|
|
};
|
|
|
|
const data_size = MAX_DATA_SIZE;
|
|
var 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 = Destination.Type.single,
|
|
.packet = PacketType.announce,
|
|
.hops = 0,
|
|
};
|
|
|
|
const data_size = MAX_DATA_SIZE;
|
|
var 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 = Destination.Type.single,
|
|
.packet = PacketType.announce,
|
|
.hops = 0,
|
|
};
|
|
|
|
const data_size = MAX_DATA_SIZE / 2 + 3;
|
|
var 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 = Destination.Type.single,
|
|
.packet = PacketType.announce,
|
|
.hops = 0,
|
|
};
|
|
|
|
const data_size = MAX_DATA_SIZE / 2 + 3;
|
|
var 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 = Destination.Type.single,
|
|
.packet = PacketType.announce,
|
|
.hops = 0,
|
|
};
|
|
|
|
const data_size = MAX_DATA_SIZE / 2 + 3;
|
|
var data: [data_size]u8 = undefined;
|
|
|
|
const packet: Packet = .{
|
|
.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: Packet = 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));
|
|
}
|