1extern crate alloc;
34use alloc::vec;
35use alloc::vec::Vec;
36
37use crate::error::{FabricError, Result};
38use crate::host;
39use crate::lease::{self, LeaseInfo, LeaseStatus, SharedLeaseState};
40
41const LEASE_TAG_MEM: u8 = 0x01;
42
43fn lease_status_from_host(code: u8) -> LeaseStatus {
44 match code {
45 host::LEASE_STATUS_ACTIVE => LeaseStatus::Active,
46 host::LEASE_STATUS_EXPIRED => LeaseStatus::Expired,
47 host::LEASE_STATUS_REVOKED => LeaseStatus::Revoked,
48 _ => LeaseStatus::Revoked,
49 }
50}
51
52fn sync_state_from_host(state: &SharedLeaseState, info: &host::FbmuLeaseInfo) {
53 lease::set_expires_at_unix_secs(state, info.expires_at_unix_secs);
54 lease::set_status(state, lease_status_from_host(info.status));
55}
56
57#[derive(Debug)]
83pub struct FabricMem {
84 lease_state: Option<SharedLeaseState>,
85 host_lease_id: Option<u128>,
86}
87
88impl FabricMem {
89 pub fn hello() -> Result<FabricMem> {
100 host::fbmu_hello()?;
101 Ok(FabricMem {
102 lease_state: None,
103 host_lease_id: None,
104 })
105 }
106
107 pub fn write(&self, offset: u64, data: &[u8]) -> Result<()> {
113 #[cfg(feature = "observe")]
114 let start = std::time::Instant::now();
115 let result = if let Some(lease_id) = self.host_lease_id {
116 if let Some(state) = &self.lease_state {
117 let info = host::fbmu_query(lease_id)?;
118 sync_state_from_host(state, &info);
119 lease::ensure_active(state)?;
120 }
121 host::fbmu_write_lease(lease_id, offset, data)
122 } else {
123 if let Some(state) = &self.lease_state {
124 lease::ensure_active(state)?;
125 }
126 host::fbmu_write(offset, data)
127 };
128 #[cfg(feature = "observe")]
129 match &result {
130 Ok(()) => {
131 let duration_us = start.elapsed().as_micros() as u64;
132 crate::observe_hooks::on_mem_write(data.len() as u64);
133 crate::observe_hooks::on_op_completed(
134 grafos_observe::OpType::Write,
135 duration_us,
136 data.len() as u64,
137 );
138 }
139 Err(e) => {
140 crate::observe_hooks::on_op_error();
141 crate::observe_hooks::on_op_failed(
142 grafos_observe::OpType::Write,
143 &alloc::format!("{e:?}"),
144 );
145 }
146 }
147 result
148 }
149
150 pub fn read(&self, offset: u64, len: u32) -> Result<Vec<u8>> {
159 #[cfg(feature = "observe")]
160 let start = std::time::Instant::now();
161 let result = if let Some(lease_id) = self.host_lease_id {
162 if let Some(state) = &self.lease_state {
163 let info = host::fbmu_query(lease_id)?;
164 sync_state_from_host(state, &info);
165 lease::ensure_active(state)?;
166 }
167 let mut buf = vec![0u8; len as usize];
168 let n = host::fbmu_read_lease(lease_id, offset, &mut buf)?;
169 buf.truncate(n);
170 Ok(buf)
171 } else {
172 if let Some(state) = &self.lease_state {
173 lease::ensure_active(state)?;
174 }
175 let mut buf = vec![0u8; len as usize];
176 let n = host::fbmu_read(offset, &mut buf)?;
177 buf.truncate(n);
178 Ok(buf)
179 };
180 #[cfg(feature = "observe")]
181 match &result {
182 Ok(buf) => {
183 let duration_us = start.elapsed().as_micros() as u64;
184 crate::observe_hooks::on_mem_read(buf.len() as u64);
185 crate::observe_hooks::on_op_completed(
186 grafos_observe::OpType::Read,
187 duration_us,
188 buf.len() as u64,
189 );
190 }
191 Err(e) => {
192 crate::observe_hooks::on_op_error();
193 crate::observe_hooks::on_op_failed(
194 grafos_observe::OpType::Read,
195 &alloc::format!("{e:?}"),
196 );
197 }
198 }
199 result
200 }
201
202 pub fn arena_size(&self) -> Result<u64> {
208 if let Some(lease_id) = self.host_lease_id {
209 let info = host::fbmu_query(lease_id)?;
210 if let Some(state) = &self.lease_state {
211 sync_state_from_host(state, &info);
212 }
213 Ok(info.arena_size)
214 } else {
215 host::fbmu_get_arena_size()
216 }
217 }
218}
219
220#[derive(Debug)]
241pub struct MemLease {
242 state: SharedLeaseState,
243 mem: FabricMem,
244 _size: u64,
245}
246
247impl MemLease {
248 fn sync_from_host(&self) {
249 let Some(lease_id) = self.mem.host_lease_id else {
250 return;
251 };
252 if let Ok(info) = host::fbmu_query(lease_id) {
253 sync_state_from_host(&self.state, &info);
254 }
255 }
256
257 pub fn mem(&self) -> &FabricMem {
259 &self.mem
260 }
261
262 pub fn info(&self) -> LeaseInfo {
264 self.sync_from_host();
265 lease::info(&self.state)
266 }
267
268 pub fn lease_id(&self) -> u128 {
270 lease::lease_id(&self.state)
271 }
272
273 pub fn created_at_unix_secs(&self) -> u64 {
275 lease::created_at_unix_secs(&self.state)
276 }
277
278 pub fn expires_at_unix_secs(&self) -> u64 {
280 self.sync_from_host();
281 lease::expires_at_unix_secs(&self.state)
282 }
283
284 pub fn status(&self) -> LeaseStatus {
286 self.sync_from_host();
287 lease::status(&self.state)
288 }
289
290 pub fn renew(&self, duration_secs: u64) -> Result<()> {
294 if let Some(lease_id) = self.mem.host_lease_id {
295 let expires_at_unix_secs = host::fbmu_renew(lease_id, duration_secs)?;
296 lease::set_expires_at_unix_secs(&self.state, expires_at_unix_secs);
297 lease::set_status(&self.state, LeaseStatus::Active);
298 return Ok(());
299 }
300 lease::renew(&self.state, duration_secs)
301 }
302
303 pub fn free(&self) {
305 if let Some(lease_id) = self.mem.host_lease_id {
306 let _ = host::fbmu_free(lease_id);
307 }
308 lease::free(&self.state);
309 }
310
311 #[cfg(any(test, feature = "test-support"))]
318 pub fn dup(&self) -> MemLease {
319 MemLease {
320 state: self.state.clone(),
321 mem: FabricMem {
322 lease_state: self.mem.lease_state.clone(),
323 host_lease_id: self.mem.host_lease_id,
324 },
325 _size: self._size,
326 }
327 }
328}
329
330impl Drop for MemLease {
331 fn drop(&mut self) {
332 #[cfg(feature = "observe")]
333 crate::observe_hooks::on_lease_dropped(LEASE_TAG_MEM, lease::lease_id(&self.state));
334 lease::free(&self.state);
335 }
336}
337
338pub struct MemBuilder {
357 min_bytes: u64,
358 lease_secs: u64,
359}
360
361impl MemBuilder {
362 pub fn new() -> Self {
364 MemBuilder {
365 min_bytes: 0,
366 lease_secs: 300,
367 }
368 }
369
370 pub fn min_bytes(mut self, n: u64) -> Self {
376 self.min_bytes = n;
377 self
378 }
379
380 pub fn lease_secs(mut self, secs: u64) -> Self {
382 self.lease_secs = secs.max(1);
383 self
384 }
385
386 pub fn acquire(self) -> Result<MemLease> {
395 let result = match host::fbmu_alloc(self.min_bytes, self.lease_secs) {
396 Ok(info) => {
397 if info.arena_size < self.min_bytes {
398 return Err(FabricError::CapacityExceeded);
399 }
400 let created_at_unix_secs = info
401 .expires_at_unix_secs
402 .saturating_sub(self.lease_secs.max(1));
403 let state = lease::new_shared_lease_from_parts(
404 info.lease_id,
405 created_at_unix_secs,
406 info.expires_at_unix_secs,
407 lease_status_from_host(info.status),
408 );
409 Ok(MemLease {
410 state: state.clone(),
411 mem: FabricMem {
412 lease_state: Some(state),
413 host_lease_id: Some(info.lease_id),
414 },
415 _size: info.arena_size,
416 })
417 }
418 Err(FabricError::Unsupported) => {
419 let state = lease::new_shared_lease(LEASE_TAG_MEM, self.lease_secs);
420 let mut mem = FabricMem::hello()?;
421 mem.lease_state = Some(state.clone());
422 let arena = mem.arena_size()?;
423 if arena < self.min_bytes {
424 return Err(FabricError::CapacityExceeded);
425 }
426 Ok(MemLease {
427 state,
428 mem,
429 _size: arena,
430 })
431 }
432 Err(e) => Err(e),
433 };
434 #[cfg(feature = "observe")]
435 if let Ok(ref lease) = result {
436 crate::observe_hooks::on_lease_acquired(
437 LEASE_TAG_MEM,
438 lease.lease_id(),
439 "local",
440 lease._size,
441 );
442 }
443 result
444 }
445
446 pub fn attach(lease_id: u128) -> Result<MemLease> {
460 let info = host::fbmu_query(lease_id)?;
461 let status = lease_status_from_host(info.status);
462 match status {
463 LeaseStatus::Active => {}
464 LeaseStatus::Expired => return Err(FabricError::LeaseExpired),
465 LeaseStatus::Revoked => return Err(FabricError::Revoked),
466 }
467 let created_at_unix_secs = 0;
471 let state = lease::new_shared_lease_from_parts(
472 info.lease_id,
473 created_at_unix_secs,
474 info.expires_at_unix_secs,
475 status,
476 );
477 let result = MemLease {
478 state: state.clone(),
479 mem: FabricMem {
480 lease_state: Some(state),
481 host_lease_id: Some(info.lease_id),
482 },
483 _size: info.arena_size,
484 };
485 #[cfg(feature = "observe")]
486 crate::observe_hooks::on_lease_acquired(
487 LEASE_TAG_MEM,
488 result.lease_id(),
489 "attach",
490 result._size,
491 );
492 Ok(result)
493 }
494}
495
496impl Default for MemBuilder {
497 fn default() -> Self {
498 Self::new()
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::host;
506
507 #[test]
508 fn mem_hello_write_read_roundtrip() {
509 host::reset_mock();
510 host::mock_set_fbmu_arena_size(4096);
511
512 let mem = FabricMem::hello().expect("hello");
513 assert_eq!(mem.arena_size().unwrap(), 4096);
514
515 let data = b"fabricBIOS-grafos-std";
516 mem.write(0, data).expect("write");
517
518 let readback = mem.read(0, data.len() as u32).expect("read");
519 assert_eq!(&readback, data);
520 }
521
522 #[test]
523 fn mem_hello_error_propagates() {
524 host::reset_mock();
525 host::mock_set_fbmu_hello_error(Some(-1));
526
527 let result = FabricMem::hello();
528 assert!(result.is_err());
529 assert_eq!(result.unwrap_err(), FabricError::Disconnected);
530
531 host::mock_set_fbmu_hello_error(None);
532 }
533
534 #[test]
535 fn mem_builder_checks_capacity() {
536 host::reset_mock();
537 host::mock_set_fbmu_arena_size(1024);
538
539 let result = MemBuilder::new().min_bytes(2048).acquire();
541 assert_eq!(result.unwrap_err(), FabricError::CapacityExceeded);
542
543 let lease = MemBuilder::new()
545 .min_bytes(1024)
546 .acquire()
547 .expect("acquire");
548 assert_eq!(lease.mem().arena_size().unwrap(), 1024);
549 }
550
551 #[test]
552 fn mem_builder_default_acquires() {
553 host::reset_mock();
554 host::mock_set_fbmu_arena_size(65536);
555
556 let lease = MemBuilder::new().acquire().expect("acquire");
557 let data = b"test";
558 lease.mem().write(100, data).expect("write");
559 let readback = lease.mem().read(100, 4).expect("read");
560 assert_eq!(&readback, data);
561 }
562
563 #[test]
564 fn mem_lease_expires_and_can_be_renewed() {
565 host::reset_mock();
566 host::mock_set_fbmu_arena_size(4096);
567 host::mock_set_unix_time_secs(1_000);
568
569 let lease = MemBuilder::new().lease_secs(10).acquire().expect("acquire");
570 assert_eq!(lease.status(), LeaseStatus::Active);
571 assert_eq!(lease.expires_at_unix_secs(), 1_010);
572
573 lease.renew(20).expect("renew");
574 assert_eq!(lease.expires_at_unix_secs(), 1_020);
575
576 host::mock_advance_time_secs(21);
577 assert_eq!(lease.status(), LeaseStatus::Expired);
578 assert_eq!(
579 lease.mem().write(0, b"x").unwrap_err(),
580 FabricError::LeaseExpired
581 );
582 }
583
584 #[test]
585 fn mem_lease_free_revokes_operations() {
586 host::reset_mock();
587 host::mock_set_fbmu_arena_size(4096);
588
589 let lease = MemBuilder::new().acquire().expect("acquire");
590 lease.free();
591 assert_eq!(lease.status(), LeaseStatus::Revoked);
592 assert_eq!(lease.mem().read(0, 1).unwrap_err(), FabricError::Revoked);
593 }
594
595 #[test]
596 fn mem_attach_reconnects_to_active_lease() {
597 host::reset_mock();
598 host::mock_set_fbmu_arena_size(4096);
599
600 let lease = MemBuilder::new()
601 .lease_secs(300)
602 .acquire()
603 .expect("acquire");
604 let id = lease.lease_id();
605
606 lease.mem().write(0, b"hello").expect("write");
608
609 let attached = MemBuilder::attach(id).expect("attach");
611 assert_eq!(attached.lease_id(), id);
612 assert_eq!(attached.status(), LeaseStatus::Active);
613
614 let readback = attached.mem().read(0, 5).expect("read");
616 assert_eq!(&readback, b"hello");
617 }
618
619 #[test]
620 fn mem_attach_expired_returns_error() {
621 host::reset_mock();
622 host::mock_set_fbmu_arena_size(4096);
623 host::mock_set_unix_time_secs(1_000);
624
625 let lease = MemBuilder::new().lease_secs(5).acquire().expect("acquire");
626 let id = lease.lease_id();
627
628 host::mock_advance_time_secs(10);
630
631 let result = MemBuilder::attach(id);
632 assert_eq!(result.unwrap_err(), FabricError::LeaseExpired);
633 }
634
635 #[test]
636 fn mem_attach_revoked_returns_error() {
637 host::reset_mock();
638 host::mock_set_fbmu_arena_size(4096);
639
640 let lease = MemBuilder::new().acquire().expect("acquire");
641 let id = lease.lease_id();
642 lease.free(); let result = MemBuilder::attach(id);
645 assert_eq!(result.unwrap_err(), FabricError::Revoked);
646 }
647
648 #[test]
649 fn mem_attach_unknown_lease_returns_error() {
650 host::reset_mock();
651 host::mock_set_fbmu_arena_size(4096);
652
653 let result = MemBuilder::attach(0xDEADBEEF);
655 assert!(result.is_err());
656 }
657
658 #[test]
659 fn mem_leases_are_isolated_by_lease_id() {
660 host::reset_mock();
661 host::mock_set_fbmu_arena_size(4096);
662
663 let lease_a = MemBuilder::new().acquire().expect("acquire a");
664 let lease_b = MemBuilder::new().acquire().expect("acquire b");
665
666 lease_a.mem().write(0, b"aaa").expect("write a");
667 lease_b.mem().write(0, b"bbb").expect("write b");
668
669 let read_a = lease_a.mem().read(0, 3).expect("read a");
670 let read_b = lease_b.mem().read(0, 3).expect("read b");
671 assert_eq!(&read_a, b"aaa");
672 assert_eq!(&read_b, b"bbb");
673 }
674}