#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#![forbid(unsafe_code)]
#[cfg(all(test, not(feature = "std")))]
extern crate std;
mod error_codes;
pub use error_codes::CodedError;
pub mod cdb;
#[cfg(feature = "drive")]
pub mod drive;
#[cfg(feature = "enc")]
pub mod enc;
#[cfg(all(feature = "image", feature = "enc"))]
pub mod envelope;
#[cfg(feature = "image")]
pub mod image;
pub mod layout;
#[cfg(feature = "oem")]
pub mod oem;
#[cfg(feature = "drive")]
pub mod receiver;
#[cfg(all(feature = "drive", feature = "image", feature = "enc"))]
pub mod recovery;
#[cfg(feature = "image")]
pub mod update;
pub const INQUIRY_LEN: usize = 36;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Chip {
Mt1959,
Mt1939,
}
impl Chip {
pub const fn label(self) -> &'static str {
match self {
Chip::Mt1959 => "MT1959",
Chip::Mt1939 => "MT1939",
}
}
pub const fn from_digits(d0: u8, d1: u8) -> Option<Self> {
match (d0, d1) {
(b'5', b'9') => Some(Chip::Mt1959),
(b'3', b'9') => Some(Chip::Mt1939),
_ => None,
}
}
pub fn from_tag(bytes: &[u8]) -> Option<Self> {
const PREFIX: &[u8] = b"MTEKMT19";
bytes
.windows(PREFIX.len() + 2)
.filter(|w| w.starts_with(PREFIX))
.find_map(|w| Self::from_digits(w[PREFIX.len()], w[PREFIX.len() + 1]))
}
pub fn from_banner(banner: &[u8]) -> Option<Self> {
banner
.windows(6)
.filter(|w| w.starts_with(b"MT19"))
.find_map(|w| Self::from_digits(w[4], w[5]))
}
}
impl core::fmt::Display for Chip {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.label())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UnknownChip;
impl core::fmt::Display for UnknownChip {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("not an MT1959 or MT1939 label")
}
}
#[cfg(feature = "std")]
impl std::error::Error for UnknownChip {}
impl core::str::FromStr for Chip {
type Err = UnknownChip;
fn from_str(label: &str) -> Result<Self, Self::Err> {
match label {
"MT1959" => Ok(Chip::Mt1959),
"MT1939" => Ok(Chip::Mt1939),
_ => Err(UnknownChip),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DriveClass {
Mt19,
}
impl DriveClass {
pub const fn can_read(self) -> bool {
match self {
Self::Mt19 => false,
}
}
pub const fn of(chip: Chip) -> Self {
match chip {
Chip::Mt1959 | Chip::Mt1939 => DriveClass::Mt19,
}
}
pub const fn chunk(self) -> usize {
cdb::CHUNK
}
pub const fn ready_timeout(self) -> core::time::Duration {
core::time::Duration::from_secs(120)
}
pub const fn poll_interval(self) -> core::time::Duration {
core::time::Duration::from_millis(500)
}
pub const fn verify_floor(self) -> usize {
layout::BOOT_AREA.end()
}
}
fn field(raw: &[u8]) -> &str {
let start = raw.iter().position(|&b| b != b' ').unwrap_or(raw.len());
let raw = &raw[start..];
let end = raw
.iter()
.rposition(|&b| b != b' ' && b != 0)
.map_or(0, |i| i + 1);
core::str::from_utf8(&raw[..end]).unwrap_or("")
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Identity {
inquiry: [u8; INQUIRY_LEN],
banner: [u8; layout::BANNER.len],
descriptor: [u8; layout::DESCRIPTOR.len],
}
impl Identity {
pub fn parse(inquiry: &[u8], banner: &[u8], descriptor: &[u8]) -> Option<Self> {
if inquiry.len() < INQUIRY_LEN {
return None;
}
let mut id = Self {
inquiry: [0; INQUIRY_LEN],
banner: [0; layout::BANNER.len],
descriptor: [0; layout::DESCRIPTOR.len],
};
id.inquiry.copy_from_slice(&inquiry[..INQUIRY_LEN]);
let n = banner.len().min(id.banner.len());
id.banner[..n].copy_from_slice(&banner[..n]);
let n = descriptor.len().min(id.descriptor.len());
id.descriptor[..n].copy_from_slice(&descriptor[..n]);
Some(id)
}
pub fn device_type(&self) -> u8 {
self.inquiry[0] & 0x1f
}
pub fn vendor(&self) -> &str {
field(&self.inquiry[8..16])
}
pub fn product(&self) -> &str {
field(&self.inquiry[16..32])
}
pub fn revision(&self) -> &str {
field(&self.inquiry[32..36])
}
pub fn banner(&self) -> &str {
let end = self
.banner
.iter()
.position(|&b| !(0x20..0x7f).contains(&b))
.unwrap_or(self.banner.len());
field(&self.banner[..end])
}
pub fn has_descriptor(&self) -> bool {
Chip::from_tag(&self.descriptor).is_some()
}
pub fn descriptor_model(&self) -> &str {
if self.has_descriptor() {
field(&self.descriptor[0x08..0x18])
} else {
""
}
}
pub fn sku(&self) -> &str {
if self.has_descriptor() {
field(&self.descriptor[0x1C..0x24])
} else {
""
}
}
pub fn chip(&self) -> Option<Chip> {
Chip::from_banner(&self.banner)
}
pub fn class(&self) -> Option<DriveClass> {
self.chip().map(DriveClass::of)
}
pub fn is_mt19(&self) -> bool {
self.banner.windows(4).any(|w| w == b"MT19")
}
pub fn inquiry_bytes(&self) -> &[u8; INQUIRY_LEN] {
&self.inquiry
}
pub fn banner_bytes(&self) -> &[u8; layout::BANNER.len] {
&self.banner
}
pub fn descriptor_bytes(&self) -> &[u8; layout::DESCRIPTOR.len] {
&self.descriptor
}
}
pub mod sense {
pub type Triplet = (u8, u8, u8);
pub const NOT_READY: u8 = 0x02;
pub const MEDIUM_ERROR: u8 = 0x03;
pub const HARDWARE_ERROR: u8 = 0x04;
pub const ILLEGAL_REQUEST: u8 = 0x05;
pub const ABORTED_COMMAND: u8 = 0x0B;
pub fn parse(data: &[u8]) -> Option<Triplet> {
match *data.first()? & 0x7f {
0x70 | 0x71 if data.len() >= 14 => Some((data[2] & 0x0f, data[12], data[13])),
0x72 | 0x73 if data.len() >= 4 => Some((data[1] & 0x0f, data[2], data[3])),
_ => None,
}
}
pub const fn is_flash_fault(key: u8) -> bool {
matches!(key, MEDIUM_ERROR | HARDWARE_ERROR | ABORTED_COMMAND)
}
pub const fn is_no_medium(key: u8, asc: u8) -> bool {
key == NOT_READY && asc == 0x3A
}
}
#[cfg(all(test, feature = "image"))]
mod fixtures;
#[cfg(test)]
#[path = "lib_tests.rs"]
mod tests;
#[cfg(all(feature = "drive", feature = "image", feature = "enc"))]
#[doc = include_str!("../README.md")]
mod readme_examples {}