Skip to main content

nord_usb/
device.rs

1//! An instrument as a value: a transport and the session bracket every operation runs
2//! inside.
3//!
4//! [`op`] is the vocabulary — one capture-pinned function per protocol operation — and
5//! [`Session`] the transaction they run in. This is where they compose:
6//!
7//! - [`Device::read`] and [`Device::destructive`] open a transaction, hand the chain the
8//!   raw [`Session`], and attempt cleanup before returning.
9//! - [`Geometry`] is the instrument's own partition and bank tables, so what bounds a
10//!   walk and what sizes a library write are numbers the device supplied.
11//! - [`Device::write`] sizes a library's cleaning pass from that partition's
12//!   [`AllocationUnit`] and the body it is about to send.
13//! - [`Device::take_changed`] carries the instrument's own "I changed" notification out
14//!   of the transaction it arrived in.
15//!
16//! Nothing here touches the wire: every frame is emitted by [`op`] or [`Session`].
17
18use crate::error::{Error, Result};
19use crate::op;
20use crate::session::{ReadOnly, ReadWrite, Session};
21use crate::transport::Transport;
22use crate::wire::{AllocationUnit, Bank, Location, ObjectClass, Partition};
23
24/// What the instrument says it holds: every partition, and each one's banks.
25///
26/// The partition index is the object class code, so this answers "does this instrument
27/// have that class, how many banks does it have, and how large are they" without any
28/// constant. The two `(Native)` partitions are carried and never consulted — they are a
29/// second view of a library this crate addresses through its user partition.
30pub struct Geometry {
31    entries: Vec<Entry>,
32}
33
34/// One partition and the banks the device reported for it.
35struct Entry {
36    partition: Partition,
37    banks: Vec<Bank>,
38}
39
40impl Geometry {
41    /// Read both tables: `PARTITIONS`, then `BANKS` for each partition's index in table
42    /// order.
43    ///
44    /// A refused `BANKS` fails the whole read, as its [`Error::DeviceStatus`]: geometry
45    /// missing a partition bounds no walk and sizes no write, and there is nothing to
46    /// gain by carrying the hole to whichever caller trips over it.
47    pub async fn read<T: Transport, C>(session: &mut Session<'_, T, C>) -> Result<Self> {
48        let mut entries = Vec::new();
49        for partition in op::partitions(session).await? {
50            let banks = op::banks(session, partition.index).await?;
51            entries.push(Entry { partition, banks });
52        }
53        Ok(Self { entries })
54    }
55
56    /// Every partition in table order with its banks. This is the whole table,
57    /// `(Native)` partitions included, rather than the classes this crate names.
58    pub fn entries(&self) -> impl Iterator<Item = (&Partition, &[Bank])> {
59        self.entries
60            .iter()
61            .map(|entry| (&entry.partition, entry.banks.as_slice()))
62    }
63
64    /// The partition storing `class`. An instrument without one is an error, never a
65    /// default: the whole point of reading the table is not to assume.
66    pub fn partition(&self, class: ObjectClass) -> Result<&Partition> {
67        Ok(&self.entry(class)?.partition)
68    }
69
70    /// The banks a walk of `class` covers, in table order.
71    pub fn banks(&self, class: ObjectClass) -> Result<&[Bank]> {
72        Ok(&self.entry(class)?.banks)
73    }
74
75    /// The unit `class`'s [`Status`](crate::wire::Status) counters are denominated in.
76    pub fn allocation_unit(&self, class: ObjectClass) -> Result<AllocationUnit> {
77        self.partition(class)?.allocation_unit()
78    }
79
80    /// Whether an address exists on this instrument, from the tables already read.
81    ///
82    /// [`op::check_address`] is the same question asked of a fresh `BANKS` read; this is
83    /// the one to use where the geometry is in hand, because it costs no frame.
84    pub fn check_address(&self, class: ObjectClass, at: Location) -> Result<Option<String>> {
85        Ok(op::address_refusal(self.banks(class)?, at))
86    }
87
88    fn entry(&self, class: ObjectClass) -> Result<&Entry> {
89        self.entries
90            .iter()
91            .find(|entry| entry.partition.index == class.to_raw())
92            .ok_or_else(|| {
93                Error::InvalidArgument(format!("the instrument has no {} partition", class.label()))
94            })
95    }
96}
97
98/// An attached instrument. See the module documentation for the shape.
99pub struct Device<T: Transport> {
100    transport: T,
101    geometry: Option<Geometry>,
102    changed: bool,
103}
104
105impl<T: Transport> Device<T> {
106    /// Wrap an already-open transport.
107    pub fn new(transport: T) -> Self {
108        Self {
109            transport,
110            geometry: None,
111            changed: false,
112        }
113    }
114
115    /// The transport itself, for what the brackets cannot express — [`op::recover`],
116    /// [`Session::probe`], or a backend-specific call.
117    pub fn transport(&mut self) -> &mut T {
118        &mut self.transport
119    }
120
121    pub fn into_transport(self) -> T {
122        self.transport
123    }
124
125    /// Run a chain of read-only operations in one transaction.
126    ///
127    /// Cleanup is attempted whether the chain succeeds or fails. When both fail the
128    /// chain's error is reported, except that a transport failure closing outranks a
129    /// device refusal in the chain: the instrument saying no is a reply, and the pipe
130    /// having failed is the finding the caller has to act on.
131    ///
132    /// ⚠️ The close is what clears the instrument's progress label. A transaction
133    /// abandoned after a read has painted `"Uploading..."` leaves that label on the
134    /// display with no way out but a power cycle; the bracket exists so no `?` can do
135    /// that.
136    pub async fn read<R>(
137        &mut self,
138        class: ObjectClass,
139        f: impl AsyncFnOnce(&mut Session<'_, T, ReadOnly>) -> Result<R>,
140    ) -> Result<R> {
141        let session = Session::open(&mut self.transport, class).await?;
142        bracket(&mut self.changed, session, f).await
143    }
144
145    /// Run a chain that may mutate the instrument, in one transaction.
146    ///
147    /// The name is the consent: this is the only route to a [`ReadWrite`] session, and a
148    /// write can destroy an object the caller never named. It brackets its chain exactly
149    /// as [`Self::read`] does, error precedence included.
150    pub async fn destructive<R>(
151        &mut self,
152        class: ObjectClass,
153        f: impl AsyncFnOnce(&mut Session<'_, T, ReadWrite>) -> Result<R>,
154    ) -> Result<R> {
155        let session = Session::open(&mut self.transport, class)
156            .await?
157            .allow_destructive_writes();
158        bracket(&mut self.changed, session, f).await
159    }
160
161    /// Whether the instrument reported changing under us since this was last asked, and
162    /// clear it.
163    ///
164    /// Every bracket preserves its session's [`Session::instrument_changed`] flag, so
165    /// state read during any completed transaction may be stale.
166    pub fn take_changed(&mut self) -> bool {
167        std::mem::take(&mut self.changed)
168    }
169
170    /// The instrument's [`Geometry`], read on first use and kept.
171    ///
172    /// Storing and deleting content leaves every field of the partition table unchanged.
173    ///
174    /// Confirmed on hardware.
175    ///
176    /// The bank table is kept on the same assumption. The sample bank declares a
177    /// capacity equal to its highest occupied slot plus one, which a high-water mark
178    /// would also produce, and no recording holds a `BANKS` reply from after a store
179    /// that moves a bank's top slot.
180    ///
181    /// Inferred from specimens; not confirmed on hardware.
182    pub async fn geometry(&mut self) -> Result<&Geometry> {
183        if self.geometry.is_none() {
184            // Any class opens a session; both tables are device-wide.
185            let read = self
186                .read(ObjectClass::Program, async |s| Geometry::read(s).await)
187                .await?;
188            self.geometry = Some(read);
189        }
190        Ok(self.geometry.as_ref().expect("just read"))
191    }
192
193    /// Write a file using the allocation unit reported for its partition.
194    ///
195    /// A library write is refused `0x16` without a prepared block per storage block of
196    /// body, so block-allocated storage reserves in the transfer's transaction, sized
197    /// by that partition's [`AllocationUnit`] and the body the file carries — the CBIN
198    /// body, which is shorter than the file by its header.
199    ///
200    /// ⚠️ Most classes refuse a write into an occupied slot with status `0x4`; see
201    /// [`ObjectClass::overwrites_in_place`]. Emptying the slot first, and putting the
202    /// occupant back when the write fails, is the caller's to sequence.
203    pub async fn write(
204        &mut self,
205        class: ObjectClass,
206        at: Location,
207        file: &[u8],
208        name: &str,
209        timestamp: u32,
210    ) -> Result<()> {
211        let unit = self.geometry().await?.allocation_unit(class)?;
212        self.destructive(class, async |s| {
213            op::write(s, unit, at, file, name, timestamp).await
214        })
215        .await
216    }
217}
218
219/// Attempt cleanup on both paths; preserve the change notification and report the error
220/// the caller has to act on.
221async fn bracket<T: Transport, C, R>(
222    changed: &mut bool,
223    mut session: Session<'_, T, C>,
224    f: impl AsyncFnOnce(&mut Session<'_, T, C>) -> Result<R>,
225) -> Result<R> {
226    let result = f(&mut session).await;
227    let (closed, session_changed) = session.commit_observing_changed().await;
228    *changed |= session_changed;
229    match (result, closed) {
230        (Ok(value), Ok(())) => Ok(value),
231        (Ok(_), Err(close)) => Err(close),
232        (Err(Error::DeviceStatus(_)), Err(close @ Error::Transport(_))) => Err(close),
233        (Err(chain), _) => Err(chain),
234    }
235}