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:
sergio
2026-05-23 05:14:43 +00:00
parent 07ab095d42
commit 42fee6fcbc
14 changed files with 1075 additions and 24 deletions
+86
View File
@@ -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
teselada, su pestaña sale en la barra de tareas entre `bitacora` y
`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
View File
@@ -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
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
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`
+ 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
+9
View File
@@ -2,6 +2,14 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "akasha"
version = "0.1.0"
dependencies = [
"postcard",
"serde",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
@@ -456,6 +464,7 @@ dependencies = [
name = "kernel"
version = "0.1.0"
dependencies = [
"akasha",
"bootloader_api",
"crossbeam-queue",
"embedded-graphics",
+5 -4
View File
@@ -15,10 +15,11 @@ resolver = "2"
members = ["boot"]
# 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.
# `formato` —el formato del grafo en disco— tambien se excluye: es un nucleo
# `no_std` que enlaza el kernel bare-metal, asi que se compila como dependencia
# de cada lado (kernel y boot) y no como miembro del workspace anfitrion.
exclude = ["kernel", "apps", "formato"]
# `formato` —el formato del grafo en disco— y `akasha` —el protocolo Akasha
# Over Ether (Fase 20)— tambien se excluyen: son nucleos `no_std` que enlaza
# el kernel bare-metal, asi que se compilan como dependencia de cada lado
# (kernel y boot) y no como miembros del workspace anfitrion.
exclude = ["kernel", "apps", "formato", "akasha"]
# -----------------------------------------------------------------------------
# Metadatos compartidos: cada miembro hereda esta identidad con `*.workspace`.
+27
View File
@@ -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.*
+43
View File
@@ -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
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
- Reutilizar infraestructura madura de la comunidad antes que reinventar.
+129
View File
@@ -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"
+38
View File
@@ -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"] }
+252
View File
@@ -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(&ETHER_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], &ETHER_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(&ETHER_TYPE_AKASHA.to_be_bytes());
frame[14..].copy_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
assert!(matches!(
analizar_frame(&frame),
Err(ErrorAkasha::PayloadInvalido)
));
}
}
+8
View File
@@ -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.
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 ---
# `mirada-layout` es el motor de teselado del compositor de brahman —
# geometría pura (rectángulos, foco, Z-order), `no_std`, sin smithay ni
+431
View File
@@ -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(())
}
}
+6 -1
View File
@@ -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
/// 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 {
let Some(tarjeta) = TARJETA.get() else {
return 0;
+21 -9
View File
@@ -45,6 +45,7 @@ use bootloader_api::{entry_point, BootInfo};
use spin::{Mutex, Once};
// --- Subsistemas del kernel ---
mod akasha;
mod almacen;
mod async_system;
mod baliza;
@@ -165,6 +166,10 @@ async fn tarea_compositor() {
// FASE 16 :: avanzar el reloj de la barra de tareas — recompone si el
// segundo cambio respecto al ultimo mostrado. Si no, vuelve enseguida.
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,
// dar a luz una aplicacion nueva — el compositor solo conto la
// 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
/// al gateway de QEMU para anunciarse, y termina: a partir de la Fase 19, los
/// apps drenan la cola RX por su cuenta via `sys_net_recibir`, asi que el
/// kernel no le quita paquetes a nadie.
/// FASE 18 — saludo inicial a la red. La tarea es CORTA y de un solo tiro:
/// deja estabilizarse la cola RX del dispositivo y envia un ARP request al
/// gateway de QEMU para anunciarse en capa-3. El demuxer Akasha (Fase 20)
/// vive en el tic del compositor; el faro periodico, en `tarea_akasha_faro`.
async fn tarea_red(mac: drivers::red::Mac) {
// Dejar un par de fotogramas para que la cola RX se estabilice.
for _ in 0..10 {
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(
mac,
drivers::red::IP_RENASER,
@@ -200,9 +203,14 @@ async fn tarea_red(mac: drivers::red::Mac) {
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
/// 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
@@ -552,6 +560,9 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
drivers::red::irq(),
);
// FASE 20 :: registrar la MAC en el respondedor Akasha, para que
// pueda firmar sus frames AoE.
akasha::montar(mac);
}
Err(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
// conduce la E/S sin detener a las aplicaciones visuales.
ejecutor.spawn(tarea_sonda_disco());
// FASE 18 :: si la tarjeta de red se monto, una tarea le envia un ARP
// request al gateway y registra por COM1 los paquetes entrantes.
// FASE 18 :: si la tarjeta de red se monto, una tarea corta envia un ARP
// 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 {
ejecutor.spawn(tarea_red(mac));
}
+14 -8
View File
@@ -18,7 +18,9 @@
// * 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_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
// 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 ---
// Saca el siguiente frame de la cola RX del dispositivo y lo copia en
// `salida`. Devuelve los bytes copiados (>0), 0 si no hay frame pendiente,
// o -1 si no hay red montada. La cola RX es del dispositivo y se comparte
// entre los apps: el primero que pregunte se lleva el paquete.
// Saca el siguiente frame de la cola del USUARIO y lo copia en `salida`.
// Desde la Fase 20, esa cola la rellena el demultiplexor del kernel
// (`akasha::drenar_y_demultiplexar`): los frames Akasha (`0x88B5` con
// 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(
"renaser",
"sys_net_recibir",
@@ -555,10 +561,10 @@ pub(crate) fn enlazar_capacidades(
"WASM :: sys_net_recibir desbordo la memoria lineal",
)?;
}
// Bufer kernel-side donde el driver vuelca el frame; luego se copia
// a la memoria del app en una sola pasada.
// Bufer kernel-side donde la cola del usuario vuelca el frame; luego
// 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 n = crate::drivers::red::recibir_en(&mut buf);
let n = crate::akasha::pop_usuario(&mut buf);
if n == 0 {
return Ok(0);
}