ternaria_dev/block.rs
1//! A block device backed by host bytes.
2//!
3//! | Offset | Register | Behaviour |
4//! |--------|----------|-----------|
5//! | 0..3 | `SECTOR` | Which sector the next load transfers. Word-sized. |
6//! | 3 | `CONTROL` | Write 1 to load the selected sector, 2 to store it. |
7//! | 4 | `STATUS` | 0 after a successful transfer, -1 if the sector is out of range. |
8//! | 5.. | window | The staging buffer, one byte per tryte (D-04). Readable and writable. |
9//!
10//! The backing bytes come from a host file or an in-memory image. A load past
11//! the end of the image zeroes the window and sets `STATUS` to -1.
12//!
13//! # Writing
14//!
15//! A store copies the window back to the selected sector. The window holds
16//! trytes and the image holds bytes, so every tryte in the window must be in
17//! 0..=255; one that is not fails the store with `NotAByte` and leaves the
18//! image untouched. The check runs over the whole window before anything is
19//! written, so a store either happens completely or not at all.
20//!
21//! A store to the sector one past the end appends, growing the image by one
22//! sector. Anything further out is an error. This is enough for a filesystem
23//! to extend a file without needing a separate resize operation, and it stops
24//! a stray sector number from inflating the image by a wild amount.
25
26use crate::{Device, SECTOR_SIZE, unmapped};
27use ternaria_arith::{Tryte, Word};
28use ternaria_mem::{
29 MemoryError,
30 bytes::{byte_to_tryte, tryte_to_byte},
31};
32
33/// Offset of the first tryte of the sector register.
34pub const SECTOR: i64 = 0;
35/// Offset of the control register.
36pub const CONTROL: i64 = 3;
37/// Control value that loads the selected sector into the window.
38pub const CONTROL_LOAD: i32 = 1;
39/// Control value that stores the window to the selected sector.
40pub const CONTROL_STORE: i32 = 2;
41/// Offset of the status register.
42pub const STATUS: i64 = 4;
43/// Offset of the first tryte of the sector window.
44pub const WINDOW: i64 = 5;
45
46/// A sector-addressed block device.
47pub struct BlockDevice {
48 image: Vec<u8>,
49 sector: i64,
50 status: i32,
51 window: Vec<Tryte>,
52}
53
54impl BlockDevice {
55 /// A device backed by the given bytes.
56 pub fn new(image: impl Into<Vec<u8>>) -> BlockDevice {
57 BlockDevice {
58 image: image.into(),
59 sector: 0,
60 status: 0,
61 window: vec![Tryte::ZERO; SECTOR_SIZE as usize],
62 }
63 }
64
65 /// A device backed by the contents of a host file.
66 pub fn from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<BlockDevice> {
67 Ok(BlockDevice::new(std::fs::read(path)?))
68 }
69
70 /// How many whole or partial sectors the image contains.
71 pub fn sector_count(&self) -> i64 {
72 self.image.len().div_ceil(SECTOR_SIZE as usize) as i64
73 }
74
75 /// Copies the selected sector into the window.
76 ///
77 /// A short final sector is zero-padded. A sector past the end of the image
78 /// leaves the window zeroed and sets `STATUS` to -1.
79 fn load(&mut self) {
80 self.window.fill(Tryte::ZERO);
81 if self.sector < 0 || self.sector >= self.sector_count() {
82 self.status = -1;
83 return;
84 }
85 let start = self.sector as usize * SECTOR_SIZE as usize;
86 let end = (start + SECTOR_SIZE as usize).min(self.image.len());
87 for (i, b) in self.image[start..end].iter().enumerate() {
88 self.window[i] = byte_to_tryte(*b);
89 }
90 self.status = 0;
91 }
92
93 /// Copies the window back to the selected sector.
94 ///
95 /// Every tryte must be a byte. The check runs first, over the whole
96 /// window, so a window holding one bad value leaves the image unchanged
97 /// rather than half written.
98 fn store(&mut self) -> Result<(), MemoryError> {
99 if self.sector < 0 || self.sector > self.sector_count() {
100 self.status = -1;
101 return Ok(());
102 }
103 let mut bytes = [0u8; SECTOR_SIZE as usize];
104 for (slot, t) in bytes.iter_mut().zip(self.window.iter()) {
105 *slot = tryte_to_byte(*t).ok_or(MemoryError::NotAByte {
106 value: t.value() as i64,
107 })?;
108 }
109 let start = self.sector as usize * SECTOR_SIZE as usize;
110 let end = start + SECTOR_SIZE as usize;
111 if end > self.image.len() {
112 self.image.resize(end, 0);
113 }
114 self.image[start..end].copy_from_slice(&bytes);
115 self.status = 0;
116 Ok(())
117 }
118
119 /// The backing bytes, for a host writing an image back to a file.
120 pub fn image(&self) -> &[u8] {
121 &self.image
122 }
123
124 /// Replaces one tryte of the sector register, leaving the others alone.
125 ///
126 /// The register is word-sized, so a program may set it with one `sw` or
127 /// one tryte at a time.
128 fn set_sector_tryte(&mut self, index: usize, value: Tryte) {
129 let current = Word::from_value(self.sector);
130 let mut trytes = [Tryte::ZERO; Word::TRYTES];
131 for (i, slot) in trytes.iter_mut().enumerate() {
132 *slot = current.tryte(i);
133 }
134 trytes[index] = value;
135 self.sector = Word::from_trytes(trytes).value();
136 }
137}
138
139impl Device for BlockDevice {
140 fn name(&self) -> &'static str {
141 "block"
142 }
143
144 fn read(&mut self, offset: i64) -> Result<Tryte, MemoryError> {
145 match offset {
146 0..=2 => Ok(Word::from_value(self.sector).tryte(offset as usize)),
147 CONTROL => Ok(Tryte::ZERO),
148 STATUS => Ok(Tryte::from_value(self.status)),
149 _ if (WINDOW..WINDOW + SECTOR_SIZE).contains(&offset) => {
150 Ok(self.window[(offset - WINDOW) as usize])
151 }
152 _ => Err(unmapped(offset)),
153 }
154 }
155
156 fn write(&mut self, offset: i64, value: Tryte) -> Result<(), MemoryError> {
157 match offset {
158 0..=2 => {
159 self.set_sector_tryte(offset as usize, value);
160 Ok(())
161 }
162 CONTROL => match value.value() {
163 CONTROL_LOAD => {
164 self.load();
165 Ok(())
166 }
167 CONTROL_STORE => self.store(),
168 // Any other value is a no-op rather than an error, so that a
169 // zero written to clear the register does nothing.
170 _ => Ok(()),
171 },
172 STATUS => Err(MemoryError::Device {
173 addr: offset,
174 reason: "block status is read-only",
175 }),
176 _ if (WINDOW..WINDOW + SECTOR_SIZE).contains(&offset) => {
177 self.window[(offset - WINDOW) as usize] = value;
178 Ok(())
179 }
180 _ => Err(unmapped(offset)),
181 }
182 }
183}