From eaf943cdb0ceec448d10822610f078a757cf9766 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 22 Apr 2020 14:49:52 -0700 Subject: [PATCH] schema: use pushdown-driven Veriform decoder Updates the handwritten decoder fields to use the new pushdown automaton-based `Decoder` API which landed in: https://github.com/iqlusioninc/veriform/pull/117 --- Cargo.lock | 132 ++++++++++++++++++++++++++++++++++++++- client/src/armistice.rs | 6 +- core/src/root.rs | 7 +-- core/tests/provision.rs | 4 +- schema/Cargo.toml | 6 +- schema/src/lib.rs | 2 +- schema/src/provision.rs | 70 +++++++-------------- schema/src/public_key.rs | 50 +++++++-------- schema/src/request.rs | 37 ++++++----- schema/src/response.rs | 39 ++++++------ 10 files changed, 225 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9364920..69bd2bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,6 +61,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +dependencies = [ + "memchr", +] + [[package]] name = "anomaly" version = "0.2.0" @@ -99,6 +108,7 @@ dependencies = [ name = "armistice_schema" version = "0.0.0" dependencies = [ + "env_logger", "heapless", "veriform", ] @@ -134,6 +144,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.0.0" @@ -303,6 +324,19 @@ dependencies = [ "subtle", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "exception-reset" version = "0.0.0" @@ -363,6 +397,24 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "hermit-abi" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d737e0f947a1864e93d33fdef4af8445a00d1ed8dc0c8ddb73139ea6abf15" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "imx6ul-pac" version = "0.0.0" @@ -377,6 +429,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.67" @@ -409,6 +467,21 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + [[package]] name = "memlog" version = "0.0.0" @@ -466,6 +539,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.3" @@ -493,6 +572,24 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "regex" +version = "1.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" + [[package]] name = "rle-decode-fast" version = "1.0.1" @@ -598,6 +695,24 @@ dependencies = [ "xattr", ] +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "typenum" version = "1.11.2" @@ -673,9 +788,11 @@ checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" [[package]] name = "veriform" version = "0.0.1" -source = "git+https://github.com/iqlusioninc/veriform.git?branch=develop#b84ba5cdff3d33b3bd034e6585de0fbccd730ec7" +source = "git+https://github.com/iqlusioninc/veriform.git?branch=develop#5d01b368a23ecc8c5819281e39fe643bc60c61a0" dependencies = [ "displaydoc", + "heapless", + "log", "tai64", "uuid", "vint64", @@ -683,8 +800,8 @@ dependencies = [ [[package]] name = "vint64" -version = "0.3.0" -source = "git+https://github.com/iqlusioninc/veriform.git?branch=develop#b84ba5cdff3d33b3bd034e6585de0fbccd730ec7" +version = "1.0.1" +source = "git+https://github.com/iqlusioninc/veriform.git?branch=develop#5d01b368a23ecc8c5819281e39fe643bc60c61a0" [[package]] name = "winapi" @@ -702,6 +819,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/client/src/armistice.rs b/client/src/armistice.rs index 66b4a79..1592058 100644 --- a/client/src/armistice.rs +++ b/client/src/armistice.rs @@ -1,7 +1,7 @@ //! Armistice client use crate::error::Error; -use armistice_schema::{Message, Request, Response}; +use armistice_schema::{veriform::Decoder, Message, Request, Response}; #[cfg(feature = "usbarmory")] use crate::usbarmory; @@ -27,6 +27,8 @@ impl Armistice { let mut buf = vec![0; self.usb.in_max_packet_size().into()]; let response = self.usb.read(&mut buf)?; - Ok(Response::decode(response)?) + + let mut decoder = Decoder::new(); + Ok(Response::decode(&mut decoder, response)?) } } diff --git a/core/src/root.rs b/core/src/root.rs index 574752a..023fc84 100644 --- a/core/src/root.rs +++ b/core/src/root.rs @@ -5,7 +5,7 @@ //! //! -use crate::{crypto::PublicKey, error::Error, schema::provision::Uuid}; +use crate::{crypto::PublicKey, error::Error, schema::Uuid}; use heapless::Vec; /// Maximum number of keys allowed for root role @@ -61,9 +61,6 @@ impl Config { /// Get a UUID which represents this root configuration pub fn uuid(&self) -> Uuid { // TODO(tarcieri): stub! - let mut uuid = Uuid::new(); - uuid.push_str("00000000-0000-0000-0000-000000000000") - .unwrap(); - uuid + Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap() } } diff --git a/core/tests/provision.rs b/core/tests/provision.rs index 83336c7..80d9e47 100644 --- a/core/tests/provision.rs +++ b/core/tests/provision.rs @@ -2,7 +2,7 @@ use aes::{block_cipher_trait::BlockCipher, Aes128}; use armistice_core::Vec; -use armistice_schema::{provision, public_key::PublicKey}; +use armistice_schema::{provision, public_key::PublicKey, Uuid}; type Armistice = armistice_core::Armistice; @@ -42,6 +42,6 @@ fn provisioning_happy_path() { // TODO(tarcieri): stub! assert_eq!( response.provision().unwrap().uuid, - "00000000-0000-0000-0000-000000000000" + Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap() ); } diff --git a/schema/Cargo.toml b/schema/Cargo.toml index fb1389a..cfe71dd 100644 --- a/schema/Cargo.toml +++ b/schema/Cargo.toml @@ -13,7 +13,11 @@ keywords = ["bls", "ed25519", "ecdsa", "hsm"] [dependencies] heapless = "0.5" -veriform = { version = "0", default-features = false } +veriform = { version = "0", default-features = false, features = ["builtins"] } + +[dev-dependencies] +veriform = { version = "0", default-features = false, features = ["builtins", "log"] } +env_logger = "0.7" [package.metadata.docs.rs] all-features = true diff --git a/schema/src/lib.rs b/schema/src/lib.rs index aed0f20..1ca34f7 100644 --- a/schema/src/lib.rs +++ b/schema/src/lib.rs @@ -11,4 +11,4 @@ pub mod request; pub mod response; pub use self::{public_key::PublicKey, request::Request, response::Response}; -pub use veriform::Message; +pub use veriform::{self, builtins::Uuid, Message}; diff --git a/schema/src/provision.rs b/schema/src/provision.rs index b585e34..d04c956 100644 --- a/schema/src/provision.rs +++ b/schema/src/provision.rs @@ -1,21 +1,14 @@ //! Armistice device provisioning messages: performs initial device setup -use crate::public_key::PublicKey; -use heapless::{ - consts::{U36, U8}, - String, Vec, -}; +use crate::{public_key::PublicKey, Uuid}; +use heapless::{consts::U8, Vec}; use veriform::{ - decoder, + decoder::{sequence, Decode, DecodeSeq, Decoder}, field::{self, WireType}, message::Element, - vint64, Decodable, Decoder, Encoder, Error, Message, + vint64, Encoder, Error, Message, }; -/// UUID type -// TODO(tarcieri): define a builtin type for UUIDs that uses the `uuid` crate -pub type Uuid = String; - /// Root keys collection pub type RootKeys = Vec; @@ -35,31 +28,19 @@ pub struct Request { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Response { /// UUID (deterministically) assigned at provisioning time - // #[field(string, tag = 0, critical = true, size = 36)] + // #[field(message, tag = 0, critical = true)] pub uuid: Uuid, } // TODO(tarcieri): custom derive support for `veriform::Message` impl Message for Request { - fn decode(bytes: impl AsRef<[u8]>) -> Result { - let mut bytes = bytes.as_ref(); - let mut decoder = Decoder::new(); - - decoder.decode_expected_header(&mut bytes, 0, WireType::UInt64)?; - let root_key_threshold = decoder.decode_uint64(&mut bytes)?; - - decoder.decode_expected_header(&mut bytes, 1, WireType::Sequence)?; - - let mut root_keys_bytes = decoder.decode_sequence(WireType::Message, &mut bytes)?; + fn decode(decoder: &mut Decoder, mut input: &[u8]) -> Result { + let root_key_threshold = decoder.decode(0, &mut input)?; + let root_keys_iter: sequence::Iter<'_, PublicKey> = decoder.decode_seq(1, &mut input)?; let mut root_keys = Vec::new(); - let mut root_keys_decoder = - decoder::sequence::Decoder::new(WireType::Message, root_keys_bytes.len()); - - while !root_keys_bytes.is_empty() { - let root_key = - PublicKey::decode(root_keys_decoder.decode_message(&mut root_keys_bytes)?)?; - root_keys.push(root_key).map_err(|_| Error::Decode { + for root_key in root_keys_iter { + root_keys.push(root_key?).map_err(|_| Error::Decode { element: Element::Value, wire_type: WireType::Sequence, })?; @@ -105,27 +86,18 @@ impl Message for Request { // TODO(tarcieri): custom derive support for `veriform::Message` impl Message for Response { - fn decode(bytes: impl AsRef<[u8]>) -> Result { - let mut bytes = bytes.as_ref(); - let mut decoder = Decoder::new(); - - decoder.decode_expected_header(&mut bytes, 0, WireType::String)?; - - let mut uuid = String::new(); - uuid.push_str(decoder.decode_string(&mut bytes)?) - .map_err(|_| Error::Length)?; - - Ok(Self { uuid }) + fn decode(decoder: &mut Decoder, mut input: &[u8]) -> Result { + decoder.decode(0, &mut input).map(|uuid| Self { uuid }) } fn encode<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a [u8], Error> { let mut encoder = Encoder::new(buffer); - encoder.string(0, true, &self.uuid)?; + encoder.message(0, true, &self.uuid)?; Ok(encoder.finish()) } fn encoded_len(&self) -> usize { - field::length::string(0, &self.uuid) + field::length::message(0, &self.uuid) } } @@ -133,8 +105,8 @@ impl Message for Response { pub(crate) mod tests { use super::{Request, Response}; use crate::public_key::PublicKey; - use heapless::{consts::U128, String, Vec}; - use veriform::Message; + use heapless::{consts::U128, Vec}; + use veriform::{builtins::Uuid, Decoder, Message}; /// Create an example `provision::Request` pub(crate) fn example_request() -> Request { @@ -160,9 +132,7 @@ pub(crate) mod tests { /// Create an example `provision::Response` pub(crate) fn example_response() -> Response { - let mut uuid = String::new(); - uuid.push_str("88888888-4444-4444-4444-121212121212") - .unwrap(); + let uuid = Uuid::parse_str("88888888-4444-4444-4444-121212121212").unwrap(); Response { uuid } } @@ -175,7 +145,8 @@ pub(crate) mod tests { request.encode(&mut buffer).unwrap(); buffer.truncate(request.encoded_len()); - assert_eq!(request, Request::decode(&buffer).unwrap()); + let mut decoder = Decoder::new(); + assert_eq!(request, Request::decode(&mut decoder, &buffer).unwrap()); } #[test] @@ -187,6 +158,7 @@ pub(crate) mod tests { response.encode(&mut buffer).unwrap(); buffer.truncate(response.encoded_len()); - assert_eq!(response, Response::decode(&buffer).unwrap()); + let mut decoder = Decoder::new(); + assert_eq!(response, Response::decode(&mut decoder, &buffer).unwrap()); } } diff --git a/schema/src/public_key.rs b/schema/src/public_key.rs index 78e8b65..0587a67 100644 --- a/schema/src/public_key.rs +++ b/schema/src/public_key.rs @@ -2,9 +2,10 @@ use core::convert::TryInto; use veriform::{ + decoder::{Decodable, Decoder}, field::{self, WireType}, - message::Element, - Decodable, Decoder, Encoder, Error, Message, + message::{Element, Message}, + Encoder, Error, }; /// Public keys @@ -17,38 +18,34 @@ pub enum PublicKey { // TODO(tarcieri): custom derive support for `veriform::Message` impl Message for PublicKey { - fn decode(bytes: impl AsRef<[u8]>) -> Result { - let mut bytes = bytes.as_ref(); - let mut decoder = Decoder::new(); - - let header = decoder.decode_header(&mut bytes)?; + fn decode(decoder: &mut Decoder, mut input: &[u8]) -> Result { + let header = decoder.peek().decode_header(&mut input)?; + let bytes = decoder.peek().decode_bytes(&mut input)?; - let result = match header.tag { - 0 => decoder.decode_bytes(&mut bytes).and_then(|slice| { - slice - .try_into() - .map(PublicKey::Ed25519) - .map_err(|_| Error::Decode { - element: Element::Value, - wire_type: WireType::Bytes, - }) - })?, - _ => { - return Err(Error::Decode { - element: Element::Tag, - wire_type: WireType::Message, + let public_key = match header.tag { + 0 => bytes + .try_into() + .map(PublicKey::Ed25519) + .map_err(|_| Error::Decode { + element: Element::Value, + wire_type: WireType::Bytes, + })?, + tag => { + return Err(Error::FieldHeader { + tag: Some(tag), + wire_type: None, }) } }; - if !bytes.is_empty() { + if !input.is_empty() { return Err(Error::Decode { element: Element::Tag, wire_type: WireType::Message, }); } - Ok(result) + Ok(public_key) } fn encode<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a [u8], Error> { @@ -72,10 +69,10 @@ impl Message for PublicKey { mod tests { use super::PublicKey; use heapless::{consts::U64, Vec}; - use veriform::Message; + use veriform::{Decoder, Message}; #[test] - fn encoding_round_trip() { + fn encoding_round_trip_new() { let public_key = PublicKey::Ed25519([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, @@ -86,6 +83,7 @@ mod tests { public_key.encode(&mut buffer).unwrap(); buffer.truncate(public_key.encoded_len()); - assert_eq!(public_key, PublicKey::decode(&buffer).unwrap()); + let mut decoder = Decoder::new(); + assert_eq!(public_key, Message::decode(&mut decoder, &buffer).unwrap()); } } diff --git a/schema/src/request.rs b/schema/src/request.rs index 52dc4d6..faf84d2 100644 --- a/schema/src/request.rs +++ b/schema/src/request.rs @@ -2,9 +2,8 @@ use crate::provision; use veriform::{ - field, - field::{Header, WireType}, - Decodable, Decoder, Encoder, Error, Message, + decoder::{Decodable, Decoder}, + field, Encoder, Error, Message, }; /// Armistice request messages @@ -34,26 +33,25 @@ impl From for Request { // TODO(tarcieri): custom derive support for `veriform::Message` impl Message for Request { - fn decode(bytes: impl AsRef<[u8]>) -> Result { - let mut bytes = bytes.as_ref(); - let mut decoder = Decoder::new(); - let request = match decoder.decode_header(&mut bytes)? { - Header { - tag: 0, - critical: true, - wire_type: WireType::Message, - } => Request::Provision(provision::Request::decode( - decoder.decode_message(&mut bytes)?, - )?), - Header { tag, wire_type, .. } => { + fn decode(decoder: &mut Decoder, mut input: &[u8]) -> Result { + let header = decoder.peek().decode_header(&mut input)?; + let message = decoder.peek().decode_message(&mut input)?; + + // TODO(tarcieri): higher-level abstraction for parsing enums + decoder.push()?; + + let request = match header.tag { + 0 => Request::Provision(provision::Request::decode(decoder, &message)?), + tag => { return Err(Error::FieldHeader { tag: Some(tag), - wire_type: Some(wire_type), + wire_type: None, }) } }; - if bytes.is_empty() { + if input.is_empty() { + decoder.pop(); Ok(request) } else { Err(Error::TrailingData) @@ -82,7 +80,7 @@ pub(crate) mod tests { use super::Request; use crate::provision; use heapless::{consts::U128, Vec}; - use veriform::Message; + use veriform::{Decoder, Message}; /// Create an example `Request` pub(crate) fn example_message() -> Request { @@ -98,6 +96,7 @@ pub(crate) mod tests { request.encode(&mut buffer).unwrap(); buffer.truncate(request.encoded_len()); - assert_eq!(request, Request::decode(&buffer).unwrap()); + let mut decoder = Decoder::new(); + assert_eq!(request, Request::decode(&mut decoder, &buffer).unwrap()); } } diff --git a/schema/src/response.rs b/schema/src/response.rs index f540c36..ca98111 100644 --- a/schema/src/response.rs +++ b/schema/src/response.rs @@ -2,9 +2,8 @@ use crate::provision; use veriform::{ - field, - field::{Header, WireType}, - Decodable, Decoder, Encoder, Error, Message, + decoder::{Decodable, Decoder}, + field, Encoder, Error, Message, }; /// Armistice response messages @@ -17,7 +16,7 @@ pub enum Response { // TODO(tarcieri): custom derive support for `veriform::Message` impl Response { - /// Get a provisioning request, if this is one + /// Get a provisioning response, if this is one pub fn provision(&self) -> Option<&provision::Response> { match self { Response::Provision(provision) => Some(provision), @@ -33,26 +32,25 @@ impl From for Response { // TODO(tarcieri): custom derive support for `veriform::Message` impl Message for Response { - fn decode(bytes: impl AsRef<[u8]>) -> Result { - let mut bytes = bytes.as_ref(); - let mut decoder = Decoder::new(); - let response = match decoder.decode_header(&mut bytes)? { - Header { - tag: 0, - critical: true, - wire_type: WireType::Message, - } => Response::Provision(provision::Response::decode( - decoder.decode_message(&mut bytes)?, - )?), - Header { tag, wire_type, .. } => { + fn decode(decoder: &mut Decoder, mut input: &[u8]) -> Result { + let header = decoder.peek().decode_header(&mut input)?; + let message = decoder.peek().decode_message(&mut input)?; + + // TODO(tarcieri): higher-level abstraction for parsing enums + decoder.push()?; + + let response = match header.tag { + 0 => Response::Provision(provision::Response::decode(decoder, &message)?), + tag => { return Err(Error::FieldHeader { tag: Some(tag), - wire_type: Some(wire_type), + wire_type: None, }) } }; - if bytes.is_empty() { + if input.is_empty() { + decoder.pop(); Ok(response) } else { Err(Error::TrailingData) @@ -81,7 +79,7 @@ pub(crate) mod tests { use super::Response; use crate::provision; use heapless::{consts::U128, Vec}; - use veriform::Message; + use veriform::{Decoder, Message}; /// Create an example `Response` pub(crate) fn example_message() -> Response { @@ -97,6 +95,7 @@ pub(crate) mod tests { response.encode(&mut buffer).unwrap(); buffer.truncate(response.encoded_len()); - assert_eq!(response, Response::decode(&buffer).unwrap()); + let mut decoder = Decoder::new(); + assert_eq!(response, Response::decode(&mut decoder, &buffer).unwrap()); } }