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