zcatp2p/API.md
reugenio 7e5b16ee15 Inicial: biblioteca zcatp2p para comunicación P2P segura
- Especificación completa del protocolo (PROTOCOL.md)
- Referencia de API (API.md)
- Implementación crypto: SHA256, ChaCha20-Poly1305
- Device ID con Base32 y verificación Luhn32
- Framing de mensajes (HELLO, PING, DATA, etc.)
- Discovery local UDP broadcast
- Estructura de conexiones y estados
- Build system para Zig 0.15.2

Pendiente: TLS 1.3, STUN, Global Discovery HTTPS, Relay

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 01:06:30 +01:00

14 KiB

zcatp2p - API Reference

1. Inicialización y Configuración

1.1 Tipos Principales

/// Identificador único de dispositivo (32 bytes)
pub const DeviceId = [32]u8;

/// Representación corta del Device ID (primeros 8 bytes)
pub const ShortId = u64;

/// Configuración del nodo P2P
pub const Config = struct {
    /// Nombre del dispositivo (mostrado a otros peers)
    device_name: []const u8 = "zcatp2p",

    /// Puerto para conexiones entrantes (0 = aleatorio)
    listen_port: u16 = 22000,

    /// Habilitar discovery local (LAN)
    local_discovery: bool = true,

    /// Servidores de discovery global (vacío = deshabilitado)
    global_discovery_servers: []const []const u8 = &.{},

    /// Servidores STUN para NAT traversal
    stun_servers: []const []const u8 = &.{
        "stun.l.google.com:19302",
        "stun.syncthing.net:3478",
    },

    /// Servidores relay (vacío = deshabilitado)
    relay_servers: []const []const u8 = &.{},

    /// Directorio para almacenar certificado y configuración
    data_dir: []const u8,

    /// Compresión LZ4 habilitada
    compression: bool = true,

    /// Callback cuando se descubre un nuevo dispositivo
    on_device_discovered: ?*const fn(DeviceId, []const []const u8) void = null,

    /// Callback cuando se recibe un mensaje
    on_message_received: ?*const fn(*Connection, Message) void = null,

    /// Callback cuando cambia el estado de conexión
    on_connection_state_changed: ?*const fn(*Connection, ConnectionState) void = null,
};

/// Estado de una conexión
pub const ConnectionState = enum {
    connecting,
    connected,
    disconnecting,
    disconnected,
    error,
};

/// Mensaje recibido
pub const Message = struct {
    id: u32,
    content_type: []const u8,
    data: []const u8,
    timestamp: i64,
};

/// Información sobre un peer conectado
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,
};

/// Error codes
pub const Error = error{
    AlreadyInitialized,
    NotInitialized,
    InvalidDeviceId,
    ConnectionFailed,
    ConnectionTimeout,
    ConnectionClosed,
    PeerNotFound,
    CertificateError,
    TlsError,
    ProtocolError,
    CompressionError,
    OutOfMemory,
    IoError,
    InvalidConfig,
};

1.2 Instancia P2P

pub const P2P = struct {
    /// Inicializa el sistema P2P
    /// - Carga o genera certificado
    /// - Inicia listeners
    /// - Inicia discovery
    pub fn init(allocator: std.mem.Allocator, config: Config) Error!*P2P;

    /// Libera todos los recursos
    pub fn deinit(self: *P2P) void;

    /// Obtiene el Device ID local
    pub fn getDeviceId(self: *P2P) DeviceId;

    /// Obtiene el Device ID como string (formato XXXXXXX-XXXXXXX-...)
    pub fn getDeviceIdString(self: *P2P, buf: []u8) []const u8;

    /// Parsea un Device ID desde string
    pub fn parseDeviceId(str: []const u8) Error!DeviceId;

    /// Compara dos Device IDs
    pub fn deviceIdEquals(a: DeviceId, b: DeviceId) bool;

    /// Obtiene el Short ID (para logging)
    pub fn getShortId(device_id: DeviceId) ShortId;
};

2. Gestión de Conexiones

pub const P2P = struct {
    // ... (continuación)

    /// Conecta a un dispositivo por su Device ID
    /// Busca automáticamente la dirección usando discovery
    pub fn connect(self: *P2P, device_id: DeviceId) Error!*Connection;

    /// Conecta a una dirección específica
    pub fn connectAddress(self: *P2P, address: []const u8) Error!*Connection;

    /// Desconecta de un peer
    pub fn disconnect(self: *P2P, device_id: DeviceId) void;

    /// Obtiene una conexión existente
    pub fn getConnection(self: *P2P, device_id: DeviceId) ?*Connection;

    /// Lista todas las conexiones activas
    pub fn getConnections(self: *P2P, buf: []*Connection) []const *Connection;

    /// Número de conexiones activas
    pub fn connectionCount(self: *P2P) usize;

    /// Información de un peer
    pub fn getPeerInfo(self: *P2P, device_id: DeviceId) ?PeerInfo;
};

