ctaphid 0.2.0

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 ctaphid_types::Command;

/// Error type for `ctaphid` operations.
#[derive(Debug)]
pub enum Error {
    /// A command-specific error.
    CommandError(CommandError),
    /// 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::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 execution failed",
            Self::RequestError(_) => "failed to send CTAPHID request",
            Self::ResponseError(_) => "failed to receive CTAPHID response",
        }
        .fmt(f)
    }
}

impl From<CommandError> for Error {
    fn from(error: CommandError) -> Self {
        Self::CommandError(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)
    }
}

/// A command-specific error.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum CommandError {
    /// A CBOR response contained a non-zero status code.
    CborError(u8),
    /// A ping response with wrong data was received.
    InvalidPingData,
    /// The command is not supported by the device.
    NotSupported(Command),
}

impl error::Error for CommandError {}

impl fmt::Display for CommandError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CborError(error) => write!(f, "received a CBOR response with status {:x}", error),
            Self::InvalidPingData => "received a ping response with wrong data".fmt(f),
            Self::NotSupported(command) => write!(
                f,
                "the command {:?} is not supported by the device",
                command
            ),
        }
    }
}

/// An error that occured while sending a request to a CTAPHID device.
#[derive(Debug)]
pub enum RequestError {
    /// The request could not be written completely.
    IncompleteWrite,
    /// The request message could not be fragmented into CTAPHID packets.
    MessageFragmentationFailed(ctaphid_types::FragmentationError),
    /// A request packet could not be sent to the device.
    PacketSendingFailed(Box<dyn std::error::Error>),
    /// A request packet could not be serialized.
    PacketSerializationFailed(ctaphid_types::SerializationError),
}

impl error::Error for RequestError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::MessageFragmentationFailed(err) => Some(err),
            Self::PacketSendingFailed(err) => Some(err.as_ref()),
            Self::PacketSerializationFailed(err) => Some(err),
            _ => None,
        }
    }
}

impl fmt::Display for RequestError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::IncompleteWrite => "the request could not be written completely",
            Self::MessageFragmentationFailed(_) => {
                "the request message could not be fragmented into CTAPHID packets"
            }
            Self::PacketSendingFailed(_) => "a request packet could not be sent to the device",
            Self::PacketSerializationFailed(_) => "a request packet could not be serialized",
        }
        .fmt(f)
    }
}

/// An error that occured while receiving a response from a CTAPHID device.
#[derive(Debug)]
pub enum ResponseError {
    /// The command execution failed.
    CommandFailed(ctaphid_types::DeviceError),
    /// The response message could not be assembled from CTAPHID packets.
    MessageDefragmentationFailed(ctaphid_types::DefragmentationError),
    /// A response packet could not be parsed.
    PacketParsingFailed(ctaphid_types::ParseError),
    /// A response packet could not be received from the device.
    PacketReceivingFailed(Box<dyn std::error::Error>),
    /// The device did not response within the specified timeout.
    Timeout,
    /// The device returned an error packet without an error code.
    MissingErrorCode,
    /// 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 sent an unexpected KEEPALIVE message while waiting for the response to a
    /// command.
    UnexpectedKeepAlive(Command),
    /// The device returned response data although an empty response was expected.
    UnexpectedResponseData(Vec<u8>),
}

impl error::Error for ResponseError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::CommandFailed(err) => Some(err),
            Self::MessageDefragmentationFailed(err) => Some(err),
            Self::PacketParsingFailed(err) => Some(err),
            Self::PacketReceivingFailed(err) => Some(err.as_ref()),
            _ => None,
        }
    }
}

impl fmt::Display for ResponseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CommandFailed(_) => "the command execution failed".fmt(f),
            Self::MessageDefragmentationFailed(_) => {
                "the response message could not be assembled from CTAPHID packets".fmt(f)
            }
            Self::PacketParsingFailed(_) => "a response packet could not be serialized".fmt(f),
            Self::PacketReceivingFailed(_) => {
                "a response packet could not be received from the device".fmt(f)
            }
            Self::Timeout => "no response was received within the specified timeout".fmt(f),
            Self::MissingErrorCode => "an error packet does not contain an error code".fmt(f),
            Self::UnexpectedCommand { expected, actual } => write!(
                f,
                "expected a response packet for command {:?} but received {:?}",
                expected, actual
            ),
            Self::UnexpectedKeepAlive(command) => {
                write!(
                    f,
                    "expected a response message to a {:?} command but received a KEEPALIVE message",
                    command
                )
            }
            Self::UnexpectedResponseData(data) => {
                write!(f, "expected an empty response but received {:x?}", data)
            }
        }
    }
}