- 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>
476 lines
14 KiB
Markdown
476 lines
14 KiB
Markdown
# zcatp2p - API Reference
|
|
|
|
## 1. Inicialización y Configuración
|
|
|
|
### 1.1 Tipos Principales
|
|
|
|
```zig
|
|
/// 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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
/// 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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
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
|