pub const Connection = struct {
    /// Device ID del peer
    pub fn getDeviceId(self: *Connection) DeviceId;

    /// Estado actual de la conexión
    pub fn getState(self: *Connection) ConnectionState;

    /// Información del peer
    pub fn getPeerInfo(self: *Connection) PeerInfo;

    /// Envía datos al peer
    /// Retorna el message_id asignado
    pub fn send(
        self: *Connection,
        content_type: []const u8,
        data: []const u8,
    ) Error!u32;

    /// Envía datos y espera confirmación
    pub fn sendAndWait(
        self: *Connection,
        content_type: []const u8,
        data: []const u8,
        timeout_ms: u32,
    ) Error!void;

    /// Cierra la conexión
    pub fn close(self: *Connection) void;

    /// Cierra con motivo
    pub fn closeWithReason(self: *Connection, reason: []const u8) void;

    /// Espera hasta que la conexión esté establecida
    pub fn waitConnected(self: *Connection, timeout_ms: u32) Error!void;

    /// Verifica si la conexión está activa
    pub fn isConnected(self: *Connection) bool;
};

3. Discovery

pub const P2P = struct {
    // ... (continuación)

    /// Busca un dispositivo por su ID
    /// Retorna lista de direcciones conocidas
    pub fn lookup(
        self: *P2P,
        device_id: DeviceId,
        buf: [][]const u8,
    ) Error![]const []const u8;

    /// Fuerza un anuncio local inmediato
    pub fn announceLocal(self: *P2P) void;

    /// Fuerza un anuncio global inmediato
    pub fn announceGlobal(self: *P2P) Error!void;

    /// Obtiene dispositivos descubiertos en la LAN
    pub fn getLocalDevices(
        self: *P2P,
        buf: []DeviceId,
    ) []const DeviceId;

    /// Añade manualmente una dirección conocida para un dispositivo
    pub fn addKnownAddress(
        self: *P2P,
        device_id: DeviceId,
        address: []const u8,
    ) void;

    /// Elimina direcciones conocidas de un dispositivo
    pub fn clearKnownAddresses(self: *P2P, device_id: DeviceId) void;
};

4. NAT y Relay

pub const P2P = struct {
    // ... (continuación)

    /// Obtiene la IP externa descubierta por STUN
    pub fn getExternalAddress(self: *P2P) ?[]const u8;

    /// Obtiene el tipo de NAT detectado
    pub fn getNatType(self: *P2P) NatType;

    /// Verifica si estamos conectados a algún relay
    pub fn isRelayConnected(self: *P2P) bool;

    /// Obtiene la dirección de relay actual
    pub fn getRelayAddress(self: *P2P) ?[]const u8;
};

pub const NatType = enum {
    unknown,
    none,           // Sin NAT (IP pública)
    full_cone,      // Hole-punchable
    restricted,     // Hole-punchable
    port_restricted,// Hole-punchable
    symmetric,      // No hole-punchable, necesita relay
    blocked,        // Sin conectividad UDP
};

5. Callbacks y Eventos

/// Callback type para dispositivo descubierto
pub const OnDeviceDiscovered = *const fn(
    device_id: DeviceId,
    addresses: []const []const u8,
) void;

/// Callback type para mensaje recibido
pub const OnMessageReceived = *const fn(
    connection: *Connection,
    message: Message,
) void;

/// Callback type para cambio de estado de conexión
pub const OnConnectionStateChanged = *const fn(
    connection: *Connection,
    old_state: ConnectionState,
    new_state: ConnectionState,
) void;

/// Callback type para error
pub const OnError = *const fn(
    error_code: Error,
    message: []const u8,
) void;

pub const P2P = struct {
    // ... (continuación)

    /// Registra callback para dispositivo descubierto
    pub fn setOnDeviceDiscovered(self: *P2P, cb: ?OnDeviceDiscovered) void;

    /// Registra callback para mensaje recibido
    pub fn setOnMessageReceived(self: *P2P, cb: ?OnMessageReceived) void;

    /// Registra callback para cambio de estado
    pub fn setOnConnectionStateChanged(self: *P2P, cb: ?OnConnectionStateChanged) void;

    /// Registra callback para errores
    pub fn setOnError(self: *P2P, cb: ?OnError) void;
};

6. Utilidades

