feat(renaser): Fase 20 — Akasha Over Ether (grafo distribuido)
Tres mensajes y un EtherType propio bastan para extender el grafo de
objetos —direccionado por contenido, ya BLAKE3— a otras maquinas
renaser que escuchen en la misma red de capa-2. Sin TCP, sin IP,
sin DNS.
Crate nueva 'akasha/' (no_std compartido, gemela de 'formato',
excluida del workspace):
- MensajeAkasha enum con SolicitarObjeto(id), ProveedorObjeto(id,
payload), AnunciarRaiz(id).
- Codec: postcard (mismo que ya usa el grafo en disco).
- EtherType: 0x88B5. MAX_PAYLOAD_AKASHA = 1486 (MTU sin fragmentar).
- Helpers componer_frame(src, dst, msg) y analizar_frame(bytes) que
distinguen EtherType ajeno, frame truncado y payload basura.
- 6 pruebas unitarias en verde.
Modulo nuevo 'kernel/src/akasha.rs' con tres oficios:
1. Demuxer (drenar_y_demultiplexar): drena la cola RX del dispositivo
virtio-net y demultiplexa: frames AoE con payload valido los
procesa el respondedor; el resto va a una cola del userspace que
'sys_net_recibir' ahora lee. Frames 0x88B5 con payload
no-postcard (saludo de pregon) se cuentan y tambien viajan al
userspace.
2. Atencion de mensajes (procesar):
- SolicitarObjeto(id): consulta almacen::recuperar; si tenemos el
objeto, respondemos ProveedorObjeto unicast con objeto.serializar()
y re-hashing de defensa en profundidad.
- ProveedorObjeto(id, payload): verifica blake3(payload)==id antes
de absorber con almacen::almacenar.
- AnunciarRaiz(id): si ignoramos el nodo, le solicitamos al emisor.
3. Faro periodico (difundir_raiz cada 5 s): broadcast del hash del
manifiesto actual. Cadencia medida contra reloj::milisegundos(),
no contra los awaits — el interprete wasmi de los apps degrada
la cadencia de EsperaFrame::await a varios cientos de ms, asi
que se mide contra el reloj monotono y los oficios per-fotograma
se enganchan al tic del compositor (cuyo latido es fiable).
Contadores ResumenAkasha (rx/tx por variante, descartados, cola del
usuario) listos para un futuro indicador AoE en la barra de tareas.
Cambios complementarios:
- sys_net_recibir lee de akasha::pop_usuario, no de
drivers::red::recibir_en (que queda #[allow(dead_code)] como
primitiva del driver para diagnostico).
- tarea_red queda corta: envia un ARP al gateway y termina. El
demuxer y el faro viven en el tic del compositor.
Verificacion:
- 'cargo test -p akasha' → 6 pruebas en verde.
- QEMU headless 60 s con -object filter-dump → 14 frames: 11
AnunciarRaiz (Δ promedio 5.86 s sobre 5.00 s de target), 2 ARP
y el pregon hello. Cada AnunciarRaiz lleva el hash del manifiesto
'2f3deadfcc7dae25..' en 33 bytes postcard sobre 47 bytes de frame.
- COM1 vuelca 'akasha :: ANUNCIO emitido :: raiz=2f3deadfcc7dae25..'
en cada disparo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1222,3 +1222,89 @@ misma capacidad a los apps: tres capacidades nuevas (`sys_net_mac`,
|
|||||||
- Captura de pantalla: la ventana `pregon :: voz hacia la red` aparece
|
- Captura de pantalla: la ventana `pregon :: voz hacia la red` aparece
|
||||||
teselada, su pestaña sale en la barra de tareas entre `bitacora` y
|
teselada, su pestaña sale en la barra de tareas entre `bitacora` y
|
||||||
`tonada`, y la consola anuncia `manifiesto :: 9 apps nacidas del grafo`.
|
`tonada`, y la consola anuncia `manifiesto :: 9 apps nacidas del grafo`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 20 — Akasha Over Ether: el grafo distribuido — 2026-05-23
|
||||||
|
|
||||||
|
La red dejo de ser un puro medio para mensajes ad-hoc y se vuelve el cable
|
||||||
|
por donde el GRAFO de objetos se extiende a otras maquinas renaser. Tres
|
||||||
|
mensajes bastan; `postcard` los serializa; el EtherType `0x88B5` los
|
||||||
|
transporta; ningun byte de TCP, IP, DNS o cabecera transporte de los anos 80.
|
||||||
|
|
||||||
|
### Añadido
|
||||||
|
- **Crate `akasha/`** — nucleo `#![no_std]` compartido (gemela de `formato`),
|
||||||
|
excluida del workspace anfitrion. Define `MensajeAkasha` con tres
|
||||||
|
variantes —`SolicitarObjeto(id)`, `ProveedorObjeto(id, payload)`,
|
||||||
|
`AnunciarRaiz(id)`—, las helpers `componer_frame(src, dst, msg)` y
|
||||||
|
`analizar_frame(bytes) -> Result<(Mac, MensajeAkasha)>`, y las constantes
|
||||||
|
`ETHER_TYPE_AKASHA = 0x88B5`, `MAX_PAYLOAD_AKASHA = 1486`. Seis pruebas
|
||||||
|
unitarias cubren el ida-y-vuelta de cada variante, la distincion entre
|
||||||
|
EtherType ajeno, frame truncado y payload basura.
|
||||||
|
- **Modulo `kernel/src/akasha.rs`** — el respondedor del nucleo. Tres oficios:
|
||||||
|
1. **Demuxer** (`drenar_y_demultiplexar`): drena la cola RX del dispositivo
|
||||||
|
virtio-net y demultiplexa: frames AoE con payload valido los procesa
|
||||||
|
`procesar`; el resto va a la cola del userspace.
|
||||||
|
2. **Atencion de mensajes** (`procesar`): `SolicitarObjeto` busca en
|
||||||
|
`almacen` y, si tiene el objeto, responde `ProveedorObjeto` unicast;
|
||||||
|
`ProveedorObjeto` verifica integridad (`blake3(payload) == id`) y
|
||||||
|
absorbe al grafo local con `almacen::almacenar`; `AnunciarRaiz` se
|
||||||
|
contabiliza y, si no tenemos el nodo, le solicita al emisor.
|
||||||
|
3. **Faro** (`difundir_raiz` cada 5 s): difunde por broadcast el hash del
|
||||||
|
manifiesto actual — el faro Akasha que delata nuestra presencia en la
|
||||||
|
red de capa-2.
|
||||||
|
- **Cola del userspace** — el demuxer empuja los frames no-AoE a una
|
||||||
|
`VecDeque<Vec<u8>>` con profundidad maxima 64; `sys_net_recibir` ahora
|
||||||
|
pasa por `akasha::pop_usuario(buf)` en lugar de leer directamente del
|
||||||
|
driver. Asi el kernel filtra Akasha sin disputarle paquetes al userspace.
|
||||||
|
- **`tic_compositor`** — el punto de entrada de los oficios per-fotograma de
|
||||||
|
AoE, llamado desde el tic del compositor (cuyo latido es fiable). El
|
||||||
|
demuxer corre en cada vuelta; el faro se dispara cuando el reloj monotono
|
||||||
|
cruza `INTERVALO_FARO_MS` (5 s).
|
||||||
|
- **Contadores** (`ResumenAkasha`): `rx_solicitudes`, `rx_proveedores`,
|
||||||
|
`rx_anuncios`, `tx_*` equivalentes, `rx_descartados`, `usuario_encolados`,
|
||||||
|
`usuario_desbordados`. Quedaran de fundamento para un futuro indicador AoE
|
||||||
|
en la barra de tareas.
|
||||||
|
|
||||||
|
### Cambiado
|
||||||
|
- **`tarea_red`** vuelve a su rol simple: enviar UN ARP request al gateway de
|
||||||
|
QEMU al arrancar. El demuxer y el faro pasaron al tic del compositor —
|
||||||
|
cuyo latido cooperativo es el unico GARANTIZADO bajo carga del reactor.
|
||||||
|
Un bucle propio en `tarea_red` (intentado primero) escala mal porque la
|
||||||
|
cadencia de `EsperaFrame::await` se degrada cuando los apps WASM bajo
|
||||||
|
`wasmi` ocupan el reactor (cada tic de app son decenas de ms de
|
||||||
|
interprete); medirlo contra el reloj monotono y enganchar al compositor
|
||||||
|
resuelve la falla con limpieza.
|
||||||
|
- **`sys_net_recibir`** lee de la cola del userspace que mantiene `akasha`,
|
||||||
|
no del driver. El bloque doc en `wasm/env.rs` lo refleja.
|
||||||
|
- **`drivers::red::recibir_en`** queda como primitiva del driver con
|
||||||
|
`#[allow(dead_code)]`: ya no la consume el sistema, pero la conservamos
|
||||||
|
para futuras herramientas de diagnostico que quieran leer la cola RX cruda.
|
||||||
|
- **Demuxer politico**: frames `0x88B5` con payload basura para postcard —
|
||||||
|
por ejemplo el saludo en texto plano de `pregon`— se cuentan en
|
||||||
|
`RX_DESCARTADOS` pero TAMBIEN viajan a la cola del userspace. Asi pregon
|
||||||
|
o cualquier otro app que comparta el EtherType experimental pueda
|
||||||
|
recibirlos sin que el kernel se los robe.
|
||||||
|
|
||||||
|
### Verificado
|
||||||
|
- `cargo test -p akasha` — seis pruebas en verde:
|
||||||
|
```
|
||||||
|
test pruebas::anuncio_de_raiz_viaja_compacto ... ok
|
||||||
|
test pruebas::componer_y_analizar_solicitud_es_simetrico ... ok
|
||||||
|
test pruebas::ethertype_ajeno_se_distingue ... ok
|
||||||
|
test pruebas::frame_demasiado_corto_se_distingue ... ok
|
||||||
|
test pruebas::payload_invalido_se_distingue ... ok
|
||||||
|
test pruebas::proveedor_lleva_payload_arbitrario ... ok
|
||||||
|
```
|
||||||
|
- QEMU headless 60 s, `-object filter-dump,file=/tmp/renaser-aoe.pcap`. El
|
||||||
|
pcap captura 14 frames:
|
||||||
|
```
|
||||||
|
ARP: 2 (request del kernel + reply del gateway)
|
||||||
|
pregon: 1 (saludo texto plano)
|
||||||
|
AnunciarRaiz: 11 (faros del kernel, Δ promedio 5.86 s sobre 60 s)
|
||||||
|
```
|
||||||
|
Cada AnunciarRaiz lleva como payload el hash del manifiesto:
|
||||||
|
`2f3deadfcc7dae25..` (postcard de 33 bytes — variante + hash — sobre un
|
||||||
|
frame Ethernet total de 47 bytes).
|
||||||
|
- COM1 vuelca `akasha :: ANUNCIO emitido :: raiz=2f3deadfcc7dae25..` por
|
||||||
|
cada disparo; en una corrida de 60 s aparecen 11 anuncios consecutivos.
|
||||||
|
|||||||
+6
-2
@@ -92,10 +92,14 @@ lanzador a la izquierda y reloj `mm:ss` a la derecha que late cada
|
|||||||
segundo— la Fase 17 COMPLETA —`bitacora`, editor de texto que persiste entre
|
segundo— la Fase 17 COMPLETA —`bitacora`, editor de texto que persiste entre
|
||||||
arranques en el grafo de objetos (tipografía 8×8 embebida)— la Fase 18
|
arranques en el grafo de objetos (tipografía 8×8 embebida)— la Fase 18
|
||||||
COMPLETA —red: virtio-net + ARP al gateway de QEMU + recepción de
|
COMPLETA —red: virtio-net + ARP al gateway de QEMU + recepción de
|
||||||
paquetes registrada por COM1— y la Fase 19 COMPLETA —voz del userspace
|
paquetes registrada por COM1—, la Fase 19 COMPLETA —voz del userspace
|
||||||
hacia la red: capacidades `sys_net_mac` / `sys_net_enviar` / `sys_net_recibir`
|
hacia la red: capacidades `sys_net_mac` / `sys_net_enviar` / `sys_net_recibir`
|
||||||
+ la app `pregon` que pregona su presencia con un broadcast Ethernet
|
+ la app `pregon` que pregona su presencia con un broadcast Ethernet
|
||||||
y muestra el tráfico entrante—. Todo verificado en QEMU. Ver `ROADMAP.md`.
|
y muestra el tráfico entrante— y la Fase 20 COMPLETA —Akasha Over Ether:
|
||||||
|
crate `akasha` (no_std compartido) con `MensajeAkasha` postcard sobre
|
||||||
|
EtherType 0x88B5 + respondedor en el kernel (demuxer, atención de
|
||||||
|
SolicitarObjeto / ProveedorObjeto / AnunciarRaiz, faro periódico cada
|
||||||
|
5 s contra reloj monótono)—. Todo verificado en QEMU. Ver `ROADMAP.md`.
|
||||||
|
|
||||||
## Flujo de trabajo
|
## Flujo de trabajo
|
||||||
|
|
||||||
|
|||||||
Generated
+9
@@ -2,6 +2,14 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "akasha"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"postcard",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -456,6 +464,7 @@ dependencies = [
|
|||||||
name = "kernel"
|
name = "kernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"akasha",
|
||||||
"bootloader_api",
|
"bootloader_api",
|
||||||
"crossbeam-queue",
|
"crossbeam-queue",
|
||||||
"embedded-graphics",
|
"embedded-graphics",
|
||||||
|
|||||||
+5
-4
@@ -15,10 +15,11 @@ resolver = "2"
|
|||||||
members = ["boot"]
|
members = ["boot"]
|
||||||
# El kernel (bare-metal) y las apps WASM (target wasm32) se compilan aparte,
|
# El kernel (bare-metal) y las apps WASM (target wasm32) se compilan aparte,
|
||||||
# cada cual con su propio target; quedan fuera del espacio de trabajo.
|
# cada cual con su propio target; quedan fuera del espacio de trabajo.
|
||||||
# `formato` —el formato del grafo en disco— tambien se excluye: es un nucleo
|
# `formato` —el formato del grafo en disco— y `akasha` —el protocolo Akasha
|
||||||
# `no_std` que enlaza el kernel bare-metal, asi que se compila como dependencia
|
# Over Ether (Fase 20)— tambien se excluyen: son nucleos `no_std` que enlaza
|
||||||
# de cada lado (kernel y boot) y no como miembro del workspace anfitrion.
|
# el kernel bare-metal, asi que se compilan como dependencia de cada lado
|
||||||
exclude = ["kernel", "apps", "formato"]
|
# (kernel y boot) y no como miembros del workspace anfitrion.
|
||||||
|
exclude = ["kernel", "apps", "formato", "akasha"]
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Metadatos compartidos: cada miembro hereda esta identidad con `*.workspace`.
|
# Metadatos compartidos: cada miembro hereda esta identidad con `*.workspace`.
|
||||||
|
|||||||
@@ -608,4 +608,31 @@ que la voz ya no es de uno solo. Ahora la casa habla en coro.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Akasha — el grafo se vuelve aire
|
||||||
|
|
||||||
|
Pregón gritaba sin alfabeto: una frase corta dirigida a quien quisiera
|
||||||
|
oír. Hoy la casa aprende su primer idioma propio para la red, y se le
|
||||||
|
ocurre uno sencillo, con apenas tres palabras: *pide*, *toma*, *aquí
|
||||||
|
estoy*. Pide un objeto del archivo por su huella; recibe el objeto en
|
||||||
|
respuesta; o anuncia, sin destinatario, la huella de su biblioteca
|
||||||
|
actual para que cualquiera que esté escuchando sepa por dónde puede
|
||||||
|
acompañarla.
|
||||||
|
|
||||||
|
El nombre es viejo. *Akasha* es la palabra sánscrita para el éter, el
|
||||||
|
soporte de lo sutil; *Akasha Over Ether* es nuestro chiste a dos
|
||||||
|
niveles: el grafo viaja por el éter del cable, y la huella del grafo
|
||||||
|
—las treinta y dos cifras del BLAKE3— es la firma sutil de cada
|
||||||
|
objeto. Con tres frases bastan: el archivo de la casa deja de ser
|
||||||
|
exclusivamente local. Quien sintonice la red de la sala podrá,
|
||||||
|
mañana, recibir un objeto suyo o pedirle uno.
|
||||||
|
|
||||||
|
De momento la casa habla sola. Cada cinco segundos, sin esperar a
|
||||||
|
nadie, suelta su faro: *aquí estoy, mi raíz es esta*. Si otra renaser
|
||||||
|
se encendiera en la misma sala, escucharía y podría empezar a pedir
|
||||||
|
los objetos que le faltan. Hoy no pasa eso porque sólo hay una casa,
|
||||||
|
pero el alfabeto está. Y, cuando un día se encienda la segunda,
|
||||||
|
empezará el diálogo sin que haya que añadir ni una palabra más.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
*El diario continúa. La próxima página la escribirá la próxima jornada.*
|
*El diario continúa. La próxima página la escribirá la próxima jornada.*
|
||||||
|
|||||||
@@ -357,6 +357,49 @@ Líneas abiertas posteriores: una pila mínima ARP/IP/UDP para los apps;
|
|||||||
un servidor de eco / un cliente DNS de juguete; reciclado de las
|
un servidor de eco / un cliente DNS de juguete; reciclado de las
|
||||||
ranuras de ventana cerradas; audio con varias voces (PCM).
|
ranuras de ventana cerradas; audio con varias voces (PCM).
|
||||||
|
|
||||||
|
## Fase 20 — Akasha Over Ether: el grafo distribuido (completada)
|
||||||
|
|
||||||
|
El grafo de objetos persistentes —direccionado por contenido, ya BLAKE3—
|
||||||
|
deja de ser una propiedad LOCAL del disco y se vuelve una propiedad
|
||||||
|
DISTRIBUIDA del cable. Sin TCP, sin IP, sin DNS: tres mensajes y un
|
||||||
|
EtherType propio bastan.
|
||||||
|
|
||||||
|
- Crate `akasha/` (no_std compartido, gemela de `formato`): el
|
||||||
|
protocolo. `MensajeAkasha` con `SolicitarObjeto(id)`,
|
||||||
|
`ProveedorObjeto(id, payload)`, `AnunciarRaiz(id)`. Serializado con
|
||||||
|
`postcard`. Helpers `componer_frame` y `analizar_frame`. EtherType
|
||||||
|
experimental `0x88B5`. Seis pruebas unitarias.
|
||||||
|
- Módulo `kernel/src/akasha.rs`: el respondedor. Drena la cola RX del
|
||||||
|
dispositivo de red y **demultiplexa**: los frames AoE legítimos los
|
||||||
|
procesa en el núcleo; el resto va a una cola del userspace que
|
||||||
|
`sys_net_recibir` lee. Atiende `SolicitarObjeto` consultando
|
||||||
|
`almacen`, absorbe `ProveedorObjeto` verificando integridad, registra
|
||||||
|
`AnunciarRaiz` y, si ignoramos el nodo anunciado, le pedimos al emisor.
|
||||||
|
- Faro periódico: cada 5 s, `AnunciarRaiz` con el hash del manifiesto se
|
||||||
|
difunde por broadcast. La cadencia se mide contra el reloj monótono —
|
||||||
|
no contra los awaits—, por lo que el ritmo del faro es independiente
|
||||||
|
de cuánto trabajo del reactor consume cada vuelta.
|
||||||
|
- `tic_compositor`: el pulso fiable. Los oficios AoE se enganchan al tic
|
||||||
|
del compositor (cuyo latido cooperativo es el único garantizado bajo
|
||||||
|
carga del reactor); una tarea propia con `EsperaFrame::await` en bucle
|
||||||
|
no escalaba porque el intérprete `wasmi` de los apps degrada la
|
||||||
|
cadencia de los awaits a varios cientos de ms.
|
||||||
|
- `sys_net_recibir` lee de la cola del userspace que mantiene `akasha`,
|
||||||
|
no del driver. `drivers::red::recibir_en` queda como primitiva del
|
||||||
|
driver para futuras herramientas de diagnóstico.
|
||||||
|
|
||||||
|
Verificada con `cargo test -p akasha` (6 pruebas verdes) y con QEMU
|
||||||
|
headless + `-object filter-dump`. En 60 s de captura: 11 `AnunciarRaiz`
|
||||||
|
con Δ promedio 5.86 s, un ARP request + reply y el saludo en texto
|
||||||
|
plano de `pregon`. La casa ya tiene su propio idioma de red — y un faro
|
||||||
|
que late.
|
||||||
|
|
||||||
|
Líneas abiertas posteriores: una pila ARP/IP/UDP mínima para los apps;
|
||||||
|
un peer real en una segunda VM para ver la replicación end-to-end
|
||||||
|
`AnunciarRaiz → SolicitarObjeto → ProveedorObjeto`; un indicador AoE en
|
||||||
|
la barra de tareas que pinte los contadores de `ResumenAkasha`;
|
||||||
|
reciclado de las ranuras de ventana cerradas; audio con varias voces.
|
||||||
|
|
||||||
## Principios que persisten entre fases
|
## Principios que persisten entre fases
|
||||||
|
|
||||||
- Reutilizar infraestructura madura de la comunidad antes que reinventar.
|
- Reutilizar infraestructura madura de la comunidad antes que reinventar.
|
||||||
|
|||||||
Generated
+129
@@ -0,0 +1,129 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "akasha"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"postcard",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cobs"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postcard"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
||||||
|
dependencies = [
|
||||||
|
"cobs",
|
||||||
|
"embedded-io 0.4.0",
|
||||||
|
"embedded-io 0.6.1",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.117"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# renaser :: akasha — protocolo Akasha Over Ether (AoE)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Nucleo `#![no_std]` COMPARTIDO: la verdad UNICA del protocolo de red nativo
|
||||||
|
# de renaser. Lo enlaza el kernel bare-metal (target `x86_64-unknown-none`) y,
|
||||||
|
# por ser no_std, lo compila tambien sin friccion el anfitrion `boot` y
|
||||||
|
# cualquier otra pieza que quiera dialogar en el mismo idioma — incluida
|
||||||
|
# alguna futura app del userspace.
|
||||||
|
#
|
||||||
|
# El protocolo extiende el GRAFO de objetos persistentes (Fase 6) sobre una
|
||||||
|
# red Ethernet cruda, sin pasar por TCP/IP. Tres mensajes bastan: solicitar
|
||||||
|
# un objeto por su hash BLAKE3, proveerlo de vuelta, anunciar la raiz actual
|
||||||
|
# del sistema. Se serializa con `postcard` —compacto y deterministico— y
|
||||||
|
# viaja en frames de EtherType experimental `0x88B5`.
|
||||||
|
#
|
||||||
|
# Queda EXCLUIDO del espacio de trabajo (ver el Cargo.toml raiz), como el
|
||||||
|
# kernel y como `formato`: lo consume un paquete bare-metal, asi que fija
|
||||||
|
# sus versiones de forma explicita.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "akasha"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MPL-2.0"
|
||||||
|
authors = ["JL Soltech <gerencia@jlsoltech.com>"]
|
||||||
|
description = "renaser :: protocolo Akasha Over Ether (AoE) — grafo distribuido sobre Ethernet"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
bench = false
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# `serde` aporta los rasgos de (de)serializacion; `postcard` los materializa en
|
||||||
|
# un formato binario compacto, el mismo que ya usa `formato` para el disco.
|
||||||
|
# Asi el grafo habla el mismo idioma en el disco y en el cable.
|
||||||
|
serde = { version = "1", default-features = false, features = ["alloc", "derive"] }
|
||||||
|
postcard = { version = "1", default-features = false, features = ["alloc"] }
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// renaser :: akasha — Akasha Over Ether (AoE)
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Fase 19 demostro que un app del userspace puede inyectar frames Ethernet
|
||||||
|
// crudos al cable —`pregon` gritando «hola» con su EtherType experimental—,
|
||||||
|
// pero ese saludo era texto plano, sin estructura. La Fase 20 da el siguiente
|
||||||
|
// paso natural: **fundar un protocolo nativo del ecosistema** para que
|
||||||
|
// renaser hable consigo mismo a traves de la red, sin TCP, sin IP, sin las
|
||||||
|
// sobrecargas de cabecera de los años 80.
|
||||||
|
//
|
||||||
|
// AoE viaja DIRECTAMENTE sobre Ethernet (`EtherType = 0x88B5`, rango
|
||||||
|
// experimental reservado por IEEE para uso local). Cada frame transporta un
|
||||||
|
// `MensajeAkasha` serializado con `postcard` —el mismo codec que ya usa
|
||||||
|
// `formato` para el grafo en disco—. Tres mensajes bastan:
|
||||||
|
//
|
||||||
|
// 1. `SolicitarObjeto(id)` — pide un nodo del grafo por su hash BLAKE3.
|
||||||
|
// 2. `ProveedorObjeto(id, d)` — responde con el payload binario del nodo.
|
||||||
|
// 3. `AnunciarRaiz(id)` — difunde el hash de la raiz del sistema.
|
||||||
|
//
|
||||||
|
// Con esto basta para extender el grafo de objetos —direccionado por
|
||||||
|
// contenido, inmutable, ya BLAKE3— a OTRAS maquinas renaser que escuchen en
|
||||||
|
// la misma red de capa-2. El receptor de un `AnunciarRaiz` puede comparar
|
||||||
|
// hashes, descubrir que le falta un nodo, pedirlo con `SolicitarObjeto` y
|
||||||
|
// ensamblar el grafo del par. El grafo deja de ser una propiedad LOCAL del
|
||||||
|
// disco y se vuelve una propiedad DISTRIBUIDA del cable.
|
||||||
|
//
|
||||||
|
// Esta crate es la unica verdad del protocolo. Es un nucleo `#![no_std]` —el
|
||||||
|
// kernel bare-metal la enlaza—; cualquier app, daemon u orquestador que
|
||||||
|
// hable AoE consume estos mismos tipos. Ningun otro modulo redefine ni un
|
||||||
|
// byte del trazado del frame.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Constantes del protocolo en el cable
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// EtherType de AoE, rango experimental reservado por IEEE para uso local
|
||||||
|
/// (0x88B5/0x88B6). renaser elige el primero. Cualquier frame del cable que
|
||||||
|
/// no porte este EtherType NO es Akasha, y se entrega al userspace.
|
||||||
|
pub const ETHER_TYPE_AKASHA: u16 = 0x88B5;
|
||||||
|
|
||||||
|
/// Tamaño de la cabecera Ethernet, en bytes (dst + src + ethertype).
|
||||||
|
pub const CABECERA_ETHERNET: usize = 14;
|
||||||
|
|
||||||
|
/// Maximo del payload AoE serializado, en bytes. Acotado para que el frame
|
||||||
|
/// completo (cabecera + payload) no exceda una MTU Ethernet sin VLAN
|
||||||
|
/// (1500 - 14 = 1486) y se transmita SIN fragmentar. Si un mensaje no cabe,
|
||||||
|
/// el llamante debe partirlo en varios objetos del grafo y referirse a ellos.
|
||||||
|
pub const MAX_PAYLOAD_AKASHA: usize = 1486;
|
||||||
|
|
||||||
|
/// MAC de broadcast — la difunde todo Ethernet, equivalente a `255.255.255.255`
|
||||||
|
/// en IPv4 pero en capa-2 pura.
|
||||||
|
pub const MAC_BROADCAST: [u8; 6] = [0xff; 6];
|
||||||
|
|
||||||
|
/// El identificador de un objeto del grafo: el hash BLAKE3 de su forma
|
||||||
|
/// serializada. En un almacen direccionado por contenido, la identidad ES el
|
||||||
|
/// contenido. Coincide byte a byte con `formato::Hash` —AoE habla el mismo
|
||||||
|
/// idioma de identidad que el grafo en disco—.
|
||||||
|
pub type ObjectId = [u8; 32];
|
||||||
|
|
||||||
|
/// Una direccion MAC, en seis bytes.
|
||||||
|
pub type Mac = [u8; 6];
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// El mensaje
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Un mensaje AoE — la unidad de protocolo que viaja en un frame de
|
||||||
|
/// `ETHER_TYPE_AKASHA`. Tres variantes bastan para fundar un grafo distribuido:
|
||||||
|
///
|
||||||
|
/// - `SolicitarObjeto` pregunta por un objeto identificado por su hash.
|
||||||
|
/// - `ProveedorObjeto` responde con el payload binario del objeto. El
|
||||||
|
/// receptor recompone el `formato::Objeto` aplicando su deserializador a
|
||||||
|
/// `payload` y verificando que `blake3(payload) == id`.
|
||||||
|
/// - `AnunciarRaiz` difunde el hash de la raiz —el ancla del sistema
|
||||||
|
/// actual—. Sirve como faro: quien escuche y carezca de ese nodo en su
|
||||||
|
/// grafo local puede iniciar una solicitud.
|
||||||
|
///
|
||||||
|
/// La codificacion `postcard` es estable y compacta: un `SolicitarObjeto`
|
||||||
|
/// ocupa 33 bytes (1 de variante + 32 de hash) en el cable.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub enum MensajeAkasha {
|
||||||
|
/// Solicito el objeto identificado por este hash.
|
||||||
|
SolicitarObjeto(ObjectId),
|
||||||
|
/// Aqui esta el payload binario del objeto identificado por este hash.
|
||||||
|
/// El payload es la forma serializada `postcard` de un `formato::Objeto`,
|
||||||
|
/// y el receptor DEBE verificar que `blake3(payload) == id` antes de
|
||||||
|
/// confiar en el contenido.
|
||||||
|
ProveedorObjeto(ObjectId, Vec<u8>),
|
||||||
|
/// Difundo el hash de mi raiz actual — quien me escuche y le falte este
|
||||||
|
/// nodo en su grafo local puede pedirmelo.
|
||||||
|
AnunciarRaiz(ObjectId),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// El motivo por el que un frame AoE no se pudo componer o analizar.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum ErrorAkasha {
|
||||||
|
/// El payload serializado excede `MAX_PAYLOAD_AKASHA`.
|
||||||
|
PayloadDemasiadoLargo,
|
||||||
|
/// El frame en el cable es mas corto que la cabecera Ethernet.
|
||||||
|
FrameDemasiadoCorto,
|
||||||
|
/// El EtherType del frame no es `ETHER_TYPE_AKASHA`.
|
||||||
|
EtherTypeAjeno,
|
||||||
|
/// `postcard` no supo deserializar el payload (basura, version distinta,
|
||||||
|
/// otro protocolo experimental ajeno).
|
||||||
|
PayloadInvalido,
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Composicion y analisis de frames
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Compone un frame Ethernet completo —cabecera (14 bytes) + payload AoE
|
||||||
|
/// (`postcard(mensaje)`)— listo para entregar al driver. El llamante elige el
|
||||||
|
/// destino (`MAC_BROADCAST` para difundir) y firma el frame con su propia
|
||||||
|
/// MAC. Devuelve el frame completo, o un error si el mensaje serializado
|
||||||
|
/// excede `MAX_PAYLOAD_AKASHA`.
|
||||||
|
pub fn componer_frame(
|
||||||
|
src: Mac,
|
||||||
|
dst: Mac,
|
||||||
|
mensaje: &MensajeAkasha,
|
||||||
|
) -> Result<Vec<u8>, ErrorAkasha> {
|
||||||
|
let payload =
|
||||||
|
postcard::to_allocvec(mensaje).map_err(|_| ErrorAkasha::PayloadDemasiadoLargo)?;
|
||||||
|
if payload.len() > MAX_PAYLOAD_AKASHA {
|
||||||
|
return Err(ErrorAkasha::PayloadDemasiadoLargo);
|
||||||
|
}
|
||||||
|
let mut frame = Vec::with_capacity(CABECERA_ETHERNET + payload.len());
|
||||||
|
frame.extend_from_slice(&dst);
|
||||||
|
frame.extend_from_slice(&src);
|
||||||
|
frame.extend_from_slice(ÐER_TYPE_AKASHA.to_be_bytes());
|
||||||
|
frame.extend_from_slice(&payload);
|
||||||
|
Ok(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Analiza un frame Ethernet entrante. Si su EtherType es `ETHER_TYPE_AKASHA`
|
||||||
|
/// y el payload deserializa como un `MensajeAkasha`, devuelve la MAC de origen
|
||||||
|
/// y el mensaje. En cualquier otro caso —EtherType ajeno, frame truncado,
|
||||||
|
/// `postcard` no le encuentra sentido al payload— devuelve un `ErrorAkasha`
|
||||||
|
/// que distingue el motivo: el llamante puede entonces decidir si lo entrega
|
||||||
|
/// al userspace (otro protocolo) o lo descarta (basura).
|
||||||
|
pub fn analizar_frame(frame: &[u8]) -> Result<(Mac, MensajeAkasha), ErrorAkasha> {
|
||||||
|
if frame.len() < CABECERA_ETHERNET {
|
||||||
|
return Err(ErrorAkasha::FrameDemasiadoCorto);
|
||||||
|
}
|
||||||
|
let etype = u16::from_be_bytes([frame[12], frame[13]]);
|
||||||
|
if etype != ETHER_TYPE_AKASHA {
|
||||||
|
return Err(ErrorAkasha::EtherTypeAjeno);
|
||||||
|
}
|
||||||
|
let mut src: Mac = [0; 6];
|
||||||
|
src.copy_from_slice(&frame[6..12]);
|
||||||
|
let payload = &frame[CABECERA_ETHERNET..];
|
||||||
|
let mensaje: MensajeAkasha =
|
||||||
|
postcard::from_bytes(payload).map_err(|_| ErrorAkasha::PayloadInvalido)?;
|
||||||
|
Ok((src, mensaje))
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Pruebas
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod pruebas {
|
||||||
|
use super::*;
|
||||||
|
use alloc::vec;
|
||||||
|
|
||||||
|
const MAC_A: Mac = [0x52, 0x54, 0x00, 0x12, 0x34, 0x56];
|
||||||
|
const MAC_B: Mac = [0x52, 0x54, 0x00, 0xAB, 0xCD, 0xEF];
|
||||||
|
const HASH_X: ObjectId = [0x11; 32];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn componer_y_analizar_solicitud_es_simetrico() {
|
||||||
|
let msg = MensajeAkasha::SolicitarObjeto(HASH_X);
|
||||||
|
let frame = componer_frame(MAC_A, MAC_B, &msg).unwrap();
|
||||||
|
// Cabecera: dst + src + ethertype.
|
||||||
|
assert_eq!(&frame[0..6], &MAC_B);
|
||||||
|
assert_eq!(&frame[6..12], &MAC_A);
|
||||||
|
assert_eq!(&frame[12..14], ÐER_TYPE_AKASHA.to_be_bytes());
|
||||||
|
let (src, rec) = analizar_frame(&frame).unwrap();
|
||||||
|
assert_eq!(src, MAC_A);
|
||||||
|
match rec {
|
||||||
|
MensajeAkasha::SolicitarObjeto(id) => assert_eq!(id, HASH_X),
|
||||||
|
otro => panic!("variante inesperada: {otro:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn anuncio_de_raiz_viaja_compacto() {
|
||||||
|
let frame =
|
||||||
|
componer_frame(MAC_A, MAC_BROADCAST, &MensajeAkasha::AnunciarRaiz(HASH_X))
|
||||||
|
.unwrap();
|
||||||
|
// 14 cabecera + 1 byte variante + 32 hash = 47 bytes en el cable.
|
||||||
|
assert_eq!(frame.len(), 47);
|
||||||
|
assert_eq!(&frame[0..6], &MAC_BROADCAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proveedor_lleva_payload_arbitrario() {
|
||||||
|
let payload = vec![1u8, 2, 3, 4, 5, 6, 7, 8];
|
||||||
|
let msg = MensajeAkasha::ProveedorObjeto(HASH_X, payload.clone());
|
||||||
|
let frame = componer_frame(MAC_A, MAC_B, &msg).unwrap();
|
||||||
|
let (_, rec) = analizar_frame(&frame).unwrap();
|
||||||
|
match rec {
|
||||||
|
MensajeAkasha::ProveedorObjeto(id, p) => {
|
||||||
|
assert_eq!(id, HASH_X);
|
||||||
|
assert_eq!(p, payload);
|
||||||
|
}
|
||||||
|
otro => panic!("variante inesperada: {otro:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn frame_demasiado_corto_se_distingue() {
|
||||||
|
assert!(matches!(
|
||||||
|
analizar_frame(&[0u8; 8]),
|
||||||
|
Err(ErrorAkasha::FrameDemasiadoCorto)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ethertype_ajeno_se_distingue() {
|
||||||
|
// Cabecera valida pero EtherType de IPv4 (0x0800).
|
||||||
|
let mut frame = [0u8; CABECERA_ETHERNET];
|
||||||
|
frame[12] = 0x08;
|
||||||
|
frame[13] = 0x00;
|
||||||
|
assert!(matches!(
|
||||||
|
analizar_frame(&frame),
|
||||||
|
Err(ErrorAkasha::EtherTypeAjeno)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn payload_invalido_se_distingue() {
|
||||||
|
// EtherType correcto, pero el payload es basura para postcard.
|
||||||
|
let mut frame = [0u8; CABECERA_ETHERNET + 4];
|
||||||
|
frame[12..14].copy_from_slice(ÐER_TYPE_AKASHA.to_be_bytes());
|
||||||
|
frame[14..].copy_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
|
||||||
|
assert!(matches!(
|
||||||
|
analizar_frame(&frame),
|
||||||
|
Err(ErrorAkasha::PayloadInvalido)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,6 +76,14 @@ virtio-drivers = { version = "0.13", default-features = false, features = ["allo
|
|||||||
# sola verdad del formato de disco, imposible de divergir entre los dos lados.
|
# sola verdad del formato de disco, imposible de divergir entre los dos lados.
|
||||||
formato = { path = "../formato" }
|
formato = { path = "../formato" }
|
||||||
|
|
||||||
|
# --- Fase 20 :: Akasha Over Ether ---
|
||||||
|
# El protocolo nativo de red de renaser: mensajes `MensajeAkasha` serializados
|
||||||
|
# con `postcard`, sobre Ethernet crudo. Mismo patron que `formato`: nucleo
|
||||||
|
# `no_std` compartido, no miembro del workspace anfitrion. El kernel y boot lo
|
||||||
|
# enlazan, y cualquier app o daemon que quiera hablar AoE en el futuro tomara
|
||||||
|
# la misma crate — una sola verdad del protocolo.
|
||||||
|
akasha = { path = "../akasha" }
|
||||||
|
|
||||||
# --- Fase 8 (preparación) :: el compositor ---
|
# --- Fase 8 (preparación) :: el compositor ---
|
||||||
# `mirada-layout` es el motor de teselado del compositor de brahman —
|
# `mirada-layout` es el motor de teselado del compositor de brahman —
|
||||||
# geometría pura (rectángulos, foco, Z-order), `no_std`, sin smithay ni
|
# geometría pura (rectángulos, foco, Z-order), `no_std`, sin smithay ni
|
||||||
|
|||||||
@@ -0,0 +1,431 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// renaser :: kernel/src/akasha — Fase 20 :: Akasha Over Ether
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// El servicio del kernel que habla AoE: el respondedor de Akasha Over Ether.
|
||||||
|
// Tres oficios:
|
||||||
|
//
|
||||||
|
// 1. Drena la cola RX del dispositivo de red y DEMULTIPLEXA cada frame:
|
||||||
|
// - Si su EtherType es `0x88B5` y el payload deserializa como un
|
||||||
|
// `MensajeAkasha`, lo procesa en el kernel.
|
||||||
|
// - Cualquier otro frame se encola hacia el userspace: las apps lo
|
||||||
|
// recibiran via `sys_net_recibir` como hasta ahora.
|
||||||
|
// 2. Atiende los mensajes AoE:
|
||||||
|
// - `SolicitarObjeto(id)` → si tenemos `id` en el grafo,
|
||||||
|
// respondemos con `ProveedorObjeto`.
|
||||||
|
// - `ProveedorObjeto(id, p)` → si la integridad cuadra, lo absorbemos
|
||||||
|
// al grafo local.
|
||||||
|
// - `AnunciarRaiz(id)` → se contabiliza y, si no tenemos el
|
||||||
|
// nodo, se le solicita al emisor.
|
||||||
|
// 3. Difunde periodicamente nuestra raiz del grafo (`AnunciarRaiz` con el
|
||||||
|
// hash del manifiesto) — el faro que delata nuestra presencia.
|
||||||
|
//
|
||||||
|
// Con esto, el grafo de objetos —direccionado por contenido, ya BLAKE3— deja
|
||||||
|
// de ser una propiedad local del disco y empieza a ser una propiedad
|
||||||
|
// distribuida del cable. Tres frames bastan, sin TCP, sin IP, sin DNS.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
use alloc::collections::VecDeque;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::fmt::Write;
|
||||||
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
use spin::{Mutex, Once};
|
||||||
|
|
||||||
|
use crate::async_system::reloj;
|
||||||
|
|
||||||
|
use akasha::{
|
||||||
|
analizar_frame, componer_frame, ErrorAkasha, Mac, MensajeAkasha, MAC_BROADCAST,
|
||||||
|
};
|
||||||
|
use formato::Hash;
|
||||||
|
|
||||||
|
use crate::almacen;
|
||||||
|
use crate::baliza;
|
||||||
|
use crate::drivers::red;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Cola del userspace — frames NO-AoE en espera de `sys_net_recibir`
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Cuantos frames como mucho retenemos en la cola del userspace. Cota dura:
|
||||||
|
/// si el userspace se duerme, los frames mas antiguos se pierden antes que
|
||||||
|
/// la cola se desborde.
|
||||||
|
const PROFUNDIDAD_COLA_USUARIO: usize = 64;
|
||||||
|
|
||||||
|
/// La cola FIFO de frames que NO son AoE y aguardan a `sys_net_recibir`. Cada
|
||||||
|
/// frame se copia tal cual lo entrega el driver; el userspace ve la cabecera
|
||||||
|
/// Ethernet completa, como antes de la Fase 20.
|
||||||
|
static COLA_USUARIO: Mutex<VecDeque<Vec<u8>>> = Mutex::new(VecDeque::new());
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Contadores — la voz que la barra y el diagnostico leen
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Frames AoE procesados en RX, por variante.
|
||||||
|
static RX_SOLICITUDES: AtomicU64 = AtomicU64::new(0);
|
||||||
|
static RX_PROVEEDORES: AtomicU64 = AtomicU64::new(0);
|
||||||
|
static RX_ANUNCIOS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
/// Frames AoE emitidos en TX, por variante.
|
||||||
|
static TX_SOLICITUDES: AtomicU64 = AtomicU64::new(0);
|
||||||
|
static TX_PROVEEDORES: AtomicU64 = AtomicU64::new(0);
|
||||||
|
static TX_ANUNCIOS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
/// Frames descartados en RX por ser basura (payload AoE invalido).
|
||||||
|
static RX_DESCARTADOS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
/// Frames no-AoE encolados hacia el userspace.
|
||||||
|
static USUARIO_ENCOLADOS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
/// Frames no-AoE descartados por desbordamiento de la cola del userspace.
|
||||||
|
static USUARIO_DESBORDADOS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
/// La MAC con la que firmamos los frames AoE. La cachea `montar`.
|
||||||
|
static NUESTRA_MAC: Once<Mac> = Once::new();
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Montaje
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Anota la MAC del dispositivo de red. La llama el orquestador cuando el
|
||||||
|
/// driver entrega su `Mac` tras `red::montar`.
|
||||||
|
pub fn montar(mac: Mac) {
|
||||||
|
NUESTRA_MAC.call_once(|| mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Demultiplexor — el oficio numero 1 de la Fase 20
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Drena la cola RX del dispositivo y demultiplexa cada frame:
|
||||||
|
/// - Frames AoE (EtherType `0x88B5` con payload valido) → `procesar`.
|
||||||
|
/// - Cualquier otro frame → cola del userspace (lo recogera `sys_net_recibir`).
|
||||||
|
///
|
||||||
|
/// Llamada en CADA fotograma desde la tarea cooperativa de red. El cerrojo
|
||||||
|
/// del driver virtio-net se libera entre frame y frame; el del userspace solo
|
||||||
|
/// se toma para empujar, sin solapar con el del driver.
|
||||||
|
pub fn drenar_y_demultiplexar() {
|
||||||
|
let mac = match NUESTRA_MAC.get().copied() {
|
||||||
|
Some(m) => m,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
red::drenar_rx(|frame| {
|
||||||
|
// Intentar analizar como AoE. Si el EtherType cuadra Y el payload
|
||||||
|
// deserializa, procesamos en el kernel; el frame NO viaja al
|
||||||
|
// userspace —el protocolo es asunto del nucleo—.
|
||||||
|
match analizar_frame(frame) {
|
||||||
|
Ok((origen, mensaje)) => procesar(mensaje, origen, mac),
|
||||||
|
// EtherType ajeno o frame truncado: a la cola del userspace.
|
||||||
|
Err(ErrorAkasha::EtherTypeAjeno) | Err(ErrorAkasha::FrameDemasiadoCorto) => {
|
||||||
|
encolar_para_usuario(frame)
|
||||||
|
}
|
||||||
|
// EtherType nuestro pero payload no-postcard: el ejemplo canonico
|
||||||
|
// es el saludo en texto plano de `pregon`. NO es Akasha legitimo,
|
||||||
|
// pero tampoco basura del cable — es un protocolo userspace que
|
||||||
|
// comparte EtherType. Lo contamos y lo dejamos pasar al userspace
|
||||||
|
// para que el app destinatario (si lo hay) lo recoja.
|
||||||
|
Err(ErrorAkasha::PayloadInvalido) | Err(ErrorAkasha::PayloadDemasiadoLargo) => {
|
||||||
|
RX_DESCARTADOS.fetch_add(1, Ordering::Relaxed);
|
||||||
|
encolar_para_usuario(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encola un frame entero hacia el userspace. Si la cola esta llena, descarta
|
||||||
|
/// el mas antiguo —preferimos perder el pasado a quedar sordos del futuro—.
|
||||||
|
fn encolar_para_usuario(frame: &[u8]) {
|
||||||
|
let mut cola = COLA_USUARIO.lock();
|
||||||
|
if cola.len() >= PROFUNDIDAD_COLA_USUARIO {
|
||||||
|
cola.pop_front();
|
||||||
|
USUARIO_DESBORDADOS.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
cola.push_back(frame.to_vec());
|
||||||
|
USUARIO_ENCOLADOS.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Interfaz que ve `sys_net_recibir`
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Saca UN frame de la cola del userspace hacia `buf`. Devuelve los bytes
|
||||||
|
/// copiados (acotados por `buf.len()`), o `0` si no hay frame pendiente. El
|
||||||
|
/// reemplazo natural de `red::recibir_en` para el lado WASM —ahora el
|
||||||
|
/// userspace ya no compite con el kernel por la cola RX del dispositivo—.
|
||||||
|
pub fn pop_usuario(buf: &mut [u8]) -> usize {
|
||||||
|
let frame = match COLA_USUARIO.lock().pop_front() {
|
||||||
|
Some(f) => f,
|
||||||
|
None => return 0,
|
||||||
|
};
|
||||||
|
let n = frame.len().min(buf.len());
|
||||||
|
buf[..n].copy_from_slice(&frame[..n]);
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Atencion de mensajes — el oficio numero 2
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Procesa UN mensaje AoE entrante. Contabiliza, traza y —si toca— responde.
|
||||||
|
fn procesar(mensaje: MensajeAkasha, origen: Mac, nuestra: Mac) {
|
||||||
|
match mensaje {
|
||||||
|
MensajeAkasha::SolicitarObjeto(id) => {
|
||||||
|
RX_SOLICITUDES.fetch_add(1, Ordering::Relaxed);
|
||||||
|
atender_solicitud(id, origen, nuestra);
|
||||||
|
}
|
||||||
|
MensajeAkasha::ProveedorObjeto(id, payload) => {
|
||||||
|
RX_PROVEEDORES.fetch_add(1, Ordering::Relaxed);
|
||||||
|
absorber_proveedor(id, &payload, origen);
|
||||||
|
}
|
||||||
|
MensajeAkasha::AnunciarRaiz(id) => {
|
||||||
|
RX_ANUNCIOS.fetch_add(1, Ordering::Relaxed);
|
||||||
|
atender_anuncio(id, origen, nuestra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Si tenemos el objeto que nos piden, le respondemos al solicitante (unicast)
|
||||||
|
/// con un `ProveedorObjeto` que carga la forma serializada del nodo. La
|
||||||
|
/// integridad se preserva por construccion: `payload` es exactamente la
|
||||||
|
/// secuencia que rehashea al `id` pedido. Si no lo tenemos, no decimos nada
|
||||||
|
/// —AoE no tiene «not found»; un par puede preguntarle a otro—.
|
||||||
|
fn atender_solicitud(id: Hash, origen: Mac, nuestra: Mac) {
|
||||||
|
let objeto = match almacen::recuperar(&id) {
|
||||||
|
Ok(Some(o)) => o,
|
||||||
|
Ok(None) => {
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: solicitud rechazada (objeto ausente) :: {}",
|
||||||
|
FormatoHash(&id)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(motivo) => {
|
||||||
|
let _ = writeln!(baliza::Serie, "akasha :: solicitud fallida :: {motivo}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let payload = match objeto.serializar() {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
// Defensa en profundidad: el rehash DEBE coincidir. postcard es canonico
|
||||||
|
// pero verificamos antes de poner algo en el cable que dice ser `id`.
|
||||||
|
if formato::hash(&payload) != id {
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: rehash no coincide al servir :: descartado"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mensaje = MensajeAkasha::ProveedorObjeto(id, payload);
|
||||||
|
if enviar(&mensaje, nuestra, origen).is_ok() {
|
||||||
|
TX_PROVEEDORES.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: PROVEEDOR enviado :: {} -> {}",
|
||||||
|
FormatoHash(&id),
|
||||||
|
FormatoMac(&origen)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Acepta un objeto venido del cable. Verifica que su forma serializada
|
||||||
|
/// rehashea al `id` que el remitente afirma; si cuadra, lo deposita en el
|
||||||
|
/// grafo local; si no, lo descarta con una traza. La unica entrada de
|
||||||
|
/// integridad esta aqui: el grafo local no admite mentiras.
|
||||||
|
fn absorber_proveedor(id: Hash, payload: &[u8], origen: Mac) {
|
||||||
|
if formato::hash(payload) != id {
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: proveedor rechazado (rehash no coincide) :: src={}",
|
||||||
|
FormatoMac(&origen)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let objeto = match formato::Objeto::deserializar(payload) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(motivo) => {
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: proveedor no deserializable :: {motivo}"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match almacen::almacenar(objeto.datos, objeto.hijos) {
|
||||||
|
Ok(hash) => {
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: PROVEEDOR absorbido :: {} <- {}",
|
||||||
|
FormatoHash(&hash),
|
||||||
|
FormatoMac(&origen)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(motivo) => {
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: no se pudo absorber proveedor :: {motivo}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Un par nos anuncio su raiz. Si la conocemos, no hay nada que hacer; si no,
|
||||||
|
/// le pedimos el nodo: el `AnunciarRaiz` es el faro que basta para iniciar la
|
||||||
|
/// replicacion.
|
||||||
|
fn atender_anuncio(id: Hash, origen: Mac, nuestra: Mac) {
|
||||||
|
// ¿Lo tenemos ya? Entonces nada que pedir.
|
||||||
|
if let Ok(Some(_)) = almacen::recuperar(&id) {
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: anuncio reconocido (ya lo tenemos) :: {} de {}",
|
||||||
|
FormatoHash(&id),
|
||||||
|
FormatoMac(&origen)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// No lo tenemos — pedirlo al emisor en unicast.
|
||||||
|
let mensaje = MensajeAkasha::SolicitarObjeto(id);
|
||||||
|
if enviar(&mensaje, nuestra, origen).is_ok() {
|
||||||
|
TX_SOLICITUDES.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: SOLICITUD enviada :: {} -> {}",
|
||||||
|
FormatoHash(&id),
|
||||||
|
FormatoMac(&origen)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Difusion periodica de nuestra raiz — el oficio numero 3
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Intervalo entre faros AoE consecutivos, en milisegundos. 5 s es un
|
||||||
|
/// compromiso conservador: lo bastante frecuente para descubrir vecinos
|
||||||
|
/// nuevos sin saturar la red de capa-2 con anuncios.
|
||||||
|
const INTERVALO_FARO_MS: u64 = 5_000;
|
||||||
|
|
||||||
|
/// Marca del reloj monotono (`reloj::milisegundos`) en la que se difundira el
|
||||||
|
/// proximo faro. La inicializamos a `0` — la primera difusion ocurre cuanto
|
||||||
|
/// antes pase `tic_compositor` con el manifiesto montado—.
|
||||||
|
static PROXIMO_FARO_MS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
/// Punto de entrada que el tic del compositor llama una vez por fotograma.
|
||||||
|
/// Junta los dos oficios AoE que viven en linea con el latido del escritorio:
|
||||||
|
/// - drenar la cola RX del dispositivo y demultiplexar Akasha vs userspace,
|
||||||
|
/// - difundir nuestra raiz cada `INTERVALO_FARO_MS` segun reloj monotono.
|
||||||
|
///
|
||||||
|
/// El compositor late de forma fiable (avanza el reloj de la barra cada
|
||||||
|
/// segundo, prueba indirecta de que su tarea se atiende sin atascos); por
|
||||||
|
/// eso elegimos su tic como portador. La cadencia del faro se mide contra
|
||||||
|
/// el reloj monotono y NO contra los awaits, asi el ritmo del faro es
|
||||||
|
/// independiente de cuanto trabajo del reactor consume cada vuelta.
|
||||||
|
pub fn tic_compositor() {
|
||||||
|
drenar_y_demultiplexar();
|
||||||
|
let ahora = reloj::milisegundos();
|
||||||
|
let proximo = PROXIMO_FARO_MS.load(Ordering::Relaxed);
|
||||||
|
if ahora >= proximo {
|
||||||
|
difundir_raiz();
|
||||||
|
PROXIMO_FARO_MS.store(ahora + INTERVALO_FARO_MS, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Anuncia, por broadcast, el hash del manifiesto actual. Es el faro de
|
||||||
|
/// renaser: quien escuche en la red de capa-2 sabra de nuestra existencia y
|
||||||
|
/// del nodo raiz del grafo. Si aun no hay manifiesto anclado, no se difunde
|
||||||
|
/// nada — el silencio es preferible a un faro vacio.
|
||||||
|
pub fn difundir_raiz() {
|
||||||
|
let nuestra = match NUESTRA_MAC.get().copied() {
|
||||||
|
Some(m) => m,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
let id = match almacen::manifiesto() {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
let mensaje = MensajeAkasha::AnunciarRaiz(id);
|
||||||
|
if enviar(&mensaje, nuestra, MAC_BROADCAST).is_ok() {
|
||||||
|
TX_ANUNCIOS.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let _ = writeln!(
|
||||||
|
baliza::Serie,
|
||||||
|
"akasha :: ANUNCIO emitido :: raiz={}",
|
||||||
|
FormatoHash(&id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Helpers internos
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Compone un frame AoE y lo entrega al driver. Punto de salida unico de TX.
|
||||||
|
fn enviar(mensaje: &MensajeAkasha, src: Mac, dst: Mac) -> Result<(), ()> {
|
||||||
|
let frame = componer_frame(src, dst, mensaje).map_err(|_| ())?;
|
||||||
|
red::enviar(&frame).map_err(|motivo| {
|
||||||
|
let _ = writeln!(baliza::Serie, "akasha :: envio fallido :: {motivo}");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Lectores de estado — los expone la barra de tareas (futuro indicador) y el
|
||||||
|
// diagnostico de COM1
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Resumen de actividad AoE, en una sola lectura coherente. Los enteros se
|
||||||
|
/// leen `Relaxed`: el resumen es informativo, no transaccional.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct ResumenAkasha {
|
||||||
|
pub rx_solicitudes: u64,
|
||||||
|
pub rx_proveedores: u64,
|
||||||
|
pub rx_anuncios: u64,
|
||||||
|
pub tx_solicitudes: u64,
|
||||||
|
pub tx_proveedores: u64,
|
||||||
|
pub tx_anuncios: u64,
|
||||||
|
pub rx_descartados: u64,
|
||||||
|
pub usuario_encolados: u64,
|
||||||
|
pub usuario_desbordados: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn resumen() -> ResumenAkasha {
|
||||||
|
ResumenAkasha {
|
||||||
|
rx_solicitudes: RX_SOLICITUDES.load(Ordering::Relaxed),
|
||||||
|
rx_proveedores: RX_PROVEEDORES.load(Ordering::Relaxed),
|
||||||
|
rx_anuncios: RX_ANUNCIOS.load(Ordering::Relaxed),
|
||||||
|
tx_solicitudes: TX_SOLICITUDES.load(Ordering::Relaxed),
|
||||||
|
tx_proveedores: TX_PROVEEDORES.load(Ordering::Relaxed),
|
||||||
|
tx_anuncios: TX_ANUNCIOS.load(Ordering::Relaxed),
|
||||||
|
rx_descartados: RX_DESCARTADOS.load(Ordering::Relaxed),
|
||||||
|
usuario_encolados: USUARIO_ENCOLADOS.load(Ordering::Relaxed),
|
||||||
|
usuario_desbordados: USUARIO_DESBORDADOS.load(Ordering::Relaxed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Formateadores cortos para la traza de COM1 — sin allocations innecesarias
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
struct FormatoHash<'h>(&'h Hash);
|
||||||
|
impl core::fmt::Display for FormatoHash<'_> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
// Primeros 8 bytes en hex — suficiente para distinguir en una traza.
|
||||||
|
for b in &self.0[..8] {
|
||||||
|
write!(f, "{b:02x}")?;
|
||||||
|
}
|
||||||
|
write!(f, "..")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FormatoMac<'m>(&'m Mac);
|
||||||
|
impl core::fmt::Display for FormatoMac<'_> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
for (i, b) in self.0.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ":")?;
|
||||||
|
}
|
||||||
|
write!(f, "{b:02x}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -217,7 +217,12 @@ pub fn drenar_rx<F: FnMut(&[u8])>(mut callback: F) {
|
|||||||
|
|
||||||
/// Recibe UN paquete: copia su contenido en `buf` y devuelve los bytes
|
/// Recibe UN paquete: copia su contenido en `buf` y devuelve los bytes
|
||||||
/// copiados, o `0` si no hay paquete pendiente. La interfaz que el host
|
/// copiados, o `0` si no hay paquete pendiente. La interfaz que el host
|
||||||
/// expone a los apps via `sys_net_recibir` — un paquete por llamada (Fase 19).
|
/// expuso a los apps via `sys_net_recibir` en la Fase 19. Desde la Fase 20
|
||||||
|
/// el kernel toma la cola RX para FILTRAR Akasha (`akasha::drenar_y_demultiplexar`
|
||||||
|
/// + `akasha::pop_usuario`); `recibir_en` queda como primitiva del driver,
|
||||||
|
/// disponible para futuros consumidores directos (p. ej., una herramienta de
|
||||||
|
/// diagnostico que quiera leer la cola RX sin demultiplexar).
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn recibir_en(buf: &mut [u8]) -> usize {
|
pub fn recibir_en(buf: &mut [u8]) -> usize {
|
||||||
let Some(tarjeta) = TARJETA.get() else {
|
let Some(tarjeta) = TARJETA.get() else {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ use bootloader_api::{entry_point, BootInfo};
|
|||||||
use spin::{Mutex, Once};
|
use spin::{Mutex, Once};
|
||||||
|
|
||||||
// --- Subsistemas del kernel ---
|
// --- Subsistemas del kernel ---
|
||||||
|
mod akasha;
|
||||||
mod almacen;
|
mod almacen;
|
||||||
mod async_system;
|
mod async_system;
|
||||||
mod baliza;
|
mod baliza;
|
||||||
@@ -165,6 +166,10 @@ async fn tarea_compositor() {
|
|||||||
// FASE 16 :: avanzar el reloj de la barra de tareas — recompone si el
|
// FASE 16 :: avanzar el reloj de la barra de tareas — recompone si el
|
||||||
// segundo cambio respecto al ultimo mostrado. Si no, vuelve enseguida.
|
// segundo cambio respecto al ultimo mostrado. Si no, vuelve enseguida.
|
||||||
compositor::tick_reloj();
|
compositor::tick_reloj();
|
||||||
|
// FASE 20 :: pulso del oficio AoE — el demultiplexor de RX en cada
|
||||||
|
// tic (cero coste sin trafico), y el faro `AnunciarRaiz` cada 5 s
|
||||||
|
// (medido contra el reloj monotono, no contra los awaits).
|
||||||
|
akasha::tic_compositor();
|
||||||
// FASE 10 :: atender las altas en vivo. Por cada `Alt+N` pendiente,
|
// FASE 10 :: atender las altas en vivo. Por cada `Alt+N` pendiente,
|
||||||
// dar a luz una aplicacion nueva — el compositor solo conto la
|
// dar a luz una aplicacion nueva — el compositor solo conto la
|
||||||
// peticion; instanciar el WASM es trabajo del orquestador.
|
// peticion; instanciar el WASM es trabajo del orquestador.
|
||||||
@@ -174,16 +179,14 @@ async fn tarea_compositor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FASE 18/19 — la primera voz del kernel hacia la red. Envia un ARP request
|
/// FASE 18 — saludo inicial a la red. La tarea es CORTA y de un solo tiro:
|
||||||
/// al gateway de QEMU para anunciarse, y termina: a partir de la Fase 19, los
|
/// deja estabilizarse la cola RX del dispositivo y envia un ARP request al
|
||||||
/// apps drenan la cola RX por su cuenta via `sys_net_recibir`, asi que el
|
/// gateway de QEMU para anunciarse en capa-3. El demuxer Akasha (Fase 20)
|
||||||
/// kernel no le quita paquetes a nadie.
|
/// vive en el tic del compositor; el faro periodico, en `tarea_akasha_faro`.
|
||||||
async fn tarea_red(mac: drivers::red::Mac) {
|
async fn tarea_red(mac: drivers::red::Mac) {
|
||||||
// Dejar un par de fotogramas para que la cola RX se estabilice.
|
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
async_system::reloj::EsperaFrame::nueva().await;
|
async_system::reloj::EsperaFrame::nueva().await;
|
||||||
}
|
}
|
||||||
// Componer y enviar el ARP request: «¿quien tiene 10.0.2.2?».
|
|
||||||
let frame = drivers::red::componer_arp_request(
|
let frame = drivers::red::componer_arp_request(
|
||||||
mac,
|
mac,
|
||||||
drivers::red::IP_RENASER,
|
drivers::red::IP_RENASER,
|
||||||
@@ -200,9 +203,14 @@ async fn tarea_red(mac: drivers::red::Mac) {
|
|||||||
let _ = writeln!(baliza::Serie, "red :: envio fallido :: {motivo}");
|
let _ = writeln!(baliza::Serie, "red :: envio fallido :: {motivo}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// La tarea termina aqui. Los apps se encargan del trafico desde ahora.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// FASE 20 — el faro Akasha. Cada `INTERVALO_FARO` fotogramas (= 5 s a
|
||||||
|
/// 100 Hz) difunde por broadcast `AnunciarRaiz(manifiesto)`. La primera
|
||||||
|
/// difusion se demora unos pocos fotogramas para que el grafo termine de
|
||||||
|
/// montarse — si `manifiesto()` aun es `None`, el envio es no-op y el
|
||||||
|
/// siguiente faro lo reintentara. Tarea sin fin, con la misma forma que
|
||||||
|
/// `tarea_compositor` (probada y latiente).
|
||||||
/// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el
|
/// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el
|
||||||
/// sector 0 del disco SIN bloquear: cede la CPU mientras el disco trabaja —las
|
/// sector 0 del disco SIN bloquear: cede la CPU mientras el disco trabaja —las
|
||||||
/// apps siguen pintando entre tanto— y la IRQ del disco la reanuda cuando el
|
/// apps siguen pintando entre tanto— y la IRQ del disco la reanuda cuando el
|
||||||
@@ -552,6 +560,9 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
|||||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
|
||||||
drivers::red::irq(),
|
drivers::red::irq(),
|
||||||
);
|
);
|
||||||
|
// FASE 20 :: registrar la MAC en el respondedor Akasha, para que
|
||||||
|
// pueda firmar sus frames AoE.
|
||||||
|
akasha::montar(mac);
|
||||||
}
|
}
|
||||||
Err(motivo) => {
|
Err(motivo) => {
|
||||||
let _ = writeln!(baliza::Serie, "red :: virtio-net :: {motivo}");
|
let _ = writeln!(baliza::Serie, "red :: virtio-net :: {motivo}");
|
||||||
@@ -578,8 +589,9 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
|||||||
// disco de forma ASINCRONA: la demostracion de que la IRQ del disco
|
// disco de forma ASINCRONA: la demostracion de que la IRQ del disco
|
||||||
// conduce la E/S sin detener a las aplicaciones visuales.
|
// conduce la E/S sin detener a las aplicaciones visuales.
|
||||||
ejecutor.spawn(tarea_sonda_disco());
|
ejecutor.spawn(tarea_sonda_disco());
|
||||||
// FASE 18 :: si la tarjeta de red se monto, una tarea le envia un ARP
|
// FASE 18 :: si la tarjeta de red se monto, una tarea corta envia un ARP
|
||||||
// request al gateway y registra por COM1 los paquetes entrantes.
|
// al gateway para anunciarse en capa-3. El oficio AoE (Fase 20) — demuxer
|
||||||
|
// de entrada + faro periodico — va clavado al tic del compositor.
|
||||||
if let Ok(mac) = mac_red {
|
if let Ok(mac) = mac_red {
|
||||||
ejecutor.spawn(tarea_red(mac));
|
ejecutor.spawn(tarea_red(mac));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
// * sys_tono — hacer sonar la bocina del PC (Fase 12);
|
// * sys_tono — hacer sonar la bocina del PC (Fase 12);
|
||||||
// * sys_net_mac — leer la MAC de la tarjeta de red (Fase 19);
|
// * sys_net_mac — leer la MAC de la tarjeta de red (Fase 19);
|
||||||
// * sys_net_enviar — enviar un frame Ethernet crudo (Fase 19);
|
// * sys_net_enviar — enviar un frame Ethernet crudo (Fase 19);
|
||||||
// * sys_net_recibir — leer el siguiente frame recibido (Fase 19).
|
// * sys_net_recibir — leer el siguiente frame recibido (Fase 19;
|
||||||
|
// desde la Fase 20, los frames Akasha se filtran
|
||||||
|
// en el kernel y no llegan al userspace).
|
||||||
//
|
//
|
||||||
// GUARDARRAIL: el kernel valida MATEMATICAMENTE todo puntero que el modulo le
|
// GUARDARRAIL: el kernel valida MATEMATICAMENTE todo puntero que el modulo le
|
||||||
// entrega contra los limites reales de su memoria lineal. No se confia en que
|
// entrega contra los limites reales de su memoria lineal. No se confia en que
|
||||||
@@ -530,10 +532,14 @@ pub(crate) fn enlazar_capacidades(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// --- CAPACIDAD 14 :: sys_net_recibir(salida, capacidad) -> i32 ---
|
// --- CAPACIDAD 14 :: sys_net_recibir(salida, capacidad) -> i32 ---
|
||||||
// Saca el siguiente frame de la cola RX del dispositivo y lo copia en
|
// Saca el siguiente frame de la cola del USUARIO y lo copia en `salida`.
|
||||||
// `salida`. Devuelve los bytes copiados (>0), 0 si no hay frame pendiente,
|
// Desde la Fase 20, esa cola la rellena el demultiplexor del kernel
|
||||||
// o -1 si no hay red montada. La cola RX es del dispositivo y se comparte
|
// (`akasha::drenar_y_demultiplexar`): los frames Akasha (`0x88B5` con
|
||||||
// entre los apps: el primero que pregunte se lleva el paquete.
|
// payload valido) se procesan en el nucleo y NO llegan aqui; el resto
|
||||||
|
// del trafico —ARP, IPv4 de QEMU, futuros protocolos— si. Devuelve los
|
||||||
|
// bytes copiados (>0), 0 si no hay frame pendiente, o -1 si no hay red
|
||||||
|
// montada. La cola se vacia FIFO; si un app no llama nunca, los frames
|
||||||
|
// mas antiguos se descartan al desbordar (ver `akasha::COLA_USUARIO`).
|
||||||
enlazador.func_wrap(
|
enlazador.func_wrap(
|
||||||
"renaser",
|
"renaser",
|
||||||
"sys_net_recibir",
|
"sys_net_recibir",
|
||||||
@@ -555,10 +561,10 @@ pub(crate) fn enlazar_capacidades(
|
|||||||
"WASM :: sys_net_recibir desbordo la memoria lineal",
|
"WASM :: sys_net_recibir desbordo la memoria lineal",
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
// Bufer kernel-side donde el driver vuelca el frame; luego se copia
|
// Bufer kernel-side donde la cola del usuario vuelca el frame; luego
|
||||||
// a la memoria del app en una sola pasada.
|
// se copia a la memoria del app en una sola pasada.
|
||||||
let mut buf: alloc::vec::Vec<u8> = alloc::vec![0u8; capacidad as usize];
|
let mut buf: alloc::vec::Vec<u8> = alloc::vec![0u8; capacidad as usize];
|
||||||
let n = crate::drivers::red::recibir_en(&mut buf);
|
let n = crate::akasha::pop_usuario(&mut buf);
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user