ctaphid 0.1.1

Rust implementation of the CTAPHID protocol
Documentation
// Copyright (C) 2021 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: Apache-2.0 or MIT

//! Error types for `ctaphid` operations.

use std::{error, fmt};

use crate::command::Command;

/// Error type for `ctaphid` operations.
#[derive(Debug)]
pub enum Error {
    /// A command-specific error.
    CommandError(CommandError),
    /// A CTAPHID error returned by the device.
    DeviceError(DeviceError),
    /// A HID communication error.
    HidError(hidapi::HidError),
    /// An error that occured while sending a CTAPHID request.
    RequestError(RequestError),
    /// An error that occured while receiving a CTAPHID response.
    ResponseError(ResponseError),
}

impl error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::CommandError(error) => Some(error),
            Self::DeviceError(error) => Some(error),
            Self::HidError(error) => Some(error),
            Self::RequestError(error) => Some(error),
            Self::ResponseError(error) => Some(error),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CommandError(_) => "command executionf ailed",
            Self::DeviceError(_) => "CTAPHID transaction failed",
            Self::HidError(_) => "HID communication failed",
            Self::RequestError(_) => "failed to receive CTAPHID response",
            Self::ResponseError(_) => "failed to send CTAPHID request",
        }
        .fmt(f)
    }
}

impl From<CommandError> for Error {
    fn from(error: CommandError) -> Self {
        Self::CommandError(error)
    }
}

impl From<DeviceError> for Error {
    fn from(error: DeviceError) -> Self {
        Self::DeviceError(error)
    }
}

impl From<RequestError> for Error {
    fn from(error: RequestError) -> Self {
        Self::RequestError(error)
    }
}

impl From<ResponseError> for Error {
    fn from(error: ResponseError) -> Self {
        Self::ResponseError(error)
    }
}

impl From<hidapi::HidError> for Error {
    fn from(error: hidapi::HidError) -> Self {
        Self::HidError(error)
    }
}

/// A command-specific error.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum CommandError {
    /// An init response with a wrong nonce was received.
    InvalidNonce,
    /// A ping response with wrong data was received.
    InvalidPingData,
}

impl error::Error for CommandError {}

impl fmt::Display for CommandError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidNonce => "received an init response with a wrong nonce",
            Self::InvalidPingData => "received a ping response with wrong data",
        }
        .fmt(f)
    }
}

/// Error code returned by the device for a failed CTAPHID transaction.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum DeviceError {
    /// The command in the request is invalid.
    InvalidCommand,
    /// The parameter(s) in the request is invalid.
    InvalidParameter,
    /// The length field (BCNT) is invalid for the request.
    InvalidLength,
    /// The sequence does not match expected value.
    InvalidSequence,
    /// The message has timed out.
    MessageTimeout,
    /// The device is busy for the requesting channel.
    ChannelBusy,
    /// Command requires channel lock.
    LockRequired,
    /// CID is not valid.
    InvalidChannel,
    /// Unspecified error.
    Other,
    /// Unknown error code.
    Unknown(u8),
}

impl From<u8> for DeviceError {
    fn from(error: u8) -> Self {
        match error {
            0x01 => Self::InvalidCommand,
            0x02 => Self::InvalidParameter,
            0x03 => Self::InvalidLength,
            0x04 => Self::InvalidSequence,
            0x05 => Self::MessageTimeout,
            0x06 => Self::ChannelBusy,
            0x0a => Self::LockRequired,
            0x0b => Self::InvalidChannel,
            0x7f => Self::Other,
            error => Self::Unknown(error),
        }
    }
}

impl error::Error for DeviceError {}

impl fmt::Display for DeviceError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidCommand => "the command in the request is invalid".fmt(f),
            Self::InvalidParameter => "the parameter(s) in the request is invalid".fmt(f),
            Self::InvalidLength => "the length field is invalid for the request".fmt(f),
            Self::InvalidSequence => "the sequence does not match expected value".fmt(f),
            Self::MessageTimeout => "the message has timed out".fmt(f),
            Self::ChannelBusy => "the device is busy for the requesting channel".fmt(f),
            Self::LockRequired => "command requires channel lock".fmt(f),
            Self::InvalidChannel => "CID is not valid".fmt(f),
            Self::Other => "unspecified error".fmt(f),
            Self::Unknown(value) => write!(f, "unknown error code 0x{:x}", value),
        }
    }
}

/// An error that occured while sending a request to a CTAPHID device.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum RequestError {
    /// The buffer for serializing the request is too small.
    BufferTooSmall,
    /// The request contains too much data.
    DataTooLong,
    /// The request could not be written completely.
    IncompleteWrite,
}

impl error::Error for RequestError {}

impl fmt::Display for RequestError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::BufferTooSmall => "the buffer for serializing the request is too small",
            Self::DataTooLong => "the request contains too much data",
            Self::IncompleteWrite => "the request could not be written completely",
        }
        .fmt(f)
    }
}

/// An error that occured while receiving a response from a CTAPHID device.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum ResponseError {
    /// The device did not return enough data for a CTAPHID packet.
    NotEnoughData,
    /// The device returned an error packet without an error code.
    MissingErrorCode,
    /// The device returned a response packet with an unexpected channel ID.
    UnexpectedChannel {
        /// The expected channel ID.
        expected: u32,
        /// The actual channel ID.
        actual: u32,
    },
    /// The device returned a response packet with an unexpected command ID.
    UnexpectedCommand {
        /// The expected command ID.
        expected: Command,
        /// The actual command ID.
        actual: Command,
    },
    /// The device returned a packet of an unexpected type.
    UnexpectedPacketType,
    /// The device returned a response packet with an unexpected sequence number.
    UnexpectedSequence {
        /// The expected sequence number.
        expected: u8,
        /// The actual sequence number.
        actual: u8,
    },
}

impl error::Error for ResponseError {}

impl fmt::Display for ResponseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NotEnoughData => "a response packet does not contain enough data".fmt(f),
            Self::MissingErrorCode => "an error packet does not contain an error code".fmt(f),
            Self::UnexpectedChannel { expected, actual } => write!(
                f,
                "expected a response packet for channel 0x{:x} but received 0x{:x}",
                expected, actual
            ),
            Self::UnexpectedCommand { expected, actual } => write!(
                f,
                "expected a response packet for command {:?} but received {:?}",
                expected, actual
            ),
            Self::UnexpectedPacketType => "received a response packet of the wrong type".fmt(f),
            Self::UnexpectedSequence { expected, actual } => write!(
                f,
                "expected a response packet with sequence 0x{:x} but received 0x{:x}",
                expected, actual
            ),
        }
    }
}