pub const utils = struct {
    /// Convierte Device ID a string
    pub fn deviceIdToString(id: DeviceId, buf: []u8) []const u8;

    /// Parsea Device ID desde string
    pub fn stringToDeviceId(str: []const u8) Error!DeviceId;

    /// Calcula el dígito de verificación Luhn
    pub fn luhn32(str: []const u8) u8;

    /// Verifica un Device ID string
    pub fn verifyDeviceIdString(str: []const u8) bool;

    /// Genera un certificado auto-firmado
    pub fn generateCertificate(
        allocator: std.mem.Allocator,
        common_name: []const u8,
    ) Error!Certificate;

    /// Carga un certificado desde archivo
    pub fn loadCertificate(path: []const u8) Error!Certificate;

    /// Guarda un certificado a archivo
    pub fn saveCertificate(cert: Certificate, path: []const u8) Error!void;
};

7. Ejemplo de Uso Completo

const std = @import("std");
const p2p = @import("zcatp2p");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Configuración
    const config = p2p.Config{
        .device_name = "Simifactu-Empresa-A",
        .listen_port = 22000,
        .local_discovery = true,
        .data_dir = "/home/user/.simifactu/p2p",
        .on_message_received = handleMessage,
        .on_device_discovered = handleDiscovery,
    };

    // Inicializar
    var node = try p2p.P2P.init(allocator, config);
    defer node.deinit();

    // Mostrar nuestro Device ID
    var id_buf: [64]u8 = undefined;
    const my_id = node.getDeviceIdString(&id_buf);
    std.debug.print("Mi Device ID: {s}\n", .{my_id});

    // Conectar a un peer conocido
    const peer_id_str = "ABCDEFG-HIJKLMN-OPQRSTU-VWXYZ23-4567ABC-DEFGHIJ-KLMNOPQ-RSTUVWX";
    const peer_id = try p2p.P2P.parseDeviceId(peer_id_str);

    var conn = try node.connect(peer_id);
    try conn.waitConnected(30000); // 30 segundos timeout

    // Enviar una factura
    const factura_data = @embedFile("factura.xml");
    const msg_id = try conn.send("application/x-simifactu-invoice", factura_data);
    std.debug.print("Factura enviada, message_id: {}\n", .{msg_id});

    // Esperar respuesta...
    // (En producción usaríamos el callback on_message_received)

    // Cerrar
    conn.close();
}

fn handleMessage(conn: *p2p.Connection, msg: p2p.Message) void {
    std.debug.print("Mensaje recibido de {}: type={s}, size={}\n", .{
        p2p.P2P.getShortId(conn.getDeviceId()),
        msg.content_type,
        msg.data.len,
    });

    // Procesar según content_type
    if (std.mem.eql(u8, msg.content_type, "application/x-simifactu-invoice")) {
        // Procesar factura recibida
        processInvoice(msg.data);
    }
}

fn handleDiscovery(device_id: p2p.DeviceId, addresses: []const []const u8) void {
    std.debug.print("Dispositivo descubierto: {} en {} direcciones\n", .{
        p2p.P2P.getShortId(device_id),
        addresses.len,
    });
}

fn processInvoice(data: []const u8) void {
    // ... procesar factura
    _ = data;
}

8. Integración con Simifactu

8.1 Content Types Recomendados

pub const ContentTypes = struct {
    pub const invoice = "application/x-simifactu-invoice";
    pub const invoice_ack = "application/x-simifactu-invoice-ack";
    pub const certificate = "application/x-simifactu-certificate";
    pub const verifactu = "application/x-simifactu-verifactu";
    pub const query = "application/x-simifactu-query";
    pub const response = "application/x-simifactu-response";
};

8.2 Flujo Típico

Empresa A                                    Empresa B
    │                                            │
    │  1. connect(device_id_B)                   │
    │────────────────────────────────────────────>│
    │                                            │
    │  2. HELLO exchange                         │
    │<═══════════════════════════════════════════>│
    │                                            │
    │  3. send(invoice, factura_xml)             │
    │────────────────────────────────────────────>│
    │                                            │
    │  4. DATA_ACK                               │
    │<────────────────────────────────────────────│
    │                                            │
    │  5. send(invoice_ack, confirmacion)        │
    │<────────────────────────────────────────────│
    │                                            │
    │  6. DATA_ACK                               │
    │────────────────────────────────────────────>│
    │                                            │
    │  7. close()                                │
    │────────────────────────────────────────────>│
    │                                            │

9. Thread Safety

  • P2P.init() y P2P.deinit() deben llamarse desde el mismo thread
  • Todas las demás funciones son thread-safe
  • Los callbacks pueden ser llamados desde cualquier thread
  • Se recomienda no hacer operaciones bloqueantes en callbacks

10. Gestión de Memoria

  • P2P.init() asigna memoria usando el allocator proporcionado
  • P2P.deinit() libera toda la memoria
  • Los strings retornados son válidos hasta la siguiente llamada
  • Los mensajes recibidos en callbacks son válidos solo durante el callback
  • Para retener datos, copiarlos a memoria propia