pub mod address;
pub mod ccd;
pub mod film;
pub mod frames;
pub mod identity;
pub mod other;
pub mod set_window;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("page {page:02X}h truncated: need {need} bytes, got {got}")]
Truncated { page: u8, need: usize, got: usize },
#[error("page {page:02X}h byte {byte}: {what} = {value:#04x}")]
BadField {
page: u8,
byte: usize,
what: &'static str,
value: u32,
},
}
#[derive(Debug)]
pub struct Capabilities {
pub identity: identity::Identity,
pub address: address::Address,
pub features: other::Features,
pub set_window: set_window::SetWindowFunction,
pub ccd: Option<ccd::CcdMeasurement>,
pub frames: Option<frames::Frames>,
}
#[derive(Debug)]
pub struct Page {
code: u8,
bytes: Vec<u8>,
}
impl Page {
pub fn new(code: u8, bytes: Vec<u8>) -> Result<Self, Error> {
if bytes.len() < 4 {
return Err(Error::Truncated {
page: code,
need: 4,
got: bytes.len(),
});
}
if bytes[1] != code {
return Err(Error::BadField {
page: code,
byte: 1,
what: "page code",
value: bytes[1] as u32,
});
}
Ok(Self { code, bytes })
}
fn array<const N: usize>(&self, i: usize) -> Result<[u8; N], Error> {
self.bytes
.get(i..i + N)
.and_then(|s| s.try_into().ok())
.ok_or_else(|| Error::Truncated {
page: self.code,
need: i + N,
got: self.bytes.len(),
})
}
fn u8(&self, i: usize) -> Result<u8, Error> {
Ok(self.array::<1>(i)?[0])
}
fn be16(&self, i: usize) -> Result<u16, Error> {
Ok(u16::from_be_bytes(self.array(i)?))
}
fn be32(&self, i: usize) -> Result<u32, Error> {
Ok(u32::from_be_bytes(self.array(i)?))
}
fn be(&self, i: usize, len: usize) -> Result<u32, Error> {
if len == 0 || len > 4 {
return Err(Error::BadField {
page: self.code,
byte: i,
what: "parameter width",
value: len as u32,
});
}
let mut value = 0u32;
for n in 0..len {
value = value << 8 | u32::from(self.u8(i + n)?);
}
Ok(value)
}
fn declared_len(&self) -> usize {
(4 + usize::from(self.bytes[3])).min(self.bytes.len())
}
fn carried_u8(&self, i: usize) -> Option<u8> {
match i < self.declared_len() {
true => self.u8(i).ok(),
false => None,
}
}
fn flags(&self, i: usize) -> Result<(u64, usize), Error> {
let mut bits = 0u64;
for n in 0..8 {
let byte = self.u8(i + n)?;
bits |= u64::from(byte) << (8 * n);
if byte & 0x80 == 0 {
return Ok((bits, n + 1));
}
}
Err(Error::BadField {
page: self.code,
byte: i + 7,
what: "flags run past 8 bytes",
value: bits as u32,
})
}
fn opt_u8(&self, i: usize) -> Result<Option<u8>, Error> {
Ok(Some(self.u8(i)?).filter(|&v| v != 0))
}
fn opt_be16(&self, i: usize) -> Result<Option<u16>, Error> {
Ok(Some(self.be16(i)?).filter(|&v| v != 0))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_short_page_errors_rather_than_panicking() {
let page = Page::new(0xC1, vec![0x06, 0xC1, 0x00, 0x04]).unwrap();
assert!(matches!(
page.be32(4),
Err(Error::Truncated {
need: 8,
got: 4,
..
})
));
}
#[test]
fn a_mismatched_page_code_is_refused() {
assert!(matches!(
Page::new(0xC1, vec![0x06, 0xD1, 0x00, 0x04]),
Err(Error::BadField { byte: 1, .. })
));
}
}