use crate::{cdb, layout, Identity, INQUIRY_LEN};
const FIELD_MAX: usize = 0xFF_FFFF;
#[derive(Debug)]
pub enum Data<'a> {
None,
In(&'a mut [u8]),
Out(&'a [u8]),
}
pub trait Transport {
type Error: core::fmt::Debug;
fn exec(&mut self, cdb: &[u8], data: Data<'_>) -> Result<usize, Self::Error>;
fn sense(&self) -> Option<(u8, u8, u8)>;
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error<E> {
Transport(E),
Short {
expected: usize,
actual: usize,
},
Oversize(usize),
}
#[cfg(feature = "std")]
impl<E: core::fmt::Debug> std::error::Error for Error<E> {}
impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Transport(e) => write!(f, "transport error: {e:?}"),
Self::Short { expected, actual } => {
write!(f, "short transfer: expected {expected} bytes, got {actual}")
}
Self::Oversize(n) => write!(f, "{n:#x} exceeds the CDB field"),
}
}
}
fn exec<T: Transport>(t: &mut T, cdb: &[u8], data: Data<'_>) -> Result<usize, Error<T::Error>> {
t.exec(cdb, data).map_err(Error::Transport)
}
pub(crate) fn read<T: Transport>(
t: &mut T,
cdb: &[u8],
buf: &mut [u8],
min: usize,
) -> Result<usize, Error<T::Error>> {
let n = exec(t, cdb, Data::In(buf))?.min(buf.len());
if n < min {
return Err(Error::Short {
expected: min,
actual: n,
});
}
Ok(n)
}
fn write<T: Transport>(t: &mut T, cdb: &[u8], data: &[u8]) -> Result<(), Error<T::Error>> {
let n = exec(t, cdb, Data::Out(data))?;
if n < data.len() {
return Err(Error::Short {
expected: data.len(),
actual: n,
});
}
Ok(())
}
fn field<E>(n: usize, max: usize) -> Result<u32, Error<E>> {
if n > max {
return Err(Error::Oversize(n));
}
Ok(n as u32)
}
pub fn identify<T: Transport>(t: &mut T) -> Result<Identity, Error<T::Error>> {
let mut inquiry = [0; cdb::INQUIRY_ALLOC as usize];
read(
t,
&cdb::inquiry(cdb::INQUIRY_ALLOC),
&mut inquiry,
INQUIRY_LEN,
)?;
let mut banner = [0; layout::BANNER.len];
let banner_len =
read_exact(t, layout::BANNER.start as u32, &mut banner).map_or(0, |_| banner.len());
let mut descriptor = [0; layout::DESCRIPTOR.len];
let descriptor_len = read_exact(t, layout::DESCRIPTOR.start as u32, &mut descriptor)
.map_or(0, |_| descriptor.len());
Identity::parse(
&inquiry,
&banner[..banner_len],
&descriptor[..descriptor_len],
)
.ok_or(Error::Short {
expected: INQUIRY_LEN,
actual: 0,
})
}
fn read_exact<T: Transport>(t: &mut T, off: u32, buf: &mut [u8]) -> Result<(), Error<T::Error>> {
let len = field(buf.len(), FIELD_MAX)?;
field::<T::Error>(off as usize, FIELD_MAX)?;
let n = buf.len();
read(t, &cdb::read_memory(off, len), buf, n).map(|_| ())
}
pub fn read_memory<T: Transport>(
t: &mut T,
off: u32,
buf: &mut [u8],
) -> Result<(), Error<T::Error>> {
read_exact(t, off, buf)
}
pub fn feature<T: Transport>(
t: &mut T,
code: u16,
buf: &mut [u8],
) -> Result<usize, Error<T::Error>> {
let alloc = field(buf.len(), u16::MAX as usize)? as u16;
read(t, &cdb::get_configuration(code, alloc), buf, 12)
}
pub fn test_unit_ready<T: Transport>(t: &mut T) -> Result<(), Error<T::Error>> {
exec(t, &cdb::test_unit_ready(), Data::None).map(|_| ())
}
pub fn request_sense<T: Transport>(
t: &mut T,
) -> Result<Option<crate::sense::Triplet>, Error<T::Error>> {
let mut buf = [0; cdb::REQUEST_SENSE_LEN];
let n = read(t, &cdb::request_sense(), &mut buf, 0)?;
Ok(crate::sense::parse(&buf[..n]))
}
pub struct Session<'a, T: Transport> {
t: &'a mut T,
}
impl<T: Transport> core::fmt::Debug for Session<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Session").finish_non_exhaustive()
}
}
impl<T: Transport> Session<'_, T> {
pub fn write(&mut self, off: u32, data: &[u8]) -> Result<(), Error<T::Error>> {
let len = field(data.len(), u16::MAX as usize)? as u16;
field::<T::Error>(off as usize, FIELD_MAX)?;
write(self.t, &cdb::transfer(off, len), data)
}
pub fn finish(self) -> Result<(), Error<T::Error>> {
write(self.t, &cdb::finish(), &[])
}
}
pub fn enter_update<T: Transport>(t: &mut T) -> Result<Session<'_, T>, Error<T::Error>> {
enter_update_command(t, cdb::Prepare::ModeOne)
}
pub(crate) fn enter_update_command<T: Transport>(
t: &mut T,
prepare: cdb::Prepare,
) -> Result<Session<'_, T>, Error<T::Error>> {
let mut probe = [0; cdb::PROBE_LEN];
read(t, &cdb::probe(), &mut probe, cdb::PROBE_LEN)?;
test_unit_ready(t)?;
write(t, &prepare.command(), &[])?;
Ok(Session { t })
}
pub fn enter_recovery<T: Transport>(t: &mut T) -> Result<Session<'_, T>, Error<T::Error>> {
write(t, &cdb::enter_update(), &[])?;
Ok(Session { t })
}
#[cfg(feature = "image")]
pub(crate) fn enter_recovery_reporting<T: Transport>(
t: &mut T,
prepare: cdb::Prepare,
mut failed: impl FnMut(&Error<T::Error>),
) -> Session<'_, T> {
if let Err(error) = write(t, &prepare.command(), &[]) {
failed(&error);
}
Session { t }
}
#[cfg(test)]
#[path = "drive_tests.rs"]
mod tests;