1extern crate alloc;
39use alloc::vec;
40use alloc::vec::Vec;
41
42use crate::error::{FabricError, Result};
43use crate::host;
44use crate::lease::{self, LeaseInfo, LeaseStatus, SharedLeaseState};
45
46const LEASE_TAG_BLOCK: u8 = 0x02;
47
48fn lease_status_from_host(code: u8) -> LeaseStatus {
49 match code {
50 host::LEASE_STATUS_ACTIVE => LeaseStatus::Active,
51 host::LEASE_STATUS_EXPIRED => LeaseStatus::Expired,
52 host::LEASE_STATUS_REVOKED => LeaseStatus::Revoked,
53 _ => LeaseStatus::Revoked,
54 }
55}
56
57fn sync_state_from_host(state: &SharedLeaseState, info: &host::FbbuLeaseInfo) {
58 lease::set_expires_at_unix_secs(state, info.expires_at_unix_secs);
59 lease::set_status(state, lease_status_from_host(info.status));
60}
61
62pub const BLOCK_SIZE: usize = 512;
67
68#[derive(Debug)]
97pub struct FabricBlock {
98 num_blocks: u64,
99 lease_state: Option<SharedLeaseState>,
100 host_lease_id: Option<u128>,
101}
102
103impl FabricBlock {
104 pub fn hello() -> Result<FabricBlock> {
114 host::fbbu_hello()?;
115 let num_blocks = host::fbbu_get_num_blocks()?;
116 Ok(FabricBlock {
117 num_blocks,
118 lease_state: None,
119 host_lease_id: None,
120 })
121 }
122
123 pub fn num_blocks(&self) -> u64 {
125 self.num_blocks
126 }
127
128 pub fn write_block(&self, lba: u64, data: &[u8; BLOCK_SIZE]) -> Result<()> {
135 #[cfg(feature = "observe")]
136 let start = std::time::Instant::now();
137 let result = if let Some(lease_id) = self.host_lease_id {
138 if let Some(state) = &self.lease_state {
139 let info = host::fbbu_query(lease_id)?;
140 sync_state_from_host(state, &info);
141 lease::ensure_active(state)?;
142 }
143 host::fbbu_write_block_lease(lease_id, lba, data)
144 } else {
145 if let Some(state) = &self.lease_state {
146 lease::ensure_active(state)?;
147 }
148 host::fbbu_write_block(lba, data)
149 };
150 #[cfg(feature = "observe")]
151 match &result {
152 Ok(()) => {
153 let duration_us = start.elapsed().as_micros() as u64;
154 crate::observe_hooks::on_block_write(BLOCK_SIZE as u64);
155 crate::observe_hooks::on_op_completed(
156 grafos_observe::OpType::WriteBlock,
157 duration_us,
158 BLOCK_SIZE as u64,
159 );
160 }
161 Err(e) => {
162 crate::observe_hooks::on_op_error();
163 crate::observe_hooks::on_op_failed(
164 grafos_observe::OpType::WriteBlock,
165 &alloc::format!("{e:?}"),
166 );
167 }
168 }
169 result
170 }
171
172 pub fn read_block(&self, lba: u64) -> Result<[u8; BLOCK_SIZE]> {
179 #[cfg(feature = "observe")]
180 let start = std::time::Instant::now();
181 let result = if let Some(lease_id) = self.host_lease_id {
182 if let Some(state) = &self.lease_state {
183 let info = host::fbbu_query(lease_id)?;
184 sync_state_from_host(state, &info);
185 lease::ensure_active(state)?;
186 }
187 let mut buf = [0u8; BLOCK_SIZE];
188 host::fbbu_read_block_lease(lease_id, lba, &mut buf)?;
189 Ok(buf)
190 } else {
191 if let Some(state) = &self.lease_state {
192 lease::ensure_active(state)?;
193 }
194 let mut buf = [0u8; BLOCK_SIZE];
195 host::fbbu_read_block(lba, &mut buf)?;
196 Ok(buf)
197 };
198 #[cfg(feature = "observe")]
199 match &result {
200 Ok(_) => {
201 let duration_us = start.elapsed().as_micros() as u64;
202 crate::observe_hooks::on_block_read(BLOCK_SIZE as u64);
203 crate::observe_hooks::on_op_completed(
204 grafos_observe::OpType::ReadBlock,
205 duration_us,
206 BLOCK_SIZE as u64,
207 );
208 }
209 Err(e) => {
210 crate::observe_hooks::on_op_error();
211 crate::observe_hooks::on_op_failed(
212 grafos_observe::OpType::ReadBlock,
213 &alloc::format!("{e:?}"),
214 );
215 }
216 }
217 result
218 }
219
220 pub fn write_blocks(&self, lba: u64, data: &[u8]) -> Result<()> {
232 if !data.len().is_multiple_of(BLOCK_SIZE) {
233 return Err(FabricError::IoError(-1));
234 }
235 let count = data.len() / BLOCK_SIZE;
236 for i in 0..count {
237 let start = i * BLOCK_SIZE;
238 let block: &[u8; BLOCK_SIZE] = data[start..start + BLOCK_SIZE]
239 .try_into()
240 .map_err(|_| FabricError::IoError(-1))?;
241 self.write_block(lba + i as u64, block)?;
242 }
243 Ok(())
244 }
245
246 pub fn read_blocks(&self, lba: u64, count: u32) -> Result<Vec<u8>> {
255 let mut out = vec![0u8; count as usize * BLOCK_SIZE];
256 for i in 0..count {
257 let block = self.read_block(lba + i as u64)?;
258 let start = i as usize * BLOCK_SIZE;
259 out[start..start + BLOCK_SIZE].copy_from_slice(&block);
260 }
261 Ok(out)
262 }
263}
264
265#[derive(Debug)]
287pub struct BlockLease {
288 state: SharedLeaseState,
289 block: FabricBlock,
290}
291
292impl BlockLease {
293 fn sync_from_host(&self) {
294 let Some(lease_id) = self.block.host_lease_id else {
295 return;
296 };
297 if let Ok(info) = host::fbbu_query(lease_id) {
298 sync_state_from_host(&self.state, &info);
299 }
300 }
301
302 pub fn block(&self) -> &FabricBlock {
304 &self.block
305 }
306
307 pub fn info(&self) -> LeaseInfo {
309 self.sync_from_host();
310 lease::info(&self.state)
311 }
312
313 pub fn lease_id(&self) -> u128 {
315 lease::lease_id(&self.state)
316 }
317
318 pub fn created_at_unix_secs(&self) -> u64 {
320 lease::created_at_unix_secs(&self.state)
321 }
322
323 pub fn expires_at_unix_secs(&self) -> u64 {
325 self.sync_from_host();
326 lease::expires_at_unix_secs(&self.state)
327 }
328
329 pub fn status(&self) -> LeaseStatus {
331 self.sync_from_host();
332 lease::status(&self.state)
333 }
334
335 pub fn renew(&self, duration_secs: u64) -> Result<()> {
337 if let Some(lease_id) = self.block.host_lease_id {
338 let expires_at_unix_secs = host::fbbu_renew(lease_id, duration_secs)?;
339 lease::set_expires_at_unix_secs(&self.state, expires_at_unix_secs);
340 lease::set_status(&self.state, LeaseStatus::Active);
341 return Ok(());
342 }
343 lease::renew(&self.state, duration_secs)
344 }
345
346 pub fn free(&self) {
348 if let Some(lease_id) = self.block.host_lease_id {
349 let _ = host::fbbu_free(lease_id);
350 }
351 lease::free(&self.state);
352 }
353}
354
355impl Drop for BlockLease {
356 fn drop(&mut self) {
357 #[cfg(feature = "observe")]
358 crate::observe_hooks::on_lease_dropped(LEASE_TAG_BLOCK, lease::lease_id(&self.state));
359 lease::free(&self.state);
360 }
361}
362
363pub struct BlockBuilder {
381 min_blocks: u64,
382 lease_secs: u64,
383}
384
385impl BlockBuilder {
386 pub fn new() -> Self {
388 BlockBuilder {
389 min_blocks: 0,
390 lease_secs: 300,
391 }
392 }
393
394 pub fn min_blocks(mut self, n: u64) -> Self {
400 self.min_blocks = n;
401 self
402 }
403
404 pub fn lease_secs(mut self, secs: u64) -> Self {
406 self.lease_secs = secs.max(1);
407 self
408 }
409
410 pub fn acquire(self) -> Result<BlockLease> {
419 let result = match host::fbbu_alloc(self.min_blocks, self.lease_secs) {
420 Ok(info) => {
421 if info.num_blocks < self.min_blocks {
422 return Err(FabricError::CapacityExceeded);
423 }
424 let created_at_unix_secs = info
425 .expires_at_unix_secs
426 .saturating_sub(self.lease_secs.max(1));
427 let state = lease::new_shared_lease_from_parts(
428 info.lease_id,
429 created_at_unix_secs,
430 info.expires_at_unix_secs,
431 lease_status_from_host(info.status),
432 );
433 Ok(BlockLease {
434 state: state.clone(),
435 block: FabricBlock {
436 num_blocks: info.num_blocks,
437 lease_state: Some(state),
438 host_lease_id: Some(info.lease_id),
439 },
440 })
441 }
442 Err(FabricError::Unsupported) => {
443 let state = lease::new_shared_lease(LEASE_TAG_BLOCK, self.lease_secs);
444 let mut block = FabricBlock::hello()?;
445 block.lease_state = Some(state.clone());
446 if block.num_blocks() < self.min_blocks {
447 return Err(FabricError::CapacityExceeded);
448 }
449 Ok(BlockLease { state, block })
450 }
451 Err(e) => Err(e),
452 };
453 #[cfg(feature = "observe")]
454 if let Ok(ref lease) = result {
455 crate::observe_hooks::on_lease_acquired(
456 LEASE_TAG_BLOCK,
457 lease.lease_id(),
458 "local",
459 lease.block().num_blocks() * BLOCK_SIZE as u64,
460 );
461 }
462 result
463 }
464
465 pub fn attach(lease_id: u128) -> Result<BlockLease> {
479 let info = host::fbbu_query(lease_id)?;
480 let status = lease_status_from_host(info.status);
481 match status {
482 LeaseStatus::Active => {}
483 LeaseStatus::Expired => return Err(FabricError::LeaseExpired),
484 LeaseStatus::Revoked => return Err(FabricError::Revoked),
485 }
486 let created_at_unix_secs = 0;
487 let state = lease::new_shared_lease_from_parts(
488 info.lease_id,
489 created_at_unix_secs,
490 info.expires_at_unix_secs,
491 status,
492 );
493 let result = BlockLease {
494 state: state.clone(),
495 block: FabricBlock {
496 num_blocks: info.num_blocks,
497 lease_state: Some(state),
498 host_lease_id: Some(info.lease_id),
499 },
500 };
501 #[cfg(feature = "observe")]
502 crate::observe_hooks::on_lease_acquired(
503 LEASE_TAG_BLOCK,
504 result.lease_id(),
505 "attach",
506 result.block().num_blocks() * BLOCK_SIZE as u64,
507 );
508 Ok(result)
509 }
510}
511
512impl Default for BlockBuilder {
513 fn default() -> Self {
514 Self::new()
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use crate::host;
522
523 #[test]
524 fn block_hello_write_read_roundtrip() {
525 host::reset_mock();
526 host::mock_set_fbbu_num_blocks(128);
527
528 let blk = FabricBlock::hello().expect("hello");
529 assert_eq!(blk.num_blocks(), 128);
530
531 let mut data = [0u8; BLOCK_SIZE];
532 for (i, byte) in data.iter_mut().enumerate() {
533 *byte = (i & 0xFF) as u8;
534 }
535 blk.write_block(0, &data).expect("write");
536
537 let readback = blk.read_block(0).expect("read");
538 assert_eq!(readback, data);
539 }
540
541 #[test]
542 fn block_hello_error_propagates() {
543 host::reset_mock();
544 host::mock_set_fbbu_hello_error(Some(-1));
545
546 let result = FabricBlock::hello();
547 assert!(result.is_err());
548 assert_eq!(result.unwrap_err(), FabricError::Disconnected);
549
550 host::mock_set_fbbu_hello_error(None);
551 }
552
553 #[test]
554 fn block_multi_block_write_read() {
555 host::reset_mock();
556 host::mock_set_fbbu_num_blocks(64);
557
558 let blk = FabricBlock::hello().expect("hello");
559
560 let mut data = vec![0u8; BLOCK_SIZE * 3];
561 for (i, b) in data.iter_mut().enumerate() {
562 *b = (i.wrapping_mul(7) & 0xFF) as u8;
563 }
564 blk.write_blocks(0, &data).expect("write_blocks");
565
566 let readback = blk.read_blocks(0, 3).expect("read_blocks");
567 assert_eq!(readback, data);
568 }
569
570 #[test]
571 fn block_builder_checks_capacity() {
572 host::reset_mock();
573 host::mock_set_fbbu_num_blocks(16);
574
575 let result = BlockBuilder::new().min_blocks(32).acquire();
576 assert_eq!(result.unwrap_err(), FabricError::CapacityExceeded);
577
578 let lease = BlockBuilder::new()
579 .min_blocks(16)
580 .acquire()
581 .expect("acquire");
582 assert_eq!(lease.block().num_blocks(), 16);
583 }
584
585 #[test]
586 fn block_write_blocks_rejects_non_aligned() {
587 host::reset_mock();
588 host::mock_set_fbbu_num_blocks(64);
589
590 let blk = FabricBlock::hello().expect("hello");
591 let bad = vec![0u8; 100]; assert!(blk.write_blocks(0, &bad).is_err());
593 }
594
595 #[test]
596 fn block_lease_expires_and_rejects_io() {
597 host::reset_mock();
598 host::mock_set_fbbu_num_blocks(16);
599 host::mock_set_unix_time_secs(2_000);
600
601 let lease = BlockBuilder::new()
602 .lease_secs(5)
603 .acquire()
604 .expect("acquire");
605 assert_eq!(lease.status(), LeaseStatus::Active);
606 host::mock_advance_time_secs(6);
607 assert_eq!(lease.status(), LeaseStatus::Expired);
608
609 let data = [0u8; BLOCK_SIZE];
610 assert_eq!(
611 lease.block().write_block(0, &data).unwrap_err(),
612 FabricError::LeaseExpired
613 );
614 }
615
616 #[test]
617 fn block_attach_reconnects_to_active_lease() {
618 host::reset_mock();
619 host::mock_set_fbbu_num_blocks(64);
620
621 let lease = BlockBuilder::new()
622 .lease_secs(300)
623 .acquire()
624 .expect("acquire");
625 let id = lease.lease_id();
626
627 let mut data = [0u8; BLOCK_SIZE];
629 data[0] = 0x42;
630 lease.block().write_block(0, &data).expect("write");
631
632 let attached = BlockBuilder::attach(id).expect("attach");
634 assert_eq!(attached.lease_id(), id);
635 assert_eq!(attached.status(), LeaseStatus::Active);
636
637 let readback = attached.block().read_block(0).expect("read");
639 assert_eq!(readback[0], 0x42);
640 }
641
642 #[test]
643 fn block_attach_expired_returns_error() {
644 host::reset_mock();
645 host::mock_set_fbbu_num_blocks(64);
646 host::mock_set_unix_time_secs(1_000);
647
648 let lease = BlockBuilder::new()
649 .lease_secs(5)
650 .acquire()
651 .expect("acquire");
652 let id = lease.lease_id();
653
654 host::mock_advance_time_secs(10);
656
657 let result = BlockBuilder::attach(id);
658 assert_eq!(result.unwrap_err(), FabricError::LeaseExpired);
659 }
660
661 #[test]
662 fn block_attach_revoked_returns_error() {
663 host::reset_mock();
664 host::mock_set_fbbu_num_blocks(64);
665
666 let lease = BlockBuilder::new().acquire().expect("acquire");
667 let id = lease.lease_id();
668 lease.free(); let result = BlockBuilder::attach(id);
671 assert_eq!(result.unwrap_err(), FabricError::Revoked);
672 }
673
674 #[test]
675 fn block_attach_unknown_lease_returns_error() {
676 host::reset_mock();
677 host::mock_set_fbbu_num_blocks(64);
678
679 let result = BlockBuilder::attach(0xDEADBEEF);
680 assert!(result.is_err());
681 }
682
683 #[test]
684 fn block_leases_are_isolated_by_lease_id() {
685 host::reset_mock();
686 host::mock_set_fbbu_num_blocks(8);
687
688 let lease_a = BlockBuilder::new().acquire().expect("acquire a");
689 let lease_b = BlockBuilder::new().acquire().expect("acquire b");
690
691 let mut a = [0u8; BLOCK_SIZE];
692 let mut b = [0u8; BLOCK_SIZE];
693 a[0] = 0xAA;
694 b[0] = 0xBB;
695
696 lease_a.block().write_block(0, &a).expect("write a");
697 lease_b.block().write_block(0, &b).expect("write b");
698
699 let read_a = lease_a.block().read_block(0).expect("read a");
700 let read_b = lease_b.block().read_block(0).expect("read b");
701 assert_eq!(read_a[0], 0xAA);
702 assert_eq!(read_b[0], 0xBB);
703 }
704}