tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198635 Definitions

⎇ 07ab095d4202aa5ff30d9a109e610d517a434e74 renaser/kernel/src/wasm/env.rs Rust · 572 lines · 23.6 KB History Who touched what raw

feat(renaser): Fase 19 — voz del userspace hacia la red (pregon) 07ab095d4 · sergio ·

1// =============================================================================
2//  renaser :: kernel/src/wasm/env.rs — Fase 4/5/6 :: la matriz de capacidades
3// -----------------------------------------------------------------------------
4//  El aislamiento de renaser no descansa en `int 0x80` ni en `sysenter`: no hay
5//  vectores de syscall. Una aplicacion WASM solo puede hacer aquello para lo
6//  que el kernel le haya inyectado una FUNCION DEL HOST. Esta matriz concede:
7//
8//    * sys_render_frame      — componer un fotograma en su region de pantalla;
9//    * sys_get_scancode      — consultar su canal de teclado;
10//    * sys_object_put        — grabar un objeto en el grafo (Fase 6.1c);
11//    * sys_object_datos      — leer la carga util de un objeto;
12//    * sys_object_hijo       — recorrer las aristas del DAG;
13//    * sys_object_raiz       — leer la raiz del grafo;
14//    * sys_object_fijar_raiz — coronar un objeto como raiz;
15//    * sys_estado_cargar     — leer el estado persistido de la app (Fase 7c);
16//    * sys_estado_guardar    — anclar el estado persistido de la app (Fase 7c);
17//    * sys_tiempo_mono       — leer el reloj monotono del sistema (Fase 11);
18//    * sys_tono              — hacer sonar la bocina del PC (Fase 12);
19//    * sys_net_mac           — leer la MAC de la tarjeta de red (Fase 19);
20//    * sys_net_enviar        — enviar un frame Ethernet crudo (Fase 19);
21//    * sys_net_recibir       — leer el siguiente frame recibido (Fase 19).
22//
23//  GUARDARRAIL: el kernel valida MATEMATICAMENTE todo puntero que el modulo le
24//  entrega contra los limites reales de su memoria lineal. No se confia en que
25//  el runtime lo haga; se verifica aqui, antes de leer o escribir un solo byte.
26//
27//  DOS CLASES DE FALLO. Un puntero fuera de limites es CULPA DE LA APP: se
28//  devuelve un `Error` que la ABORTA (el kernel la captura y la desaloja). Un
29//  fallo del almacenamiento —disco, objeto inexistente— NO es culpa de la app:
30//  se le devuelve un codigo de error negativo, y la app decide que hacer.
31// =============================================================================
32
33use wasmi::{Caller, Error, Extern, Linker, Memory, StoreLimits};
34
35use crate::almacen::Hash;
36use crate::async_system::teclado::CanalTeclado;
37
38/// El estado del host adscrito al `Store` de una aplicacion: cuanto necesita
39/// una capacidad para servir a ESA app y a ninguna otra — su region de pantalla,
40/// su canal de teclado y sus cuotas de recursos. Dos apps jamas comparten nada.
41pub(crate) struct ContextoCapacidades {
42    /// El tamaño natural del lienzo de la app, en pixeles. El fotograma que
43    /// entrega `sys_render_frame` mide exactamente `natural_ancho × natural_alto`;
44    /// el compositor lo cachea y lo compone, sin deformarlo, en el marco que el
45    /// teselado le asigno.
46    pub(crate) natural_ancho: usize,
47    pub(crate) natural_alto: usize,
48    /// El canal de teclado propio de la aplicacion.
49    pub(crate) canal: CanalTeclado,
50    /// El techo de recursos de la aplicacion — hoy, su memoria lineal maxima.
51    /// `wasmi` lo consulta en cada `memory.grow` via `Store::limiter`.
52    pub(crate) limites: StoreLimits,
53    /// El indice de esta app — su identidad. La usan las capacidades de estado
54    /// (Fase 7c) para hallar su `EntradaApp` del manifiesto, y el compositor
55    /// (Fase 8) para hallar su ventana en el escritorio: jamas la de otra.
56    pub(crate) indice_app: usize,
57}
58
59/// Recupera la memoria lineal exportada por el modulo. Que no la exporte es un
60/// modulo mal formado: se aborta.
61fn obtener_memoria(caller: &Caller<'_, ContextoCapacidades>) -> Result<Memory, Error> {
62    caller
63        .get_export("memory")
64        .and_then(Extern::into_memory)
65        .ok_or_else(|| Error::new("WASM :: el modulo no exporta su memoria lineal"))
66}
67
68/// VALIDACION INFRANQUEABLE DE LIMITES. Comprueba que `[ptr, ptr + len)` cae
69/// entera dentro de la memoria lineal `m` y devuelve ese sub-slice. Un rango
70/// que se desborde aborta la app — el `Error` se traduce en una trampa de WASM.
71fn rango<'a>(m: &'a [u8], ptr: u32, len: usize, fallo: &'static str) -> Result<&'a [u8], Error> {
72    let inicio = ptr as usize;
73    match inicio.checked_add(len) {
74        Some(fin) if fin <= m.len() => Ok(&m[inicio..fin]),
75        _ => Err(Error::new(fallo)),
76    }
77}
78
79/// Lee un hash de 32 bytes de la memoria lineal, con sus limites verificados.
80fn leer_hash(m: &[u8], ptr: u32, fallo: &'static str) -> Result<Hash, Error> {
81    let bytes = rango(m, ptr, 32, fallo)?;
82    let mut hash = [0u8; 32];
83    hash.copy_from_slice(bytes);
84    Ok(hash)
85}
86
87/// Inyecta en el enlazador la matriz de capacidades del modulo WASM. Todo lo
88/// que no se defina aqui le queda, al modulo, fisicamente fuera de alcance.
89///
90/// Devuelve `Err` si una capacidad no se pudo enlazar — un fallo del kernel,
91/// no de la app; aun asi se propaga como `Result` para no incendiar nada.
92pub(crate) fn enlazar_capacidades(
93    enlazador: &mut Linker<ContextoCapacidades>,
94) -> Result<(), Error> {
95    // --- CAPACIDAD 1 :: sys_render_frame(ptr, len) ---
96    // El modulo entrega (ptr, len) hacia su PROPIA memoria lineal; el kernel
97    // valida esos limites y, solo entonces, compone el fotograma DENTRO de la
98    // region asignada a la app.
99    enlazador.func_wrap(
100        "renaser",
101        "sys_render_frame",
102        |caller: Caller<'_, ContextoCapacidades>, ptr: u32, len: u32| -> Result<(), Error> {
103            let indice = caller.data().indice_app;
104            let nat_ancho = caller.data().natural_ancho;
105            let nat_alto = caller.data().natural_alto;
106
107            // El fotograma debe medir EXACTAMENTE el lienzo natural de la app.
108            // Un tamaño distinto delata a una app que pinta fuera de su lienzo:
109            // se aborta antes de tocar un byte.
110            let esperado = nat_ancho * nat_alto * 4;
111            if len as usize != esperado {
112                return Err(Error::new(
113                    "WASM :: sys_render_frame con un fotograma ajeno al lienzo natural",
114                ));
115            }
116
117            let memoria = obtener_memoria(&caller)?;
118            let datos: &[u8] = memoria.data(&caller);
119
120            // VALIDACION INFRANQUEABLE: si (ptr, len) se sale de la memoria
121            // lineal del modulo, se aborta la app —no el kernel—.
122            let fotograma = rango(
123                datos,
124                ptr,
125                len as usize,
126                "WASM :: sys_render_frame desbordo la memoria lineal del modulo",
127            )?;
128
129            // Limites verificados: el compositor cachea el fotograma —para
130            // poder recomponerlo si el escritorio se re-tesela— y lo compone,
131            // centrado, en el marco que el teselado asigno a esta app.
132            crate::compositor::presentar_fotograma(indice, fotograma);
133            Ok(())
134        },
135    )?;
136
137    // --- CAPACIDAD 2 :: sys_get_scancode() -> u32 ---
138    // Expone, sin bloquear, el siguiente scancode del canal PROPIO de la app.
139    enlazador.func_wrap(
140        "renaser",
141        "sys_get_scancode",
142        |caller: Caller<'_, ContextoCapacidades>| -> u32 {
143            caller.data().canal.pop().unwrap_or(0) as u32
144        },
145    )?;
146
147    // --- CAPACIDAD 3 :: sys_object_put(datos, datos_len, hijos, hijos_cnt, salida) -> i32 ---
148    // Graba un objeto en el grafo. El modulo entrega, en su memoria lineal, la
149    // carga util y un arreglo de `hijos_cnt` hashes de 32 bytes (las aristas).
150    // El kernel escribe el hash resultante —la identidad del objeto— en
151    // `salida`. Devuelve 0 si el objeto se grabo (o ya existia), -1 si el
152    // almacenamiento fallo.
153    enlazador.func_wrap(
154        "renaser",
155        "sys_object_put",
156        |mut caller: Caller<'_, ContextoCapacidades>,
157         datos_ptr: u32,
158         datos_len: u32,
159         hijos_ptr: u32,
160         hijos_cnt: u32,
161         salida: u32|
162         -> Result<i32, Error> {
163            let memoria = obtener_memoria(&caller)?;
164
165            // --- Leer las entradas de la memoria lineal, con limites firmes. ---
166            let (datos, hijos) = {
167                let m = memoria.data(&caller);
168
169                let datos = rango(
170                    m,
171                    datos_ptr,
172                    datos_len as usize,
173                    "WASM :: sys_object_put desbordo la memoria lineal (datos)",
174                )?
175                .to_vec();
176
177                // El arreglo de hijos: `hijos_cnt` hashes contiguos de 32 bytes.
178                let bytes_hijos = (hijos_cnt as usize).checked_mul(32).ok_or_else(|| {
179                    Error::new("WASM :: sys_object_put con un conteo de hijos imposible")
180                })?;
181                let crudo = rango(
182                    m,
183                    hijos_ptr,
184                    bytes_hijos,
185                    "WASM :: sys_object_put desbordo la memoria lineal (hijos)",
186                )?;
187                let mut hijos: alloc::vec::Vec<Hash> =
188                    alloc::vec::Vec::with_capacity(hijos_cnt as usize);
189                for trozo in crudo.chunks_exact(32) {
190                    let mut h = [0u8; 32];
191                    h.copy_from_slice(trozo);
192                    hijos.push(h);
193                }
194
195                // Verificar que el hash de salida cabe ANTES de tocar el disco.
196                rango(
197                    m,
198                    salida,
199                    32,
200                    "WASM :: sys_object_put desbordo la memoria lineal (salida)",
201                )?;
202
203                (datos, hijos)
204            };
205
206            // --- Grabar. Un fallo del almacen NO es culpa de la app: -1. ---
207            match crate::almacen::almacenar(datos, hijos) {
208                Ok(hash) => {
209                    let m = memoria.data_mut(&mut caller);
210                    m[salida as usize..salida as usize + 32].copy_from_slice(&hash);
211                    Ok(0)
212                }
213                Err(_) => Ok(-1),
214            }
215        },
216    )?;
217
218    // --- CAPACIDAD 4 :: sys_object_datos(hash, salida, capacidad) -> i32 ---
219    // Copia la carga util del objeto `hash` en `salida`. Devuelve el numero de
220    // bytes copiados, o -1 si el objeto no existe, -2 si `capacidad` no basta,
221    // -3 si el almacenamiento fallo.
222    enlazador.func_wrap(
223        "renaser",
224        "sys_object_datos",
225        |mut caller: Caller<'_, ContextoCapacidades>,
226         hash_ptr: u32,
227         salida: u32,
228         capacidad: u32|
229         -> Result<i32, Error> {
230            let memoria = obtener_memoria(&caller)?;
231
232            let hash = {
233                let m = memoria.data(&caller);
234                leer_hash(
235                    m,
236                    hash_ptr,
237                    "WASM :: sys_object_datos desbordo la memoria lineal (hash)",
238                )?
239            };
240
241            let objeto = match crate::almacen::recuperar(&hash) {
242                Ok(Some(objeto)) => objeto,
243                Ok(None) => return Ok(-1),
244                Err(_) => return Ok(-3),
245            };
246            if objeto.datos.len() > capacidad as usize {
247                return Ok(-2);
248            }
249
250            // Verificar que el destino cabe, y solo entonces copiar.
251            {
252                let m = memoria.data(&caller);
253                rango(
254                    m,
255                    salida,
256                    objeto.datos.len(),
257                    "WASM :: sys_object_datos desbordo la memoria lineal (salida)",
258                )?;
259            }
260            let n = objeto.datos.len();
261            let m = memoria.data_mut(&mut caller);
262            m[salida as usize..salida as usize + n].copy_from_slice(&objeto.datos);
263            Ok(n as i32)
264        },
265    )?;
266
267    // --- CAPACIDAD 5 :: sys_object_hijo(hash, indice, salida) -> i32 ---
268    // Recorre las aristas del DAG. Devuelve el NUMERO de hijos del objeto
269    // `hash`; si `indice` es valido, ademas escribe el hash de ese hijo en
270    // `salida`. Devuelve -1 si el objeto no existe, -3 si el almacen fallo.
271    enlazador.func_wrap(
272        "renaser",
273        "sys_object_hijo",
274        |mut caller: Caller<'_, ContextoCapacidades>,
275         hash_ptr: u32,
276         indice: u32,
277         salida: u32|
278         -> Result<i32, Error> {
279            let memoria = obtener_memoria(&caller)?;
280
281            let hash = {
282                let m = memoria.data(&caller);
283                leer_hash(
284                    m,
285                    hash_ptr,
286                    "WASM :: sys_object_hijo desbordo la memoria lineal (hash)",
287                )?
288            };
289
290            let objeto = match crate::almacen::recuperar(&hash) {
291                Ok(Some(objeto)) => objeto,
292                Ok(None) => return Ok(-1),
293                Err(_) => return Ok(-3),
294            };
295            let total = objeto.hijos.len();
296
297            // Si el indice apunta a un hijo real, entregar su hash.
298            if let Some(hijo) = objeto.hijos.get(indice as usize) {
299                {
300                    let m = memoria.data(&caller);
301                    rango(
302                        m,
303                        salida,
304                        32,
305                        "WASM :: sys_object_hijo desbordo la memoria lineal (salida)",
306                    )?;
307                }
308                let m = memoria.data_mut(&mut caller);
309                m[salida as usize..salida as usize + 32].copy_from_slice(hijo);
310            }
311            Ok(total as i32)
312        },
313    )?;
314
315    // --- CAPACIDAD 6 :: sys_object_raiz(salida) -> i32 ---
316    // Escribe en `salida` el hash de la raiz del grafo. Devuelve 1 si hay
317    // raiz, 0 si el grafo aun no tiene ninguna.
318    enlazador.func_wrap(
319        "renaser",
320        "sys_object_raiz",
321        |mut caller: Caller<'_, ContextoCapacidades>, salida: u32| -> Result<i32, Error> {
322            let memoria = obtener_memoria(&caller)?;
323            match crate::almacen::raiz() {
324                Some(hash) => {
325                    {
326                        let m = memoria.data(&caller);
327                        rango(
328                            m,
329                            salida,
330                            32,
331                            "WASM :: sys_object_raiz desbordo la memoria lineal (salida)",
332                        )?;
333                    }
334                    let m = memoria.data_mut(&mut caller);
335                    m[salida as usize..salida as usize + 32].copy_from_slice(&hash);
336                    Ok(1)
337                }
338                None => Ok(0),
339            }
340        },
341    )?;
342
343    // --- CAPACIDAD 7 :: sys_object_fijar_raiz(hash) -> i32 ---
344    // Corona el objeto `hash` como raiz del grafo. Devuelve 0 si se logro, -3
345    // si el almacenamiento fallo.
346    enlazador.func_wrap(
347        "renaser",
348        "sys_object_fijar_raiz",
349        |caller: Caller<'_, ContextoCapacidades>, hash_ptr: u32| -> Result<i32, Error> {
350            let memoria = obtener_memoria(&caller)?;
351            let hash = {
352                let m = memoria.data(&caller);
353                leer_hash(
354                    m,
355                    hash_ptr,
356                    "WASM :: sys_object_fijar_raiz desbordo la memoria lineal (hash)",
357                )?
358            };
359            match crate::almacen::fijar_raiz(hash) {
360                Ok(()) => Ok(0),
361                Err(_) => Ok(-3),
362            }
363        },
364    )?;
365
366    // --- CAPACIDAD 8 :: sys_estado_cargar(salida, capacidad) -> i32 ---
367    // Copia el estado persistido de ESTA app —el objeto que su `EntradaApp` del
368    // manifiesto tiene anclado— en `salida`. Devuelve el numero de bytes
369    // copiados, 0 si la app no tiene estado previo, -1 si el objeto anclado no
370    // existe, -2 si `capacidad` no basta, -3 si el almacenamiento fallo.
371    enlazador.func_wrap(
372        "renaser",
373        "sys_estado_cargar",
374        |mut caller: Caller<'_, ContextoCapacidades>,
375         salida: u32,
376         capacidad: u32|
377         -> Result<i32, Error> {
378            let indice = caller.data().indice_app;
379            // El hash del estado de esta app, segun el manifiesto vivo.
380            let hash = match crate::manifiesto::estado_de(indice) {
381                Some(hash) => hash,
382                None => return Ok(0), // Sin estado previo: nada que cargar.
383            };
384            let objeto = match crate::almacen::recuperar(&hash) {
385                Ok(Some(objeto)) => objeto,
386                Ok(None) => return Ok(-1),
387                Err(_) => return Ok(-3),
388            };
389            if objeto.datos.len() > capacidad as usize {
390                return Ok(-2);
391            }
392
393            let memoria = obtener_memoria(&caller)?;
394            // Verificar que el destino cabe, y solo entonces copiar.
395            {
396                let m = memoria.data(&caller);
397                rango(
398                    m,
399                    salida,
400                    objeto.datos.len(),
401                    "WASM :: sys_estado_cargar desbordo la memoria lineal (salida)",
402                )?;
403            }
404            let n = objeto.datos.len();
405            let m = memoria.data_mut(&mut caller);
406            m[salida as usize..salida as usize + n].copy_from_slice(&objeto.datos);
407            Ok(n as i32)
408        },
409    )?;
410
411    // --- CAPACIDAD 9 :: sys_estado_guardar(datos, datos_len) -> i32 ---
412    // Graba `datos` como el estado persistido de ESTA app: el kernel lo
413    // almacena como un objeto del grafo y ancla su hash en la `EntradaApp` de
414    // la app, re-grabando y re-anclando el manifiesto. El estado sobrevivira al
415    // reinicio. Devuelve 0 si se logro, -3 si el almacenamiento fallo.
416    enlazador.func_wrap(
417        "renaser",
418        "sys_estado_guardar",
419        |caller: Caller<'_, ContextoCapacidades>,
420         datos_ptr: u32,
421         datos_len: u32|
422         -> Result<i32, Error> {
423            let indice = caller.data().indice_app;
424            let memoria = obtener_memoria(&caller)?;
425            // Leer el estado de la memoria lineal, con limites firmes.
426            let datos = {
427                let m = memoria.data(&caller);
428                rango(
429                    m,
430                    datos_ptr,
431                    datos_len as usize,
432                    "WASM :: sys_estado_guardar desbordo la memoria lineal (datos)",
433                )?
434                .to_vec()
435            };
436            // Grabar el objeto de estado. Un fallo del almacen NO es culpa de
437            // la app: se le devuelve -3, y ella decide que hacer.
438            let hash = match crate::almacen::almacenar(datos, alloc::vec::Vec::new()) {
439                Ok(hash) => hash,
440                Err(_) => return Ok(-3),
441            };
442            // Anclarlo: muta el manifiesto vivo, lo re-graba y lo re-ancla.
443            match crate::manifiesto::fijar_estado(indice, hash) {
444                Ok(()) => Ok(0),
445                Err(_) => Ok(-3),
446            }
447        },
448    )?;
449
450    // --- CAPACIDAD 10 :: sys_tiempo_mono() -> u64 ---
451    // El reloj MONOTONO del sistema: milisegundos transcurridos desde el
452    // arranque. Le da al userspace un sentido del tiempo independiente del
453    // ritmo de los fotogramas — una app sabe CUANTO ha pasado, no solo CUANTAS
454    // veces la han llamado—. Jamas retrocede. No toca la memoria del modulo:
455    // es una lectura pura, sin puntero que validar.
456    enlazador.func_wrap(
457        "renaser",
458        "sys_tiempo_mono",
459        |_caller: Caller<'_, ContextoCapacidades>| -> u64 {
460            crate::async_system::reloj::milisegundos()
461        },
462    )?;
463
464    // --- CAPACIDAD 11 :: sys_tono(frecuencia_hz) ---
465    // Hace sonar la bocina del PC a `frecuencia_hz` (un 0 la silencia). La
466    // bocina es un recurso UNICO y global: para que dos apps no se la disputen,
467    // pertenece —como el teclado desde la Fase 8c— a la ventana ENFOCADA. Una
468    // app sin foco puede pedir un tono; sencillamente, no se oye. Y cuando el
469    // foco cambia, el compositor calla la bocina: la nueva dueña la reclamara
470    // en su proximo fotograma si quiere sonar.
471    enlazador.func_wrap(
472        "renaser",
473        "sys_tono",
474        |caller: Caller<'_, ContextoCapacidades>, frecuencia_hz: u32| {
475            // Prioridad del kernel: mientras suena una nota agendada por el
476            // sistema (acorde de bienvenida, repique al lanzar o cerrar una
477            // app, bajo de desalojo), las llamadas de los apps se ignoran. El
478            // kernel no se interrumpe a si mismo en mitad de su voz propia.
479            if crate::drivers::altavoz::kernel_sonando() {
480                return;
481            }
482            if crate::compositor::foco() == caller.data().indice_app {
483                crate::drivers::altavoz::tono(frecuencia_hz);
484            }
485        },
486    )?;
487
488    // --- CAPACIDAD 12 :: sys_net_mac(salida) -> i32 ---
489    // Copia los 6 bytes de la MAC de la tarjeta de red en `salida`. Devuelve 0
490    // si la red esta montada; -1 si no hay tarjeta o aun no se monto.
491    enlazador.func_wrap(
492        "renaser",
493        "sys_net_mac",
494        |mut caller: Caller<'_, ContextoCapacidades>, salida: u32| -> Result<i32, Error> {
495            let Some(mac) = crate::drivers::red::mac() else {
496                return Ok(-1);
497            };
498            let memoria = obtener_memoria(&caller)?;
499            {
500                let m = memoria.data(&caller);
501                rango(m, salida, 6, "WASM :: sys_net_mac desbordo la memoria lineal")?;
502            }
503            let m = memoria.data_mut(&mut caller);
504            m[salida as usize..salida as usize + 6].copy_from_slice(&mac);
505            Ok(0)
506        },
507    )?;
508
509    // --- CAPACIDAD 13 :: sys_net_enviar(ptr, len) -> i32 ---
510    // Envia un frame Ethernet crudo (cabecera + payload, sin CRC). El app
511    // construye el frame entero en su memoria lineal. Devuelve 0 si el
512    // envio se entrego al dispositivo; -1 si fallo el envio o no hay red.
513    enlazador.func_wrap(
514        "renaser",
515        "sys_net_enviar",
516        |caller: Caller<'_, ContextoCapacidades>, ptr: u32, len: u32| -> Result<i32, Error> {
517            let memoria = obtener_memoria(&caller)?;
518            let datos = memoria.data(&caller);
519            let frame = rango(
520                datos,
521                ptr,
522                len as usize,
523                "WASM :: sys_net_enviar desbordo la memoria lineal",
524            )?;
525            match crate::drivers::red::enviar(frame) {
526                Ok(()) => Ok(0),
527                Err(_) => Ok(-1),
528            }
529        },
530    )?;
531
532    // --- CAPACIDAD 14 :: sys_net_recibir(salida, capacidad) -> i32 ---
533    // Saca el siguiente frame de la cola RX del dispositivo y lo copia en
534    // `salida`. Devuelve los bytes copiados (>0), 0 si no hay frame pendiente,
535    // o -1 si no hay red montada. La cola RX es del dispositivo y se comparte
536    // entre los apps: el primero que pregunte se lleva el paquete.
537    enlazador.func_wrap(
538        "renaser",
539        "sys_net_recibir",
540        |mut caller: Caller<'_, ContextoCapacidades>,
541         salida: u32,
542         capacidad: u32|
543         -> Result<i32, Error> {
544            if crate::drivers::red::mac().is_none() {
545                return Ok(-1);
546            }
547            let memoria = obtener_memoria(&caller)?;
548            // Verificar que el destino cabe ANTES de tocar la cola.
549            {
550                let m = memoria.data(&caller);
551                rango(
552                    m,
553                    salida,
554                    capacidad as usize,
555                    "WASM :: sys_net_recibir desbordo la memoria lineal",
556                )?;
557            }
558            // Bufer kernel-side donde el driver vuelca el frame; luego se copia
559            // a la memoria del app en una sola pasada.
560            let mut buf: alloc::vec::Vec<u8> = alloc::vec![0u8; capacidad as usize];
561            let n = crate::drivers::red::recibir_en(&mut buf);
562            if n == 0 {
563                return Ok(0);
564            }
565            let m = memoria.data_mut(&mut caller);
566            m[salida as usize..salida as usize + n].copy_from_slice(&buf[..n]);
567            Ok(n as i32)
568        },
569    )?;
570
571    Ok(())
572}