//! Módulo de conexión - Gestión de conexiones P2P //! //! Maneja conexiones TLS, keepalives, y multiplexación de mensajes. const std = @import("std"); const identity = @import("identity.zig"); const protocol = @import("protocol.zig"); const discovery = @import("discovery.zig"); pub const DeviceId = identity.DeviceId; /// Configuración del nodo P2P pub const Config = struct { /// Nombre del dispositivo device_name: []const u8 = "zcatp2p", /// Puerto para conexiones entrantes listen_port: u16 = 22000, /// Habilitar discovery local local_discovery: bool = true, /// Servidores de discovery global global_discovery_servers: []const []const u8 = &.{}, /// Servidores STUN stun_servers: []const []const u8 = &.{ "stun.l.google.com:19302", "stun.syncthing.net:3478", }, /// Servidores relay relay_servers: []const []const u8 = &.{}, /// Directorio de datos data_dir: []const u8, /// Compresión habilitada compression: bool = true, /// Callbacks on_device_discovered: ?*const fn (DeviceId, []const []const u8) void = null, on_message_received: ?*const fn (*Connection, protocol.Message) void = null, on_connection_state_changed: ?*const fn (*Connection, ConnectionState) void = null, }; /// Estado de conexión pub const ConnectionState = enum { connecting, connected, disconnecting, disconnected, @"error", }; /// Tipo de NAT detectado pub const NatType = enum { unknown, none, full_cone, restricted, port_restricted, symmetric, blocked, }; /// Errores del módulo pub const Error = error{ AlreadyInitialized, NotInitialized, InvalidDeviceId, ConnectionFailed, ConnectionTimeout, ConnectionClosed, PeerNotFound, CertificateError, TlsError, ProtocolError, CompressionError, OutOfMemory, IoError, InvalidConfig, }; /// Información de un peer pub const PeerInfo = struct { device_id: DeviceId, device_name: []const u8, client_name: []const u8, client_version: []const u8, addresses: []const []const u8, connected_at: i64, is_local: bool, bytes_sent: u64, bytes_received: u64, }; /// Conexión con un peer pub const Connection = struct { allocator: std.mem.Allocator, device_id: DeviceId, state: ConnectionState, peer_info: ?PeerInfo, socket: ?std.posix.socket_t, next_message_id: u32, bytes_sent: u64, bytes_received: u64, connected_at: i64, pub fn init(allocator: std.mem.Allocator, device_id: DeviceId) Connection { return .{ .allocator = allocator, .device_id = device_id, .state = .disconnected, .peer_info = null, .socket = null, .next_message_id = 1, .bytes_sent = 0, .bytes_received = 0, .connected_at = 0, }; } pub fn deinit(self: *Connection) void { if (self.socket) |sock| { std.posix.close(sock); } } pub fn getDeviceId(self: *Connection) DeviceId { return self.device_id; } pub fn getState(self: *Connection) ConnectionState { return self.state; } pub fn getPeerInfo(self: *Connection) ?PeerInfo { return self.peer_info; } pub fn isConnected(self: *Connection) bool { return self.state == .connected; } /// Envía datos al peer pub fn send( self: *Connection, content_type: []const u8, data: []const u8, ) Error!u32 { if (self.state != .connected) return Error.ConnectionClosed; const msg_id = self.next_message_id; self.next_message_id += 1; const msg = protocol.DataMessage{ .message_id = msg_id, .content_type = content_type, .data = data, }; const encoded = msg.encode(self.allocator) catch return Error.OutOfMemory; defer self.allocator.free(encoded); // TODO: Enviar por socket con TLS self.bytes_sent += encoded.len; return msg_id; } /// Cierra la conexión pub fn close(self: *Connection) void { self.closeWithReason("closed by user"); } /// Cierra la conexión con motivo pub fn closeWithReason(self: *Connection, reason: []const u8) void { _ = reason; if (self.socket) |sock| { std.posix.close(sock); self.socket = null; } self.state = .disconnected; } /// Espera hasta que la conexión esté establecida pub fn waitConnected(self: *Connection, timeout_ms: u32) Error!void { _ = timeout_ms; if (self.state == .connected) return; if (self.state == .@"error" or self.state == .disconnected) { return Error.ConnectionFailed; } // TODO: Implementar espera con timeout return Error.ConnectionTimeout; } }; /// Instancia principal P2P pub const P2P = struct { allocator: std.mem.Allocator, config: Config, device_id: DeviceId, connections: std.AutoHashMapUnmanaged(DeviceId, *Connection), discovery_manager: discovery.DiscoveryManager, listener_socket: ?std.posix.socket_t, external_address: ?[]const u8, nat_type: NatType, pub fn init(allocator: std.mem.Allocator, config: Config) Error!*P2P { const self = allocator.create(P2P) catch return Error.OutOfMemory; errdefer allocator.destroy(self); // Generar o cargar Device ID // TODO: Implementar carga de certificado var device_id: DeviceId = undefined; std.crypto.random.bytes(&device_id); self.* = .{ .allocator = allocator, .config = config, .device_id = device_id, .connections = .{}, .discovery_manager = discovery.DiscoveryManager.init(allocator, device_id), .listener_socket = null, .external_address = null, .nat_type = .unknown, }; return self; } pub fn deinit(self: *P2P) void { // Cerrar todas las conexiones var iter = self.connections.iterator(); while (iter.next()) |entry| { entry.value_ptr.*.deinit(); self.allocator.destroy(entry.value_ptr.*); } self.connections.deinit(self.allocator); // Cerrar listener if (self.listener_socket) |sock| { std.posix.close(sock); } // Limpiar discovery self.discovery_manager.deinit(); // Limpiar external address if (self.external_address) |addr| { self.allocator.free(addr); } self.allocator.destroy(self); } /// Obtiene el Device ID local pub fn getDeviceId(self: *P2P) DeviceId { return self.device_id; } /// Obtiene el Device ID como string pub fn getDeviceIdString(self: *P2P, buf: []u8) []const u8 { return identity.deviceIdToString(self.device_id, buf); } /// Parsea un Device ID desde string pub fn parseDeviceId(str: []const u8) Error!DeviceId { return identity.stringToDeviceId(str) catch Error.InvalidDeviceId; } /// Compara dos Device IDs pub fn deviceIdEquals(a: DeviceId, b: DeviceId) bool { return identity.deviceIdEquals(a, b); } /// Obtiene el Short ID pub fn getShortId(device_id: DeviceId) identity.ShortId { return identity.getShortId(device_id); } /// Conecta a un dispositivo pub fn connect(self: *P2P, device_id: DeviceId) Error!*Connection { // Verificar si ya existe conexión if (self.connections.get(device_id)) |conn| { if (conn.isConnected()) return conn; } // Crear nueva conexión const conn = self.allocator.create(Connection) catch return Error.OutOfMemory; conn.* = Connection.init(self.allocator, device_id); conn.state = .connecting; self.connections.put(self.allocator, device_id, conn) catch { self.allocator.destroy(conn); return Error.OutOfMemory; }; // Buscar direcciones del peer const addresses = self.discovery_manager.lookup(device_id) catch null; if (addresses == null) { conn.state = .@"error"; return Error.PeerNotFound; } // TODO: Intentar conectar a las direcciones return conn; } /// Desconecta de un peer pub fn disconnect(self: *P2P, device_id: DeviceId) void { if (self.connections.get(device_id)) |conn| { conn.close(); } } /// Obtiene una conexión existente pub fn getConnection(self: *P2P, device_id: DeviceId) ?*Connection { return self.connections.get(device_id); } /// Número de conexiones activas pub fn connectionCount(self: *P2P) usize { var count: usize = 0; var iter = self.connections.iterator(); while (iter.next()) |entry| { if (entry.value_ptr.*.isConnected()) count += 1; } return count; } /// Obtiene la IP externa pub fn getExternalAddress(self: *P2P) ?[]const u8 { return self.external_address; } /// Obtiene el tipo de NAT pub fn getNatType(self: *P2P) NatType { return self.nat_type; } }; // ============================================================================= // Tests // ============================================================================= test "p2p init/deinit" { const allocator = std.testing.allocator; const p2p = try P2P.init(allocator, .{ .data_dir = "/tmp/zcatp2p-test", }); defer p2p.deinit(); try std.testing.expect(p2p.connectionCount() == 0); } test "connection init" { const allocator = std.testing.allocator; const device_id = [_]u8{0xab} ** 32; var conn = Connection.init(allocator, device_id); defer conn.deinit(); try std.testing.expect(conn.state == .disconnected); try std.testing.expect(!conn.isConnected()); }