mirror of
https://github.com/ion232/reticulum-zig.git
synced 2025-12-22 06:37:05 +00:00
feat: add snapshot based simulation test
This commit is contained in:
20
README.md
20
README.md
@@ -2,15 +2,6 @@
|
||||
|
||||
An implementation of [Reticulum](https://github.com/markqvist/Reticulum) in [Zig](https://ziglang.org/) targeting operating systems and embedded devices.
|
||||
|
||||
# Roadmap
|
||||
|
||||
- Implement core transport.
|
||||
- Test core transport.
|
||||
- Implement a transport node pico build.
|
||||
- Test transport node pico build.
|
||||
- Implement app.
|
||||
- Test app.
|
||||
|
||||
# Structure
|
||||
|
||||
## App
|
||||
@@ -26,14 +17,13 @@ An implementation of [Reticulum](https://github.com/markqvist/Reticulum) in [Zig
|
||||
- The crypto implementation currently leverages std.crypto from the zig std library.
|
||||
- Eventually I will provide an option to use OpenSSL.
|
||||
|
||||
## Hardware
|
||||
## Boards
|
||||
|
||||
- `hardware/` stores image setups for embedded devices that users can build with one command.
|
||||
- `boards/` stores image setups for embedded devices that users can build with one command.
|
||||
- Makes use of [microzig](https://github.com/ZigEmbeddedGroup/microzig) as a submodule for targeting embedded devices.
|
||||
- Currently there is a very simple proof of concept for the pico.
|
||||
- It makes an identity, endpoint and announce packet and sends it over serial.
|
||||
- The image can be built by running `zig build -Doptimize=ReleaseSafe pico` from `hardware`.
|
||||
- Note that the upstream USB CBC code is known to be buggy.
|
||||
- The image can be built by running `zig build -Doptimize=ReleaseSafe pico` from `boards`.
|
||||
|
||||
## Test
|
||||
|
||||
@@ -53,3 +43,7 @@ An implementation of [Reticulum](https://github.com/markqvist/Reticulum) in [Zig
|
||||
# Anti-goals
|
||||
|
||||
- Exact parity to reference implementation in terminology/structure.
|
||||
|
||||
# Licence
|
||||
|
||||
- Currently under Apache 2.0 for now; if I move over to the Reticulum licence at some point, it will only be after significant thought and consideration.
|
||||
|
||||
44
build.zig
44
build.zig
@@ -4,6 +4,7 @@ pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// Core module.
|
||||
const core = .{
|
||||
.name = "reticulum",
|
||||
.module = b.addModule("reticulum-core", .{
|
||||
@@ -13,6 +14,7 @@ pub fn build(b: *std.Build) void {
|
||||
}),
|
||||
};
|
||||
|
||||
// All tests.
|
||||
const test_step = b.step("test", "Run all tests.");
|
||||
|
||||
// Unit tests.
|
||||
@@ -37,33 +39,41 @@ pub fn build(b: *std.Build) void {
|
||||
unit_tests_step.dependOn(&b.addRunArtifact(t_core).step);
|
||||
}
|
||||
|
||||
// Integration tests.
|
||||
// Deterministic simulation tests.
|
||||
{
|
||||
const integration_tests_step = b.step("integration-tests", "Run integration tests.");
|
||||
test_step.dependOn(integration_tests_step);
|
||||
const simulation_tests_step = b.step("simulation-tests", "Run simulation tests.");
|
||||
test_step.dependOn(simulation_tests_step);
|
||||
|
||||
const fixtures = b.createModule(.{
|
||||
.root_source_file = b.path("test/fixtures.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{core},
|
||||
});
|
||||
|
||||
const integration_tests = .{
|
||||
"announce",
|
||||
const ohsnap = blk: {
|
||||
// Root directories map to module names.
|
||||
const module_names: []const []const u8 = &.{
|
||||
"root",
|
||||
};
|
||||
const root_directory: []const []const u8 = &.{
|
||||
"test/simulation",
|
||||
};
|
||||
break :blk b.dependency("ohsnap", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.module_name = module_names,
|
||||
.root_directory = root_directory,
|
||||
});
|
||||
};
|
||||
// const ohsnap = b.dependency("ohsnap", .{});s
|
||||
|
||||
inline for (integration_tests) |name| {
|
||||
const simulation_tests = .{
|
||||
"plain",
|
||||
};
|
||||
|
||||
inline for (simulation_tests) |name| {
|
||||
const t = b.addTest(.{
|
||||
.name = name,
|
||||
.root_source_file = b.path("test/integration/" ++ name ++ ".zig"),
|
||||
.root_source_file = b.path("test/simulation/" ++ name ++ ".zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
t.root_module.addImport("fixtures", fixtures);
|
||||
t.root_module.addImport(core.name, core.module);
|
||||
integration_tests_step.dependOn(&b.addRunArtifact(t).step);
|
||||
t.root_module.addImport("ohsnap", ohsnap.module("ohsnap"));
|
||||
simulation_tests_step.dependOn(&b.addRunArtifact(t).step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
.{
|
||||
.name = "reticulum",
|
||||
.version = "0.1.0",
|
||||
.minimum_zig_version = "0.13.0",
|
||||
.name = .reticulum,
|
||||
.version = "0.2.0",
|
||||
.minimum_zig_version = "0.14.1",
|
||||
.fingerprint = 0x357e04a91bafe743,
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"src/",
|
||||
"core/",
|
||||
},
|
||||
.dependencies = .{
|
||||
// TODO: Use in integration testing.
|
||||
// .ohsnap = .{
|
||||
// .url = "https://github.com/mnemnion/ohsnap/archive/refs/tags/v0.3.1.tar.gz",
|
||||
// .hash = "1220380908ede3cce3dafb2e69b4994a3df55a468597d9d571a495c66ad38ac73ef8",
|
||||
// },
|
||||
// TODO: Move this out into just the tests if possible. Currently zig doesn't have dev dependencies.
|
||||
.ohsnap = .{
|
||||
.url = "git+https://github.com/mnemnion/ohsnap#a140626e388ad3aea2a6a678183f5c0c03887fde",
|
||||
.hash = "ohsnap-0.3.1-iWxzyu6bAADX1OdmK7FFg94X6RDfDvbt0iwQFH8mSFJE",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,44 +1,53 @@
|
||||
const std = @import("std");
|
||||
const BitRate = @import("units.zig").BitRate;
|
||||
const data = @import("data.zig");
|
||||
|
||||
pub const Manager = @import("interface/Manager.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Element = @import("Node.zig").Element;
|
||||
const BitRate = @import("unit.zig").BitRate;
|
||||
const Event = @import("Node.zig").Event;
|
||||
const Endpoint = @import("endpoint.zig").Managed;
|
||||
const Hash = @import("crypto.zig").Hash;
|
||||
const Packet = @import("packet.zig").Packet;
|
||||
const PacketBuilder = @import("packet.zig").Builder;
|
||||
const PacketFactory = @import("packet.zig").Factory;
|
||||
const Payload = @import("packet.zig").Payload;
|
||||
const Name = @import("endpoint/Name.zig");
|
||||
const ThreadSafeFifo = @import("internal/ThreadSafeFifo.zig").ThreadSafeFifo;
|
||||
|
||||
// Probably rework this file.
|
||||
|
||||
pub const Id = usize;
|
||||
pub const Incoming = ThreadSafeFifo(Element.In);
|
||||
pub const Outgoing = ThreadSafeFifo(Element.Out);
|
||||
pub const Mode = enum {
|
||||
full,
|
||||
point_to_point,
|
||||
access_point,
|
||||
roaming,
|
||||
boundary,
|
||||
gateway,
|
||||
};
|
||||
pub const Incoming = ThreadSafeFifo(Event.In);
|
||||
pub const Outgoing = ThreadSafeFifo(Event.Out);
|
||||
pub const Config = struct {
|
||||
name: []const u8 = "unknown",
|
||||
access_code: ?[]const u8 = null,
|
||||
initial_bit_rate: BitRate = .{
|
||||
.bits = .{
|
||||
.count = 1,
|
||||
.prefix = .kilo,
|
||||
},
|
||||
.rate = .per_second,
|
||||
},
|
||||
mode: Mode = .full,
|
||||
initial_bit_rate: BitRate = BitRate.default,
|
||||
max_held_packets: usize = 1000,
|
||||
};
|
||||
|
||||
pub const Error = Incoming.Error || Outgoing.Error || PacketFactory.Error || Allocator.Error;
|
||||
|
||||
const ForCollection = std.fifo.LinearFifo(Packet, .Dynamic);
|
||||
|
||||
const Self = @This();
|
||||
|
||||
// TODO: Account for interfaces that only receive packets and don't transmit.
|
||||
// TODO: Find a less error prone way to define the API.
|
||||
// TODO: Rethink and refactor the event API.
|
||||
|
||||
ally: Allocator,
|
||||
id: Id,
|
||||
incoming: *Incoming,
|
||||
outgoing: *Outgoing,
|
||||
for_collection: ForCollection,
|
||||
packet_factory: PacketFactory,
|
||||
bit_rate: BitRate,
|
||||
bit_rate: ?BitRate,
|
||||
|
||||
pub fn init(
|
||||
ally: Allocator,
|
||||
@@ -47,76 +56,105 @@ pub fn init(
|
||||
incoming: *Incoming,
|
||||
outgoing: *Outgoing,
|
||||
packet_factory: PacketFactory,
|
||||
) !Self {
|
||||
) Self {
|
||||
return Self{
|
||||
.ally = ally,
|
||||
.id = id,
|
||||
.incoming = incoming,
|
||||
.outgoing = outgoing,
|
||||
.for_collection = ForCollection.init(ally),
|
||||
.packet_factory = packet_factory,
|
||||
.bit_rate = config.initial_bit_rate,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deliver_raw(ptr: *anyopaque, bytes: []const u8) !void {
|
||||
const self: *Self = @ptrCast(@alignCast(ptr));
|
||||
const packet = try self.packet_factory.from_bytes(bytes);
|
||||
try deliver(ptr, packet);
|
||||
pub fn announce(ptr: *anyopaque, hash: Hash, app_data: ?data.Bytes) Error!void {
|
||||
try deliverEvent(ptr, Event.In{
|
||||
.announce = .{
|
||||
.hash = hash,
|
||||
.app_data = app_data,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deliver(ptr: *anyopaque, packet: Packet) !void {
|
||||
const self: *Self = @ptrCast(@alignCast(ptr));
|
||||
try self.incoming.push(.{ .packet = packet });
|
||||
pub fn plain(ptr: *anyopaque, name: Name, payload: Payload) Error!void {
|
||||
try deliverEvent(ptr, Event.In{
|
||||
.plain = .{
|
||||
.name = name,
|
||||
.payload = payload,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send(ptr: *anyopaque, packet: Packet) !void {
|
||||
pub fn deliverRawPacket(ptr: *anyopaque, bytes: []const u8) !void {
|
||||
const self: *Self = @ptrCast(@alignCast(ptr));
|
||||
try self.outgoing.push(.{ .packet = packet });
|
||||
const packet = try self.packet_factory.fromBytes(bytes);
|
||||
try deliverPacket(ptr, packet);
|
||||
}
|
||||
|
||||
pub fn collect(ptr: *anyopaque, current_bit_rate: BitRate) ?Packet {
|
||||
pub fn deliverPacket(ptr: *anyopaque, packet: Packet) !void {
|
||||
try deliverEvent(ptr, .{ .packet = packet });
|
||||
}
|
||||
|
||||
pub fn deliverEvent(ptr: *anyopaque, event: Event.In) !void {
|
||||
const self: *Self = @ptrCast(@alignCast(ptr));
|
||||
self.bit_rate = current_bit_rate;
|
||||
return self.for_collection.readItem();
|
||||
try self.incoming.push(event);
|
||||
}
|
||||
|
||||
pub fn collectEvent(ptr: *anyopaque) ?Event.Out {
|
||||
const self: *Self = @ptrCast(@alignCast(ptr));
|
||||
return self.outgoing.pop();
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.incoming.deinit();
|
||||
self.outgoing.deinit();
|
||||
self.ally.destroy(self.incoming);
|
||||
self.ally.destroy(self.outgoing);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn api(self: *Self) Api {
|
||||
return Api{
|
||||
return .{
|
||||
.ptr = self,
|
||||
.deliverRawFn = deliver_raw,
|
||||
.deliverFn = deliver,
|
||||
.sendFn = send,
|
||||
.collectFn = collect,
|
||||
.announceFn = announce,
|
||||
.plainFn = plain,
|
||||
.deliverRawPacketFn = deliverRawPacket,
|
||||
.deliverPacketFn = deliverPacket,
|
||||
.deliverEventFn = deliverEvent,
|
||||
.collectEventFn = collectEvent,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Api = struct {
|
||||
ptr: *anyopaque,
|
||||
deliverRawFn: *const fn (ptr: *anyopaque, raw_bytes: []const u8) Error!void,
|
||||
deliverFn: *const fn (ptr: *anyopaque, packet: Packet) Error!void,
|
||||
sendFn: *const fn (ptr: *anyopaque, packet: Packet) Error!void,
|
||||
collectFn: *const fn (ptr: *anyopaque, current_bit_rate: BitRate) ?Packet,
|
||||
announceFn: *const fn (ptr: *anyopaque, hash: Hash, app_data: ?data.Bytes) Error!void,
|
||||
plainFn: *const fn (ptr: *anyopaque, name: Name, payload: Payload) Error!void,
|
||||
deliverRawPacketFn: *const fn (ptr: *anyopaque, raw_bytes: []const u8) Error!void,
|
||||
deliverPacketFn: *const fn (ptr: *anyopaque, packet: Packet) Error!void,
|
||||
deliverEventFn: *const fn (ptr: *anyopaque, event: Event.In) Error!void,
|
||||
collectEventFn: *const fn (ptr: *anyopaque) ?Event.Out,
|
||||
|
||||
pub fn announce(self: *@This(), endpoint: *const Endpoint, application_data: ?[]const u8) Error!void {
|
||||
const engine: *Self = @ptrCast(@alignCast(self.ptr));
|
||||
const packet = try engine.packet_factory.make_announce(endpoint, application_data);
|
||||
try self.send(packet);
|
||||
pub fn announce(self: *@This(), hash: Hash, app_data: ?data.Bytes) Error!void {
|
||||
return self.announceFn(self.ptr, hash, app_data);
|
||||
}
|
||||
|
||||
pub fn deliver_raw(self: *@This(), raw_bytes: []const u8) Error!void {
|
||||
return self.deliverRawFn(self.ptr, raw_bytes);
|
||||
pub fn plain(self: *@This(), name: Name, payload: Payload) Error!void {
|
||||
return self.plainFn(self.ptr, name, payload);
|
||||
}
|
||||
|
||||
pub fn deliver(self: *@This(), packet: Packet) Error!void {
|
||||
return self.deliverFn(self.ptr, packet);
|
||||
pub fn deliverRawPacket(self: *@This(), raw_bytes: []const u8) Error!void {
|
||||
return self.deliverRawPacketFn(self.ptr, raw_bytes);
|
||||
}
|
||||
|
||||
pub fn send(self: *@This(), packet: Packet) Error!void {
|
||||
return self.sendFn(self.ptr, packet);
|
||||
pub fn deliverPacket(self: *@This(), packet: Packet) Error!void {
|
||||
return self.deliverPacketFn(self.ptr, packet);
|
||||
}
|
||||
|
||||
pub fn collect(self: *@This(), current_bit_rate: BitRate) ?Packet {
|
||||
return self.collectFn(self.ptr, current_bit_rate);
|
||||
pub fn deliverEvent(self: *@This(), event: Event.In) Error!void {
|
||||
return self.deliverEventFn(self.ptr, event);
|
||||
}
|
||||
|
||||
pub fn collectEvent(self: *@This()) ?Event.Out {
|
||||
return self.collectEventFn(self.ptr);
|
||||
}
|
||||
};
|
||||
|
||||
426
core/Node.zig
426
core/Node.zig
@@ -1,57 +1,294 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Element = @import("node/Element.zig");
|
||||
pub const Event = @import("node/Event.zig");
|
||||
pub const Options = @import("node/Options.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const BitRate = @import("units.zig").BitRate;
|
||||
const BitRate = @import("unit.zig").BitRate;
|
||||
const Endpoint = @import("endpoint.zig").Managed;
|
||||
const EndpointStore = @import("endpoint/Store.zig");
|
||||
const EndpointBuilder = @import("endpoint.zig").Builder;
|
||||
const Endpoints = @import("endpoint/Store.zig");
|
||||
const Hash = @import("crypto.zig").Hash;
|
||||
const Interface = @import("Interface.zig");
|
||||
const Interfaces = @import("interface/Manager.zig");
|
||||
const Identity = @import("crypto.zig").Identity;
|
||||
const Packet = @import("packet.zig").Packet;
|
||||
const PacketFactory = @import("packet.zig").Factory;
|
||||
const Routes = @import("Routes.zig");
|
||||
const Name = @import("endpoint/Name.zig");
|
||||
const ThreadSafeFifo = @import("internal/ThreadSafeFifo.zig").ThreadSafeFifo;
|
||||
const System = @import("System.zig");
|
||||
|
||||
pub const Error = error{
|
||||
InterfaceNotFound,
|
||||
TooManyInterfaces,
|
||||
TooManyIncoming,
|
||||
} || Allocator.Error;
|
||||
|
||||
const Route = struct {
|
||||
timestamp: u64,
|
||||
interface_id: Interface.Id,
|
||||
next_hop: Hash.Short,
|
||||
hops: u8,
|
||||
// More fields.
|
||||
};
|
||||
pub const Error = Interfaces.Error || Identity.Error || EndpointBuilder.Error || Name.Error || Allocator.Error;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
ally: Allocator,
|
||||
mutex: std.Thread.Mutex,
|
||||
system: System,
|
||||
options: Options,
|
||||
mutex: std.Thread.Mutex,
|
||||
endpoints: EndpointStore,
|
||||
interfaces: std.AutoHashMap(Interface.Id, *Interface),
|
||||
routes: std.StringHashMap(Route),
|
||||
current_interface_id: Interface.Id,
|
||||
endpoints: Endpoints,
|
||||
interfaces: Interfaces,
|
||||
routes: Routes,
|
||||
|
||||
pub fn init(ally: Allocator, system: *System, identity: ?Identity, options: Options) Error!Self {
|
||||
var endpoint_builder = EndpointBuilder.init(ally);
|
||||
const main_endpoint = try endpoint_builder
|
||||
.setIdentity(identity orelse try Identity.random(&system.rng))
|
||||
.setDirection(.in)
|
||||
.setMethod(.single)
|
||||
.setName(try Name.init(options.name, &.{}, ally))
|
||||
.build();
|
||||
const endpoints = try Endpoints.init(ally, main_endpoint);
|
||||
const interfaces = Interfaces.init(ally, system.*);
|
||||
const routes = Routes.init(ally);
|
||||
|
||||
pub fn init(ally: Allocator, system: System, options: Options) Allocator.Error!Self {
|
||||
return .{
|
||||
.ally = ally,
|
||||
.system = system,
|
||||
.options = options,
|
||||
.mutex = .{},
|
||||
.endpoints = EndpointStore.init(ally),
|
||||
.interfaces = std.AutoHashMap(Interface.Id, *Interface).init(ally),
|
||||
.routes = std.StringHashMap(Route).init(ally),
|
||||
.current_interface_id = 0,
|
||||
.system = system.*,
|
||||
.options = options,
|
||||
.endpoints = endpoints,
|
||||
.interfaces = interfaces,
|
||||
.routes = routes,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addInterface(self: *Self, config: Interface.Config) Error!Interface.Api {
|
||||
self.mutex.lock();
|
||||
|
||||
defer {
|
||||
self.mutex.unlock();
|
||||
}
|
||||
|
||||
return try self.interfaces.add(config);
|
||||
}
|
||||
|
||||
pub fn removeInterface(self: *Self, id: Interface.Id) void {
|
||||
self.mutex.lock();
|
||||
|
||||
defer {
|
||||
self.mutex.unlock();
|
||||
}
|
||||
|
||||
self.interfaces.remove(id);
|
||||
}
|
||||
|
||||
pub fn process(self: *Self) !void {
|
||||
self.mutex.lock();
|
||||
|
||||
defer {
|
||||
self.mutex.unlock();
|
||||
}
|
||||
|
||||
const now = self.system.clock.monotonicMicros();
|
||||
var interfaces = self.interfaces.iterator();
|
||||
|
||||
while (interfaces.next()) |entry| {
|
||||
try self.processEventsIn(now, entry.interface);
|
||||
}
|
||||
|
||||
interfaces = self.interfaces.iterator();
|
||||
|
||||
while (interfaces.next()) |entry| {
|
||||
try self.processEventsOut(now, entry.interface, &entry.pending_out);
|
||||
}
|
||||
}
|
||||
|
||||
fn processEventsIn(self: *Self, now: u64, interface: *Interface) !void {
|
||||
while (interface.incoming.pop()) |event_in| {
|
||||
var event = event_in;
|
||||
|
||||
defer {
|
||||
event.deinit();
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
.announce => |announce| {
|
||||
if (self.endpoints.getPtr(announce.hash)) |endpoint| {
|
||||
const app_data: ?[]const u8 = blk: {
|
||||
if (announce.app_data) |app_data| {
|
||||
break :blk app_data.items;
|
||||
} else {
|
||||
break :blk null;
|
||||
}
|
||||
};
|
||||
|
||||
const packet = try interface.packet_factory.makeAnnounce(endpoint, app_data);
|
||||
try self.interfaces.propagate(packet, null);
|
||||
}
|
||||
},
|
||||
.packet => |*packet| {
|
||||
const header = packet.header;
|
||||
|
||||
defer {
|
||||
packet.deinit();
|
||||
}
|
||||
|
||||
if (shouldDrop(packet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try packet.validate();
|
||||
|
||||
if (shouldRemember(packet)) {
|
||||
// Add packet hash to set.
|
||||
}
|
||||
|
||||
packet.header.hops += 1;
|
||||
|
||||
try self.processTransport(now, packet);
|
||||
|
||||
try switch (header.purpose) {
|
||||
.announce => self.processAnnounce(now, interface, packet),
|
||||
.data => self.processData(now, packet),
|
||||
.link_request => self.processLinkRequest(now, packet),
|
||||
.proof => self.processProof(now, packet),
|
||||
};
|
||||
},
|
||||
.plain => |plain| {
|
||||
const packet = try interface.packet_factory.makePlain(plain.name, plain.payload);
|
||||
try self.interfaces.propagate(packet, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn processEventsOut(self: *Self, now: u64, interface: *Interface, pending_out: *Interface.Outgoing) !void {
|
||||
while (pending_out.pop()) |event_out| {
|
||||
var event = event_out;
|
||||
var event_sent = false;
|
||||
|
||||
defer {
|
||||
if (!event_sent) {
|
||||
event.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
.packet => |*packet| {
|
||||
const endpoint = packet.endpoints.endpoint();
|
||||
_ = endpoint;
|
||||
|
||||
const purpose = packet.header.purpose;
|
||||
const method = packet.header.method;
|
||||
const hops = packet.header.hops;
|
||||
|
||||
if (purpose == .announce) {
|
||||
try interface.outgoing.push(event);
|
||||
event_sent = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method == .plain and hops == 0) {
|
||||
try interface.outgoing.push(event);
|
||||
event_sent = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Put into transport if we know where it's going.
|
||||
// Otherwise broadcast.
|
||||
// Store the packet hash.
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
_ = self;
|
||||
_ = now;
|
||||
}
|
||||
|
||||
fn processTransport(self: *Self, now: u64, packet: *Packet) !void {
|
||||
_ = now;
|
||||
|
||||
if (!self.options.transport_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.endpoints == .transport and packet.header.purpose != .announce) {
|
||||
const next_hop = packet.endpoints.nextHop();
|
||||
const our_identity = self.endpoints.main.identity orelse return Error.MissingIdentity;
|
||||
const our_hash = our_identity.hash.short();
|
||||
|
||||
if (std.mem.eql(u8, &next_hop, our_hash)) {
|
||||
const endpoint = packet.endpoints.endpoint();
|
||||
|
||||
if (try self.routes.hops(endpoint)) |hops| {
|
||||
if (hops == 1) {
|
||||
packet.endpoints = .{
|
||||
.normal = .{
|
||||
.endpoint = endpoint,
|
||||
},
|
||||
};
|
||||
packet.header.format = .normal;
|
||||
packet.header.propagation = .broadcast;
|
||||
} else if (hops > 1) {
|
||||
packet.endpoints.transport.transport_id = next_hop;
|
||||
}
|
||||
|
||||
if (packet.header.purpose == .link_request) {
|
||||
// Link request stuff.
|
||||
} else {
|
||||
// Add to reverse table [if_in, if_out, timestamp].
|
||||
}
|
||||
|
||||
// Transmit.
|
||||
// Update endpoint timestamp in table.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Link transport.
|
||||
if (packet.header.purpose != .announce and packet.header.purpose != .link_request and packet.context != .link_request_proof) {
|
||||
// If packet endpoint not in link table, return.
|
||||
// Otherwise do link table stuff and transmit.
|
||||
}
|
||||
}
|
||||
|
||||
fn processAnnounce(self: *Self, now: u64, interface: *Interface, packet: *Packet) !void {
|
||||
try self.routes.update_from(packet, interface, now);
|
||||
|
||||
if (!self.options.transport_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Propagating for now - will add the more sophisticated announce logic later.
|
||||
var announce = try packet.clone();
|
||||
defer {
|
||||
announce.deinit();
|
||||
}
|
||||
try announce.setTransport(self.endpoints.main.hash.short());
|
||||
|
||||
try self.interfaces.propagate(announce, interface.id);
|
||||
}
|
||||
|
||||
fn processData(self: *Self, now: u64, packet: *Packet) !void {
|
||||
_ = self;
|
||||
_ = now;
|
||||
_ = packet;
|
||||
}
|
||||
|
||||
fn processLinkRequest(self: *Self, now: u64, packet: *Packet) !void {
|
||||
_ = self;
|
||||
_ = now;
|
||||
_ = packet;
|
||||
}
|
||||
|
||||
fn processProof(self: *Self, now: u64, packet: *Packet) !void {
|
||||
_ = self;
|
||||
_ = now;
|
||||
_ = packet;
|
||||
}
|
||||
|
||||
fn shouldDrop(packet: *const Packet) bool {
|
||||
_ = packet;
|
||||
return false;
|
||||
}
|
||||
|
||||
fn shouldRemember(packet: *const Packet) bool {
|
||||
_ = packet;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.mutex.lock();
|
||||
|
||||
@@ -62,138 +299,5 @@ pub fn deinit(self: *Self) void {
|
||||
|
||||
self.endpoints.deinit();
|
||||
self.interfaces.deinit();
|
||||
self.incoming.deinit(self.ally);
|
||||
self.outgoing.deinit(self.ally);
|
||||
self.routes.deinit();
|
||||
}
|
||||
|
||||
pub fn addInterface(self: *Self, config: Interface.Config) Error!Interface.Api {
|
||||
self.mutex.lock();
|
||||
|
||||
defer {
|
||||
self.mutex.unlock();
|
||||
}
|
||||
|
||||
if (self.interfaces.count() > self.options.max_interfaces) {
|
||||
return Error.TooManyInterfaces;
|
||||
}
|
||||
|
||||
const id = self.current_interface_id;
|
||||
self.current_interface_id += 1;
|
||||
|
||||
const incoming = try self.ally.create(Interface.Incoming);
|
||||
const outgoing = try self.ally.create(Interface.Outgoing);
|
||||
incoming.* = try Interface.Incoming.init(self.ally);
|
||||
outgoing.* = try Interface.Outgoing.init(self.ally);
|
||||
const packet_factory = PacketFactory.init(self.ally, self.system.clock, self.system.rng, config);
|
||||
|
||||
const engine = try self.ally.create(Interface);
|
||||
engine.* = try Interface.init(self.ally, config, id, incoming, outgoing, packet_factory);
|
||||
try self.interfaces.put(id, engine);
|
||||
|
||||
return engine.api();
|
||||
}
|
||||
|
||||
pub fn removeInterface(self: *Self, id: Interface.Id) Error!void {
|
||||
self.mutex.lock();
|
||||
|
||||
defer {
|
||||
self.mutex.unlock();
|
||||
}
|
||||
|
||||
if (self.interfaces.get(id)) |engine| {
|
||||
engine.deinit(self.ally);
|
||||
self.ally.destroy(engine);
|
||||
return;
|
||||
}
|
||||
|
||||
return Error.InterfaceNotFound;
|
||||
}
|
||||
|
||||
pub fn process(self: *Self) !void {
|
||||
self.mutex.lock();
|
||||
|
||||
defer {
|
||||
self.mutex.unlock();
|
||||
}
|
||||
|
||||
const now = self.system.clock.monotonicMicros();
|
||||
try self.process_incoming(now);
|
||||
try self.process_outgoing(now);
|
||||
}
|
||||
|
||||
fn process_incoming(self: *Self, now: u64) !void {
|
||||
var iterator = self.interfaces.iterator();
|
||||
|
||||
while (iterator.next()) |entry| {
|
||||
var incoming = entry.value_ptr.*.incoming;
|
||||
while (incoming.pop()) |element| {
|
||||
var packet = element.packet;
|
||||
var header = packet.header;
|
||||
// defer {
|
||||
// self.ally.free(element.packet.deinit());
|
||||
// }
|
||||
|
||||
if (self.shouldDrop(&packet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
header.hops += 1;
|
||||
|
||||
const is_valid = try packet.validate();
|
||||
if (!is_valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.purpose == .announce) {
|
||||
const endpoint_hash = packet.endpoints.endpoint();
|
||||
const next_hop = packet.endpoints.next_hop();
|
||||
|
||||
try self.routes.put(&endpoint_hash, Route{
|
||||
.timestamp = now,
|
||||
.interface_id = entry.key_ptr.*,
|
||||
.next_hop = next_hop,
|
||||
.hops = header.hops,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_outgoing(self: *Self, now: u64) !void {
|
||||
var iterator = self.interfaces.iterator();
|
||||
_ = now;
|
||||
|
||||
while (iterator.next()) |entry| {
|
||||
var outgoing = entry.value_ptr.*.outgoing;
|
||||
while (outgoing.pop()) |element| {
|
||||
var packet = element.packet;
|
||||
const endpoint = packet.endpoints.endpoint();
|
||||
|
||||
if (packet.header.purpose == .announce) {
|
||||
var interfaces = self.interfaces.valueIterator();
|
||||
while (interfaces.next()) |interface| {
|
||||
if (entry.value_ptr != interface) {
|
||||
interface.outgoing.push(.{ .packet = packet });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const route = self.routes.get(&endpoint) orelse return;
|
||||
if (route.hops == 1) {
|
||||
if (self.interfaces.get(entry.key_ptr.*)) |engine| {
|
||||
try engine.outgoing.push(.{ .packet = packet });
|
||||
}
|
||||
} else {
|
||||
// Modify the packet for transport.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn shouldDrop(self: *Self, packet: *const Packet) bool {
|
||||
_ = self;
|
||||
_ = packet;
|
||||
return false;
|
||||
}
|
||||
|
||||
51
core/Routes.zig
Normal file
51
core/Routes.zig
Normal file
@@ -0,0 +1,51 @@
|
||||
const std = @import("std");
|
||||
const crypto = @import("crypto.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Hash = crypto.Hash;
|
||||
const Interface = @import("Interface.zig");
|
||||
const Packet = @import("packet.zig").Managed;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const Entry = struct {
|
||||
timestamp: u64,
|
||||
interface_id: Interface.Id,
|
||||
next_hop: Hash.Short,
|
||||
hops: u8,
|
||||
};
|
||||
|
||||
ally: Allocator,
|
||||
entries: std.StringHashMap(Entry),
|
||||
|
||||
pub fn init(ally: Allocator) Self {
|
||||
return Self{
|
||||
.ally = ally,
|
||||
.entries = std.StringHashMap(Entry).init(ally),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hops(self: *Self, endpoint: Hash.Short) !?u8 {
|
||||
if (self.entries.get(&endpoint)) |entry| {
|
||||
return entry.hops;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn update_from(self: *Self, packet: *const Packet, interface: *const Interface, now: u64) !void {
|
||||
const endpoint = packet.endpoints.endpoint();
|
||||
const next_hop = packet.endpoints.nextHop();
|
||||
|
||||
try self.entries.put(&endpoint, Entry{
|
||||
.timestamp = now,
|
||||
.interface_id = interface.id,
|
||||
.next_hop = next_hop,
|
||||
.hops = packet.header.hops,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.entries.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
const std = @import("std");
|
||||
const Aes128 = std.crypto.core.aes.Aes128;
|
||||
const Pkcs7 = @import("pkcs7.zig").Pkcs7;
|
||||
const Aes = std.crypto.core.aes.Aes256;
|
||||
const Pkcs7 = @import("pkcs7.zig").Impl;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const block_length = Aes128.block.block_length;
|
||||
pub const key_length = Aes.key_bits / 8;
|
||||
pub const block_length = Aes.block.block_length;
|
||||
|
||||
pub fn encrypt(dst: []u8, src: []const u8, key: [block_length]u8, iv: [block_length]u8) []u8 {
|
||||
const context = Aes128.initEnc(key);
|
||||
pub fn encrypt(dst: []u8, src: []const u8, key: [key_length]u8, iv: [block_length]u8) []u8 {
|
||||
const context = Aes.initEnc(key);
|
||||
var previous: *const [block_length]u8 = &iv;
|
||||
var i: usize = 0;
|
||||
|
||||
while (i + block_length <= src.len) : (i += block_length) {
|
||||
var current = Aes128.block.fromBytes(src[i .. i + block_length][0..block_length]);
|
||||
var current = Aes.block.fromBytes(src[i .. i + block_length][0..block_length]);
|
||||
const xored_block = current.xorBytes(previous);
|
||||
var out = dst[i .. i + block_length];
|
||||
context.encrypt(out[0..block_length], &xored_block);
|
||||
@@ -24,13 +25,13 @@ pub fn encrypt(dst: []u8, src: []const u8, key: [block_length]u8, iv: [block_len
|
||||
@memcpy(dst[i..src.len], src[i..src.len]);
|
||||
}
|
||||
const ciphertext = Pkcs7(block_length).pad(dst[0..src.len], dst);
|
||||
const xored_block = Aes128.block.fromBytes(last_block).xorBytes(previous);
|
||||
const xored_block = Aes.block.fromBytes(last_block).xorBytes(previous);
|
||||
context.encrypt(last_block, &xored_block);
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
pub fn decrypt(dst: []u8, src: []const u8, key: [block_length]u8, iv: [block_length]u8) ![]const u8 {
|
||||
const context = Aes128.initDec(key);
|
||||
pub fn decrypt(dst: []u8, src: []const u8, key: [key_length]u8, iv: [block_length]u8) ![]const u8 {
|
||||
const context = Aes.initDec(key);
|
||||
var previous: *const [block_length]u8 = &iv;
|
||||
var i: usize = 0;
|
||||
|
||||
@@ -40,7 +41,7 @@ pub fn decrypt(dst: []u8, src: []const u8, key: [block_length]u8, iv: [block_len
|
||||
const current = src[i .. i + block_length][0..block_length];
|
||||
const out = dst[i .. i + block_length];
|
||||
context.decrypt(out[0..block_length], current);
|
||||
const plaintext = Aes128.block.fromBytes(out[0..block_length]).xorBytes(previous);
|
||||
const plaintext = Aes.block.fromBytes(out[0..block_length]).xorBytes(previous);
|
||||
@memcpy(dst[i .. i + block_length], &plaintext);
|
||||
previous = current;
|
||||
}
|
||||
@@ -49,7 +50,7 @@ pub fn decrypt(dst: []u8, src: []const u8, key: [block_length]u8, iv: [block_len
|
||||
}
|
||||
|
||||
const t = std.testing;
|
||||
const test_key = [Self.block_length]u8{ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF };
|
||||
const test_key = [Self.key_length]u8{ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF };
|
||||
const test_iv = [Self.block_length]u8{ 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0 };
|
||||
|
||||
test "Encrypt and decrypt" {
|
||||
@@ -84,7 +85,7 @@ test "Encrypt and decrypt empty" {
|
||||
test "Encrypt and decrypt of block length" {
|
||||
const initial_plaintext = "this is 16 chars";
|
||||
var ciphertext_buffer: [2 * Self.block_length]u8 = undefined;
|
||||
t.expect(initial_plaintext.len == Self.block_length);
|
||||
try t.expect(initial_plaintext.len == Self.block_length);
|
||||
|
||||
const ciphertext = Self.encrypt(ciphertext_buffer[0..], initial_plaintext[0..], test_key, test_iv);
|
||||
try t.expect(ciphertext.len % Self.block_length == 0);
|
||||
@@ -100,7 +101,7 @@ test "Encrypt and decrypt of block length" {
|
||||
test "Encrypt and decrypt of two block lengths" {
|
||||
const initial_plaintext = "this is a total of 32 chars wide";
|
||||
var ciphertext_buffer: [3 * Self.block_length]u8 = undefined;
|
||||
t.expect(initial_plaintext.len == 2 * Self.block_length);
|
||||
try t.expect(initial_plaintext.len == 2 * Self.block_length);
|
||||
|
||||
const ciphertext = Self.encrypt(ciphertext_buffer[0..], initial_plaintext[0..], test_key, test_iv);
|
||||
try t.expect(ciphertext.len % Self.block_length == 0);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const std = @import("std");
|
||||
const data = @import("../data.zig");
|
||||
|
||||
const Sha256 = std.crypto.hash.sha2.Sha256;
|
||||
|
||||
const Self = @This();
|
||||
@@ -19,11 +21,12 @@ pub fn from_long(hash: Long) Self {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hash_data(data: []const u8) Self {
|
||||
return hash_items(.{ .data = data });
|
||||
pub fn ofData(bytes: []const u8) Self {
|
||||
return ofItems(.{ .bytes = bytes });
|
||||
}
|
||||
|
||||
pub fn hash_items(items: anytype) Self {
|
||||
// TODO: Refactor out magic values and handle more cases.
|
||||
pub fn ofItems(items: anytype) Self {
|
||||
var hash = Self{ .bytes = undefined };
|
||||
var hasher = Sha256.init(.{});
|
||||
|
||||
@@ -31,17 +34,17 @@ pub fn hash_items(items: anytype) Self {
|
||||
const value = @field(items, field.name);
|
||||
switch (@TypeOf(value)) {
|
||||
[]const u8, []u8 => hasher.update(value),
|
||||
[]const std.ArrayList(u8) => |lists| {
|
||||
for (lists) |l| {
|
||||
hasher.update(l.items);
|
||||
[]const data.Bytes => |bytes_list| {
|
||||
for (bytes_list) |bytes| {
|
||||
hasher.update(bytes.items);
|
||||
}
|
||||
},
|
||||
*const Long => hasher.update(value),
|
||||
*const Short => hasher.update(value),
|
||||
*const Name => hasher.update(value),
|
||||
[long_length]u8 => hasher.update(&value),
|
||||
[name_length]u8 => hasher.update(&value),
|
||||
else => switch (@typeInfo(@TypeOf(value))) {
|
||||
.Int => hasher.update(std.mem.asBytes(&value)),
|
||||
.Array => |_| hasher.update(std.mem.sliceAsBytes(value[0..])),
|
||||
else => @compileError("Unsupported type: " ++ @typeName(@TypeOf(value))),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -79,12 +79,12 @@ pub fn signer(self: *const Self, rng: *Rng) Error!Ed25519.Signer {
|
||||
return Error.MissingSecretKey;
|
||||
}
|
||||
|
||||
pub fn has_secret(self: *Self) bool {
|
||||
pub fn hasSecret(self: *Self) bool {
|
||||
return self.secret != null;
|
||||
}
|
||||
|
||||
fn make_hash(public: Public) Hash {
|
||||
return Hash.hash_items(.{
|
||||
return Hash.ofItems(.{
|
||||
.dh = public.dh,
|
||||
.signature = public.signature.bytes,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn Pkcs7(comptime block_size: u8) type {
|
||||
pub fn Impl(comptime block_size: u8) type {
|
||||
return struct {
|
||||
pub const Error = error{
|
||||
InvalidLength,
|
||||
@@ -46,7 +46,7 @@ pub fn Pkcs7(comptime block_size: u8) type {
|
||||
}
|
||||
|
||||
const t = std.testing;
|
||||
const P = Pkcs7(16);
|
||||
const P = Impl(16);
|
||||
|
||||
test "pad - empty" {
|
||||
const data: [0]u8 = undefined;
|
||||
|
||||
10
core/data.zig
Normal file
10
core/data.zig
Normal file
@@ -0,0 +1,10 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub const Bytes = std.ArrayList(u8);
|
||||
|
||||
pub fn makeBytes(slice: []const u8, ally: Allocator) !Bytes {
|
||||
var bytes = Bytes.init(ally);
|
||||
try bytes.appendSlice(slice);
|
||||
return bytes;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub const Builder = @import("endpoint/Builder.zig");
|
||||
pub const Managed = @import("endpoint/Managed.zig");
|
||||
pub const Name = @import("endpoint/Name.zig");
|
||||
pub const Store = @import("endpoint/Store.zig");
|
||||
|
||||
pub const Direction = enum {
|
||||
|
||||
@@ -1,130 +1,102 @@
|
||||
const std = @import("std");
|
||||
const crypto = @import("../crypto.zig");
|
||||
const data = @import("../data.zig");
|
||||
const endpoint = @import("../endpoint.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Bytes = std.ArrayList(u8);
|
||||
const Identity = crypto.Identity;
|
||||
const Direction = endpoint.Direction;
|
||||
const Method = endpoint.Method;
|
||||
const Name = endpoint.Name;
|
||||
const Hash = crypto.Hash;
|
||||
const Managed = @import("Managed.zig");
|
||||
|
||||
const Self = @This();
|
||||
const Fields = std.bit_set.IntegerBitSet(std.meta.fields(Managed).len - 4);
|
||||
|
||||
pub const Error = error{
|
||||
Incomplete,
|
||||
InvalidName,
|
||||
InvalidAspect,
|
||||
HasIdentity,
|
||||
MissingIdentity,
|
||||
MissingDirection,
|
||||
MissingMethod,
|
||||
MissingName,
|
||||
} || Allocator.Error;
|
||||
|
||||
ally: Allocator,
|
||||
fields: Fields,
|
||||
identity: Identity = undefined,
|
||||
direction: Direction = undefined,
|
||||
method: Method = undefined,
|
||||
application_name: Bytes,
|
||||
aspects: std.ArrayList(Bytes),
|
||||
identity: ?Identity,
|
||||
direction: ?Direction,
|
||||
method: ?Method,
|
||||
name: ?Name,
|
||||
|
||||
pub fn init(ally: Allocator) Self {
|
||||
return Self{
|
||||
.ally = ally,
|
||||
.fields = Fields.initEmpty(),
|
||||
.application_name = Bytes.init(ally),
|
||||
.aspects = std.ArrayList(Bytes).init(ally),
|
||||
.identity = null,
|
||||
.direction = null,
|
||||
.method = null,
|
||||
.name = null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_identity(self: *Self, identity: Identity) *Self {
|
||||
self.fields.set(0);
|
||||
pub fn setIdentity(self: *Self, identity: Identity) *Self {
|
||||
self.identity = identity;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_direction(self: *Self, direction: Direction) *Self {
|
||||
self.fields.set(1);
|
||||
pub fn setDirection(self: *Self, direction: Direction) *Self {
|
||||
self.direction = direction;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_method(self: *Self, method: Method) *Self {
|
||||
self.fields.set(2);
|
||||
pub fn setMethod(self: *Self, method: Method) *Self {
|
||||
self.method = method;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_application_name(self: *Self, application_name: []const u8) !*Self {
|
||||
self.fields.set(3);
|
||||
self.application_name = Bytes.init(self.ally);
|
||||
try self.application_name.appendSlice(application_name);
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn append_aspect(self: *Self, aspect: []const u8) !*Self {
|
||||
var managed_aspect = Bytes.init(self.ally);
|
||||
try managed_aspect.appendSlice(aspect);
|
||||
try self.aspects.append(managed_aspect);
|
||||
pub fn setName(self: *Self, name: Name) *Self {
|
||||
self.name = name;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn build(self: *Self) Error!Managed {
|
||||
errdefer {
|
||||
self.application_name.clearAndFree();
|
||||
const direction = self.direction orelse return Error.MissingDirection;
|
||||
const method = self.method orelse return Error.MissingMethod;
|
||||
const name = self.name orelse return Error.MissingName;
|
||||
|
||||
for (self.aspects.items) |*a| {
|
||||
a.clearAndFree();
|
||||
if (method == .plain and self.identity != null) {
|
||||
return Error.HasIdentity;
|
||||
} else if (method != .plain and self.identity == null) {
|
||||
return Error.MissingIdentity;
|
||||
}
|
||||
|
||||
const hash = blk: {
|
||||
const name_hash = name.hash.name();
|
||||
|
||||
if (self.identity) |identity| {
|
||||
break :blk Hash.ofItems(.{
|
||||
.name_hash = name_hash,
|
||||
.identity_hash = identity.hash.short(),
|
||||
});
|
||||
} else {
|
||||
break :blk Hash.ofItems(.{
|
||||
.name_hash = name_hash,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.aspects.clearAndFree();
|
||||
}
|
||||
|
||||
if (self.fields.count() == self.fields.capacity()) {
|
||||
const name_hash = blk: {
|
||||
// TODO: Do this incrementally without copying.
|
||||
var name_bytes = Bytes.init(self.ally);
|
||||
try name_bytes.appendSlice(self.application_name.items);
|
||||
|
||||
errdefer {
|
||||
name_bytes.deinit();
|
||||
}
|
||||
|
||||
for (self.application_name.items) |c| {
|
||||
if (c == '.') {
|
||||
return Error.InvalidName;
|
||||
}
|
||||
}
|
||||
|
||||
for (self.aspects.items) |a| {
|
||||
for (a.items) |c| {
|
||||
if (c == '.') {
|
||||
return Error.InvalidAspect;
|
||||
}
|
||||
}
|
||||
|
||||
try name_bytes.append('.');
|
||||
try name_bytes.appendSlice(a.items);
|
||||
}
|
||||
|
||||
break :blk Hash.hash_data(name_bytes.items);
|
||||
};
|
||||
|
||||
const hash = Hash.hash_items(.{
|
||||
.name_hash = name_hash.name(),
|
||||
.identity_hash = self.identity.hash.short(),
|
||||
});
|
||||
|
||||
return Managed{
|
||||
.ally = self.ally,
|
||||
.identity = self.identity,
|
||||
.direction = self.direction,
|
||||
.method = self.method,
|
||||
.application_name = self.application_name,
|
||||
.aspects = self.aspects,
|
||||
.hash = hash,
|
||||
.name_hash = name_hash,
|
||||
};
|
||||
}
|
||||
|
||||
return Error.Incomplete;
|
||||
return Managed{
|
||||
.ally = self.ally,
|
||||
.identity = self.identity,
|
||||
.direction = direction,
|
||||
.method = method,
|
||||
.name = name,
|
||||
.hash = hash,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
if (self.name) |name| {
|
||||
name.deinit();
|
||||
}
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
@@ -1,44 +1,36 @@
|
||||
const std = @import("std");
|
||||
const crypto = @import("../crypto.zig");
|
||||
const data = @import("../data.zig");
|
||||
const endpoint = @import("../endpoint.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Bytes = std.ArrayList(u8);
|
||||
const Identity = crypto.Identity;
|
||||
const Direction = endpoint.Direction;
|
||||
const Method = endpoint.Method;
|
||||
const Name = endpoint.Name;
|
||||
const Hash = crypto.Hash;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
ally: Allocator,
|
||||
identity: Identity,
|
||||
identity: ?Identity,
|
||||
direction: Direction,
|
||||
method: Method,
|
||||
application_name: Bytes,
|
||||
aspects: std.ArrayList(Bytes),
|
||||
name: Name,
|
||||
hash: Hash,
|
||||
name_hash: Hash,
|
||||
|
||||
pub fn copy(self: *Self) !Self {
|
||||
var new = Self{
|
||||
pub fn clone(self: *const Self) !Self {
|
||||
return Self{
|
||||
.ally = self.ally,
|
||||
.identity = self.identity,
|
||||
.direction = self.direction,
|
||||
.method = self.method,
|
||||
.application_name = Bytes.init(self.ally),
|
||||
.aspects = std.ArrayList(Bytes).init(self.ally),
|
||||
.name = try self.name.clone(),
|
||||
.hash = self.hash,
|
||||
.name_hash = self.name_hash,
|
||||
};
|
||||
|
||||
try new.application_name.appendSlice(self.application_name.items);
|
||||
|
||||
for (self.aspects.items) |aspect| {
|
||||
var new_aspect = Bytes.init(self.ally);
|
||||
try new_aspect.appendSlice(aspect.items);
|
||||
try new.aspects.append(new_aspect);
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.name.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
102
core/endpoint/Name.zig
Normal file
102
core/endpoint/Name.zig
Normal file
@@ -0,0 +1,102 @@
|
||||
const std = @import("std");
|
||||
const crypto = @import("../crypto.zig");
|
||||
const data = @import("../data.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Identity = crypto.Identity;
|
||||
const Hash = crypto.Hash;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = error{
|
||||
InvalidName,
|
||||
InvalidAspect,
|
||||
};
|
||||
|
||||
pub const AppName = data.Bytes;
|
||||
pub const Aspect = data.Bytes;
|
||||
pub const Aspects = std.ArrayList(Aspect);
|
||||
|
||||
ally: Allocator,
|
||||
app_name: AppName,
|
||||
aspects: Aspects,
|
||||
hash: Hash,
|
||||
|
||||
pub fn init(app_name: []const u8, aspects: []const []const u8, ally: Allocator) !Self {
|
||||
var self = Self{
|
||||
.ally = ally,
|
||||
.app_name = .init(ally),
|
||||
.aspects = .init(ally),
|
||||
.hash = undefined,
|
||||
};
|
||||
|
||||
errdefer {
|
||||
self.app_name.deinit();
|
||||
self.aspects.deinit();
|
||||
}
|
||||
|
||||
for (app_name) |char| {
|
||||
if (char == '.') {
|
||||
return Error.InvalidName;
|
||||
}
|
||||
}
|
||||
|
||||
try self.app_name.appendSlice(app_name);
|
||||
|
||||
for (aspects) |aspect| {
|
||||
for (aspect) |char| {
|
||||
if (char == '.') {
|
||||
return Error.InvalidAspect;
|
||||
}
|
||||
}
|
||||
|
||||
var new_aspect = Aspect.init(self.ally);
|
||||
errdefer {
|
||||
new_aspect.deinit();
|
||||
}
|
||||
|
||||
try new_aspect.appendSlice(aspect);
|
||||
try self.aspects.append(new_aspect);
|
||||
}
|
||||
|
||||
self.hash = blk: {
|
||||
var name = data.Bytes.init(self.ally);
|
||||
defer {
|
||||
name.deinit();
|
||||
}
|
||||
|
||||
try name.appendSlice(app_name);
|
||||
|
||||
for (aspects) |aspect| {
|
||||
try name.append('.');
|
||||
try name.appendSlice(aspect);
|
||||
}
|
||||
|
||||
break :blk Hash.ofData(name.items);
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn clone(self: *Self) !Self {
|
||||
var cloned = self.*;
|
||||
cloned.app_name = try cloned.app_name.clone();
|
||||
cloned.aspects = Aspects.init(self.ally);
|
||||
|
||||
for (self.aspects) |aspect| {
|
||||
cloned.aspects.append(try aspect.clone());
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.app_name.deinit();
|
||||
|
||||
for (self.aspects.items) |aspect| {
|
||||
aspect.deinit();
|
||||
}
|
||||
|
||||
self.aspects.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ const std = @import("std");
|
||||
const crypto = @import("../crypto.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Builder = @import("Builder.zig");
|
||||
const Endpoint = @import("Managed.zig");
|
||||
const Identity = crypto.Identity;
|
||||
const Interface = @import("../Interface.zig");
|
||||
@@ -12,27 +13,6 @@ const Rng = @import("../System.zig").Rng;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
ally: Allocator,
|
||||
entries: std.StringHashMap(Entry),
|
||||
|
||||
pub fn init(ally: Allocator) Self {
|
||||
return .{
|
||||
.ally = ally,
|
||||
.entries = std.StringHashMap(Entry).init(ally),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.entries.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn add(self: *Self, endpoint: Endpoint) Allocator.Error!void {
|
||||
try self.entries.put(endpoint.hash.bytes[0..], Entry{
|
||||
.endpoint = endpoint,
|
||||
});
|
||||
}
|
||||
|
||||
const Entry = struct {
|
||||
// timestamp: i64,
|
||||
// expiry_time: i64,
|
||||
@@ -47,3 +27,45 @@ const Entry = struct {
|
||||
// origin_announce: ...,
|
||||
// application_data: []const u8,
|
||||
};
|
||||
|
||||
ally: Allocator,
|
||||
main: Endpoint,
|
||||
entries: std.StringHashMap(Entry),
|
||||
|
||||
pub fn init(ally: Allocator, main: Endpoint) !Self {
|
||||
var entries = std.StringHashMap(Entry).init(ally);
|
||||
try entries.put(main.hash.bytes[0..], Entry{
|
||||
.endpoint = main,
|
||||
});
|
||||
|
||||
return Self{
|
||||
.ally = ally,
|
||||
.main = main,
|
||||
.entries = entries,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getPtr(self: *Self, hash: Hash) ?*const Endpoint {
|
||||
if (self.entries.get(hash.bytes[0..])) |entry| {
|
||||
return &entry.endpoint;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn add(self: *Self, endpoint: *const Endpoint) !void {
|
||||
try self.entries.put(endpoint.hash.bytes[0..], Entry{
|
||||
.endpoint = try endpoint.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
var entries = self.entries.valueIterator();
|
||||
|
||||
while (entries.next()) |entry| {
|
||||
entry.endpoint.deinit();
|
||||
}
|
||||
|
||||
self.entries.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
133
core/interface/Manager.zig
Normal file
133
core/interface/Manager.zig
Normal file
@@ -0,0 +1,133 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Interface = @import("../Interface.zig");
|
||||
const Packet = @import("../packet/Managed.zig");
|
||||
const PacketFactory = @import("../packet/Factory.zig");
|
||||
const System = @import("../System.zig");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = error{
|
||||
InterfaceNotFound,
|
||||
TooManyInterfaces,
|
||||
} || Allocator.Error;
|
||||
|
||||
const Entry = struct {
|
||||
interface: *Interface,
|
||||
pending_out: Interface.Outgoing,
|
||||
};
|
||||
|
||||
const interface_limit = 256;
|
||||
|
||||
ally: Allocator,
|
||||
system: System,
|
||||
entries: std.AutoHashMap(Interface.Id, Entry),
|
||||
current_interface_id: Interface.Id,
|
||||
|
||||
pub fn init(ally: Allocator, system: System) Self {
|
||||
return Self{
|
||||
.ally = ally,
|
||||
.system = system,
|
||||
.entries = std.AutoHashMap(Interface.Id, Entry).init(ally),
|
||||
.current_interface_id = 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn iterator(self: *Self) std.AutoHashMap(Interface.Id, Entry).ValueIterator {
|
||||
return self.entries.valueIterator();
|
||||
}
|
||||
|
||||
pub fn add(self: *Self, config: Interface.Config) Error!Interface.Api {
|
||||
if (self.entries.count() >= interface_limit) {
|
||||
return Error.TooManyInterfaces;
|
||||
}
|
||||
|
||||
const id = self.current_interface_id;
|
||||
self.current_interface_id += 1;
|
||||
|
||||
const incoming = try self.ally.create(Interface.Incoming);
|
||||
incoming.* = Interface.Incoming.init(self.ally);
|
||||
|
||||
errdefer {
|
||||
self.ally.destroy(incoming);
|
||||
}
|
||||
|
||||
const outgoing = try self.ally.create(Interface.Outgoing);
|
||||
outgoing.* = Interface.Outgoing.init(self.ally);
|
||||
|
||||
errdefer {
|
||||
self.ally.destroy(outgoing);
|
||||
}
|
||||
|
||||
const packet_factory = PacketFactory.init(
|
||||
self.ally,
|
||||
self.system.clock,
|
||||
self.system.rng,
|
||||
config,
|
||||
);
|
||||
|
||||
const interface = try self.ally.create(Interface);
|
||||
|
||||
errdefer {
|
||||
self.ally.destroy(interface);
|
||||
}
|
||||
|
||||
interface.* = Interface.init(
|
||||
self.ally,
|
||||
config,
|
||||
id,
|
||||
incoming,
|
||||
outgoing,
|
||||
packet_factory,
|
||||
);
|
||||
|
||||
try self.entries.put(id, Entry{
|
||||
.interface = interface,
|
||||
.pending_out = Interface.Outgoing.init(self.ally),
|
||||
});
|
||||
|
||||
return interface.api();
|
||||
}
|
||||
|
||||
pub fn remove(self: *Self, id: Interface.Id) void {
|
||||
if (self.entries.get(id)) |entry| {
|
||||
entry.interface.deinit(self.ally);
|
||||
self.ally.destroy(entry.interface);
|
||||
self.entries.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Refactor source_id.
|
||||
pub fn propagate(self: *Self, packet: Packet, source_id: ?Interface.Id) !void {
|
||||
var entries = self.entries.valueIterator();
|
||||
|
||||
while (entries.next()) |entry| {
|
||||
if (source_id) |source| {
|
||||
if (entry.interface.id == source) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try entry.pending_out.push(.{
|
||||
.packet = try packet.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
var entries = self.entries.valueIterator();
|
||||
|
||||
while (entries.next()) |entry| {
|
||||
while (entry.pending_out.pop()) |event| {
|
||||
var e = event;
|
||||
e.deinit();
|
||||
}
|
||||
entry.pending_out.deinit();
|
||||
entry.interface.deinit();
|
||||
self.ally.destroy(entry.interface);
|
||||
}
|
||||
|
||||
self.entries.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
@@ -12,16 +12,16 @@ pub fn ThreadSafeFifo(comptime T: type) type {
|
||||
mutex: std.Thread.Mutex,
|
||||
impl: Impl,
|
||||
|
||||
pub fn init(ally: Allocator) Error!Self {
|
||||
pub fn init(ally: Allocator) Self {
|
||||
return Self{
|
||||
.mutex = .{},
|
||||
.impl = Impl.init(ally),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, ally: Allocator) void {
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.mutex.lock();
|
||||
self.impl.deinit(ally);
|
||||
self.impl.deinit();
|
||||
self.mutex.unlock();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
const interface = @import("../interface.zig");
|
||||
const Packet = @import("../packet.zig").Managed;
|
||||
|
||||
pub const In = struct {
|
||||
packet: Packet,
|
||||
};
|
||||
|
||||
pub const Out = struct {
|
||||
packet: Packet,
|
||||
};
|
||||
169
core/node/Event.zig
Normal file
169
core/node/Event.zig
Normal file
@@ -0,0 +1,169 @@
|
||||
const std = @import("std");
|
||||
const data = @import("../data.zig");
|
||||
const endpoint = @import("../endpoint.zig");
|
||||
const Packet = @import("../packet.zig").Managed;
|
||||
const Payload = @import("../packet.zig").Payload;
|
||||
const Hash = @import("../crypto/Hash.zig");
|
||||
|
||||
// TODO: Perhaps distinguish between tasks and packets.
|
||||
|
||||
pub const In = union(enum) {
|
||||
announce: Announce,
|
||||
packet: Packet,
|
||||
plain: Plain,
|
||||
|
||||
pub const Announce = struct {
|
||||
hash: Hash,
|
||||
app_data: ?data.Bytes,
|
||||
};
|
||||
|
||||
pub const Plain = struct {
|
||||
name: endpoint.Name,
|
||||
payload: Payload,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
switch (self.*) {
|
||||
.announce => |*announce| {
|
||||
if (announce.app_data) |app_data| {
|
||||
app_data.deinit();
|
||||
}
|
||||
},
|
||||
.packet => |*packet| {
|
||||
packet.deinit();
|
||||
},
|
||||
.plain => |*plain| {
|
||||
plain.name.deinit();
|
||||
plain.payload.deinit();
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Out = union(enum) {
|
||||
packet: Packet,
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
switch (self.*) {
|
||||
.packet => |*packet| {
|
||||
packet.deinit();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Replace this with a cleaner implementation.
|
||||
pub fn format(this: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, w: anytype) !void {
|
||||
_ = fmt;
|
||||
_ = options;
|
||||
|
||||
const F = struct {
|
||||
const Self = @This();
|
||||
|
||||
writer: @TypeOf(w),
|
||||
indentation: u8 = 0,
|
||||
|
||||
fn init(writer: @TypeOf(w)) Self {
|
||||
return .{
|
||||
.writer = writer,
|
||||
};
|
||||
}
|
||||
|
||||
fn indent(self: *Self) !void {
|
||||
for (0..self.indentation) |_| {
|
||||
try self.writer.print(" ", .{});
|
||||
}
|
||||
}
|
||||
|
||||
fn entry(self: *Self, key: []const u8, comptime value_fmt: []const u8, args: anytype) !void {
|
||||
try self.indent();
|
||||
try self.writer.print(".{s} = ", .{key});
|
||||
try self.writer.print(value_fmt ++ ",\n", args);
|
||||
}
|
||||
|
||||
fn objectStart(self: *Self, key: []const u8, tag: []const u8) !void {
|
||||
try self.indent();
|
||||
try self.writer.print(".{s} = .{s}{{\n", .{ key, tag });
|
||||
self.indentation += 2;
|
||||
}
|
||||
|
||||
fn objectEnd(self: *Self) !void {
|
||||
self.indentation -= 2;
|
||||
try self.indent();
|
||||
try self.writer.print("}},\n", .{});
|
||||
}
|
||||
|
||||
fn print(self: *Self, comptime text: []const u8, args: anytype) !void {
|
||||
try self.writer.print(text, args);
|
||||
}
|
||||
};
|
||||
|
||||
const hex = std.fmt.fmtSliceHexLower;
|
||||
var f = F.init(w);
|
||||
|
||||
switch (this) {
|
||||
.packet => |p| {
|
||||
try f.objectStart("packet", "");
|
||||
|
||||
const h = p.header;
|
||||
try f.entry("header", ".{{.{s}, .{s}, .{s}, .{s}, .{s}, .{s}, hops({d})}}", .{
|
||||
@tagName(h.interface),
|
||||
@tagName(h.format),
|
||||
@tagName(h.context),
|
||||
@tagName(h.propagation),
|
||||
@tagName(h.method),
|
||||
@tagName(h.purpose),
|
||||
h.hops,
|
||||
});
|
||||
|
||||
if (p.interface_access_code.items.len > 0) {
|
||||
try f.entry("interface_access_code", "{x}", .{hex(p.interface_access_code.items)});
|
||||
}
|
||||
|
||||
switch (p.endpoints) {
|
||||
.normal => |n| {
|
||||
try f.entry("endpoints", ".normal{{{x}}}", .{
|
||||
hex(&n.endpoint),
|
||||
});
|
||||
},
|
||||
.transport => |t| {
|
||||
try f.entry("endpoints", ".transport{{{x}, {x}}}", .{
|
||||
hex(&t.endpoint),
|
||||
hex(&t.transport_id),
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
try f.entry("context", ".{s}", .{@tagName(p.context)});
|
||||
|
||||
switch (p.payload) {
|
||||
.announce => |a| {
|
||||
try f.objectStart("payload", "announce");
|
||||
|
||||
try f.entry("public.dh", "{x}", .{hex(&a.public.dh)});
|
||||
try f.entry("public.signature", "{x}", .{hex(&a.public.signature.bytes)});
|
||||
try f.entry("name_hash", "{x}", .{hex(&a.name_hash)});
|
||||
try f.entry("noise", "{x}", .{hex(&a.noise)});
|
||||
try f.entry("timestamp", "{}", .{a.timestamp});
|
||||
try f.entry("signature", "{x}", .{hex(&a.signature.toBytes())});
|
||||
|
||||
if (a.application_data.items.len > 0) {
|
||||
try f.entry("application_data", "{x}", .{hex(a.application_data.items)});
|
||||
}
|
||||
|
||||
try f.objectEnd();
|
||||
},
|
||||
.raw => |r| {
|
||||
try f.entry("payload", ".raw{{{x}}}", .{hex(r.items)});
|
||||
},
|
||||
.none => {
|
||||
try f.entry("payload", ".none", .{});
|
||||
},
|
||||
}
|
||||
|
||||
f.indentation -= 2;
|
||||
try f.indent();
|
||||
try f.print("}}", .{});
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,9 @@
|
||||
const Identity = @import("../crypto/Identity.zig");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
// TODO: Load the version from build.zig.zon.
|
||||
name: []const u8 = "reticulum-zig",
|
||||
transport_enabled: bool = false,
|
||||
max_interfaces: usize = 256,
|
||||
max_incoming_packets: usize = 1024,
|
||||
max_outgoing_packets: usize = 1024,
|
||||
incoming_packets_limit: usize = 1024,
|
||||
outgoing_packets_limit: usize = 1024,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const std = @import("std");
|
||||
const crypto = @import("crypto.zig");
|
||||
const data = @import("data.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Bytes = std.ArrayList(u8);
|
||||
const Endpoint = @import("endpoint.zig").Managed;
|
||||
const EndpointMethod = @import("endpoint.zig").Method;
|
||||
const Hash = crypto.Hash;
|
||||
@@ -14,6 +14,8 @@ pub const Managed = @import("packet/Managed.zig");
|
||||
pub const Packet = Managed;
|
||||
|
||||
pub const Payload = union(enum) {
|
||||
const Self = @This();
|
||||
|
||||
pub const Announce = struct {
|
||||
pub const Noise = [5]u8;
|
||||
pub const Timestamp = u40;
|
||||
@@ -22,8 +24,8 @@ pub const Payload = union(enum) {
|
||||
total += crypto.X25519.public_length;
|
||||
total += crypto.Ed25519.PublicKey.encoded_length;
|
||||
total += crypto.Hash.name_length;
|
||||
total += 5;
|
||||
total += 5;
|
||||
total += @sizeOf(Noise);
|
||||
total += @sizeOf(Timestamp);
|
||||
total += crypto.Ed25519.Signature.encoded_length;
|
||||
break :blk total;
|
||||
};
|
||||
@@ -34,22 +36,47 @@ pub const Payload = union(enum) {
|
||||
timestamp: Timestamp,
|
||||
// rachet: ?*const [N]u8,
|
||||
signature: crypto.Ed25519.Signature,
|
||||
application_data: Bytes,
|
||||
application_data: data.Bytes,
|
||||
};
|
||||
|
||||
announce: Announce,
|
||||
raw: Bytes,
|
||||
raw: data.Bytes,
|
||||
none,
|
||||
|
||||
pub fn size(self: @This()) usize {
|
||||
pub fn makeRaw(bytes: data.Bytes) Self {
|
||||
return Self{
|
||||
.raw = bytes,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clone(self: Self) !Self {
|
||||
return switch (self) {
|
||||
.announce => |a| Self{
|
||||
.announce = Announce{
|
||||
.public = a.public,
|
||||
.name_hash = a.name_hash,
|
||||
.noise = a.noise,
|
||||
.timestamp = a.timestamp,
|
||||
.signature = a.signature,
|
||||
.application_data = try a.application_data.clone(),
|
||||
},
|
||||
},
|
||||
.raw => |r| Self{
|
||||
.raw = try r.clone(),
|
||||
},
|
||||
.none => Self.none,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn size(self: *const Self) usize {
|
||||
return switch (self) {
|
||||
.announce => |*a| blk: {
|
||||
var total: usize = 0;
|
||||
total += crypto.X25519.public_length;
|
||||
total += crypto.Ed25519.PublicKey.encoded_length;
|
||||
total += crypto.Hash.name_length;
|
||||
total += 5;
|
||||
total += 5;
|
||||
total += @sizeOf(Announce.Noise);
|
||||
total += @sizeOf(Announce.Timestamp);
|
||||
total += crypto.Ed25519.Signature.encoded_length;
|
||||
total += a.application_data.items.len;
|
||||
break :blk total;
|
||||
@@ -58,6 +85,14 @@ pub const Payload = union(enum) {
|
||||
.none => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
return switch (self.*) {
|
||||
.announce => |*announce| announce.application_data.deinit(),
|
||||
.raw => |*raw| raw.deinit(),
|
||||
.none => {},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Endpoints = union(Header.Flag.Format) {
|
||||
@@ -82,7 +117,7 @@ pub const Endpoints = union(Header.Flag.Format) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn next_hop(self: Self) Hash.Short {
|
||||
pub fn nextHop(self: Self) Hash.Short {
|
||||
return switch (self) {
|
||||
.normal => |n| n.endpoint,
|
||||
.transport => |t| t.transport_id,
|
||||
@@ -132,7 +167,7 @@ pub const Header = packed struct {
|
||||
};
|
||||
|
||||
pub const Context = enum(u8) {
|
||||
none,
|
||||
none = 0,
|
||||
resource,
|
||||
resource_advertisement,
|
||||
resource_request,
|
||||
@@ -155,6 +190,6 @@ pub const Context = enum(u8) {
|
||||
link_request_proof,
|
||||
};
|
||||
|
||||
test "Header size" {
|
||||
test "header-size" {
|
||||
try std.testing.expect(@sizeOf(Header) == 2);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const std = @import("std");
|
||||
const crypto = @import("../crypto.zig");
|
||||
const data = @import("../data.zig");
|
||||
const packet = @import("../packet.zig");
|
||||
|
||||
pub const Endpoints = packet.Endpoints;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Bytes = std.ArrayList(u8);
|
||||
const Managed = @import("Managed.zig");
|
||||
const Hash = crypto.Hash;
|
||||
const Header = packet.Header;
|
||||
@@ -14,54 +14,53 @@ const Payload = packet.Payload;
|
||||
const Endpoint = @import("../endpoint.zig").Managed;
|
||||
|
||||
const Self = @This();
|
||||
const Fields = std.bit_set.IntegerBitSet(1);
|
||||
|
||||
pub const Error = error{Incomplete};
|
||||
|
||||
ally: Allocator,
|
||||
fields: Fields,
|
||||
header: Header,
|
||||
interface_access_code: Bytes,
|
||||
endpoints: Endpoints,
|
||||
interface_access_code: data.Bytes,
|
||||
endpoints: ?Endpoints,
|
||||
context: Context,
|
||||
payload: Payload,
|
||||
|
||||
pub fn init(ally: Allocator) Self {
|
||||
return Self{
|
||||
.ally = ally,
|
||||
.fields = Fields.initEmpty(),
|
||||
.header = .{},
|
||||
.interface_access_code = Bytes.init(ally),
|
||||
.endpoints = undefined,
|
||||
.interface_access_code = data.Bytes.init(ally),
|
||||
.endpoints = null,
|
||||
.context = .none,
|
||||
.payload = .none,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_header(self: *Self, header: Header) *Self {
|
||||
pub fn setHeader(self: *Self, header: Header) *Self {
|
||||
self.header = header;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_interface_access_code(self: *Self, interface_access_code: []const u8) !*Self {
|
||||
pub fn setInterfaceAccessCode(self: *Self, interface_access_code: []const u8) !*Self {
|
||||
try self.interface_access_code.appendSlice(interface_access_code);
|
||||
|
||||
if (interface_access_code.len > 0) {
|
||||
self.header.interface = .authenticated;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_endpoint(self: *Self, endpoint_hash: Hash.Short) *Self {
|
||||
self.fields.set(0);
|
||||
pub fn setEndpoint(self: *Self, endpoint_hash: Hash.Short) *Self {
|
||||
self.endpoints = .{
|
||||
.normal = .{ .endpoint = endpoint_hash },
|
||||
.normal = .{
|
||||
.endpoint = endpoint_hash,
|
||||
},
|
||||
};
|
||||
self.header.format = .normal;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_transport(self: *Self, endpoint_hash: Hash.Short, transport_id: Hash.Short) *Self {
|
||||
self.fields.set(0);
|
||||
pub fn setTransport(self: *Self, endpoint_hash: Hash.Short, transport_id: Hash.Short) *Self {
|
||||
self.endpoints = .{
|
||||
.transport = .{
|
||||
.endpoint = endpoint_hash,
|
||||
@@ -73,23 +72,23 @@ pub fn set_transport(self: *Self, endpoint_hash: Hash.Short, transport_id: Hash.
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_method(self: *Self, method: Header.Flag.Method) *Self {
|
||||
pub fn setMethod(self: *Self, method: Header.Flag.Method) *Self {
|
||||
self.header.method = method;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_purpose(self: *Self, purpose: Header.Flag.Purpose) *Self {
|
||||
pub fn setPurpose(self: *Self, purpose: Header.Flag.Purpose) *Self {
|
||||
self.header.purpose = purpose;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_context(self: *Self, context: Context) *Self {
|
||||
pub fn setContext(self: *Self, context: Context) *Self {
|
||||
self.context = context;
|
||||
self.header.context = .some;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn set_payload(self: *Self, payload: Payload) *Self {
|
||||
pub fn setPayload(self: *Self, payload: Payload) *Self {
|
||||
self.header.purpose = switch (payload) {
|
||||
.announce => .announce,
|
||||
else => self.header.purpose,
|
||||
@@ -98,22 +97,20 @@ pub fn set_payload(self: *Self, payload: Payload) *Self {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn append_payload(self: *Self, payload: []const u8) !*Self {
|
||||
pub fn appendPayload(self: *Self, payload: []const u8) !*Self {
|
||||
try self.payload.appendSlice(payload);
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn build(self: *Self) !Managed {
|
||||
if (self.fields.count() == self.fields.capacity()) {
|
||||
return Managed{
|
||||
.ally = self.ally,
|
||||
.header = self.header,
|
||||
.interface_access_code = self.interface_access_code,
|
||||
.endpoints = self.endpoints,
|
||||
.context = self.context,
|
||||
.payload = self.payload,
|
||||
};
|
||||
}
|
||||
const endpoints = self.endpoints orelse return Error.Incomplete;
|
||||
|
||||
return Error.Incomplete;
|
||||
return Managed{
|
||||
.ally = self.ally,
|
||||
.header = self.header,
|
||||
.interface_access_code = self.interface_access_code,
|
||||
.endpoints = endpoints,
|
||||
.context = self.context,
|
||||
.payload = self.payload,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
const std = @import("std");
|
||||
const crypto = @import("../crypto.zig");
|
||||
const data = @import("../data.zig");
|
||||
const packet = @import("../packet.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Rng = @import("../System.zig").Rng;
|
||||
const Clock = @import("../System.zig").Clock;
|
||||
const Interface = @import("../Interface.zig");
|
||||
const Bytes = std.ArrayList(u8);
|
||||
const Endpoint = @import("../endpoint.zig").Managed;
|
||||
const Name = @import("../endpoint.zig").Name;
|
||||
const Builder = @import("Builder.zig");
|
||||
const Packet = @import("Managed.zig");
|
||||
|
||||
@@ -16,6 +17,7 @@ const Packet = @import("Managed.zig");
|
||||
pub const Error = error{
|
||||
InvalidBytesLength,
|
||||
InvalidAuthentication,
|
||||
MissingIdentity,
|
||||
} || crypto.Identity.Error || Builder.Error || Allocator.Error;
|
||||
const Self = @This();
|
||||
|
||||
@@ -33,7 +35,7 @@ pub fn init(ally: Allocator, clock: Clock, rng: Rng, config: Interface.Config) S
|
||||
};
|
||||
}
|
||||
|
||||
pub fn from_bytes(self: *Self, bytes: []const u8) Error!Packet {
|
||||
pub fn fromBytes(self: *Self, bytes: []const u8) Error!Packet {
|
||||
var index: usize = 0;
|
||||
const header_size = @sizeOf(packet.Header);
|
||||
|
||||
@@ -52,7 +54,7 @@ pub fn from_bytes(self: *Self, bytes: []const u8) Error!Packet {
|
||||
return Error.InvalidAuthentication;
|
||||
}
|
||||
|
||||
var interface_access_code = Bytes.init(self.ally);
|
||||
var interface_access_code = data.Bytes.init(self.ally);
|
||||
errdefer {
|
||||
interface_access_code.deinit();
|
||||
}
|
||||
@@ -62,7 +64,7 @@ pub fn from_bytes(self: *Self, bytes: []const u8) Error!Packet {
|
||||
return Error.InvalidBytesLength;
|
||||
}
|
||||
|
||||
// I need to decrypt the packet here.
|
||||
// TODO: I need to decrypt the packet here.
|
||||
|
||||
try interface_access_code.appendSlice(bytes[index .. index + access_code.len]);
|
||||
index += access_code.len;
|
||||
@@ -141,14 +143,14 @@ pub fn from_bytes(self: *Self, bytes: []const u8) Error!Packet {
|
||||
announce.signature = Signature.fromBytes(signature_bytes);
|
||||
index += Signature.encoded_length;
|
||||
|
||||
var application_data = Bytes.init(self.ally);
|
||||
var application_data = data.Bytes.init(self.ally);
|
||||
try application_data.appendSlice(bytes[index..]);
|
||||
announce.application_data = application_data;
|
||||
|
||||
break :blk announce;
|
||||
} },
|
||||
else => .{ .raw = blk: {
|
||||
var raw = Bytes.init(self.ally);
|
||||
var raw = data.Bytes.init(self.ally);
|
||||
errdefer {
|
||||
raw.deinit();
|
||||
}
|
||||
@@ -168,21 +170,22 @@ pub fn from_bytes(self: *Self, bytes: []const u8) Error!Packet {
|
||||
}
|
||||
|
||||
// TODO: Add ratchet.
|
||||
pub fn make_announce(self: *Self, endpoint: *const Endpoint, application_data: ?[]const u8) Error!Packet {
|
||||
pub fn makeAnnounce(self: *Self, endpoint: *const Endpoint, application_data: ?[]const u8) Error!Packet {
|
||||
const identity = endpoint.identity orelse return Error.MissingIdentity;
|
||||
var announce: packet.Payload.Announce = undefined;
|
||||
|
||||
announce.public = endpoint.identity.public;
|
||||
announce.name_hash = endpoint.name_hash.name().*;
|
||||
announce.public = identity.public;
|
||||
announce.name_hash = endpoint.name.hash.name().*;
|
||||
self.rng.bytes(&announce.noise);
|
||||
announce.timestamp = @truncate(std.mem.nativeToBig(u64, self.clock.monotonicMicros()));
|
||||
announce.application_data = Bytes.init(self.ally);
|
||||
announce.application_data = data.Bytes.init(self.ally);
|
||||
|
||||
if (application_data) |data| {
|
||||
try announce.application_data.appendSlice(data);
|
||||
if (application_data) |app_data| {
|
||||
try announce.application_data.appendSlice(app_data);
|
||||
}
|
||||
|
||||
announce.signature = blk: {
|
||||
var signer = try endpoint.identity.signer(&self.rng);
|
||||
var signer = try identity.signer(&self.rng);
|
||||
signer.update(endpoint.hash.short());
|
||||
signer.update(announce.public.dh[0..]);
|
||||
signer.update(announce.public.signature.bytes[0..]);
|
||||
@@ -196,11 +199,27 @@ pub fn make_announce(self: *Self, endpoint: *const Endpoint, application_data: ?
|
||||
var builder = Builder.init(self.ally);
|
||||
|
||||
if (self.config.access_code) |interface_access_code| {
|
||||
_ = try builder.set_interface_access_code(interface_access_code);
|
||||
_ = try builder.setInterfaceAccessCode(interface_access_code);
|
||||
}
|
||||
|
||||
return try builder
|
||||
.set_endpoint(endpoint.hash.short().*)
|
||||
.set_payload(.{ .announce = announce })
|
||||
.setEndpoint(endpoint.hash.short().*)
|
||||
.setPayload(.{ .announce = announce })
|
||||
.build();
|
||||
}
|
||||
|
||||
pub fn makePlain(self: *Self, name: Name, payload: packet.Payload) Error!Packet {
|
||||
var builder = Builder.init(self.ally);
|
||||
|
||||
if (self.config.access_code) |interface_access_code| {
|
||||
_ = try builder.setInterfaceAccessCode(interface_access_code);
|
||||
}
|
||||
|
||||
const plain = try builder
|
||||
.setMethod(.plain)
|
||||
.setEndpoint(name.hash.short().*)
|
||||
.setPayload(payload)
|
||||
.build();
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const std = @import("std");
|
||||
const crypto = @import("../crypto.zig");
|
||||
const data = @import("../data.zig");
|
||||
const packet = @import("../packet.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Bytes = std.ArrayList(u8);
|
||||
const Header = packet.Header;
|
||||
const Context = packet.Context;
|
||||
const Endpoints = packet.Endpoints;
|
||||
@@ -13,25 +13,39 @@ const Hash = crypto.Hash;
|
||||
const Self = @This();
|
||||
|
||||
ally: Allocator,
|
||||
header: Header = undefined,
|
||||
interface_access_code: Bytes,
|
||||
endpoints: Endpoints = undefined,
|
||||
context: Context = undefined,
|
||||
header: Header,
|
||||
interface_access_code: data.Bytes,
|
||||
endpoints: Endpoints,
|
||||
context: Context,
|
||||
payload: Payload,
|
||||
|
||||
pub fn init(ally: Allocator) Self {
|
||||
return Self{
|
||||
.ally = ally,
|
||||
.interface_access_code = Bytes.init(ally),
|
||||
.header = undefined,
|
||||
.interface_access_code = data.Bytes.init(ally),
|
||||
.endpoints = undefined,
|
||||
.context = undefined,
|
||||
.payload = .none,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) Self {
|
||||
_ = self;
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.interface_access_code.deinit();
|
||||
self.payload.deinit();
|
||||
}
|
||||
|
||||
pub fn validate(self: *Self) !bool {
|
||||
pub fn setTransport(self: *Self, transport_id: *const Hash.Short) !void {
|
||||
self.header.format = .transport;
|
||||
self.endpoints = Endpoints{
|
||||
.transport = Endpoints.Transport{
|
||||
.transport_id = transport_id.*,
|
||||
.endpoint = self.endpoints.endpoint(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn validate(self: *const Self) !void {
|
||||
switch (self.payload) {
|
||||
.announce => |a| {
|
||||
const endpoint_hash = self.endpoints.endpoint();
|
||||
@@ -46,20 +60,22 @@ pub fn validate(self: *Self) !bool {
|
||||
try verifier.verify();
|
||||
|
||||
const identity = crypto.Identity.from_public(a.public);
|
||||
const expected_hash = Hash.hash_items(.{
|
||||
const expected_hash = Hash.ofItems(.{
|
||||
.name_hash = a.name_hash,
|
||||
.public_hash = identity.hash.short(),
|
||||
});
|
||||
|
||||
const matching_hashes = std.mem.eql(u8, endpoint_hash[0..], expected_hash.short()[0..]);
|
||||
return matching_hashes;
|
||||
if (!matching_hashes) {
|
||||
return error.MismatchingHashes;
|
||||
}
|
||||
},
|
||||
else => return true,
|
||||
else => return,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this take a Writer interface.
|
||||
// Make sure to encrypt the packet with the interface access code here.
|
||||
// TODO: Make sure to encrypt the packet with the interface access code here.
|
||||
pub fn write(self: *const Self, buffer: []u8) []u8 {
|
||||
if (buffer.len < self.size()) {
|
||||
return &.{};
|
||||
@@ -123,13 +139,13 @@ pub fn hash(self: *const Self) Hash {
|
||||
const header: u8 = std.mem.bytesAsSlice(u4, self.header)[1];
|
||||
|
||||
return switch (self.endpoints) {
|
||||
.normal => |normal| Hash.from_items(.{
|
||||
.normal => |normal| Hash.fromItems(.{
|
||||
.header = header,
|
||||
.endpoint = normal.endpoint,
|
||||
.context = self.context,
|
||||
.payload = self.payload,
|
||||
}),
|
||||
.transport => |transport| Hash.from_items(.{
|
||||
.transport => |transport| Hash.fromItems(.{
|
||||
.header = header,
|
||||
.transport_id = transport.transport_id,
|
||||
.endpoint = transport.endpoint,
|
||||
@@ -153,3 +169,14 @@ pub fn size(self: *const Self) usize {
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
pub fn clone(self: *const Self) !Self {
|
||||
return Self{
|
||||
.ally = self.ally,
|
||||
.context = self.context,
|
||||
.endpoints = self.endpoints,
|
||||
.header = self.header,
|
||||
.interface_access_code = try self.interface_access_code.clone(),
|
||||
.payload = try self.payload.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
pub const crypto = @import("crypto.zig");
|
||||
pub const data = @import("data.zig");
|
||||
pub const endpoint = @import("endpoint.zig");
|
||||
pub const packet = @import("packet.zig");
|
||||
pub const units = @import("units.zig");
|
||||
pub const unit = @import("unit.zig");
|
||||
|
||||
pub const Endpoint = endpoint.Managed;
|
||||
pub const Identity = crypto.Identity;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub const Bandwidth = BitRate;
|
||||
|
||||
pub const BitRate = struct {
|
||||
pub const none: ?BitRate = null;
|
||||
pub const default = BitRate{
|
||||
.bits = .{ .count = 1, .prefix = .kilo },
|
||||
.rate = .per_second,
|
||||
@@ -1,2 +0,0 @@
|
||||
pub const Clock = @import("fixtures/Clock.zig");
|
||||
pub const Framework = @import("fixtures/Framework.zig");
|
||||
124
test/fixtures/Framework.zig
vendored
124
test/fixtures/Framework.zig
vendored
@@ -1,124 +0,0 @@
|
||||
const std = @import("std");
|
||||
const rt = @import("reticulum");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Clock = @import("Clock.zig");
|
||||
|
||||
pub const Error = error{
|
||||
UnknownName,
|
||||
DuplicateName,
|
||||
} || Allocator.Error;
|
||||
|
||||
const Node = struct {
|
||||
node: rt.Node,
|
||||
endpoints: std.ArrayList(rt.Endpoint),
|
||||
api: rt.Interface.Api,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
ally: Allocator,
|
||||
options: rt.Node.Options,
|
||||
clock: Clock,
|
||||
system: rt.System,
|
||||
indices: std.StringHashMap(usize),
|
||||
edges: std.ArrayList(std.AutoHashMap(usize, void)),
|
||||
nodes: std.ArrayList(Node),
|
||||
|
||||
pub fn init(ally: Allocator, options: rt.Node.Options) Self {
|
||||
var clock = Clock.init();
|
||||
|
||||
return Self{
|
||||
.ally = ally,
|
||||
.options = options,
|
||||
.clock = clock,
|
||||
.system = rt.System{
|
||||
.clock = clock.clock(),
|
||||
.rng = std.crypto.random,
|
||||
},
|
||||
.indices = std.StringHashMap(usize).init(ally),
|
||||
.edges = std.ArrayList(std.AutoHashMap(usize, void)).init(ally),
|
||||
.nodes = std.ArrayList(Node).init(ally),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addEndpoint(self: *Self, name: []const u8) !rt.Endpoint {
|
||||
const identity = try rt.Identity.random(&self.system.rng);
|
||||
var builder = rt.endpoint.Builder.init(self.ally);
|
||||
_ = try builder
|
||||
.set_identity(identity)
|
||||
.set_direction(.in)
|
||||
.set_method(.single)
|
||||
.set_application_name(name);
|
||||
var endpoint = try builder.build();
|
||||
|
||||
if (self.indices.get(name)) |index| {
|
||||
var node = self.nodes.items[index];
|
||||
const endpoint_copy = try endpoint.copy();
|
||||
try node.endpoints.append(endpoint_copy);
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
pub fn getNode(self: *Self, name: []const u8) ?*Node {
|
||||
const index = self.indices.get(name) orelse return null;
|
||||
return &self.nodes.items[index];
|
||||
}
|
||||
|
||||
pub fn send(self: *Self, src: []const u8, dst: []const u8, data: []const u8) !void {
|
||||
const n1 = self.getNode(src) orelse return;
|
||||
const n2 = self.getNode(dst) orelse return;
|
||||
const e1 = n1.endpoints.getLast();
|
||||
const e2 = n2.endpoints.getLast();
|
||||
|
||||
const packet = rt.packet.Builder.init(self.ally)
|
||||
.set_transport(e1.hash, e2.hash)
|
||||
.append_payload(data)
|
||||
.build();
|
||||
|
||||
try n1.api.send(packet);
|
||||
}
|
||||
|
||||
pub fn process(self: *Self) !void {
|
||||
var indices = self.indices.valueIterator();
|
||||
while (indices.next()) |index| {
|
||||
var target = self.nodes.items[index.*];
|
||||
try target.node.process();
|
||||
while (target.api.collect(rt.units.BitRate.default)) |packet| {
|
||||
var keys = self.edges.items[index.*].keyIterator();
|
||||
while (keys.next()) |k| {
|
||||
var connected = self.nodes.items[k.*];
|
||||
try connected.api.deliver(packet);
|
||||
try connected.node.process();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addNode(self: *Self, name: []const u8) !void {
|
||||
if (self.indices.contains(name)) {
|
||||
return Error.DuplicateName;
|
||||
}
|
||||
|
||||
const index = self.indices.count();
|
||||
try self.indices.put(name, index);
|
||||
try self.edges.append(std.AutoHashMap(usize, void).init(self.ally));
|
||||
|
||||
var node = try rt.Node.init(self.ally, self.system, self.options);
|
||||
const api = try node.addInterface(.{});
|
||||
|
||||
try self.nodes.append(Node{
|
||||
.node = node,
|
||||
.endpoints = std.ArrayList(rt.Endpoint).init(self.ally),
|
||||
.api = api,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn connect(self: *Self, a: []const u8, b: []const u8) !void {
|
||||
const a_index = self.indices.get(a) orelse return Error.UnknownName;
|
||||
const b_index = self.indices.get(b) orelse return Error.UnknownName;
|
||||
|
||||
try self.edges.items[a_index].put(b_index, {});
|
||||
try self.edges.items[b_index].put(a_index, {});
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
const std = @import("std");
|
||||
const rt = @import("reticulum");
|
||||
const fixtures = @import("fixtures");
|
||||
// const ohsnap = @import("ohsnap");
|
||||
|
||||
test {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
var thread_safe_gpa = std.heap.ThreadSafeAllocator{
|
||||
.child_allocator = gpa.allocator(),
|
||||
};
|
||||
const ally = thread_safe_gpa.allocator();
|
||||
var f = try abc(ally);
|
||||
var c = f.getNode("C").?;
|
||||
|
||||
while (c.api.collect(rt.units.BitRate.default)) |packet| {
|
||||
std.debug.print("{any}\n", .{packet});
|
||||
}
|
||||
}
|
||||
|
||||
fn abc(ally: std.mem.Allocator) !fixtures.Framework {
|
||||
var f = fixtures.Framework.init(ally, .{});
|
||||
const names = [_][]const u8{ "A", "B", "C" };
|
||||
|
||||
for (names) |name| {
|
||||
try f.addNode(name);
|
||||
}
|
||||
|
||||
try f.connect("A", "B");
|
||||
try f.connect("C", "B");
|
||||
|
||||
for (names) |name| {
|
||||
const n = f.getNode(name).?;
|
||||
const endpoint = try f.addEndpoint(name);
|
||||
try n.api.announce(&endpoint, name);
|
||||
f.clock.advance(200, .ms);
|
||||
try f.process();
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
const rt = @import("reticulum");
|
||||
|
||||
pub const Unit = enum { s, ms, us, ns };
|
||||
pub const Unit = enum {
|
||||
seconds,
|
||||
milliseconds,
|
||||
microseconds,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
@@ -14,12 +18,11 @@ pub fn init() Self {
|
||||
|
||||
pub fn advance(self: *Self, count: u64, unit: Unit) void {
|
||||
const factor: u64 = switch (unit) {
|
||||
.s => 1,
|
||||
.s => 1_000_000,
|
||||
.ms => 1_000,
|
||||
.us => 1_000_000,
|
||||
.ns => 1_000_000_000,
|
||||
.us => 1,
|
||||
};
|
||||
self.timestamp += (factor * count);
|
||||
self.timestamp += (count * factor);
|
||||
}
|
||||
|
||||
pub fn monotonicMicros(ptr: *anyopaque) u64 {
|
||||
231
test/simulation/kit/Simulator.zig
Normal file
231
test/simulation/kit/Simulator.zig
Normal file
@@ -0,0 +1,231 @@
|
||||
const std = @import("std");
|
||||
const rt = @import("reticulum");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ManualClock = @import("ManualClock.zig");
|
||||
|
||||
pub const Error = error{
|
||||
UnknownName,
|
||||
DuplicateName,
|
||||
} || Allocator.Error;
|
||||
|
||||
const Interface = struct {
|
||||
api: rt.Interface.Api,
|
||||
config: rt.Interface.Config,
|
||||
to: []const u8,
|
||||
event_buffer: std.ArrayList(rt.Node.Event.Out),
|
||||
};
|
||||
|
||||
const Node = struct {
|
||||
node: rt.Node,
|
||||
interfaces: std.StringHashMap(void),
|
||||
};
|
||||
|
||||
// Could potentially make this a compile time function instead of a struct.
|
||||
// TODO: Refactor.
|
||||
|
||||
const Self = @This();
|
||||
|
||||
ally: Allocator,
|
||||
manual_clock: ManualClock,
|
||||
prng: std.Random.DefaultPrng,
|
||||
system: rt.System,
|
||||
nodes: std.StringHashMap(Node),
|
||||
interfaces: std.StringHashMap(Interface),
|
||||
|
||||
pub fn init(ally: Allocator) Self {
|
||||
var manual_clock = ManualClock.init();
|
||||
|
||||
return Self{
|
||||
.ally = ally,
|
||||
.manual_clock = manual_clock,
|
||||
.system = rt.System{
|
||||
.clock = manual_clock.clock(),
|
||||
.rng = std.crypto.random,
|
||||
},
|
||||
.nodes = std.StringHashMap(Node).init(ally),
|
||||
.interfaces = std.StringHashMap(Interface).init(ally),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
var nodes = self.nodes.valueIterator();
|
||||
var interfaces = self.interfaces.valueIterator();
|
||||
|
||||
while (nodes.next()) |node| {
|
||||
node.node.deinit();
|
||||
node.interfaces.deinit();
|
||||
}
|
||||
|
||||
while (interfaces.next()) |interface| {
|
||||
for (interface.event_buffer.items) |*event| {
|
||||
event.deinit();
|
||||
}
|
||||
|
||||
interface.event_buffer.deinit();
|
||||
}
|
||||
|
||||
self.nodes.deinit();
|
||||
self.interfaces.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn fromTopology(comptime topology: anytype, ally: Allocator) !Self {
|
||||
var manual_clock = ManualClock.init();
|
||||
var prng = std.Random.DefaultPrng.init(42);
|
||||
|
||||
var self = Self{
|
||||
.ally = ally,
|
||||
.manual_clock = manual_clock,
|
||||
.prng = prng,
|
||||
.system = rt.System{
|
||||
.clock = manual_clock.clock(),
|
||||
.rng = prng.random(),
|
||||
},
|
||||
.nodes = std.StringHashMap(Node).init(ally),
|
||||
.interfaces = std.StringHashMap(Interface).init(ally),
|
||||
};
|
||||
|
||||
inline for (std.meta.fields(@TypeOf(topology))) |node_field| {
|
||||
const node = @field(topology, node_field.name);
|
||||
// TODO: Allow for specifying some fields and defaulting others.
|
||||
var node_options: rt.Node.Options = .{};
|
||||
if (@hasField(@TypeOf(node), "options")) {
|
||||
const topo_options = node.options;
|
||||
if (@hasField(@TypeOf(topo_options), "transport_enabled")) {
|
||||
node_options.transport_enabled = topo_options.transport_enabled;
|
||||
}
|
||||
if (@hasField(@TypeOf(topo_options), "incoming_packets_limit")) {
|
||||
node_options.incoming_packets_limit = topo_options.incoming_packets_limit;
|
||||
}
|
||||
if (@hasField(@TypeOf(topo_options), "outgoing_packets_limit")) {
|
||||
node_options.outgoing_packets_limit = topo_options.outgoing_packets_limit;
|
||||
}
|
||||
if (@hasField(@TypeOf(topo_options), "name")) {
|
||||
node_options.name = topo_options.name;
|
||||
}
|
||||
}
|
||||
node_options.name = node_field.name;
|
||||
|
||||
const simulator_node = try self.addNode(node_field.name, node_options);
|
||||
|
||||
inline for (std.meta.fields(@TypeOf(node.interfaces))) |interface_field| {
|
||||
const interface = @field(node.interfaces, interface_field.name);
|
||||
// TODO: Allow for specifying some fields and defaulting others.
|
||||
var interface_config = rt.Interface.Config{};
|
||||
if (@hasField(@TypeOf(interface), "config")) {
|
||||
interface_config = interface.config;
|
||||
}
|
||||
|
||||
interface_config.name = interface_field.name;
|
||||
|
||||
const simulator_interface = Interface{
|
||||
.api = try simulator_node.node.addInterface(interface_config),
|
||||
.config = interface_config,
|
||||
.to = @tagName(interface.to),
|
||||
.event_buffer = std.ArrayList(rt.Node.Event.Out).init(self.ally),
|
||||
};
|
||||
|
||||
try simulator_node.interfaces.put(interface_field.name, {});
|
||||
try self.interfaces.put(interface_field.name, simulator_interface);
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn addNode(self: *Self, name: []const u8, options: rt.Node.Options) !*Node {
|
||||
if (self.nodes.contains(name)) {
|
||||
return Error.DuplicateName;
|
||||
}
|
||||
|
||||
const node = Node{
|
||||
.node = try rt.Node.init(self.ally, &self.system, null, options),
|
||||
.interfaces = std.StringHashMap(void).init(self.ally),
|
||||
};
|
||||
|
||||
try self.nodes.put(name, node);
|
||||
|
||||
return self.nodes.getPtr(name).?;
|
||||
}
|
||||
|
||||
pub fn addEndpoint(self: *Self, name: []const u8) !rt.Endpoint {
|
||||
const identity = try rt.Identity.random(&self.system.rng);
|
||||
var builder = rt.endpoint.Builder.init(self.ally);
|
||||
|
||||
_ = try builder
|
||||
.setIdentity(identity)
|
||||
.setDirection(.in)
|
||||
.setMethod(.single)
|
||||
.setApplicationName(name);
|
||||
|
||||
var endpoint = try builder.build();
|
||||
|
||||
if (self.indices.get(name)) |index| {
|
||||
var node = self.nodes.items[index];
|
||||
const endpoint_copy = try endpoint.clone();
|
||||
try node.endpoints.append(endpoint_copy);
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
pub fn getNode(self: *Self, name: []const u8) ?*Node {
|
||||
return self.nodes.getPtr(name);
|
||||
}
|
||||
|
||||
pub fn getInterface(self: *Self, name: []const u8) ?*Interface {
|
||||
return self.interfaces.getPtr(name);
|
||||
}
|
||||
|
||||
pub fn step(self: *Self) !void {
|
||||
try self.processBuffers();
|
||||
try self.processNodes();
|
||||
}
|
||||
|
||||
pub fn stepAfter(self: *Self, count: u64, unit: ManualClock.Unit) !void {
|
||||
self.manual_clock.advance(count, unit);
|
||||
try self.step();
|
||||
}
|
||||
|
||||
pub fn processBuffers(self: *Self) !void {
|
||||
var node_names = self.nodes.keyIterator();
|
||||
|
||||
while (node_names.next()) |node_name| {
|
||||
const node = self.getNode(node_name.*).?;
|
||||
var interface_names = node.interfaces.keyIterator();
|
||||
|
||||
while (interface_names.next()) |interface_name| {
|
||||
var source_interface = self.interfaces.getPtr(interface_name.*).?;
|
||||
const target_interface = self.getInterface(source_interface.to).?;
|
||||
|
||||
for (source_interface.event_buffer.items) |event_out| {
|
||||
if (event_out == .packet) {
|
||||
try target_interface.api.deliverEvent(rt.Node.Event.In{
|
||||
.packet = event_out.packet,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
source_interface.event_buffer.clearRetainingCapacity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn processNodes(self: *Self) !void {
|
||||
var node_names = self.nodes.keyIterator();
|
||||
|
||||
while (node_names.next()) |node_name| {
|
||||
const node = self.getNode(node_name.*).?;
|
||||
try node.node.process();
|
||||
|
||||
var interface_names = node.interfaces.keyIterator();
|
||||
while (interface_names.next()) |interface_name| {
|
||||
var interface = self.interfaces.getPtr(interface_name.*).?;
|
||||
|
||||
while (interface.api.collectEvent()) |event_out| {
|
||||
try interface.event_buffer.append(event_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
test/simulation/kit/topology.zig
Normal file
27
test/simulation/kit/topology.zig
Normal file
@@ -0,0 +1,27 @@
|
||||
//! ┌────────┐ ┌─────────┐ ┌────────┐
|
||||
//! │ a0───┼────┼─b0[T]b1─┼────┼───c0 │
|
||||
//! └────────┘ └─────────┘ └────────┘
|
||||
pub const abc = .{
|
||||
.a = .{
|
||||
.interfaces = .{
|
||||
.a0 = .{ .to = .b0 },
|
||||
},
|
||||
},
|
||||
.b = .{
|
||||
.options = .{
|
||||
.transport_enabled = true,
|
||||
.interface_limit = 256,
|
||||
.incoming_packets_limit = 1024,
|
||||
.outgoing_packets_limit = 1024,
|
||||
},
|
||||
.interfaces = .{
|
||||
.b0 = .{ .to = .a0 },
|
||||
.b1 = .{ .to = .c0 },
|
||||
},
|
||||
},
|
||||
.c = .{
|
||||
.interfaces = .{
|
||||
.c0 = .{ .to = .b1 },
|
||||
},
|
||||
},
|
||||
};
|
||||
54
test/simulation/plain.zig
Normal file
54
test/simulation/plain.zig
Normal file
@@ -0,0 +1,54 @@
|
||||
const std = @import("std");
|
||||
const rt = @import("reticulum");
|
||||
const t = std.testing;
|
||||
const topology = @import("kit/topology.zig");
|
||||
|
||||
const Golden = @import("ohsnap");
|
||||
const Simulator = @import("kit/Simulator.zig");
|
||||
|
||||
test "abc" {
|
||||
const golden = Golden{};
|
||||
const ally = t.allocator;
|
||||
var s = try Simulator.fromTopology(topology.abc, ally);
|
||||
|
||||
defer {
|
||||
s.deinit();
|
||||
}
|
||||
|
||||
const a0 = s.getInterface("a0").?;
|
||||
const b0 = s.getInterface("b0").?;
|
||||
const b1 = s.getInterface("b1").?;
|
||||
const c0 = s.getInterface("c0").?;
|
||||
|
||||
const name = try rt.endpoint.Name.init(
|
||||
"plain",
|
||||
&.{ "test", "endpoint" },
|
||||
ally,
|
||||
);
|
||||
const payload = rt.packet.Payload.makeRaw(try rt.data.makeBytes(
|
||||
"this is some payload data",
|
||||
ally,
|
||||
));
|
||||
|
||||
try a0.api.plain(name, payload);
|
||||
try s.step();
|
||||
|
||||
try t.expect(a0.event_buffer.items.len == 1);
|
||||
|
||||
inline for (&.{ b0, b1, c0 }) |n| {
|
||||
try t.expect(n.event_buffer.items.len == 0);
|
||||
}
|
||||
|
||||
const plain_packet = a0.event_buffer.items[0];
|
||||
|
||||
try golden.snap(
|
||||
@src(),
|
||||
\\.packet = .{
|
||||
\\ .header = .{.open, .normal, .none, .broadcast, .plain, .data, hops(0)},
|
||||
\\ .endpoints = .normal{358602dccbe1449748d6ef6b0c1b0471},
|
||||
\\ .context = .none,
|
||||
\\ .payload = .raw{7468697320697320736f6d65207061796c6f61642064617461},
|
||||
\\}
|
||||
,
|
||||
).expectEqualFmt(plain_packet);
|
||||
}
|
||||
Reference in New Issue
Block a user