⎇ 42fee6fcbc7eb98267f5abfad803018bf43224a9
renaser/akasha/src/lib.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(renaser): Fase 20 — Akasha Over Ether (grafo distribuido)
1// =============================================================================
2// renaser :: akasha — Akasha Over Ether (AoE)
3// -----------------------------------------------------------------------------
4// Fase 19 demostro que un app del userspace puede inyectar frames Ethernet
5// crudos al cable —`pregon` gritando «hola» con su EtherType experimental—,
6// pero ese saludo era texto plano, sin estructura. La Fase 20 da el siguiente
7// paso natural: **fundar un protocolo nativo del ecosistema** para que
8// renaser hable consigo mismo a traves de la red, sin TCP, sin IP, sin las
9// sobrecargas de cabecera de los años 80.
10//
11// AoE viaja DIRECTAMENTE sobre Ethernet (`EtherType = 0x88B5`, rango
12// experimental reservado por IEEE para uso local). Cada frame transporta un
13// `MensajeAkasha` serializado con `postcard` —el mismo codec que ya usa
14// `formato` para el grafo en disco—. Tres mensajes bastan:
15//
16// 1. `SolicitarObjeto(id)` — pide un nodo del grafo por su hash BLAKE3.
17// 2. `ProveedorObjeto(id, d)` — responde con el payload binario del nodo.
18// 3. `AnunciarRaiz(id)` — difunde el hash de la raiz del sistema.
19//
20// Con esto basta para extender el grafo de objetos —direccionado por
21// contenido, inmutable, ya BLAKE3— a OTRAS maquinas renaser que escuchen en
22// la misma red de capa-2. El receptor de un `AnunciarRaiz` puede comparar
23// hashes, descubrir que le falta un nodo, pedirlo con `SolicitarObjeto` y
24// ensamblar el grafo del par. El grafo deja de ser una propiedad LOCAL del
25// disco y se vuelve una propiedad DISTRIBUIDA del cable.
26//
27// Esta crate es la unica verdad del protocolo. Es un nucleo `#![no_std]` —el
28// kernel bare-metal la enlaza—; cualquier app, daemon u orquestador que
29// hable AoE consume estos mismos tipos. Ningun otro modulo redefine ni un
30// byte del trazado del frame.
31// =============================================================================
32
33#![no_std]
34#![deny(unsafe_op_in_unsafe_fn)]
35
36extern crate alloc;
37
38use alloc::vec::Vec;
39
40use serde::{Deserialize, Serialize};
41
42// =============================================================================
43// Constantes del protocolo en el cable
44// =============================================================================
45
46/// EtherType de AoE, rango experimental reservado por IEEE para uso local
47/// (0x88B5/0x88B6). renaser elige el primero. Cualquier frame del cable que
48/// no porte este EtherType NO es Akasha, y se entrega al userspace.
49pub const ETHER_TYPE_AKASHA: u16 = 0x88B5;
50
51/// Tamaño de la cabecera Ethernet, en bytes (dst + src + ethertype).
52pub const CABECERA_ETHERNET: usize = 14;
53
54/// Maximo del payload AoE serializado, en bytes. Acotado para que el frame
55/// completo (cabecera + payload) no exceda una MTU Ethernet sin VLAN
56/// (1500 - 14 = 1486) y se transmita SIN fragmentar. Si un mensaje no cabe,
57/// el llamante debe partirlo en varios objetos del grafo y referirse a ellos.
58pub const MAX_PAYLOAD_AKASHA: usize = 1486;
59
60/// MAC de broadcast — la difunde todo Ethernet, equivalente a `255.255.255.255`
61/// en IPv4 pero en capa-2 pura.
62pub const MAC_BROADCAST: [u8; 6] = [0xff; 6];
63
64/// El identificador de un objeto del grafo: el hash BLAKE3 de su forma
65/// serializada. En un almacen direccionado por contenido, la identidad ES el
66/// contenido. Coincide byte a byte con `formato::Hash` —AoE habla el mismo
67/// idioma de identidad que el grafo en disco—.
68pub type ObjectId = [u8; 32];
69
70/// Una direccion MAC, en seis bytes.
71pub type Mac = [u8; 6];
72
73// =============================================================================
74// El mensaje
75// =============================================================================
76
77/// Un mensaje AoE — la unidad de protocolo que viaja en un frame de
78/// `ETHER_TYPE_AKASHA`. Tres variantes bastan para fundar un grafo distribuido:
79///
80/// - `SolicitarObjeto` pregunta por un objeto identificado por su hash.
81/// - `ProveedorObjeto` responde con el payload binario del objeto. El
82/// receptor recompone el `formato::Objeto` aplicando su deserializador a
83/// `payload` y verificando que `blake3(payload) == id`.
84/// - `AnunciarRaiz` difunde el hash de la raiz —el ancla del sistema
85/// actual—. Sirve como faro: quien escuche y carezca de ese nodo en su
86/// grafo local puede iniciar una solicitud.
87///
88/// La codificacion `postcard` es estable y compacta: un `SolicitarObjeto`
89/// ocupa 33 bytes (1 de variante + 32 de hash) en el cable.
90#[derive(Clone, Debug, Serialize, Deserialize)]
91pub enum MensajeAkasha {
92 /// Solicito el objeto identificado por este hash.
93 SolicitarObjeto(ObjectId),
94 /// Aqui esta el payload binario del objeto identificado por este hash.
95 /// El payload es la forma serializada `postcard` de un `formato::Objeto`,
96 /// y el receptor DEBE verificar que `blake3(payload) == id` antes de
97 /// confiar en el contenido.
98 ProveedorObjeto(ObjectId, Vec<u8>),
99 /// Difundo el hash de mi raiz actual — quien me escuche y le falte este
100 /// nodo en su grafo local puede pedirmelo.
101 AnunciarRaiz(ObjectId),
102}
103
104/// El motivo por el que un frame AoE no se pudo componer o analizar.
105#[derive(Clone, Copy, Debug)]
106pub enum ErrorAkasha {
107 /// El payload serializado excede `MAX_PAYLOAD_AKASHA`.
108 PayloadDemasiadoLargo,
109 /// El frame en el cable es mas corto que la cabecera Ethernet.
110 FrameDemasiadoCorto,
111 /// El EtherType del frame no es `ETHER_TYPE_AKASHA`.
112 EtherTypeAjeno,
113 /// `postcard` no supo deserializar el payload (basura, version distinta,
114 /// otro protocolo experimental ajeno).
115 PayloadInvalido,
116}
117
118// =============================================================================
119// Composicion y analisis de frames
120// =============================================================================
121
122/// Compone un frame Ethernet completo —cabecera (14 bytes) + payload AoE
123/// (`postcard(mensaje)`)— listo para entregar al driver. El llamante elige el
124/// destino (`MAC_BROADCAST` para difundir) y firma el frame con su propia
125/// MAC. Devuelve el frame completo, o un error si el mensaje serializado
126/// excede `MAX_PAYLOAD_AKASHA`.
127pub fn componer_frame(
128 src: Mac,
129 dst: Mac,
130 mensaje: &MensajeAkasha,
131) -> Result<Vec<u8>, ErrorAkasha> {
132 let payload =
133 postcard::to_allocvec(mensaje).map_err(|_| ErrorAkasha::PayloadDemasiadoLargo)?;
134 if payload.len() > MAX_PAYLOAD_AKASHA {
135 return Err(ErrorAkasha::PayloadDemasiadoLargo);
136 }
137 let mut frame = Vec::with_capacity(CABECERA_ETHERNET + payload.len());
138 frame.extend_from_slice(&dst);
139 frame.extend_from_slice(&src);
140 frame.extend_from_slice(ÐER_TYPE_AKASHA.to_be_bytes());
141 frame.extend_from_slice(&payload);
142 Ok(frame)
143}
144
145/// Analiza un frame Ethernet entrante. Si su EtherType es `ETHER_TYPE_AKASHA`
146/// y el payload deserializa como un `MensajeAkasha`, devuelve la MAC de origen
147/// y el mensaje. En cualquier otro caso —EtherType ajeno, frame truncado,
148/// `postcard` no le encuentra sentido al payload— devuelve un `ErrorAkasha`
149/// que distingue el motivo: el llamante puede entonces decidir si lo entrega
150/// al userspace (otro protocolo) o lo descarta (basura).
151pub fn analizar_frame(frame: &[u8]) -> Result<(Mac, MensajeAkasha), ErrorAkasha> {
152 if frame.len() < CABECERA_ETHERNET {
153 return Err(ErrorAkasha::FrameDemasiadoCorto);
154 }
155 let etype = u16::from_be_bytes([frame[12], frame[13]]);
156 if etype != ETHER_TYPE_AKASHA {
157 return Err(ErrorAkasha::EtherTypeAjeno);
158 }
159 let mut src: Mac = [0; 6];
160 src.copy_from_slice(&frame[6..12]);
161 let payload = &frame[CABECERA_ETHERNET..];
162 let mensaje: MensajeAkasha =
163 postcard::from_bytes(payload).map_err(|_| ErrorAkasha::PayloadInvalido)?;
164 Ok((src, mensaje))
165}
166
167// =============================================================================
168// Pruebas
169// =============================================================================
170
171#[cfg(test)]
172mod pruebas {
173 use super::*;
174 use alloc::vec;
175
176 const MAC_A: Mac = [0x52, 0x54, 0x00, 0x12, 0x34, 0x56];
177 const MAC_B: Mac = [0x52, 0x54, 0x00, 0xAB, 0xCD, 0xEF];
178 const HASH_X: ObjectId = [0x11; 32];
179
180 #[test]
181 fn componer_y_analizar_solicitud_es_simetrico() {
182 let msg = MensajeAkasha::SolicitarObjeto(HASH_X);
183 let frame = componer_frame(MAC_A, MAC_B, &msg).unwrap();
184 // Cabecera: dst + src + ethertype.
185 assert_eq!(&frame[0..6], &MAC_B);
186 assert_eq!(&frame[6..12], &MAC_A);
187 assert_eq!(&frame[12..14], ÐER_TYPE_AKASHA.to_be_bytes());
188 let (src, rec) = analizar_frame(&frame).unwrap();
189 assert_eq!(src, MAC_A);
190 match rec {
191 MensajeAkasha::SolicitarObjeto(id) => assert_eq!(id, HASH_X),
192 otro => panic!("variante inesperada: {otro:?}"),
193 }
194 }
195
196 #[test]
197 fn anuncio_de_raiz_viaja_compacto() {
198 let frame =
199 componer_frame(MAC_A, MAC_BROADCAST, &MensajeAkasha::AnunciarRaiz(HASH_X))
200 .unwrap();
201 // 14 cabecera + 1 byte variante + 32 hash = 47 bytes en el cable.
202 assert_eq!(frame.len(), 47);
203 assert_eq!(&frame[0..6], &MAC_BROADCAST);
204 }
205
206 #[test]
207 fn proveedor_lleva_payload_arbitrario() {
208 let payload = vec![1u8, 2, 3, 4, 5, 6, 7, 8];
209 let msg = MensajeAkasha::ProveedorObjeto(HASH_X, payload.clone());
210 let frame = componer_frame(MAC_A, MAC_B, &msg).unwrap();
211 let (_, rec) = analizar_frame(&frame).unwrap();
212 match rec {
213 MensajeAkasha::ProveedorObjeto(id, p) => {
214 assert_eq!(id, HASH_X);
215 assert_eq!(p, payload);
216 }
217 otro => panic!("variante inesperada: {otro:?}"),
218 }
219 }
220
221 #[test]
222 fn frame_demasiado_corto_se_distingue() {
223 assert!(matches!(
224 analizar_frame(&[0u8; 8]),
225 Err(ErrorAkasha::FrameDemasiadoCorto)
226 ));
227 }
228
229 #[test]
230 fn ethertype_ajeno_se_distingue() {
231 // Cabecera valida pero EtherType de IPv4 (0x0800).
232 let mut frame = [0u8; CABECERA_ETHERNET];
233 frame[12] = 0x08;
234 frame[13] = 0x00;
235 assert!(matches!(
236 analizar_frame(&frame),
237 Err(ErrorAkasha::EtherTypeAjeno)
238 ));
239 }
240
241 #[test]
242 fn payload_invalido_se_distingue() {
243 // EtherType correcto, pero el payload es basura para postcard.
244 let mut frame = [0u8; CABECERA_ETHERNET + 4];
245 frame[12..14].copy_from_slice(ÐER_TYPE_AKASHA.to_be_bytes());
246 frame[14..].copy_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
247 assert!(matches!(
248 analizar_frame(&frame),
249 Err(ErrorAkasha::PayloadInvalido)
250 ));
251 }
252}