53dbdf0f1d
Workspace en 4 ejes (core/modules/apps/shared):
- core/: 24 crates de arje (Init systemd-compatible: ente-card, ente-zero,
ente-kernel, ente-bus, ente-cas, ente-soma, ente-wasm, ente-snapshot,
ente-brain, ente-echo, ente-policy-provider, + 12 crates *-compat)
- modules/semantic_dht/: 5 crates de minga (minga-core con AST/CAS/MST,
minga-p2p con libp2p Kad, minga-store, minga-vfs, minga-cli)
- modules/ui_engine/: 11 crates de yahweh (libs/{core,theme,bus,providers},
widgets/{tree,splitter,tabs,tiled,container_core,text_input})
- apps/: 5 crates de yahweh (file_explorer, database_explorer, text_viewer,
image_viewer, yahweh-shell)
- shared_wit/protocol.wit: handshake/lifecycle inicial
Cargo.toml unificado: thiserror bumped a 2 (transparente para arje), tokio
"full", paths intra-workspace de yahweh redirigidos a su nueva ubicación.
cargo check --workspace: 0 errores, 17 warnings (dead code preexistente).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
3.9 KiB
Rust
95 lines
3.9 KiB
Rust
//! Mensajes del protocolo de sincronización (versión recursiva sobre
|
|
//! la estructura del MST).
|
|
//!
|
|
//! El protocolo es simétrico — ambos peers ejecutan el mismo rol y
|
|
//! emiten los mismos mensajes — y consta de seis tipos:
|
|
//!
|
|
//! 1. `Hello { root_subtree_hash }` anuncia el hash Merkle del MST raíz
|
|
//! del emisor. Si ambos hashes coinciden, los dos repos son idénticos
|
|
//! y la sincronización termina sin un solo byte adicional.
|
|
//!
|
|
//! 2. `ProbeReq { subtree_hash }` solicita la **estructura** (level +
|
|
//! keys + child_hashes) de un subárbol previamente anunciado por el
|
|
//! otro peer. Es lo que permite descender el árbol del peer paso a
|
|
//! paso, podando ramas idénticas por igualdad de hash.
|
|
//!
|
|
//! 3. `ProbeRes { subtree_hash, probe }` responde con el `NodeProbe`,
|
|
//! o `None` si el subárbol era el vacío. Cada subárbol que el peer
|
|
//! no reconoce dispara un `ProbeReq` recursivo; cuando el peer ya
|
|
//! tiene un subárbol con el mismo hash, la rama se poda.
|
|
//!
|
|
//! 4. `Fetch { hash }` y `Deliver { hash, stored }` mueven los nodos
|
|
//! propiamente dichos. El receptor del `Deliver` **verifica
|
|
//! criptográficamente** que `hash_stored(stored) == hash` antes de
|
|
//! insertar — un peer malicioso no puede colar un `StoredNode`
|
|
//! distinto bajo un hash anunciado.
|
|
//!
|
|
//! 5. `Done` cierra el lado del emisor: ya recibió el `Hello` del otro,
|
|
//! no tiene probes ni fetches pendientes. Cuando ambos `Done`s han
|
|
//! cruzado, la sesión termina con ambos repos convergentes.
|
|
|
|
use minga_core::{Attestation, ContentHash, Did, NodeProbe, Signature, StoredNode};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub enum Message {
|
|
/// Reto de session-handshake: 32 bytes aleatorios. Cada peer envía
|
|
/// uno al inicio. El otro lado lo incrustará en el payload del
|
|
/// `Hello` que firme con su llave privada — así un `Hello`
|
|
/// capturado en una sesión no puede replayearse en otra (que
|
|
/// tendrá un nonce distinto).
|
|
Challenge {
|
|
nonce: [u8; 32],
|
|
},
|
|
|
|
/// Saludo autenticado anti-replay: el emisor presenta su DID, el
|
|
/// hash del subárbol raíz de su MST, y una firma sobre el payload
|
|
/// `(peer_did || root_subtree_hash || nonce_recibido_del_peer)`.
|
|
/// El receptor reconstruye el payload con su PROPIO nonce (el que
|
|
/// envió en su Challenge) y verifica con la llave pública del
|
|
/// peer. Sin Challenge previo no hay Hello válido posible.
|
|
Hello {
|
|
peer_did: Did,
|
|
root_subtree_hash: ContentHash,
|
|
signature: Signature,
|
|
},
|
|
ProbeReq {
|
|
subtree_hash: ContentHash,
|
|
},
|
|
ProbeRes {
|
|
subtree_hash: ContentHash,
|
|
probe: Option<NodeProbe>,
|
|
},
|
|
Fetch {
|
|
hash: ContentHash,
|
|
},
|
|
Deliver {
|
|
hash: ContentHash,
|
|
stored: StoredNode,
|
|
},
|
|
/// Empuje de atestaciones: el emisor entrega al peer las pruebas
|
|
/// criptográficas de autoría que conoce. Cada `Attestation` es
|
|
/// auto-verificable (firma + autor + contenido), así que el
|
|
/// receptor puede validar y mezclar sin confiar en la palabra del
|
|
/// remitente. Se envían tras el `Hello` autenticado para que el
|
|
/// peer verifique la identidad del remitente antes de procesarlas.
|
|
AttestPush {
|
|
attestations: Vec<Attestation>,
|
|
},
|
|
Done,
|
|
}
|
|
|
|
impl Message {
|
|
/// Codifica el mensaje a bytes vía postcard. Diseñado para
|
|
/// transferir sobre cualquier transporte que mueva `Vec<u8>`.
|
|
/// Postcard es compacto, sin overhead de schema runtime.
|
|
pub fn encode(&self) -> Vec<u8> {
|
|
postcard::to_allocvec(self).expect("postcard encoding cannot fail for our types")
|
|
}
|
|
|
|
/// Decodifica bytes a un `Message`. `Err` si los bytes son
|
|
/// malformados o no representan un `Message` válido.
|
|
pub fn decode(bytes: &[u8]) -> Result<Self, postcard::Error> {
|
|
postcard::from_bytes(bytes)
|
|
}
|
|
}
|