use crate::transport::{Completion, Sense, Status};
#[derive(Debug, thiserror::Error)]
pub enum Failure {
#[error("mechanical error")]
Mechanism, #[error("command aborted")]
Aborted, #[error("hardware error")]
Hardware, #[error("medium error")]
Medium, #[error("unexpected status {0:#04x}")]
UnexpectedStatus(u8),
#[error("unrecognized sense")]
Unrecognized,
#[error("autofocus did not reach focus")]
OutOfFocus, }
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum Refusal {
#[error("invalid field in CDB")]
BadCdbField, #[error("invalid field in parameter list")]
BadParamField, #[error("parameter list length error")]
BadLength, #[error("invalid operation code")]
UnknownOpcode, #[error("invalid logical unit")]
BadLun, #[error("command sequence error")]
OutOfSequence, #[error("invalid combination of windows")]
BadWindowCombo, #[error("frame beyond the number of frames in the film")]
FrameOutOfRange, }
#[derive(Debug, thiserror::Error)]
pub enum Contention {
#[error("the scanner is busy")]
Busy,
#[error("another initiator holds the scanner")]
Reserved,
}
#[derive(Debug, thiserror::Error)]
pub enum Fault {
#[error("scanner fault: {0} ({1:?})")]
Reported(Failure, Option<Sense>),
#[error("scanner rejected the command: {0} ({1:?})")]
Rejected(Refusal, Option<Sense>),
#[error(transparent)]
Caps(super::caps::Error),
}
#[derive(Debug, thiserror::Error)]
pub enum Intervention {
#[error("The adapter is ejected")]
AdapterEjected, #[error("IA-20: The LL door is not completely opened")]
DoorNotOpen, #[error("Undefined adapter")]
UnknownAdapter, #[error("SA-30: The film of 6 frames or more is loaded when the film gate is closed")]
FilmGateClosed, #[error("SA-21/SA-30: The adapter is pulled out a little in the locked status")]
AdapterUnlocked, #[error("FH-869GR: the mask is not set")]
MaskNotSet, #[error("Undefined holder")]
UnknownHolder,
#[error("No film or holder is loaded")]
NoMedium, #[error("The adapter has nothing left to load")]
NothingToLoad, #[error("SA-21/SA-30: a nonstandard film was inserted")]
FilmOutOfStandard, #[error("User intervention required with no additional details")]
Unreported,
}
impl Intervention {
fn manual(tsc: Option<u8>) -> Self {
match tsc {
Some(0x00) => Self::AdapterEjected,
Some(0x01) => Self::DoorNotOpen,
Some(0x02) => Self::UnknownAdapter,
Some(0x03) => Self::FilmGateClosed,
Some(0x04) => Self::AdapterUnlocked,
Some(0x06) => Self::MaskNotSet,
Some(0x07) => Self::UnknownHolder,
_ => Self::Unreported,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Activity {
ActivatingOperation, MovingMechanism, MeasuringCorrection, AutoShadingOrWb, Initializing, TargetBusy,
Unreported,
}
impl Activity {
fn becoming_ready(tsc: Option<u8>) -> Self {
match tsc {
Some(0x00) => Self::ActivatingOperation,
Some(0x01 | 0x03) => Self::MovingMechanism,
Some(0x02) => Self::MeasuringCorrection,
Some(0x04) => Self::AutoShadingOrWb,
_ => Self::Unreported,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Coop {
Thumbnail, Averaging, MultiLineRegistration, Truncate, CcdData, Unknown(u8),
}
impl From<u8> for Coop {
fn from(ascq: u8) -> Self {
match ascq {
0x01 => Self::Thumbnail,
0x02 => Self::Averaging,
0x04 => Self::MultiLineRegistration,
0x06 => Self::Truncate,
0x07 => Self::CcdData,
x => Self::Unknown(x),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Adjustment {
ResolutionRounded, Unreported,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Change {
MediumChanged, Reset, ModeParameters, Capabilities, Attached, Unreported,
}
impl Change {
fn from_code(asc: u8, ascq: u8) -> Self {
match (asc, ascq) {
(0x28, 0x00) => Self::MediumChanged,
(0x29, _) => Self::Reset,
(0x2A, 0x01) => Self::ModeParameters,
(0x3F, 0x03) => Self::Capabilities,
(0x3F, 0x04) => Self::Attached,
_ => Self::Unreported,
}
}
}
#[derive(Debug)]
pub enum Outcome {
Complete,
CompleteWith(Adjustment),
Working(Activity),
NeedsHost(Coop),
NeedsOperator(Intervention),
StateChanged(Change),
Contended(Contention),
Refused(Refusal),
Failed(Failure),
}
pub fn diagnosed(sense: &Sense) -> Failure {
match (sense.key, sense.asc, sense.ascq) {
(0x01, 0x61, 0x02) => Failure::OutOfFocus,
_ => Failure::Mechanism,
}
}
pub fn interpret(c: &Completion) -> Outcome {
match c.status {
Status::Good | Status::CheckCondition => match &c.sense {
Some(sense) => from_sense(sense),
None => Outcome::Complete,
},
Status::Busy => Outcome::Working(Activity::TargetBusy),
Status::ReservationConflict => Outcome::Contended(Contention::Reserved),
Status::Other(x) => Outcome::Failed(Failure::UnexpectedStatus(x)),
}
}
fn from_sense(s: &Sense) -> Outcome {
use Outcome::*;
match (s.key, s.asc, s.ascq) {
(0x00, 0x00, 0x00) => Complete,
(0x01, 0x37, 0x00) => CompleteWith(Adjustment::ResolutionRounded),
(0x01, 0x61, 0x02) => Failed(Failure::OutOfFocus),
(0x01, ..) => CompleteWith(Adjustment::Unreported),
(0x02, 0x04, 0x00) => Working(Activity::Initializing),
(0x02, 0x04, 0x01) => Working(Activity::becoming_ready(s.tsc)),
(0x02, 0x04, 0x02) => Failed(Failure::Mechanism),
(0x02, 0x04, 0x03) => NeedsOperator(Intervention::manual(s.tsc)),
(0x02, 0x05, 0x00) => Working(Activity::Initializing),
(0x02, 0x3A, 0x00) => match s.tsc {
Some(0x00) => NeedsOperator(Intervention::NothingToLoad),
Some(0x03) => NeedsOperator(Intervention::FilmOutOfStandard),
Some(0x04) => Refused(Refusal::FrameOutOfRange),
_ => NeedsOperator(Intervention::NoMedium),
},
(0x03, ..) => Failed(Failure::Medium),
(0x04, ..) => Failed(Failure::Hardware),
(0x05, 0x1A, _) => Refused(Refusal::BadLength),
(0x05, 0x20, _) => Refused(Refusal::UnknownOpcode),
(0x05, 0x24, _) => Refused(Refusal::BadCdbField),
(0x05, 0x25, _) => Refused(Refusal::BadLun),
(0x05, 0x26, _) => Refused(Refusal::BadParamField),
(0x05, 0x2C, 0x00) => Refused(Refusal::OutOfSequence),
(0x05, 0x2C, 0x02) => Refused(Refusal::BadWindowCombo),
(0x06, asc, ascq) => StateChanged(Change::from_code(asc, ascq)),
(0x09, 0x80, q) => NeedsHost(Coop::from(q)),
(0x0B, 0x08, _) => Working(Activity::TargetBusy),
(0x0B, 0x3E, _) => Working(Activity::Initializing),
(0x0B, ..) => Failed(Failure::Aborted),
_ => Failed(Failure::Unrecognized),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::{Completion, Status};
fn sense(key: u8, asc: u8, ascq: u8) -> Sense {
Sense {
key,
asc,
ascq,
tsc: None,
ili: false,
information: None,
raw: Vec::new(),
}
}
#[test]
fn out_of_focus_on_the_command_itself_fails_rather_than_completes() {
let completion = Completion {
status: Status::CheckCondition,
sense: Some(sense(0x01, 0x61, 0x02)),
transferred: 0,
};
assert!(matches!(
interpret(&completion),
Outcome::Failed(Failure::OutOfFocus)
));
}
#[test]
fn other_recovered_errors_still_complete() {
let completion = Completion {
status: Status::CheckCondition,
sense: Some(sense(0x01, 0x37, 0x00)),
transferred: 0,
};
assert!(matches!(
interpret(&completion),
Outcome::CompleteWith(Adjustment::ResolutionRounded)
));
}
}