diff --git a/Cargo.toml b/Cargo.toml index 1618660..0f261c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ctap_hmac" description = "A Rust implementation of the FIDO2 CTAP protocol, including the HMAC extension" -version = "0.4.2" +version = "0.4.3" license = "Apache-2.0/MIT" homepage = "https://github.com/shimunn/ctap" repository = "https://github.com/shimunn/ctap" @@ -20,12 +20,16 @@ cbor-codec = "0.7" ring = "0.13" untrusted = "0.6" rust-crypto = "0.2" -csv-core = "0.1.6" derive_builder = "0.9.0" crossbeam = { version = "0.7.3", optional = true } [dev-dependencies] crossbeam = "0.7.3" hex = "0.4.0" +[build-dependencies] +csv = "1.1.3" +serde = "1.0.106" +serde_derive = "1.0.106" + [features] request_multiple = ["crossbeam"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7cf8ad2 --- /dev/null +++ b/build.rs @@ -0,0 +1,41 @@ +use csv::{Reader, StringRecord}; +use serde_derive::Deserialize; +use std::env; +use std::fs::File; +use std::io::{Result, Write}; +use std::iter::FromIterator; +use std::string::String; + +fn main() { + parse_error_codes().expect("Failed to parse error codes") +} + +fn parse_error_codes() -> Result<()> { + println!("cargo:rerun-if-changed=ctap_error_codes.csv"); + let mut out_file = File::create(format!( + "{}/ctap_error_codes.rs", + env::var("OUT_DIR").unwrap() + ))?; + out_file.write_all(b"static CTAP_ERROR_CODES: &[(usize, &str, &str)] = &[")?; + let mut rdr = Reader::from_path("ctap_error_codes.csv")?; + rdr.set_headers(StringRecord::from_iter(&["code", "name", "desc"])); + #[derive(Debug, Deserialize)] + struct ErrorCode { + code: String, + name: String, + desc: String, + } + for result in rdr.deserialize() { + let record: ErrorCode = result.unwrap(); + out_file.write_all( + format!( + "({}, \"{}\", \"{}\"),\n", + i64::from_str_radix(&record.code[2..], 16).unwrap(), + record.name, + record.desc + ) + .as_bytes(), + )?; + } + out_file.write_all(b"];") +} diff --git a/src/ctap_error_codes.csv b/ctap_error_codes.csv similarity index 100% rename from src/ctap_error_codes.csv rename to ctap_error_codes.csv diff --git a/src/error.rs b/src/error.rs index a022d98..90fcf5b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,8 +5,8 @@ // http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. use cbor_codec::{DecodeError, EncodeError}; -use csv_core::{ReadFieldResult, Reader}; use failure::_core::fmt::{Error, Formatter}; +use failure::_core::option::Option; use failure::{Backtrace, Context, Fail}; use std::fmt; use std::fmt::Display; @@ -19,6 +19,32 @@ pub struct FidoError(Context); #[derive(Debug, Copy, Clone, Fail, Eq, PartialEq)] pub struct CborErrorCode(u8); +// generated using build.rs +include!(concat!(env!("OUT_DIR"), "/ctap_error_codes.rs")); + +impl CborErrorCode { + fn detail(&self) -> Option<(u8, &'static str, &'static str)> { + for (code, name, desc) in CTAP_ERROR_CODES { + if *code == self.0 as usize { + return Some((self.0, name, desc)); + } + } + None + } + + pub fn name(&self) -> Option<&'static str> { + self.detail().map(|(_, name, _)| name) + } + + pub fn description(&self) -> Option<&'static str> { + self.detail().map(|(_, _, desc)| desc) + } + + pub fn code(&self) -> u8 { + self.0 + } +} + #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] pub enum FidoErrorKind { #[fail(display = "Read/write error with device.")] @@ -116,58 +142,24 @@ impl From for CborErrorCode { impl Display for CborErrorCode { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - let messages = include_str!("ctap_error_codes.csv"); - let mut rdr = Reader::new(); - let mut bytes = messages.as_bytes(); - let mut col: usize = 0; - let mut row: usize = 0; - let mut correct_row: bool = false; - let mut field = [0u8; 1024]; - let mut name: Option = None; - let mut desc: Option = None; - loop { - let (result, nin, read) = rdr.read_field(&bytes, &mut field); - bytes = &bytes[nin..]; - match result { - ReadFieldResult::InputEmpty => {} - ReadFieldResult::OutputFull => panic!("field too large"), - ReadFieldResult::Field { record_end } => { - let text = String::from_utf8(field[..read].iter().cloned().collect()).unwrap(); - if row > 0 { - match col { - 0 if i64::from_str_radix(&text[2..], 16) - .expect("malformed ctap_error_codes.csv") - == self.0 as i64 => - { - correct_row = true - } - 1 | 2 if correct_row => { - if let Some(_) = name { - desc = Some(text); - break; - } else { - name = Some(text); - } - } - _ => (), - } - } - col += 1; - if record_end { - col = 0; - row += 1; - } - } - ReadFieldResult::End => break, - } - } - if let Some((code, _name, desc)) = - name.and_then(|name| desc.map(|desc| (self.0, name, desc))) - { + if let Some((code, _name, desc)) = self.detail() { write!(f, "CborError: 0x{:x?}: {}", code, desc)?; } else { - write!(f, "CborError: 0x{:x?}", self.0)?; + write!(f, "CborError: 0x{:x?}: unknown", self.code())?; } Ok(()) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn cbor_error_code() { + assert_eq!( + CborErrorCode(0x33).to_string(), + "CborError: 0x33: PIN authentication, pinAuth, verification failed." + ) + } +}