diff --git a/capsule-core/src/config.zig b/capsule-core/src/config.zig index d3e2233..a409f24 100644 --- a/capsule-core/src/config.zig +++ b/capsule-core/src/config.zig @@ -24,6 +24,15 @@ pub const NodeConfig = struct { /// Enable Gateway Service (Layer 1 Coordination) gateway_enabled: bool = false, + /// Enable Relay Service (Layer 2 Forwarding) + relay_enabled: bool = false, + + /// Enable Bridge Service (Layer 3 Protocol Translation) + bridge_enabled: bool = false, + + /// QVL minimum trust score for relay selection + relay_trust_threshold: f64 = 0.5, + /// Free allocated memory (strings, slices) pub fn deinit(self: *NodeConfig, allocator: std.mem.Allocator) void { allocator.free(self.data_dir); @@ -43,6 +52,9 @@ pub const NodeConfig = struct { .identity_key_path = try allocator.dupe(u8, "data/identity.key"), .port = 8710, .gateway_enabled = false, + .relay_enabled = false, + .bridge_enabled = false, + .relay_trust_threshold = 0.5, }; } diff --git a/capsule-core/src/node.zig b/capsule-core/src/node.zig index fcc1521..b63c1aa 100644 --- a/capsule-core/src/node.zig +++ b/capsule-core/src/node.zig @@ -22,6 +22,7 @@ const qvl_store_mod = @import("qvl_store.zig"); const control_mod = @import("control.zig"); const quarantine_mod = @import("quarantine"); const circuit_mod = @import("circuit.zig"); +const relay_service_mod = @import("relay_service.zig"); const NodeConfig = config_mod.NodeConfig; const UTCP = utcp_mod.UTCP; @@ -73,6 +74,8 @@ pub const CapsuleNode = struct { sessions: std.HashMap(std.net.Address, PeerSession, AddressContext, std.hash_map.default_max_load_percentage), dht: DhtService, gateway: ?gateway_mod.Gateway, + relay_service: ?relay_service_mod.RelayService, + circuit_builder: ?circuit_mod.CircuitBuilder, storage: *StorageService, qvl_store: *QvlStore, control_socket: std.net.Server, @@ -178,6 +181,8 @@ pub const CapsuleNode = struct { .sessions = std.HashMap(std.net.Address, PeerSession, AddressContext, 80).init(allocator), .dht = undefined, // Initialized below .gateway = null, // Initialized below + .relay_service = null, // Initialized below + .circuit_builder = null, // Initialized below .storage = storage, .qvl_store = qvl_store, .control_socket = control_socket, @@ -193,6 +198,23 @@ pub const CapsuleNode = struct { self.gateway = gateway_mod.Gateway.init(allocator, &self.dht); std.log.info("Gateway Service: ENABLED", .{}); } + + // Initialize Relay Service + if (config.relay_enabled) { + self.relay_service = relay_service_mod.RelayService.init(allocator); + std.log.info("Relay Service: ENABLED", .{}); + } + + // Initialize Circuit Builder + if (config.relay_enabled) { + self.circuit_builder = circuit_mod.CircuitBuilder.init( + allocator, + qvl_store, + &self.peer_table, + ); + std.log.info("Circuit Builder: ENABLED (trust threshold: {d})", .{config.relay_trust_threshold}); + } + self.dht_timer = std.time.milliTimestamp(); self.qvl_timer = std.time.milliTimestamp(); @@ -213,6 +235,8 @@ pub const CapsuleNode = struct { self.peer_table.deinit(); self.sessions.deinit(); if (self.gateway) |*gw| gw.deinit(); + if (self.relay_service) |*rs| rs.deinit(); + // circuit_builder has no resources to free self.dht.deinit(); self.storage.deinit(); self.qvl_store.deinit(); diff --git a/capsule-core/src/relay_service.zig b/capsule-core/src/relay_service.zig new file mode 100644 index 0000000..38af370 --- /dev/null +++ b/capsule-core/src/relay_service.zig @@ -0,0 +1,102 @@ +//! Relay Service - Layer 2 Packet Forwarding +//! +//! This service handles incoming relay packets, unwraps them, +//! and forwards them to the next hop in the circuit. + +const std = @import("std"); +const relay_mod = @import("relay"); +const dht_mod = @import("dht"); + +pub const RelayService = struct { + allocator: std.mem.Allocator, + onion_builder: relay_mod.OnionBuilder, + + // Statistics + packets_forwarded: u64, + packets_dropped: u64, + + pub fn init(allocator: std.mem.Allocator) RelayService { + return .{ + .allocator = allocator, + .onion_builder = relay_mod.OnionBuilder.init(allocator), + .packets_forwarded = 0, + .packets_dropped = 0, + }; + } + + pub fn deinit(self: *RelayService) void { + _ = self; + } + + /// Forward a relay packet to the next hop + /// Returns the next hop address and the inner payload + pub fn forwardPacket( + self: *RelayService, + packet: relay_mod.RelayPacket, + shared_secret: [32]u8, + ) !struct { next_hop: [32]u8, payload: []u8 } { + // Unwrap the onion layer + const result = try self.onion_builder.unwrapLayer(packet, shared_secret); + + // Check if next_hop is all zeros (meaning we're the final destination) + const is_final = blk: { + for (result.next_hop) |b| { + if (b != 0) break :blk false; + } + break :blk true; + }; + + if (is_final) { + // We're the final destination - deliver locally + std.log.info("Relay: Final destination reached, delivering payload locally", .{}); + self.packets_dropped += 1; // Not actually dropped, just not forwarded + return result; + } + + // Forward to next hop + std.log.debug("Relay: Forwarding to next hop: {x}", .{std.fmt.fmtSliceHexLower(&result.next_hop)}); + self.packets_forwarded += 1; + + return result; + } + + /// Get relay statistics + pub fn getStats(self: *const RelayService) RelayStats { + return .{ + .packets_forwarded = self.packets_forwarded, + .packets_dropped = self.packets_dropped, + }; + } +}; + +pub const RelayStats = struct { + packets_forwarded: u64, + packets_dropped: u64, +}; + +test "RelayService: Forward packet" { + const allocator = std.testing.allocator; + + var relay_service = RelayService.init(allocator); + defer relay_service.deinit(); + + // Create a test packet + const payload = "Test payload"; + const next_hop = [_]u8{0xAB} ** 32; + const shared_secret = [_]u8{0} ** 32; + + var onion_builder = relay_mod.OnionBuilder.init(allocator); + var packet = try onion_builder.wrapLayer(payload, next_hop, shared_secret); + defer packet.deinit(allocator); + + // Forward the packet + const result = try relay_service.forwardPacket(packet, shared_secret); + defer allocator.free(result.payload); + + try std.testing.expectEqualSlices(u8, &next_hop, &result.next_hop); + try std.testing.expectEqualSlices(u8, payload, result.payload); + + // Check stats + const stats = relay_service.getStats(); + try std.testing.expectEqual(@as(u64, 1), stats.packets_forwarded); +}