Initial library structure, build system and packet implementation

This commit is contained in:
Gu://em_ 2026-06-09 19:29:01 +02:00
parent f02cd37a40
commit dad6a6819c
5 changed files with 381 additions and 0 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
.zig-cache/
zig-out/
/release/
/debug/
/build/
/build-*/
/docgen_tmp/

36
build.zig Normal file
View file

@ -0,0 +1,36 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Create library
const reticulumzero = b.addLibrary(.{
.name = "reticulum-zero",
.linkage = .static,
.root_module = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
}),
});
// Output library object
b.installArtifact(reticulumzero);
// Expose library as a module
try b.modules.put(b.dupe("reticulum-zero"), reticulumzero.root_module);
// Set up unit testing
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunStep(unit_tests);
// `zig build test` command
const test_step = b.step("test", "Run library unit tests");
test_step.dependency(&run_unit_tests.step);
}

13
build.zig.zon Normal file
View file

@ -0,0 +1,13 @@
.{
.name = "reticulum-zero",
.version = "0.0.0",
.minimum_zig_version = "0.16.0",
.dependencies = .{},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"LICENSE",
"README.md",
},
}

324
src/packet.zig Normal file
View file

@ -0,0 +1,324 @@
///////////////// 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);
}

1
src/root.zig Normal file
View file

@ -0,0 +1 @@
const packet = @import("packet.zig");