diff --git a/app/Makefile.version b/app/Makefile.version index 85f2b6ab..318b9aaa 100644 --- a/app/Makefile.version +++ b/app/Makefile.version @@ -3,4 +3,4 @@ APPVERSION_M=3 # This is the minor version of this release APPVERSION_N=2 # This is the patch version of this release -APPVERSION_P=0 +APPVERSION_P=1 diff --git a/app/rust/Cargo.lock b/app/rust/Cargo.lock index 5f04adda..b5e8cdd5 100644 --- a/app/rust/Cargo.lock +++ b/app/rust/Cargo.lock @@ -60,7 +60,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.76", + "syn 2.0.87", "which", ] @@ -175,6 +175,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bstr" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "build_const" version = "0.2.2" @@ -213,6 +223,18 @@ dependencies = [ "libloading", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -420,6 +442,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "enum-ordinalize" version = "3.1.15" @@ -430,7 +458,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -450,7 +478,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -512,6 +540,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "group" version = "0.13.0" @@ -600,12 +641,32 @@ dependencies = [ "subtle", ] +[[package]] +name = "insta" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +dependencies = [ + "console", + "globset", + "lazy_static", + "linked-hash-map", + "similar", + "walkdir", +] + [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "k256" version = "0.13.3" @@ -648,6 +709,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -683,7 +750,7 @@ checksum = "a6bdc119b1a405df86a8cde673295114179dbd0ebe18877c26ba89fb080365c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -810,7 +877,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -992,10 +1059,13 @@ dependencies = [ "hex", "ic-certification", "ic-verify-bls-signature", + "insta", "minicbor", "no-std-compat", "nom", + "serde", "serde_cbor", + "serde_json", "sha2 0.10.8", "zemu-sys", "zuit", @@ -1020,6 +1090,21 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "sec1" version = "0.7.3" @@ -1036,9 +1121,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -1064,13 +1149,25 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa 1.0.11", + "memchr", + "ryu", + "serde", ] [[package]] @@ -1119,6 +1216,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "spki" version = "0.7.3" @@ -1148,9 +1251,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -1190,6 +1293,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1214,6 +1327,15 @@ dependencies = [ "rustix", ] +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1296,7 +1418,7 @@ dependencies = [ "bolos-sys", "cfg-if", "cty", - "itoa", + "itoa 0.4.8", ] [[package]] @@ -1317,7 +1439,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -1337,7 +1459,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] diff --git a/app/rust/Cargo.toml b/app/rust/Cargo.toml index 33004fc1..2199cba1 100644 --- a/app/rust/Cargo.toml +++ b/app/rust/Cargo.toml @@ -26,7 +26,10 @@ hex = { version = "0.4" } minicbor = { version = "0.24.2", features = ["std"] } ic-certification = { version = "2.5.0", features = ["serde"] } serde_cbor = "0.11.2" +serde_json = "1.0.85" zuit = { path = "../../deps/ledger-rust/zuit" } +insta = { version = "1", features = ["glob"] } +serde = { version = "1.0.215", features = ["derive"] } [target.'cfg(fuzzing)'.dependencies] @@ -43,3 +46,4 @@ panic = "abort" [features] clippy = [] +derive-debug = [] diff --git a/app/rust/src/argument_list.rs b/app/rust/src/argument_list.rs new file mode 100644 index 00000000..9bb5c763 --- /dev/null +++ b/app/rust/src/argument_list.rs @@ -0,0 +1,58 @@ +use crate::{ + error::ParserError, + utils::{decompress_leb128, decompress_sleb128}, +}; + +#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] +#[derive(Clone, Copy)] +pub struct ArgumentTypes { + // Could be either primitive type (negative) or type table index (positive) + pub types: [i64; MAX_ARGS], + pub count: u8, +} + +impl ArgumentTypes { + pub fn new() -> Self { + Self { + types: [0; MAX_ARGS], + count: 0, + } + } + + pub fn find_type(&self, index: usize) -> Option { + if index < self.count as usize { + Some(self.types[index]) + } else { + None + } + } +} + +impl Default for ArgumentTypes { + fn default() -> Self { + Self::new() + } +} + +pub fn parse_argument_types( + input: &[u8], +) -> Result<(&[u8], ArgumentTypes), ParserError> { + let (rem, arg_count) = decompress_leb128(input)?; + #[cfg(test)] + std::println!("arg_count: {}", arg_count); + if arg_count > MAX_ARGS as u64 { + return Err(ParserError::TooManyTypes); + } + + let mut args = ArgumentTypes::new(); + args.count = arg_count as u8; + + let mut current = rem; + for i in 0..arg_count { + let (new_rem, type_code) = decompress_sleb128(current)?; + args.types[i as usize] = type_code; + current = new_rem; + } + + Ok((current, args)) +} diff --git a/app/rust/src/candid_header.rs b/app/rust/src/candid_header.rs new file mode 100644 index 00000000..8da3f9fb --- /dev/null +++ b/app/rust/src/candid_header.rs @@ -0,0 +1,39 @@ +use crate::{ + argument_list::{parse_argument_types, ArgumentTypes}, + error::ParserError, + type_table::{parse_type_table, TypeTable}, +}; + +#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] +pub struct CandidHeader { + pub type_table: TypeTable, + pub arguments: ArgumentTypes, +} + +impl CandidHeader { + pub fn new(type_table: TypeTable, arguments: ArgumentTypes) -> Self { + Self { + type_table, + arguments, + } + } +} + +pub fn parse_candid_header( + input: &[u8], +) -> Result<(&[u8], CandidHeader), ParserError> { + // 1. Magic number + let (rem, _) = nom::bytes::complete::tag("DIDL")(input) + .map_err(|_: nom::Err| ParserError::UnexpectedError)?; + + // 2. Type table + let (rem, type_table) = parse_type_table(rem)?; + + #[cfg(test)] + crate::type_table::print_type_table(&type_table); + + // 3. Argument types + let (rem, arguments) = parse_argument_types(rem)?; + + Ok((rem, CandidHeader::new(type_table, arguments))) +} diff --git a/app/rust/src/constants.rs b/app/rust/src/constants.rs index ffba5cc9..e5aff86b 100644 --- a/app/rust/src/constants.rs +++ b/app/rust/src/constants.rs @@ -15,8 +15,10 @@ ********************************************************************************/ pub const BLS_PUBLIC_KEY_SIZE: usize = 96; pub const BLS_SIGNATURE_SIZE: usize = 48; -pub const MAX_LINES: usize = 2; -pub const MAX_PAGES: usize = 20; +// means max characteres per line +pub const MAX_LINES: usize = 3; +// means mx number of lines per page +pub const MAX_PAGES: usize = 10; pub const MAX_CHARS_PER_LINE: usize = 25; pub const REPLY_PATH: &str = "reply"; pub const CANISTER_RANGES_PATH: &str = "canister_ranges"; @@ -27,6 +29,7 @@ pub const CBOR_CERTIFICATE_TAG: u64 = CBOR_TAG; pub const CALL_REQUEST_TAG: u64 = CBOR_TAG; pub const CONSENT_MSG_REQUEST_TAG: u64 = CBOR_TAG; pub const CANISTER_RANGES_TAG: u64 = CBOR_TAG; +pub const CANISTER_CALL_TAG: u64 = CBOR_TAG; pub const PRINCIPAL_MAX_LEN: usize = 29; // Sender are principals @@ -40,8 +43,6 @@ pub const NANOSECONDS_PER_SECOND: u64 = 1_000_000_000; // The max offset between the certificate.time and the call message request ingress_expiry // otherwise, call request must be considered invalid/outdated and not processed at all pub const MAX_CERT_INGRESS_OFFSET: u64 = 15 * SECONDS_PER_MINUTE * NANOSECONDS_PER_SECOND; -// separator_len(1-bytes) + separator(13-bytes) + hash(32-bytes) -pub const BLS_MSG_SIZE: usize = 1 + 13 + 32; // The official root key for consent message verification // including certificate, provided by the ICP team pub const CANISTER_ROOT_KEY: &str = "814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae"; @@ -49,3 +50,13 @@ pub const CANISTER_ROOT_KEY: &str = "814c0e6ec71fab583b08bd81373c255c3c371b2e848 // Provided in testing data // indicating sender in there is the default one, whose value is 0x04 pub const DEFAULT_SENDER: u8 = 0x04; + +// Defines the minimum number of elements +// in our candid type table in order +// to parse the type using it +pub const MAX_TABLE_FIELDS: usize = 12; +// the max number of candid arguments in memory +pub const MAX_ARGS: usize = 5; + +// The size of the hash +pub const SHA256_DIGEST_LENGTH: usize = 32; diff --git a/app/rust/src/ffi.rs b/app/rust/src/ffi.rs index fc383562..a2292933 100644 --- a/app/rust/src/ffi.rs +++ b/app/rust/src/ffi.rs @@ -22,39 +22,39 @@ mod verify_certificate; #[cfg(test)] mod ffi_verify_cert { - use crate::{constants::CANISTER_ROOT_KEY, error::ParserError}; + use crate::error::ParserError; use super::{ call_request::rs_parse_canister_call_request, consent_request::rs_parse_consent_request, resources::rs_clear_resources, verify_certificate::rs_verify_certificate, }; - const CERT_DATA: &str = "d9d9f7a3647472656583018301820458200bbcc71092da3ce262b8154d398b9a6114bee87f1c0b72e16912757aa023626a8301820458200628a8e00432e8657ad99c4d1bf167dd54ace9199609bfc5d57d89f48d97565f83024e726571756573745f737461747573830258204ea057c46292fedb573d35319dd1ccab3fb5d6a2b106b785d1f7757cfa5a254283018302457265706c79820358b44449444c0b6b02bc8a0101c5fed201086c02efcee7800402e29fdcc806036c01d880c6d007716b02d9e5b0980404fcdfd79a0f716c01c4d6b4ea0b056d066c01ffbb87a807076d716b04d1c4987c09a3f2efe6020a9a8597e6030ae3c581900f0a6c02fc91f4f80571c498b1b50d7d6c01fc91f4f8057101000002656e0001021e50726f647563652074686520666f6c6c6f77696e67206772656574696e6714746578743a202248656c6c6f2c20746f626921228302467374617475738203477265706c696564830182045820891af3e8982f1ac3d295c29b9fdfedc52301c03fbd4979676c01059184060b0583024474696d65820349cbf7dd8ca1a2a7e217697369676e6174757265583088078c6fe75f32594bf4e322b14d47e5c849cf24a370e3bab0cab5daffb7ab6a2c49de18b7f2d631893217d0c716cd656a64656c65676174696f6ea2697375626e65745f6964581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd026b6365727469666963617465590294d9d9f7a264747265658301820458200b0d62dc7b9d7de735bb9a6393b59c9f32ff7c4d2aacdfc9e6ffc70e341fb6f783018301820458204468514ca4af8224c055c386e3f7b0bfe018c2d9cfd5837e427b43e1ab0934f98302467375626e65748301830183018301820458208739fbbedd3dedaa8fef41870367c0905bde376b63dd37e2b176fb08b582052f830182045820f8c3eae0377ee00859223bf1c6202f5885c4dcdc8fd13b1d48c3c838688919bc83018302581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd02830183024f63616e69737465725f72616e67657382035832d9d9f782824a000000000060000001014a00000000006000ae0101824a00000000006000b001014a00000000006fffff010183024a7075626c69635f6b657982035885308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c0503020103610090075120778eb21a530a02bcc763e7f4a192933506966af7b54c10a4d2b24de6a86b200e3440bae6267bf4c488d9a11d0472c38c1b6221198f98e4e6882ba38a5a4e3aa5afce899b7f825ed95adfa12629688073556f2747527213e8d73e40ce8204582036f3cd257d90fb38e42597f193a5e031dbd585b6292793bb04db4794803ce06e82045820028fc5e5f70868254e7215e7fc630dbd29eefc3619af17ce231909e1faf97e9582045820696179fceb777eaed283265dd690241999eb3ede594091748b24456160edc1278204582081398069f9684da260cfb002eac42211d0dbf22c62d49aee61617d62650e793183024474696d65820349a5948992aaa195e217697369676e6174757265583094e5f544a7681b0c2c3c5dbf97950c96fd837f2d19342f1050d94d3068371b0a95a5ee20c36c4395c2dbb4204f2b4742"; - const CALL_DATA: &str ="d9d9f7a167636f6e74656e74a6636172674c4449444c00017104746f62696b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49d610e2008806b6d6574686f645f6e616d656567726565746c726571756573745f747970656571756572796673656e6465724104"; - const CONSENT_DATA: &str = "d9d9f7a167636f6e74656e74a763617267586b4449444c076d7b6c01d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d026e036c02efcee7800401c4fbf2db05046c03d6fca70200e1edeb4a7184f7fee80a0501060c4449444c00017104746f626905677265657402656e01011e0003006b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49d49c5a920806b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e636550a3788c1805553fb69b20f08e87e23b136c726571756573745f747970656463616c6c6673656e6465724104"; - - const CERT2: &str = "d9d9f7a3647472656583018301820458200bbcc71092da3ce262b8154d398b9a6114bee87f1c0b72e16912757aa023626a8301820458200628a8e00432e8657ad99c4d1bf167dd54ace9199609bfc5d57d89f48d97565f83024e726571756573745f737461747573830258204ea057c46292fedb573d35319dd1ccab3fb5d6a2b106b785d1f7757cfa5a254283018302457265706c79820358b44449444c0b6b02bc8a0101c5fed201086c02efcee7800402e29fdcc806036c01d880c6d007716b02d9e5b0980404fcdfd79a0f716c01c4d6b4ea0b056d066c01ffbb87a807076d716b04d1c4987c09a3f2efe6020a9a8597e6030ae3c581900f0a6c02fc91f4f80571c498b1b50d7d6c01fc91f4f8057101000002656e0001021e50726f647563652074686520666f6c6c6f77696e67206772656574696e6714746578743a202248656c6c6f2c20746f626921228302467374617475738203477265706c696564830182045820891af3e8982f1ac3d295c29b9fdfedc52301c03fbd4979676c01059184060b0583024474696d65820349cbf7dd8ca1a2a7e217697369676e6174757265583088078c6fe75f32594bf4e322b14d47e5c849cf24a370e3bab0cab5daffb7ab6a2c49de18b7f2d631893217d0c716cd656a64656c65676174696f6ea2697375626e65745f6964581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd026b6365727469666963617465590294d9d9f7a264747265658301820458200b0d62dc7b9d7de735bb9a6393b59c9f32ff7c4d2aacdfc9e6ffc70e341fb6f783018301820458204468514ca4af8224c055c386e3f7b0bfe018c2d9cfd5837e427b43e1ab0934f98302467375626e65748301830183018301820458208739fbbedd3dedaa8fef41870367c0905bde376b63dd37e2b176fb08b582052f830182045820f8c3eae0377ee00859223bf1c6202f5885c4dcdc8fd13b1d48c3c838688919bc83018302581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd02830183024f63616e69737465725f72616e67657382035832d9d9f782824a000000000060000001014a00000000006000ae0101824a00000000006000b001014a00000000006fffff010183024a7075626c69635f6b657982035885308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c0503020103610090075120778eb21a530a02bcc763e7f4a192933506966af7b54c10a4d2b24de6a86b200e3440bae6267bf4c488d9a11d0472c38c1b6221198f98e4e6882ba38a5a4e3aa5afce899b7f825ed95adfa12629688073556f2747527213e8d73e40ce8204582036f3cd257d90fb38e42597f193a5e031dbd585b6292793bb04db4794803ce06e82045820028fc5e5f70868254e7215e7fc630dbd29eefc3619af17ce231909e1faf97e9582045820696179fceb777eaed283265dd690241999eb3ede594091748b24456160edc1278204582081398069f9684da260cfb002eac42211d0dbf22c62d49aee61617d62650e793183024474696d65820349a5948992aaa195e217697369676e6174757265583094e5f544a7681b0c2c3c5dbf97950c96fd837f2d19342f1050d94d3068371b0a95a5ee20c36c4395c2dbb4204f2b4742"; - const CALL2: &str = "d9d9f7a167636f6e74656e74a6636172674c4449444c00017104746f62696b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49db0b64dfb806b6d6574686f645f6e616d656567726565746c726571756573745f747970656571756572796673656e646572581d19aa3d42c048dd7d14f0cfa0df69a1c1381780f6e9a137abaa6a82e302"; - const CONSENT2: &str = "d9d9f7a167636f6e74656e74a763617267586b4449444c076d7b6c01d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d026e036c02efcee7800401c4fbf2db05046c03d6fca70200e1edeb4a7184f7fee80a0501060c4449444c00017104746f626905677265657402656e01011e0003006b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49d49c5a920806b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e636550a3788c1805553fb69b20f08e87e23b136c726571756573745f747970656463616c6c6673656e6465724104"; + const CERT: &str = "d9d9f7a264747265658301830182045820d4cff6a25570a56ac14e743e694daa2aa88b80a4bea761116471b56e4945ed6d830182045820f26d51d511039fcb5058441e6204fec42a0da824541f57d0c1c3468a13e16cbf83024e726571756573745f737461747573830182045820198df32f6757100316e899c7a3afec26f6933c06bf5d2f6233f6b0f14ac5b96f83025820ea37fdc5229d7273d500dc8ae3c009f0421049c1f02cc5ad85ea838ae7dfc04583018302457265706c7982035903304449444c0c6b02bc8a0101c5fed201096c02efcee7800402e29fdcc806046c02aeaeb1cc0503d880c6d007716e766b02d9e5b0980405fcdfd79a0f716c01c4d6b4ea0b066d076c01ffbb87a807086d716b04d1c4987c0aa3f2efe6020b9a8597e6030be3c581900f0b6c02fc91f4f80571c498b1b50d7d6c01fc91f4f805710100000002656e0007031e2320417574686f72697a6520616e6f74686572206164647265737320746f2077697468647261772066726f6d20796f7572206163636f756e74202a2a5468651f666f6c6c6f77696e67206164647265737320697320616c6c6f77656420746f031d77697468647261772066726f6d20796f7572206163636f756e743a2a2a2272646d78362d6a616161612d61616161612d61616164712d636169202a2a596f75720d7375626163636f756e743a2a2a032330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030232a2a526571756573746564207769746864726177616c20616c6c6f77616e63653a2a2a032031302049435020e29aa02054686520616c6c6f77616e63652077696c6c2062652273657420746f2031302049435020696e646570656e64656e746c79206f6620616e791e70726576696f757320616c6c6f77616e63652e20556e74696c207468697303217472616e73616374696f6e20686173206265656e206578656375746564207468651e7370656e6465722063616e207374696c6c206578657263697365207468652370726576696f757320616c6c6f77616e63652028696620616e792920746f2069742773032166756c6c20616d6f756e742e202a2a45787069726174696f6e20646174653a2a2a204e6f2065787069726174696f6e2e202a2a417070726f76616c206665653a2a2a23302e3030303120494350202a2a5472616e73616374696f6e206665657320746f206265031a7061696420627920796f7572207375626163636f756e743a2a2a2330303030303030303030303030303030303030303030303030303030303030303030301d30303030303030303030303030303030303030303030303030303030308302467374617475738203477265706c6965648301820458206d8327eb52806a887c0e4f444261e7bde20005e64d9b31479b9af72f8f89886083024474696d65820349c8ccbcfea4c8ca8318697369676e61747572655830b9eb03718c42aa1926bab9956dcef37432045ba1122baf120b8fc3e9fb56f75df8eee419e4e6488e60db79dcaba8c153"; + const CALL: &str = "d9d9f7a167636f6e74656e74a76361726758684449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e04010501904e0000008094ebdc030000010a00000000000000070101006b63616e69737465725f69644a000000000000000201016e696e67726573735f6578706972791b18072a6f7894d0006b6d6574686f645f6e616d656d69637263325f617070726f7665656e6f6e6365506b99f1c2338b4543152aae206d5286726c726571756573745f747970656463616c6c6673656e646572581d052c5f6f270fc4a3a882a8075732cba90ad4bd25d30bd2cf7b0bfe7c02"; + const CONSENT: &str = "d9d9f7a167636f6e74656e74a76361726758d84449444c086d7b6e766c02aeaeb1cc0501d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d036e046c02efcee7800402c4fbf2db05056c03d6fca70200e1edeb4a7184f7fee80a060107684449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e04010501904e0000008094ebdc030000010a00000000000000070101000d69637263325f617070726f76650002656e0101230003006b63616e69737465725f69644a000000000000000201016e696e67726573735f6578706972791b18072a6f7894d0006b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e636550369f1914fd64438f5e6329fcb66b1d4d6c726571756573745f747970656463616c6c6673656e6465724104"; + const ROOT_KEY: &str = + "b354faa40626ebc91ed7e55b2307feff70d119ef37f89915bd4561a1ed8c5c26c8c2cb8c4711eec681bf213a75cb988008fb1f4d7aa278cd4fad6f295c83bab04b8cabcb32640cf926083daf865551f9f3b76fd800dac027a583858b9d1d3f64"; fn bls_flow(cert: &str, call: &str, consent: &str) -> u32 { let cert_data = hex::decode(cert).unwrap(); let call_data = hex::decode(call).unwrap(); + let root_key = hex::decode(ROOT_KEY).unwrap(); let consent_data = hex::decode(consent).unwrap(); - let root_key = hex::decode(CANISTER_ROOT_KEY).unwrap(); unsafe { // 1. send consent_request if rs_parse_consent_request(consent_data.as_ptr(), consent_data.len() as u16) - != ParserError::Ok as _ + != ParserError::Ok as u32 { + std::println!("Error parsing consent request"); return ParserError::InvalidConsentMsg as u32; } // 2. send call request if rs_parse_canister_call_request(call_data.as_ptr(), call_data.len() as u16) - != ParserError::Ok as _ + != ParserError::Ok as u32 { + std::println!("Error parsing call request"); return ParserError::InvalidCallRequest as u32; } // 3. send certificate, by default use root key @@ -62,8 +62,9 @@ mod ffi_verify_cert { cert_data.as_ptr(), cert_data.len() as u16, root_key.as_ptr(), - ) != ParserError::Ok as _ + ) != ParserError::Ok as u32 { + std::println!("Error verifying certificate"); return ParserError::InvalidCertificate as u32; } @@ -73,28 +74,10 @@ mod ffi_verify_cert { #[test] fn test_bls_flow() { - assert_eq!( - bls_flow(CERT_DATA, CALL_DATA, CONSENT_DATA), - ParserError::Ok as u32 - ); + let res = bls_flow(CERT, CALL, CONSENT); + assert_eq!(res, ParserError::Ok as u32); unsafe { rs_clear_resources(); } - - // now test again and ensure we error if resources are not empty - assert_eq!( - bls_flow(CERT_DATA, CALL_DATA, CONSENT_DATA), - ParserError::Ok as u32 - ); - // trying to verify without cleaning resources - assert_ne!( - bls_flow(CERT_DATA, CALL_DATA, CONSENT_DATA), - ParserError::Ok as u32 - ); - } - - #[test] - fn test_bls_flow_with_app_signer() { - assert_eq!(bls_flow(CERT2, CALL2, CONSENT2), ParserError::Ok as u32); } } diff --git a/app/rust/src/ffi/call_request.rs b/app/rust/src/ffi/call_request.rs index ad903487..a26f13a9 100644 --- a/app/rust/src/ffi/call_request.rs +++ b/app/rust/src/ffi/call_request.rs @@ -84,6 +84,7 @@ impl ByteSerializable for CanisterCallT { } impl CanisterCallT { + #[inline(never)] fn fill_from(&mut self, request: &CallRequest<'_>) -> Result<(), ParserError> { check_canary(); crate::zlog("CanisterCallT::fill_from\x00"); @@ -95,7 +96,7 @@ impl CanisterCallT { // Compute arg hash let mut hasher = Sha256::new(); - hasher.update(request.arg); + hasher.update(request.arg().raw_data()); let arg_hash = hasher.finalize(); self.arg_hash.copy_from_slice(arg_hash.as_slice()); @@ -104,24 +105,24 @@ impl CanisterCallT { return Err(ParserError::ValueOutOfRange); } - self.canister_id.copy_from_slice(request.canister_id); - self.canister_id_len = request.canister_id.len() as u16; - self.ingress_expiry = request.ingress_expiry; + self.canister_id.copy_from_slice(request.canister_id()); + self.canister_id_len = request.canister_id().len() as u16; + self.ingress_expiry = request.ingress_expiry(); - if request.method_name.len() > METHOD_MAX_LEN { + if request.method_name().len() > METHOD_MAX_LEN { return Err(ParserError::ValueOutOfRange); } - self.method_name[..request.method_name.len()] - .copy_from_slice(request.method_name.as_bytes()); - self.method_name_len = request.method_name.len() as u16; + self.method_name[..request.method_name().len()] + .copy_from_slice(request.method_name().as_bytes()); + self.method_name_len = request.method_name().len() as u16; - if request.sender.len() > SENDER_MAX_LEN { + if request.sender().len() > SENDER_MAX_LEN { return Err(ParserError::ValueOutOfRange); } - self.sender[..request.sender.len()].copy_from_slice(request.sender); - self.sender_len = request.sender.len() as u16; + self.sender[..request.sender().len()].copy_from_slice(request.sender()); + self.sender_len = request.sender().len() as u16; crate::zlog("CanisterCallT::fill_from: done!\x00"); @@ -157,14 +158,13 @@ pub unsafe extern "C" fn rs_parse_canister_call_request(data: *const u8, data_le #[inline(never)] fn fill_request(request: &CallRequest<'_>) -> Result<(), ParserError> { + let mut serialized = [0; core::mem::size_of::()]; + unsafe { - let mut consent_request = CanisterCallT::default(); + let call_request = &mut *(serialized.as_mut_ptr() as *mut CanisterCallT); // Update our consent request - consent_request.fill_from(request)?; - - let mut serialized = [0; core::mem::size_of::()]; - consent_request.fill_to(&mut serialized)?; + call_request.fill_from(request)?; MEMORY_CALL_REQUEST .write(0, &serialized) diff --git a/app/rust/src/ffi/consent_request.rs b/app/rust/src/ffi/consent_request.rs index 927e0a1b..f58df7a6 100644 --- a/app/rust/src/ffi/consent_request.rs +++ b/app/rust/src/ffi/consent_request.rs @@ -91,6 +91,7 @@ impl ByteSerializable for ConsentRequestT { // also it assigns the inner args and method name of the candid // encoded icrc21_consent_message_request impl ConsentRequestT { + #[inline(never)] fn fill_from(&mut self, request: &ConsentMsgRequest<'_>) -> Result<(), ParserError> { check_canary(); @@ -98,11 +99,12 @@ impl ConsentRequestT { // Compute and store request_id let request_id = request.request_id(); + self.request_id.copy_from_slice(&request_id); // Compute arg_hash // remember this is the inner hash - let Ok(icrc21) = request.arg().icrc21_msg_request() else { + let Ok(icrc21) = request.icrc21_msg_request() else { return Err(ParserError::InvalidConsentMsg); }; @@ -114,6 +116,7 @@ impl ConsentRequestT { // Copy method_name // the one encoded in the inner args let method = icrc21.method()?; + if method.len() > METHOD_MAX_LEN { return Err(ParserError::ValueOutOfRange); } @@ -121,19 +124,21 @@ impl ConsentRequestT { self.method_name_len = method.len() as u16; // Copy canister_id - if request.canister_id.len() > CANISTER_MAX_LEN { + let canister_id = request.canister_id(); + if canister_id.len() > CANISTER_MAX_LEN { return Err(ParserError::ValueOutOfRange); } - self.canister_id[..request.canister_id.len()].copy_from_slice(request.canister_id); - self.canister_id_len = request.canister_id.len() as u16; + self.canister_id[..canister_id.len()].copy_from_slice(canister_id); + self.canister_id_len = canister_id.len() as u16; // Copy sender - if request.sender.len() > SENDER_MAX_LEN { + let sender = request.sender(); + if sender.len() > SENDER_MAX_LEN { return Err(ParserError::ValueOutOfRange); } - self.sender[..request.sender.len()].copy_from_slice(request.sender); - self.sender_len = request.sender.len() as u16; + self.sender[..sender.len()].copy_from_slice(sender); + self.sender_len = sender.len() as u16; Ok(()) } @@ -141,6 +146,8 @@ impl ConsentRequestT { #[no_mangle] pub unsafe extern "C" fn rs_parse_consent_request(data: *const u8, data_len: u16) -> u32 { + crate::zlog("rs_parse_consent_request\x00"); + if data.is_null() { return ParserError::NoData as u32; } @@ -167,6 +174,8 @@ pub unsafe extern "C" fn rs_parse_consent_request(data: *const u8, data_len: u16 return e as u32; } + crate::zlog("consent_request::ok\x00"); + ParserError::Ok as u32 } Err(_) => ParserError::InvalidConsentMsg as u32, @@ -175,18 +184,21 @@ pub unsafe extern "C" fn rs_parse_consent_request(data: *const u8, data_len: u16 #[inline(never)] fn fill_request(request: &ConsentMsgRequest<'_>) -> Result<(), ParserError> { + crate::zlog("consent_request::fill\x00"); + let mut serialized = [0; core::mem::size_of::()]; unsafe { - let mut consent_request = ConsentRequestT::default(); - - // Update our consent request - consent_request.fill_from(request)?; + { + // Lets transmute the array in order to re-use serialized memory + // saving a bunch of stack + let consent_request = &mut *(serialized.as_mut_ptr() as *mut ConsentRequestT); - let mut serialized = [0; core::mem::size_of::()]; - consent_request.fill_to(&mut serialized)?; + // Update our consent request + consent_request.fill_from(request)?; - MEMORY_CONSENT_REQUEST - .write(0, &serialized) - .map_err(|_| ParserError::UnexpectedError)?; + MEMORY_CONSENT_REQUEST + .write(0, &serialized) + .map_err(|_| ParserError::UnexpectedError)?; + } let consent2 = ConsentRequestT::from_bytes(&**MEMORY_CONSENT_REQUEST)?; consent2.validate()?; diff --git a/app/rust/src/ffi/verify_certificate.rs b/app/rust/src/ffi/verify_certificate.rs index c3a56c98..45ca86ac 100644 --- a/app/rust/src/ffi/verify_certificate.rs +++ b/app/rust/src/ffi/verify_certificate.rs @@ -94,6 +94,7 @@ pub unsafe extern "C" fn rs_verify_certificate( let mut cert = MaybeUninit::uninit(); let Ok(_) = Certificate::from_bytes_into(data, &mut cert) else { + crate::zlog("Fail parsing certificate***\x00"); return ParserError::InvalidCertificate as u32; }; diff --git a/app/rust/src/lib.rs b/app/rust/src/lib.rs index f9fcc6ba..abddf30d 100644 --- a/app/rust/src/lib.rs +++ b/app/rust/src/lib.rs @@ -8,7 +8,7 @@ extern crate no_std_compat as std; pub mod candid_types; mod constants; -mod error; +pub mod error; mod ffi; mod parser; mod principal; @@ -16,6 +16,10 @@ pub mod type_table; pub mod utils; pub use principal::Principal; use zemu_sys as _; +pub mod argument_list; +pub mod candid_header; +#[cfg(test)] +pub mod test_ui; pub use parser::*; diff --git a/app/rust/src/parser.rs b/app/rust/src/parser.rs index 3b7453a6..81cedcd5 100644 --- a/app/rust/src/parser.rs +++ b/app/rust/src/parser.rs @@ -15,7 +15,10 @@ ********************************************************************************/ use core::mem::MaybeUninit; -use crate::error::ViewError; +use crate::{ + candid_header::CandidHeader, + error::{ParserError, ViewError}, +}; pub mod call_request; pub mod candid_utils; @@ -26,6 +29,16 @@ mod snapshots_common; pub use certificate::*; +pub trait FromCandidHeader<'a> { + fn from_candid_header( + input: &'a [u8], + out: &mut core::mem::MaybeUninit, + header: &CandidHeader, + ) -> Result<&'a [u8], ParserError> + where + Self: Sized; +} + ///This trait defines an useful interface to parse ///objects from bytes. ///this gives different objects in a transaction diff --git a/app/rust/src/parser/call_request.rs b/app/rust/src/parser/call_request.rs index d704ada9..61902493 100644 --- a/app/rust/src/parser/call_request.rs +++ b/app/rust/src/parser/call_request.rs @@ -1,12 +1,12 @@ mod arg; +mod canister_call; mod consent_msg_request; -mod icrc21_consent_msg_metadata; mod icrc21_message_request; mod icrc21_message_spec; mod request; pub use arg::*; +pub use canister_call::*; pub use consent_msg_request::*; -pub use icrc21_consent_msg_metadata::*; pub use icrc21_message_request::*; pub use icrc21_message_spec::*; pub use request::*; diff --git a/app/rust/src/parser/call_request/arg.rs b/app/rust/src/parser/call_request/arg.rs index 56b8fe39..bc593ae7 100644 --- a/app/rust/src/parser/call_request/arg.rs +++ b/app/rust/src/parser/call_request/arg.rs @@ -2,8 +2,6 @@ use core::ptr::addr_of_mut; use crate::{error::ParserError, FromBytes}; -use super::Icrc21ConsentMessageRequest; - #[derive(PartialEq)] #[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] pub struct RawArg<'a>(&'a [u8]); @@ -33,39 +31,4 @@ impl<'a> RawArg<'a> { pub fn raw_data(&self) -> &[u8] { self.0 } - - pub fn icrc21_msg_request(&self) -> Result { - // 1. Read the "DIDL" magic number - let (rem, _) = nom::bytes::complete::tag("DIDL")(self.0) - .map_err(|_: nom::Err| ParserError::UnexpectedError)?; - - // lazy parsing on demand in order to reduce stack usage - Ok(Icrc21ConsentMessageRequest::new_unchecked(rem)) - } -} -#[cfg(test)] -mod test_arg { - use super::*; - - const ARG: &str = "4449444c076d7b6c01d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d026e036c02efcee7800401c4fbf2db05046c03d6fca70200e1edeb4a7184f7fee80a0501060c4449444c00017104746f626905677265657402656e01011e000300"; - const REQUEST_ARG: &[u8] = &[68, 73, 68, 76, 0, 1, 113, 4, 116, 111, 98, 105]; - const METHOD: &str = "greet"; - const CHARS_PER_LINE: u16 = 30; - const PAGE_LINES: u16 = 3; - - #[test] - fn parse_arg() { - let data = hex::decode(ARG).unwrap(); - let arg = RawArg::from_bytes(&data).unwrap(); - let msg_request = arg.icrc21_msg_request().unwrap(); - std::println!("{:?}", msg_request); - - assert_eq!(msg_request.method().unwrap(), METHOD); - assert_eq!(msg_request.arg().unwrap(), REQUEST_ARG); - - let preferences = msg_request.user_preferences().unwrap(); - - assert_eq!(preferences.chars_per_line(), Some(CHARS_PER_LINE)); - assert_eq!(preferences.lines_per_page(), Some(PAGE_LINES)); - } } diff --git a/app/rust/src/parser/call_request/canister_call.rs b/app/rust/src/parser/call_request/canister_call.rs new file mode 100644 index 00000000..87ee78e6 --- /dev/null +++ b/app/rust/src/parser/call_request/canister_call.rs @@ -0,0 +1,143 @@ +use core::{mem::MaybeUninit, ptr::addr_of_mut}; + +use minicbor::Decoder; + +use crate::{ + constants::{BIG_NUM_TAG, CANISTER_CALL_TAG}, + error::ParserError, + zlog, FromBytes, +}; + +use super::RawArg; + +#[derive(PartialEq)] +#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] +pub struct CanisterCall<'a> { + pub arg: RawArg<'a>, + pub nonce: Option<&'a [u8]>, + // Sender is allowed to be either default sender(h04) or + // this signer + pub sender: &'a [u8], + pub canister_id: &'a [u8], + pub method_name: &'a str, + pub request_type: &'a str, + pub ingress_expiry: u64, +} + +impl<'a> CanisterCall<'a> { + // this sums up the nonce although + // it could be missing + // + const MAP_ENTRIES: u64 = 7; + + pub fn arg(&self) -> &RawArg { + &self.arg + } + pub fn sender(&self) -> &[u8] { + self.sender + } + pub fn canister_id(&self) -> &[u8] { + self.canister_id + } + pub fn method_name(&self) -> &str { + self.method_name + } + pub fn request_type(&self) -> &str { + self.request_type + } + pub fn ingress_expiry(&self) -> u64 { + self.ingress_expiry + } + pub fn nonce(&self) -> Option<&[u8]> { + self.nonce + } +} + +impl<'a> FromBytes<'a> for CanisterCall<'a> { + #[inline(never)] + fn from_bytes_into( + input: &'a [u8], + out: &mut MaybeUninit, + ) -> Result<&'a [u8], ParserError> { + zlog("CanisterCall::from_bytes_into\x00"); + + let mut d = Decoder::new(input); + + let out = out.as_mut_ptr(); + // Check tag + if let Ok(tag) = d.tag() { + if tag.as_u64() != CANISTER_CALL_TAG { + return Err(ParserError::InvalidTag); + } + } + + // Decode outer map + let len = d.map()?.ok_or(ParserError::UnexpectedValue)?; + if len != 1 { + return Err(ParserError::UnexpectedValue); + } + + let _key = d.str()?; + + // Decode content map + let content_len = d.map()?.ok_or(ParserError::UnexpectedBufferEnd)?; + + // Nonce could be an optional argument + // so it could have 6 or 7 map entries depending + // on the presence of nonce + let max_entries = Self::MAP_ENTRIES; + + if content_len != max_entries && content_len != max_entries - 1 { + return Err(ParserError::UnexpectedValue); + } + + let mut nonce = None; + + for _ in 0..content_len { + let key = d.str()?; + unsafe { + match key { + "arg" => { + let arg: &mut MaybeUninit> = + &mut *addr_of_mut!((*out).arg).cast(); + let arg_bytes = d.bytes()?; + _ = RawArg::from_bytes_into(arg_bytes, arg)?; + } + "nonce" => nonce = Some(d.bytes()?), + "sender" => addr_of_mut!((*out).sender).write(d.bytes()?), + "canister_id" => addr_of_mut!((*out).canister_id).write(d.bytes()?), + "method_name" => addr_of_mut!((*out).method_name).write(d.str()?), + "request_type" => addr_of_mut!((*out).request_type).write(d.str()?), + "ingress_expiry" => { + let timestamp = if let Ok(n) = d.u64() { + // Direct uint64 case (what we have in the data) + n + } else { + let tag = d.tag()?; + // Your existing tagged bignum case + if tag.as_u64() != BIG_NUM_TAG { + return Err(ParserError::InvalidCallRequest); + } + let bytes = d.bytes()?; + if bytes.len() > core::mem::size_of::() { + return Err(ParserError::InvalidTime); + } + let mut num_bytes = [0u8; core::mem::size_of::()]; + num_bytes[..bytes.len()].copy_from_slice(bytes); + u64::from_be_bytes(num_bytes) + }; + + addr_of_mut!((*out).ingress_expiry).write(timestamp); + } + _ => return Err(ParserError::UnexpectedField), + } + } + } + + unsafe { + addr_of_mut!((*out).nonce).write(nonce); + } + + Ok(&input[d.position()..]) + } +} diff --git a/app/rust/src/parser/call_request/consent_msg_request.rs b/app/rust/src/parser/call_request/consent_msg_request.rs index 1d7bd02a..fe6f27fc 100644 --- a/app/rust/src/parser/call_request/consent_msg_request.rs +++ b/app/rust/src/parser/call_request/consent_msg_request.rs @@ -1,14 +1,12 @@ use core::{mem::MaybeUninit, ptr::addr_of_mut}; -use minicbor::Decoder; use crate::{ - constants::{BIG_NUM_TAG, CONSENT_MSG_REQUEST_TAG}, error::ParserError, utils::{compress_leb128, hash_blob, hash_str}, zlog, FromBytes, }; -use super::RawArg; +use super::{CanisterCall, Icrc21ConsentMessageRequest, RawArg}; const METHOD_NAME_LEN: usize = 36; const METHOD_NAME: &[u8] = b"icrc21_canister_call_consent_message"; @@ -22,19 +20,7 @@ const METHOD_NAME: &[u8] = b"icrc21_canister_call_consent_message"; // "request_type": "call", "sender": h'04'}} #[derive(PartialEq)] #[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] -pub struct ConsentMsgRequest<'a> { - // Bellow arg contains - // a candid encoded type the Icrc21ConsentMessageRequest - pub arg: RawArg<'a>, - pub nonce: Option<&'a [u8]>, - // Sender is allowed to be either default sender(h04) or - // this signer - pub sender: &'a [u8], - pub canister_id: &'a [u8], - pub method_name: &'a str, - pub request_type: &'a str, - pub ingress_expiry: u64, -} +pub struct ConsentMsgRequest<'a>(CanisterCall<'a>); impl<'a> ConsentMsgRequest<'a> { // this sums up the nonce although @@ -42,26 +28,38 @@ impl<'a> ConsentMsgRequest<'a> { const MAP_ENTRIES: u64 = 7; // Getter methods (unchanged) - pub fn arg(&self) -> &RawArg<'a> { - &self.arg + pub fn arg(&'a self) -> &RawArg<'a> { + self.0.arg() } pub fn nonce(&self) -> Option<&[u8]> { - self.nonce + self.0.nonce() } pub fn sender(&self) -> &[u8] { - self.sender + self.0.sender() } pub fn canister_id(&self) -> &[u8] { - self.canister_id + self.0.canister_id() } pub fn request_type(&self) -> &str { - self.request_type + self.0.request_type() } pub fn ingress_expiry(&self) -> u64 { - self.ingress_expiry + self.0.ingress_expiry() + } + + pub fn method_name(&self) -> &str { + self.0.method_name() + } + + #[inline(never)] + pub fn icrc21_msg_request(&self) -> Result { + // lazy parsing on demand in order to reduce stack usage + Ok(Icrc21ConsentMessageRequest::new_unchecked( + self.arg().raw_data(), + )) } /// Computes the request_id which is the hash @@ -83,7 +81,7 @@ impl<'a> ConsentMsgRequest<'a> { ]; let mut field_hashes = [[0u8; 64]; MAX_FIELDS]; let mut field_count = 0; - let max_fields = if self.nonce.is_some() { + let max_fields = if self.0.nonce().is_some() { MAX_FIELDS } else { MAX_FIELDS - 1 @@ -93,18 +91,18 @@ impl<'a> ConsentMsgRequest<'a> { let key_hash = hash_str(key); let value_hash = match idx { - 0 => hash_blob(self.request_type.as_bytes()), - 1 => hash_blob(self.sender), + 0 => hash_blob(self.0.request_type.as_bytes()), + 1 => hash_blob(self.0.sender), 2 => { let mut buf = [0u8; 10]; - let leb = compress_leb128(self.ingress_expiry, &mut buf); + let leb = compress_leb128(self.0.ingress_expiry, &mut buf); hash_blob(leb) } - 3 => hash_blob(self.canister_id), - 4 => hash_blob(self.method_name.as_bytes()), - 5 => hash_blob(self.arg.raw_data()), + 3 => hash_blob(self.0.canister_id()), + 4 => hash_blob(self.0.method_name().as_bytes()), + 5 => hash_blob(self.0.arg.raw_data()), 6 => { - if let Some(nonce) = self.nonce { + if let Some(nonce) = self.0.nonce { hash_blob(nonce) } else { break; @@ -137,83 +135,12 @@ impl<'a> FromBytes<'a> for ConsentMsgRequest<'a> { ) -> Result<&'a [u8], ParserError> { zlog("ConsentMsgRequest::from_bytes_into\x00"); - let mut d = Decoder::new(input); - let out = out.as_mut_ptr(); - // Check tag - if let Ok(tag) = d.tag() { - if tag.as_u64() != CONSENT_MSG_REQUEST_TAG { - return Err(ParserError::InvalidTag); - } - } - // Decode outer map - let len = d.map()?.ok_or(ParserError::UnexpectedValue)?; - if len != 1 { - return Err(ParserError::UnexpectedValue); - } - - let _key = d.str()?; + let call: &mut MaybeUninit> = + unsafe { &mut *addr_of_mut!((*out).0).cast() }; - // Decode content map - let content_len = d.map()?.ok_or(ParserError::UnexpectedBufferEnd)?; - - // Nonce could be an optional argument - // so it could have 6 or 7 map entries depending - // on the presence of nonce - let max_entries = Self::MAP_ENTRIES; - - if content_len != max_entries && content_len != max_entries - 1 { - return Err(ParserError::UnexpectedValue); - } - - let mut nonce = None; - - for _ in 0..content_len { - let key = d.str()?; - unsafe { - match key { - "arg" => { - let arg: &mut MaybeUninit> = - &mut *addr_of_mut!((*out).arg).cast(); - _ = RawArg::from_bytes_into(d.bytes()?, arg)?; - } - "nonce" => nonce = Some(d.bytes()?), - "sender" => addr_of_mut!((*out).sender).write(d.bytes()?), - "canister_id" => addr_of_mut!((*out).canister_id).write(d.bytes()?), - "method_name" => addr_of_mut!((*out).method_name).write(d.str()?), - "request_type" => addr_of_mut!((*out).request_type).write(d.str()?), - "ingress_expiry" => { - if let Ok(tag) = d.tag() { - if tag.as_u64() != BIG_NUM_TAG { - return Err(ParserError::InvalidCallRequest); - } - } - - let Ok(bytes) = d.bytes() else { - return Err(ParserError::UnexpectedValue); - }; - - // read bytes as a timestamp of 8 bytes - if bytes.len() > core::mem::size_of::() { - return Err(ParserError::InvalidTime); - } - - let mut num_bytes = [0u8; core::mem::size_of::()]; - num_bytes[..bytes.len()].copy_from_slice(bytes); - - let timestamp = u64::from_be_bytes(num_bytes); - - addr_of_mut!((*out).ingress_expiry).write(timestamp); - } - _ => return Err(ParserError::UnexpectedField), - } - } - } - - unsafe { - addr_of_mut!((*out).nonce).write(nonce); - } + let rem = CanisterCall::from_bytes_into(input, call)?; let out = unsafe { &mut *out }; @@ -222,11 +149,12 @@ impl<'a> FromBytes<'a> for ConsentMsgRequest<'a> { let mut name = [0u8; METHOD_NAME_LEN]; name.copy_from_slice(METHOD_NAME); - if out.method_name.as_bytes() != name { + if out.0.method_name.as_bytes() != name { return Err(ParserError::InvalidConsentMsgRequest); } - Ok(&input[d.position()..]) + crate::zlog("ConsentMsgRequest::from_bytes_into ok\x00"); + Ok(rem) } } @@ -236,53 +164,38 @@ mod call_request_test { use super::*; - const REQUEST: &str = "d9d9f7a167636f6e74656e74a763617267586b4449444c076d7b6c01d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d026e036c02efcee7800401c4fbf2db05046c03d6fca70200e1edeb4a7184f7fee80a0501060c4449444c00017104746f626905677265657402656e01011e0003006b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49d49c5a920806b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e636550a3788c1805553fb69b20f08e87e23b136c726571756573745f747970656463616c6c6673656e6465724104"; - const REQUEST2: &str = "d9d9f7a167636f6e74656e74a763617267586b4449444c076d7b6c01d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d026e036c02efcee7800401c4fbf2db05046c03d6fca70200e1edeb4a7184f7fee80a0501060c4449444c00017104746f626905677265657402656e01011e0003006b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49d49c5a920806b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e636550a3788c1805553fb69b20f08e87e23b136c726571756573745f747970656463616c6c6673656e6465724104"; + const REQUEST: &str = "d9d9f7a167636f6e74656e74a76361726758d84449444c086d7b6e766c02aeaeb1cc0501d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d036e046c02efcee7800402c4fbf2db05056c03d6fca70200e1edeb4a7184f7fee80a060107684449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e04010501904e0000008094ebdc030000010a00000000000000070101000d69637263325f617070726f76650002656e0101230003006b63616e69737465725f69644a000000000000000201016e696e67726573735f6578706972791b18072a6f7894d0006b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e636550369f1914fd64438f5e6329fcb66b1d4d6c726571756573745f747970656463616c6c6673656e6465724104"; const ARG: &str = "4449444c00017104746f6269"; - const NONCE: &str = "a3788c1805553fb69b20f08e87e23b13"; - const REQUEST_ID: &str = "4ea057c46292fedb573d35319dd1ccab3fb5d6a2b106b785d1f7757cfa5a2542"; - const CANISTER_ID: &str = "00000000006000fd0101"; + const NONCE: &str = "369f1914fd64438f5e6329fcb66b1d4d"; + const REQUEST_ID: &str = "ea37fdc5229d7273d500dc8ae3c009f0421049c1f02cc5ad85ea838ae7dfc045"; + const CANISTER_ID: &str = "00000000000000020101"; const METHOD: &str = "icrc21_canister_call_consent_message"; const REQUEST_TYPE: &str = "call"; // The default sender - const INGRESS_EXPIRY: u64 = 1712666698482000000; + const INGRESS_EXPIRY: u64 = 1731399240000000000; #[test] fn msg_request() { let data = hex::decode(REQUEST).unwrap(); let msg_req = ConsentMsgRequest::from_bytes(&data).unwrap(); - let request_id = hex::encode(msg_req.request_id()); - - std::println!("ConsentMsgRequest: {:?}", msg_req); - - assert_eq!(msg_req.sender.len(), 1); - assert_eq!(msg_req.sender[0], DEFAULT_SENDER); - assert_eq!(hex::encode(msg_req.canister_id), CANISTER_ID); - assert_eq!(msg_req.method_name, METHOD); - assert_eq!(msg_req.request_type, REQUEST_TYPE); + let icrc_msg_request = msg_req.icrc21_msg_request().unwrap(); - assert_eq!(msg_req.ingress_expiry, INGRESS_EXPIRY); - assert_eq!(hex::encode(msg_req.nonce.unwrap()), NONCE); - assert_eq!(request_id, REQUEST_ID); - } - - #[test] - fn msg_request2() { - let data = hex::decode(REQUEST2).unwrap(); - let msg_req = ConsentMsgRequest::from_bytes(&data).unwrap(); + let method = icrc_msg_request.method().unwrap(); + assert_eq!(method, "icrc2_approve"); let request_id = hex::encode(msg_req.request_id()); - assert_eq!(msg_req.sender.len(), 1); - assert_eq!(msg_req.sender[0], DEFAULT_SENDER); - assert_eq!(hex::encode(msg_req.canister_id), CANISTER_ID); - assert_eq!(msg_req.method_name, METHOD); - assert_eq!(msg_req.request_type, REQUEST_TYPE); + assert_eq!(msg_req.sender().len(), 1); + assert_eq!(msg_req.sender()[0], DEFAULT_SENDER); + assert_eq!(hex::encode(msg_req.canister_id()), CANISTER_ID); + assert_eq!(msg_req.method_name(), METHOD); + assert_eq!(msg_req.request_type(), REQUEST_TYPE); + std::println!("request_id: {}", request_id); - assert_eq!(msg_req.ingress_expiry, INGRESS_EXPIRY); - assert_eq!(hex::encode(msg_req.nonce.unwrap()), NONCE); + assert_eq!(msg_req.ingress_expiry(), INGRESS_EXPIRY); + assert_eq!(hex::encode(msg_req.nonce().unwrap()), NONCE); assert_eq!(request_id, REQUEST_ID); } } diff --git a/app/rust/src/parser/call_request/icrc21_consent_msg_metadata.rs b/app/rust/src/parser/call_request/icrc21_consent_msg_metadata.rs deleted file mode 100644 index e9bc943f..00000000 --- a/app/rust/src/parser/call_request/icrc21_consent_msg_metadata.rs +++ /dev/null @@ -1,58 +0,0 @@ -use core::{mem::MaybeUninit, ptr::addr_of_mut}; - -use crate::{ - candid_utils::{parse_opt_i16, parse_text}, - error::ParserError, - type_table::TypeTable, - FromTable, -}; - -#[repr(C)] -#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] -pub struct Icrc21ConsentMessageMetadata<'a> { - pub language: &'a str, - pub utc_offset_minutes: Option, -} - -impl<'a> FromTable<'a> for Icrc21ConsentMessageMetadata<'a> { - fn from_table( - input: &'a [u8], - out: &mut MaybeUninit, - type_table: &TypeTable, - type_index: usize, - ) -> Result<&'a [u8], ParserError> { - let entry = type_table - .find_type_entry(type_index) - .ok_or(ParserError::FieldNotFound)?; - - let mut rem = input; - let mut language = None; - let mut utc_offset_minutes = None; - - for i in 0..entry.field_count as usize { - let (hash, _) = entry.fields[i]; - match hash { - 2047967320 => { - // language - let (new_rem, value) = parse_text(rem)?; - language = Some(value); - rem = new_rem; - } - 271406923 => { - // utc_offset_minutes - let (new_rem, value) = parse_opt_i16(rem)?; - utc_offset_minutes = value; - rem = new_rem; - } - _ => return Err(ParserError::UnexpectedField), - } - } - - let out_ptr = out.as_mut_ptr(); - unsafe { - addr_of_mut!((*out_ptr).language).write(language.ok_or(ParserError::FieldNotFound)?); - addr_of_mut!((*out_ptr).utc_offset_minutes).write(utc_offset_minutes); - } - Ok(rem) - } -} diff --git a/app/rust/src/parser/call_request/icrc21_message_request.rs b/app/rust/src/parser/call_request/icrc21_message_request.rs index 9cf422ad..18e2195e 100644 --- a/app/rust/src/parser/call_request/icrc21_message_request.rs +++ b/app/rust/src/parser/call_request/icrc21_message_request.rs @@ -1,21 +1,19 @@ use core::mem::MaybeUninit; use crate::{ + candid_header::parse_candid_header, + candid_types::IDLTypes, candid_utils::{parse_bytes, parse_text}, + constants::{MAX_ARGS, MAX_TABLE_FIELDS}, error::ParserError, - type_table::parse_type_table, - utils::decompress_leb128, - zlog, FromTable, + type_table::{TypeTable, TypeTableEntry}, + zlog, FromCandidHeader, }; use super::Icrc21ConsentMessageSpec; -// The minimun size of the candid table -// in order to parse a icrc21 message -// and inner types -const MAX_TABLE_FIELDS: usize = 7; - #[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] +// https://github.com/dfinity/wg-identity-authentication/blob/main/topics/ICRC-21/ICRC-21.did#L38 pub struct Icrc21ConsentMessageRequest<'a>(&'a [u8]); enum Icrc21Field<'a> { @@ -25,152 +23,180 @@ enum Icrc21Field<'a> { } impl<'a> Icrc21ConsentMessageRequest<'a> { - pub const INDEX: usize = 6; + pub const INDEX: usize = 7; const ARG_FIELD: u32 = 4849238; const METHOD_FIELD: u32 = 156956385; const USER_PREFERENCES_FIELD: u32 = 2904537988; - pub fn new_unchecked(input: &'a [u8]) -> Self { + pub(crate) fn new_unchecked(input: &'a [u8]) -> Self { Self(input) } - fn get_field(&self, field: u32) -> Result>, ParserError> { - zlog("get_field\x00"); - let (raw_request, table) = parse_type_table::(self.0)?; + // Public methods using clear names + #[inline(never)] + pub fn method(&self) -> Result<&str, ParserError> { + self.get_field(Self::METHOD_FIELD)? + .ok_or(ParserError::UnexpectedField) + .and_then(|field| match field { + Icrc21Field::Method(method) => Ok(method), + _ => Err(ParserError::UnexpectedField), + }) + } + + #[inline(never)] + pub fn arg(&self) -> Result<&[u8], ParserError> { + self.get_field(Self::ARG_FIELD)? + .ok_or(ParserError::UnexpectedField) + .and_then(|field| match field { + Icrc21Field::Arg(arg) => Ok(arg), + _ => Err(ParserError::UnexpectedField), + }) + } + + #[inline(never)] + pub fn user_preferences(&self) -> Result, ParserError> { + self.get_field(Self::USER_PREFERENCES_FIELD)? + .ok_or(ParserError::UnexpectedField) + .and_then(|field| match field { + Icrc21Field::UserPreferences(prefs) => Ok(prefs), + _ => Err(ParserError::UnexpectedField), + }) + } + + fn find_request_type( + table: &TypeTable, + ) -> Option<&TypeTableEntry> { + // In order to not depend on Self::INDEX + // we can try to look at the table for the entry + // that contains our 3 fields, method, arg and preferences + // using their hashes. + for i in 0..table.entry_count { + let entry = &table.entries[i as usize]; + if entry.type_code != IDLTypes::Record { + continue; + } - let entry = table - .find_type_entry(Self::INDEX) - .ok_or(ParserError::FieldNotFound)?; + let mut found_arg = false; + let mut found_method = false; + let mut found_preferences = false; + + // Check each field in this record + for j in 0..entry.field_count as usize { + let (hash, _) = entry.fields[j]; + match hash { + h if h == Self::ARG_FIELD => found_arg = true, + h if h == Self::METHOD_FIELD => found_method = true, + h if h == Self::USER_PREFERENCES_FIELD => found_preferences = true, + _ => continue, + } + } + + // If we found all three fields, this is our record type + if found_arg && found_method && found_preferences { + return Some(entry); + } + } + None + } + + fn get_field(&self, field: u32) -> Result>, ParserError> { + zlog("Icrc21ConsentMessageRequest::get_field\x00"); + + let (raw_request, header) = parse_candid_header::(self.0)?; + + let entry = + Self::find_request_type(&header.type_table).ok_or(ParserError::FieldNotFound)?; + + #[cfg(test)] + { + std::println!("Found request record type:"); + for (i, (hash, _field_type)) in entry + .fields + .iter() + .take(entry.field_count as usize) + .enumerate() + { + std::println!( + "Field {}: hash={}, type={:?}, matches_target={}", + i, + hash, + _field_type, + hash == &field + ); + } + } let mut rem = raw_request; + // Fields must be parsed in order of appearance for i in 0..entry.field_count as usize { - let (hash, field_type) = entry.fields[i]; + let (hash, _field_type) = entry.fields[i]; + match hash { - 4849238 => { - // arg - // skip type information of args - let (new_rem, _) = decompress_leb128(rem)?; - let (new_rem, _) = decompress_leb128(new_rem)?; - let (new_rem, value) = parse_bytes(new_rem)?; + Self::ARG_FIELD => { + let (new_rem, value) = parse_bytes(rem)?; rem = new_rem; if hash == field { return Ok(Some(Icrc21Field::Arg(value))); } } - 156956385 => { - // method + Self::METHOD_FIELD => { let (new_rem, value) = parse_text(rem)?; rem = new_rem; if hash == field { return Ok(Some(Icrc21Field::Method(value))); } } - 2904537988 => { - // user_preferences + Self::USER_PREFERENCES_FIELD => { let mut user_preferences = MaybeUninit::uninit(); - - rem = Icrc21ConsentMessageSpec::from_table::( + rem = Icrc21ConsentMessageSpec::from_candid_header( rem, &mut user_preferences, - &table, - field_type.as_index().ok_or(ParserError::FieldNotFound)?, + &header, )?; if hash == field { - let user = unsafe { user_preferences.assume_init() }; - return Ok(Some(Icrc21Field::UserPreferences(user))); + return Ok(Some(Icrc21Field::UserPreferences(unsafe { + user_preferences.assume_init() + }))); } } - _ => return Err(ParserError::UnexpectedField), - } + _ => { + #[cfg(test)] + std::println!("Unknown field hash: {}", hash); + continue; + } + }; } - Ok(None) - } - pub fn method(&self) -> Result<&str, ParserError> { - let field = self - .get_field(Self::METHOD_FIELD)? - .ok_or(ParserError::UnexpectedField)?; - let Icrc21Field::Method(method) = field else { - return Err(ParserError::UnexpectedField); - }; - Ok(method) - } - pub fn arg(&self) -> Result<&[u8], ParserError> { - let field = self - .get_field(Self::ARG_FIELD)? - .ok_or(ParserError::UnexpectedField)?; - let Icrc21Field::Arg(arg) = field else { - return Err(ParserError::UnexpectedField); - }; - Ok(arg) + Ok(None) } +} - pub fn user_preferences(&self) -> Result, ParserError> { - let field = self - .get_field(Self::USER_PREFERENCES_FIELD)? - .ok_or(ParserError::UnexpectedField)?; - let Icrc21Field::UserPreferences(user) = field else { - return Err(ParserError::UnexpectedField); - }; - Ok(user) +#[cfg(test)] +mod icrc21_msg_request_test { + use super::*; + + const ICRC21_DATA: &str = + "4449444c086d7b6e766c02aeaeb1cc0501d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d036e046c02efcee7800402c4fbf2db05056c03d6fca70200e1edeb4a7184f7fee80a060107684449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e04010501904e0000008094ebdc030000010a00000000000000070101000d69637263325f617070726f76650002656e010123000300"; + + const METHOD: &str = "icrc2_approve"; + const ARGS: &str = "4449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e04010501904e0000008094ebdc030000010a0000000000000007010100"; + const LANGUAGE: &str = "en"; + const LINES_PER_PAGE: u16 = 3; + + #[test] + fn test_icrc21_msg_request() { + let icrc = hex::decode(ICRC21_DATA).unwrap(); + let icrc = Icrc21ConsentMessageRequest::new_unchecked(&icrc); + + let method = icrc.method().unwrap(); + assert_eq!(method, METHOD); + let arg = icrc.arg().unwrap(); + assert_eq!(hex::encode(arg), ARGS); + let user_preferences = icrc.user_preferences().unwrap(); + assert_eq!(user_preferences.language(), LANGUAGE); + assert_eq!(user_preferences.lines_per_page(), Some(LINES_PER_PAGE)); + assert_eq!(user_preferences.utc_offset(), None); } } - -// impl<'a> FromTable<'a> for Icrc21ConsentMessageRequest<'a> { -// #[inline(never)] -// fn from_table( -// input: &'a [u8], -// out: &mut MaybeUninit, -// type_table: &TypeTable, -// type_index: usize, -// ) -> Result<&'a [u8], ParserError> { -// let entry = type_table -// .find_type_entry(type_index) -// .ok_or(ParserError::FieldNotFound)?; -// -// let out_ptr = out.as_mut_ptr(); -// -// let mut rem = input; -// -// for i in 0..entry.field_count as usize { -// let (hash, field_type) = entry.fields[i]; -// match hash { -// 4849238 => { -// // arg -// // skip type information of args -// let (new_rem, _) = decompress_leb128(rem)?; -// let (new_rem, _) = decompress_leb128(new_rem)?; -// let (new_rem, value) = parse_bytes(new_rem)?; -// unsafe { -// addr_of_mut!((*out_ptr).arg).write(value); -// } -// rem = new_rem; -// } -// 156956385 => { -// // method -// let (new_rem, value) = parse_text(rem)?; -// unsafe { -// addr_of_mut!((*out_ptr).method).write(value); -// } -// rem = new_rem; -// } -// 2904537988 => { -// // user_preferences -// let user_preferences: &mut MaybeUninit> = -// unsafe { &mut *addr_of_mut!((*out_ptr).user_preferences).cast() }; -// -// rem = Icrc21ConsentMessageSpec::from_table( -// rem, -// user_preferences, -// type_table, -// field_type.as_index().ok_or(ParserError::FieldNotFound)?, -// )?; -// } -// _ => return Err(ParserError::UnexpectedField), -// } -// } -// Ok(rem) -// } -// } diff --git a/app/rust/src/parser/call_request/icrc21_message_spec.rs b/app/rust/src/parser/call_request/icrc21_message_spec.rs index a392a8a1..a8358358 100644 --- a/app/rust/src/parser/call_request/icrc21_message_spec.rs +++ b/app/rust/src/parser/call_request/icrc21_message_spec.rs @@ -2,23 +2,40 @@ use core::{mem::MaybeUninit, ptr::addr_of_mut}; use nom::number::complete::le_u16; -use crate::{error::ParserError, type_table::TypeTable, utils::decompress_leb128, FromTable}; - -use super::Icrc21ConsentMessageMetadata; - -type Hash256 = [u8; 32]; - +use crate::{ + candid_header::CandidHeader, consent_message::msg_metadata::ConsentMessageMetadata, + error::ParserError, type_table::TypeTable, utils::decompress_leb128, +}; + +// type icrc21_consent_message_spec = record { +// metadata: icrc21_consent_message_metadata; +// +// device_spec: opt variant { +// GenericDisplay; +// LineDisplay: record { +// characters_per_line: nat16; +// lines_per_page: nat16; +// }; +// }; +// }; #[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] pub struct Icrc21ConsentMessageSpec<'a> { - metadata: Icrc21ConsentMessageMetadata<'a>, // 2 - device_spec: Option, // 4 + pub(crate) metadata: ConsentMessageMetadata<'a>, // 2 + pub(crate) device_spec: Option, // 4 } impl<'a> Icrc21ConsentMessageSpec<'a> { + const METADATA_HASH: u32 = 1075439471; + const DEVSPEC_HASH: u32 = 1534901700; + pub fn language(&self) -> &str { self.metadata.language } + pub fn utc_offset(&self) -> Option { + self.metadata.utc_offset + } + pub fn lines_per_page(&self) -> Option { match self.device_spec.as_ref()? { DeviceSpec::LineDisplay { lines_per_page, .. } => Some(*lines_per_page), @@ -38,16 +55,9 @@ impl<'a> Icrc21ConsentMessageSpec<'a> { } // device_spec: opt variant { -// // A generic display able to handle large documents and do line wrapping and pagination / scrolling. -// // Text must be Markdown formatted, no external resources (e.g. images) are allowed. // GenericDisplay; -// // Simple display able to handle lines of text with a maximum number of characters per line. -// // Multiple pages can be used if the text does no fit on a single page. -// // Text must be plain text without any embedded formatting elements. // LineDisplay: record { -// // Maximum number of characters that can be displayed per line. // characters_per_line: nat16; -// // Maximum number of lines that can be displayed at once on a single page. // lines_per_page: nat16; // }; // }; @@ -60,51 +70,31 @@ pub enum DeviceSpec { }, } -impl<'a> FromTable<'a> for Icrc21ConsentMessageSpec<'a> { - #[inline(never)] - fn from_table( +impl<'a> crate::FromCandidHeader<'a> for Icrc21ConsentMessageSpec<'a> { + fn from_candid_header( input: &'a [u8], out: &mut MaybeUninit, - type_table: &TypeTable, - type_index: usize, + header: &CandidHeader, ) -> Result<&'a [u8], ParserError> { - let entry = type_table - .find_type_entry(type_index) - .ok_or(ParserError::FieldNotFound)?; + crate::zlog("Icrc21ConsentMessageSpec::from_candid_header\x00"); let mut rem = input; let mut metadata = MaybeUninit::uninit(); - let mut device_spec = None; - - for i in 0..entry.field_count as usize { - let (hash, field_type) = entry.fields[i]; - match hash { - 1075439471 => { - // metadata - - rem = Icrc21ConsentMessageMetadata::from_table( - rem, - &mut metadata, - type_table, - field_type.as_index().ok_or(ParserError::FieldNotFound)?, - )?; - } - 1534901700 => { - // device_spec - let index = field_type.as_index().ok_or(ParserError::FieldNotFound)?; - let (value, new_rem) = parse_opt_device_spec(rem, type_table, index)?; - device_spec = value; - rem = new_rem; - } - _ => return Err(ParserError::UnexpectedField), - } - } + + // Parse metadata field (METADATA_HASH = 1075439471) + rem = ConsentMessageMetadata::from_candid_header(rem, &mut metadata, header)?; + + // Parse optional device spec field (DEVSPEC_HASH = 1534901700) + let (new_rem, value) = parse_opt_device_spec(rem, &header.type_table)?; + let device_spec = value; + rem = new_rem; let out_ptr = out.as_mut_ptr(); unsafe { addr_of_mut!((*out_ptr).metadata).write(metadata.assume_init()); addr_of_mut!((*out_ptr).device_spec).write(device_spec); } + Ok(rem) } } @@ -112,37 +102,30 @@ impl<'a> FromTable<'a> for Icrc21ConsentMessageSpec<'a> { fn parse_opt_device_spec<'a, const MAX_FIELDS: usize>( input: &'a [u8], _type_table: &TypeTable, - _type_index: usize, -) -> Result<(Option, &'a [u8]), ParserError> { - let (rem, opt_tag) = decompress_leb128(input)?; +) -> Result<(&'a [u8], Option), ParserError> { + let (rem, has_value) = decompress_leb128(input)?; + + if has_value == 0 { + return Ok((rem, None)); + } + + let (rem, variant_idx) = decompress_leb128(rem)?; - match opt_tag { - 0 => Ok((None, rem)), + match variant_idx { + 0 => Ok((rem, Some(DeviceSpec::GenericDisplay))), 1 => { - let (rem, variant_tag) = decompress_leb128(rem)?; - - match variant_tag { - 0 => Ok((Some(DeviceSpec::GenericDisplay), rem)), - 1 => { - let (rem, characters_per_line) = - le_u16(rem).map_err(|_: nom::Err>| { - ParserError::UnexpectedError - })?; - let (rem, lines_per_page) = - le_u16(rem).map_err(|_: nom::Err>| { - ParserError::UnexpectedError - })?; - - Ok(( - Some(DeviceSpec::LineDisplay { - characters_per_line, - lines_per_page, - }), - rem, - )) - } - _ => Err(ParserError::UnexpectedError), - } + let (rem, characters_per_line) = le_u16(rem) + .map_err(|_: nom::Err>| ParserError::UnexpectedError)?; + let (rem, lines_per_page) = le_u16(rem) + .map_err(|_: nom::Err>| ParserError::UnexpectedError)?; + + Ok(( + rem, + Some(DeviceSpec::LineDisplay { + characters_per_line, + lines_per_page, + }), + )) } _ => Err(ParserError::UnexpectedError), } diff --git a/app/rust/src/parser/call_request/request.rs b/app/rust/src/parser/call_request/request.rs index 2c84221c..99eb4dbc 100644 --- a/app/rust/src/parser/call_request/request.rs +++ b/app/rust/src/parser/call_request/request.rs @@ -1,52 +1,37 @@ use core::{mem::MaybeUninit, ptr::addr_of_mut}; -use minicbor::Decoder; +use crate::{error::ParserError, utils::compress_leb128, zlog, FromBytes}; -use crate::{ - constants::{BIG_NUM_TAG, CALL_REQUEST_TAG}, - error::ParserError, - utils::compress_leb128, - zlog, FromBytes, -}; +use super::{CanisterCall, RawArg}; // {"content": {"arg": h'4449444C00017104746F6269', "canister_id": h'00000000006000FD0101', // "ingress_expiry": 1712667140606000000, "method_name": "greet", "request_type": "query", "sender": h'04'}} #[derive(PartialEq)] #[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] -pub struct CallRequest<'a> { - pub arg: &'a [u8], - // Sender is allowed to be either default sender(h04) or - // this signer - pub sender: &'a [u8], - pub canister_id: &'a [u8], - pub method_name: &'a str, - pub request_type: &'a str, - pub ingress_expiry: u64, - pub nonce: Option<&'a [u8]>, -} +pub struct CallRequest<'a>(CanisterCall<'a>); impl<'a> CallRequest<'a> { - pub fn arg(&self) -> &[u8] { - self.arg + pub fn arg(&'a self) -> &RawArg<'a> { + self.0.arg() } pub fn sender(&self) -> &[u8] { - self.sender + self.0.sender() } pub fn canister_id(&self) -> &[u8] { - self.canister_id + self.0.canister_id() } pub fn method_name(&self) -> &str { - self.method_name + self.0.method_name() } pub fn request_type(&self) -> &str { - self.request_type + self.0.request_type() } pub fn ingress_expiry(&self) -> u64 { - self.ingress_expiry + self.0.ingress_expiry() } pub fn nonce(&self) -> Option<&[u8]> { - self.nonce + self.0.nonce() } // Compute the hash of the call request @@ -65,26 +50,26 @@ impl<'a> CallRequest<'a> { }; // Hash fields in the same order as the C function - hash_field("sender", self.sender); - hash_field("canister_id", self.canister_id); + hash_field("sender", self.sender()); + hash_field("canister_id", self.canister_id()); // Hash ingress_expiry (u64) let mut buf = [0u8; 10]; hash_field( "ingress_expiry", - compress_leb128(self.ingress_expiry, &mut buf), + compress_leb128(self.ingress_expiry(), &mut buf), ); - hash_field("method_name", self.method_name.as_bytes()); - hash_field("request_type", self.request_type.as_bytes()); + hash_field("method_name", self.method_name().as_bytes()); + hash_field("request_type", self.request_type().as_bytes()); // Hash nonce if present - if let Some(nonce) = self.nonce { + if let Some(nonce) = self.nonce() { hash_field("nonce", nonce); } // Hash arg last - hash_field("arg", self.arg); + hash_field("arg", self.arg().raw_data()); // Finalize and return the hash let result = hasher.finalize(); @@ -98,71 +83,16 @@ impl<'a> FromBytes<'a> for CallRequest<'a> { fn from_bytes_into( input: &'a [u8], out: &mut MaybeUninit, - ) -> Result<&'a [u8], crate::error::ParserError> { + ) -> Result<&'a [u8], ParserError> { zlog("CallRequest::from_bytes_into\x00"); let out = out.as_mut_ptr(); - let mut d = Decoder::new(input); - - if let Ok(tag) = d.tag() { - if tag.as_u64() != CALL_REQUEST_TAG { - return Err(ParserError::InvalidCallRequest); - } - } - - let len = d.map()?.ok_or(ParserError::InvalidCallRequest)?; - if len != 1 { - return Err(ParserError::InvalidCallRequest); - } + let call: &mut MaybeUninit> = + unsafe { &mut *addr_of_mut!((*out).0).cast() }; - let _key = d.str()?; + let rem = CanisterCall::from_bytes_into(input, call)?; - // Decode content map - let content_len = d.map()?.ok_or(ParserError::CborUnexpected)?; - if content_len != 6 && content_len != 7 { - return Err(ParserError::InvalidCallRequest); - } - - let mut nonce = None; - - for _ in 0..content_len as usize { - let key = d.str()?; - unsafe { - match key { - "arg" => addr_of_mut!((*out).arg).write(d.bytes()?), - "nonce" => nonce = Some(d.bytes()?), - "sender" => addr_of_mut!((*out).sender).write(d.bytes()?), - "canister_id" => addr_of_mut!((*out).canister_id).write(d.bytes()?), - "method_name" => addr_of_mut!((*out).method_name).write(d.str()?), - "request_type" => addr_of_mut!((*out).request_type).write(d.str()?), - "ingress_expiry" => { - if let Ok(tag) = d.tag() { - if tag.as_u64() != BIG_NUM_TAG { - return Err(ParserError::InvalidCallRequest); - } - } - let bytes = d.bytes()?; - // read bytes as a timestamp of 8 bytes - if bytes.len() > core::mem::size_of::() { - return Err(ParserError::InvalidTime); - } - - let mut num_bytes = [0u8; core::mem::size_of::()]; - num_bytes[..bytes.len()].copy_from_slice(bytes); - - let timestamp = u64::from_be_bytes(num_bytes); - - addr_of_mut!((*out).ingress_expiry).write(timestamp); - } - _ => return Err(ParserError::UnexpectedField), - } - } - } - unsafe { - addr_of_mut!((*out).nonce).write(nonce); - } - - Ok(&input[d.position()..]) + Ok(rem) } } @@ -171,59 +101,26 @@ mod call_request_test { use super::*; - const REQUEST: &str = "d9d9f7a167636f6e74656e74a6636172674c4449444c00017104746f62696b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49db0b64dfb806b6d6574686f645f6e616d656567726565746c726571756573745f747970656571756572796673656e6465724104"; - const REQUEST2: &str = "d9d9f7a167636f6e74656e74a6636172674c4449444c00017104746f62696b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49d610e2008806b6d6574686f645f6e616d656567726565746c726571756573745f747970656571756572796673656e6465724104"; - const REQUEST3: &str = "d9d9f7a167636f6e74656e74a6636172674c4449444c00017104746f62696b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49db0b64dfb806b6d6574686f645f6e616d656567726565746c726571756573745f747970656571756572796673656e646572581d19aa3d42c048dd7d14f0cfa0df69a1c1381780f6e9a137abaa6a82e302"; - const TIME_EXPIRY: u64 = 1712667140606000000; - const CANISTER_ID: &str = "00000000006000FD0101"; + const REQUEST: &str = "d9d9f7a167636f6e74656e74a76361726758684449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e04010501904e0000008094ebdc030000010a00000000000000070101006b63616e69737465725f69644a000000000000000201016e696e67726573735f6578706972791b18072a6f7894d0006b6d6574686f645f6e616d656d69637263325f617070726f7665656e6f6e6365506b99f1c2338b4543152aae206d5286726c726571756573745f747970656463616c6c6673656e646572581d052c5f6f270fc4a3a882a8075732cba90ad4bd25d30bd2cf7b0bfe7c02"; + const TIME_EXPIRY: u64 = 1731399240000000000; + const CANISTER_ID: &str = "00000000000000020101"; - const ARG3: &str = "4449444C00017104746F6269"; + const ARG: &str = "4449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e04010501904e0000008094ebdc030000010a0000000000000007010100"; + const SENDER: &str = "052c5f6f270fc4a3a882a8075732cba90ad4bd25d30bd2cf7b0bfe7c02"; + const METHOD_NAME: &str = "icrc2_approve"; + const REQUEST_TYPE: &str = "call"; #[test] - fn call_parse() { + fn call_parse4() { let data = hex::decode(REQUEST).unwrap(); let call_request = CallRequest::from_bytes(&data).unwrap(); - std::println!("CallRequest: {:?}", call_request); - - assert_eq!( - call_request.arg, - &[68, 73, 68, 76, 0, 1, 113, 4, 116, 111, 98, 105] - ); - assert_eq!(call_request.sender, &[4]); - assert_eq!(call_request.canister_id, &[0, 0, 0, 0, 0, 96, 0, 253, 1, 1]); - assert_eq!(call_request.method_name, "greet"); - assert_eq!(call_request.request_type, "query"); - assert_eq!(call_request.ingress_expiry, TIME_EXPIRY); - } - - #[test] - fn call_parse2() { - let data = hex::decode(REQUEST2).unwrap(); - let call_request = CallRequest::from_bytes(&data).unwrap(); - std::println!("CallRequest: {:?}", call_request); - - assert_eq!( - call_request.arg, - &[68, 73, 68, 76, 0, 1, 113, 4, 116, 111, 98, 105] - ); - assert_eq!(call_request.sender, &[4]); - assert_eq!(call_request.canister_id, &[0, 0, 0, 0, 0, 96, 0, 253, 1, 1]); - assert_eq!(call_request.method_name, "greet"); - assert_eq!(call_request.request_type, "query"); - assert_eq!(call_request.ingress_expiry, 1712666798482000000); - } - - #[test] - fn call_parse3() { - let data = hex::decode(REQUEST3).unwrap(); - let call_request = CallRequest::from_bytes(&data).unwrap(); - assert_eq!(call_request.arg, &hex::decode(ARG3).unwrap()); + assert_eq!(hex::encode(call_request.arg().raw_data()), ARG); // Sender in this tests is not the default one - assert_ne!(call_request.sender, &[4]); - assert_eq!(call_request.canister_id, hex::decode(CANISTER_ID).unwrap()); - assert_eq!(call_request.method_name, "greet"); - assert_eq!(call_request.request_type, "query"); - assert_eq!(call_request.ingress_expiry, TIME_EXPIRY); + assert_eq!(hex::encode(call_request.sender()), SENDER); + assert_eq!(hex::encode(call_request.canister_id()), CANISTER_ID); + assert_eq!(call_request.method_name(), METHOD_NAME); + assert_eq!(call_request.request_type(), REQUEST_TYPE); + assert_eq!(call_request.ingress_expiry(), TIME_EXPIRY); } } diff --git a/app/rust/src/parser/candid_utils.rs b/app/rust/src/parser/candid_utils.rs index 53454be0..e34c04ab 100644 --- a/app/rust/src/parser/candid_utils.rs +++ b/app/rust/src/parser/candid_utils.rs @@ -1,6 +1,6 @@ use nom::bytes::complete::{take, take_until}; -use crate::{error::ParserError, utils::decompress_leb128}; +use crate::error::ParserError; /// Parse a text from the candid encoded input pub fn parse_text(input: &[u8]) -> Result<(&[u8], &str), nom::Err> { @@ -29,14 +29,19 @@ fn parse_candid_arg_slice(input: &[u8]) -> Result<(&[u8], &[u8]), nom::Err from the candid encoded input /// Number could be either a u8, ..., u64 or i8, ..., i64 -/// This version is more flexible, returning None for unexpected or missing data +/// This version follows Candid spec exactly: i8(0) for None, i8(1) for Some macro_rules! generate_opt_number { ($num_type:ty, $func_name:ident, $le_type:ident) => { pub fn $func_name(input: &[u8]) -> Result<(&[u8], Option<$num_type>), ParserError> { - let (rem, opt_tag) = decompress_leb128(input)?; + if input.is_empty() { + return Err(ParserError::UnexpectedBufferEnd); + } + // Read single byte for opt tag (not leb128) + let opt_tag = input[0]; + let rem = &input[1..]; + match opt_tag { 0 => Ok((rem, None)), 1 => { @@ -44,7 +49,7 @@ macro_rules! generate_opt_number { .map_err(|_: nom::Err| ParserError::UnexpectedError)?; Ok((rem, Some(value))) } - _ => Err(ParserError::UnexpectedError), + _ => Err(ParserError::UnexpectedType), } } }; @@ -55,8 +60,31 @@ generate_opt_number!(u8, parse_opt_u8, le_u8); generate_opt_number!(u16, parse_opt_u16, le_u16); generate_opt_number!(u32, parse_opt_u32, le_u32); generate_opt_number!(u64, parse_opt_u64, le_u64); - generate_opt_number!(i8, parse_opt_i8, le_i8); generate_opt_number!(i16, parse_opt_i16, le_i16); generate_opt_number!(i32, parse_opt_i32, le_i32); generate_opt_number!(i64, parse_opt_i64, le_i64); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_opt_i16_parser() { + // Test None case + let input = &[0, 0xFF, 0xFF]; // 0 followed by garbage + let (rem, value) = parse_opt_i16(input).unwrap(); + assert_eq!(value, None); + assert_eq!(rem, &[0xFF, 0xFF]); + + // Test Some case + let input = &[1, 0x2A, 0x00]; // 1 followed by 42 in little endian + let (rem, value) = parse_opt_i16(input).unwrap(); + assert_eq!(value, Some(42)); + assert!(rem.is_empty()); + + // Test invalid tag + let input = &[2, 0x00, 0x00]; + assert!(parse_opt_i16(input).is_err()); + } +} diff --git a/app/rust/src/parser/certificate/cert.rs b/app/rust/src/parser/certificate/cert.rs index c6e1016d..1404d677 100644 --- a/app/rust/src/parser/certificate/cert.rs +++ b/app/rust/src/parser/certificate/cert.rs @@ -21,8 +21,8 @@ use minicbor::{Decode, Decoder}; use crate::{ consent_message::msg_response::ConsentMessageResponse, constants::{ - BLS_MSG_SIZE, CANISTER_RANGES_PATH, CBOR_CERTIFICATE_TAG, MAX_CERT_INGRESS_OFFSET, - REPLY_PATH, + CANISTER_RANGES_PATH, CBOR_CERTIFICATE_TAG, MAX_CERT_INGRESS_OFFSET, REPLY_PATH, + SHA256_DIGEST_LENGTH, }, error::{ParserError, ViewError}, zlog, DisplayableItem, FromBytes, Signature, @@ -32,6 +32,10 @@ use super::{ canister_ranges::CanisterRanges, delegation::Delegation, hash_tree::HashTree, raw_value::RawValue, }; +// separator_len(1-bytes) + separator(13-bytes) + hash(32-bytes) +pub const SEPARATOR: &[u8] = b"ic-state-root"; +pub const SEPARATOR_LEN: usize = SEPARATOR.len(); +pub const BLS_MSG_SIZE: usize = 1 + SEPARATOR_LEN + SHA256_DIGEST_LENGTH; #[derive(Clone, Copy, PartialEq)] #[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] @@ -109,6 +113,9 @@ impl<'a> FromBytes<'a> for Certificate<'a> { // Verify that the tree raw value can be parsed sucessfully into a HashTree let _: HashTree = cert.tree.try_into().map_err(|_| ParserError::InvalidTree)?; + #[cfg(test)] + HashTree::parse_and_print_hash_tree(&cert.tree, 0).unwrap(); + Ok(&input[d.position()..]) } } @@ -150,7 +157,7 @@ impl<'a> Certificate<'a> { } #[inline(never)] - pub fn bls_message(&self) -> Result<[u8; 46], ParserError> { + pub fn bls_message(&self) -> Result<[u8; BLS_MSG_SIZE], ParserError> { // Step 1: Compute root hash, this is computed using certificate.tree // This hash is computed correctly as per testing data passed from icp team // we get to the same hash. @@ -158,10 +165,13 @@ impl<'a> Certificate<'a> { // Step 4: Verify signature // separator_len(1-bytes) + separator(13-bytes) + hash(32-bytes) + crate::zlog("Certificate::separator****\x00"); let mut message = [0u8; BLS_MSG_SIZE]; - message[0] = 13; - message[1..14].copy_from_slice(b"ic-state-root"); + message[0] = SEPARATOR_LEN as u8; + // message[1..14].copy_from_slice(b"ic-state-root"); + message[1..14].copy_from_slice(SEPARATOR); message[14..].copy_from_slice(&root_hash); + crate::zlog("Certificate::separator**** OK\x00"); Ok(message) } @@ -188,7 +198,7 @@ impl<'a> Certificate<'a> { None => Ok(true), Some(delegation) => { // Ensure the delegation's certificate contains the subnet's public key - zlog("dele_pubkey\x00"); + zlog("delegation_pubkey\x00"); if delegation.public_key()?.is_none() { zlog("delegation_without_key\x00"); return Ok(false); @@ -220,6 +230,7 @@ impl<'a> Certificate<'a> { match &self.delegation { None => Ok(root_key), // Use root_public_key if no delegation Some(d) => { + zlog("using_delegation_key\x00"); let key = d.public_key()?.ok_or(ParserError::UnexpectedValue)?; Ok(key.as_bytes()) } @@ -263,6 +274,8 @@ impl<'a> Certificate<'a> { let tree = self.tree(); let found = HashTree::lookup_path(&REPLY_PATH.into(), tree)?; let bytes = found.value().ok_or(ParserError::InvalidConsentMsg)?; + #[cfg(test)] + std::println!("\nConsent message bytes: {:?}\n", hex::encode(bytes)); let mut msg = MaybeUninit::uninit(); ConsentMessageResponse::from_bytes_into(bytes, &mut msg)?; @@ -319,19 +332,44 @@ impl<'a> DisplayableItem for Certificate<'a> { #[cfg(test)] mod test_certificate { - use crate::constants::CANISTER_ROOT_KEY; + use crate::consent_message::msg_response::ResponseType; use super::*; use ic_certification::Certificate as IcpCertificate; - const REAL_CERT: &str = "d9d9f7a3647472656583018301820458200bbcc71092da3ce262b8154d398b9a6114bee87f1c0b72e16912757aa023626a8301820458200628a8e00432e8657ad99c4d1bf167dd54ace9199609bfc5d57d89f48d97565f83024e726571756573745f737461747573830258204ea057c46292fedb573d35319dd1ccab3fb5d6a2b106b785d1f7757cfa5a254283018302457265706c79820358b44449444c0b6b02bc8a0101c5fed201086c02efcee7800402e29fdcc806036c01d880c6d007716b02d9e5b0980404fcdfd79a0f716c01c4d6b4ea0b056d066c01ffbb87a807076d716b04d1c4987c09a3f2efe6020a9a8597e6030ae3c581900f0a6c02fc91f4f80571c498b1b50d7d6c01fc91f4f8057101000002656e0001021e50726f647563652074686520666f6c6c6f77696e67206772656574696e6714746578743a202248656c6c6f2c20746f626921228302467374617475738203477265706c696564830182045820891af3e8982f1ac3d295c29b9fdfedc52301c03fbd4979676c01059184060b0583024474696d65820349cbf7dd8ca1a2a7e217697369676e6174757265583088078c6fe75f32594bf4e322b14d47e5c849cf24a370e3bab0cab5daffb7ab6a2c49de18b7f2d631893217d0c716cd656a64656c65676174696f6ea2697375626e65745f6964581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd026b6365727469666963617465590294d9d9f7a264747265658301820458200b0d62dc7b9d7de735bb9a6393b59c9f32ff7c4d2aacdfc9e6ffc70e341fb6f783018301820458204468514ca4af8224c055c386e3f7b0bfe018c2d9cfd5837e427b43e1ab0934f98302467375626e65748301830183018301820458208739fbbedd3dedaa8fef41870367c0905bde376b63dd37e2b176fb08b582052f830182045820f8c3eae0377ee00859223bf1c6202f5885c4dcdc8fd13b1d48c3c838688919bc83018302581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd02830183024f63616e69737465725f72616e67657382035832d9d9f782824a000000000060000001014a00000000006000ae0101824a00000000006000b001014a00000000006fffff010183024a7075626c69635f6b657982035885308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c0503020103610090075120778eb21a530a02bcc763e7f4a192933506966af7b54c10a4d2b24de6a86b200e3440bae6267bf4c488d9a11d0472c38c1b6221198f98e4e6882ba38a5a4e3aa5afce899b7f825ed95adfa12629688073556f2747527213e8d73e40ce8204582036f3cd257d90fb38e42597f193a5e031dbd585b6292793bb04db4794803ce06e82045820028fc5e5f70868254e7215e7fc630dbd29eefc3619af17ce231909e1faf97e9582045820696179fceb777eaed283265dd690241999eb3ede594091748b24456160edc1278204582081398069f9684da260cfb002eac42211d0dbf22c62d49aee61617d62650e793183024474696d65820349a5948992aaa195e217697369676e6174757265583094e5f544a7681b0c2c3c5dbf97950c96fd837f2d19342f1050d94d3068371b0a95a5ee20c36c4395c2dbb4204f2b4742"; - const CERT_HASH: &str = "bcedf2eab3980aedd4d0d9f2159efebd5597cbad5f49217e0c9686b93d30d503"; - const DEL_CERT_HASH: &str = "04a94256c02e83aab4f203cb0784340279d7902f9b09305c978be1746e19b742"; - const CANISTER_ID: &str = "00000000006000FD0101"; - - const REQUEST_ID: &str = "4ea057c46292fedb573d35319dd1ccab3fb5d6a2b106b785d1f7757cfa5a2542"; - - const INGRESS_EXPIRY: u64 = 1712666798482000000; + const REAL_CERT: &str = "d9d9f7a264747265658301830182045820d4cff6a25570a56ac14e743e694daa2aa88b80a4bea761116471b56e4945ed6d830182045820f26d51d511039fcb5058441e6204fec42a0da824541f57d0c1c3468a13e16cbf83024e726571756573745f737461747573830182045820198df32f6757100316e899c7a3afec26f6933c06bf5d2f6233f6b0f14ac5b96f83025820ea37fdc5229d7273d500dc8ae3c009f0421049c1f02cc5ad85ea838ae7dfc04583018302457265706c7982035903304449444c0c6b02bc8a0101c5fed201096c02efcee7800402e29fdcc806046c02aeaeb1cc0503d880c6d007716e766b02d9e5b0980405fcdfd79a0f716c01c4d6b4ea0b066d076c01ffbb87a807086d716b04d1c4987c0aa3f2efe6020b9a8597e6030be3c581900f0b6c02fc91f4f80571c498b1b50d7d6c01fc91f4f805710100000002656e0007031e2320417574686f72697a6520616e6f74686572206164647265737320746f2077697468647261772066726f6d20796f7572206163636f756e74202a2a5468651f666f6c6c6f77696e67206164647265737320697320616c6c6f77656420746f031d77697468647261772066726f6d20796f7572206163636f756e743a2a2a2272646d78362d6a616161612d61616161612d61616164712d636169202a2a596f75720d7375626163636f756e743a2a2a032330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030232a2a526571756573746564207769746864726177616c20616c6c6f77616e63653a2a2a032031302049435020e29aa02054686520616c6c6f77616e63652077696c6c2062652273657420746f2031302049435020696e646570656e64656e746c79206f6620616e791e70726576696f757320616c6c6f77616e63652e20556e74696c207468697303217472616e73616374696f6e20686173206265656e206578656375746564207468651e7370656e6465722063616e207374696c6c206578657263697365207468652370726576696f757320616c6c6f77616e63652028696620616e792920746f2069742773032166756c6c20616d6f756e742e202a2a45787069726174696f6e20646174653a2a2a204e6f2065787069726174696f6e2e202a2a417070726f76616c206665653a2a2a23302e3030303120494350202a2a5472616e73616374696f6e206665657320746f206265031a7061696420627920796f7572207375626163636f756e743a2a2a2330303030303030303030303030303030303030303030303030303030303030303030301d30303030303030303030303030303030303030303030303030303030308302467374617475738203477265706c6965648301820458206d8327eb52806a887c0e4f444261e7bde20005e64d9b31479b9af72f8f89886083024474696d65820349c8ccbcfea4c8ca8318697369676e61747572655830b9eb03718c42aa1926bab9956dcef37432045ba1122baf120b8fc3e9fb56f75df8eee419e4e6488e60db79dcaba8c153"; + const CANISTER_ID: &str = "00000000000000020101"; + const REQUEST_ID: &str = "ea37fdc5229d7273d500dc8ae3c009f0421049c1f02cc5ad85ea838ae7dfc045"; + const INGRESS_EXPIRY: u64 = 1731399240000000000; + const CERT_GENERIC_DISPLAY: &str = "d9d9f7a2647472656583018301820458207970ca0b7b0c0e63228c4cf47ce6f4a94268cfc004a99ae8aba5e97f204126a183018204582080b175729756e05010ceab7db6bed386cc6db61d274fe7d38a5f0bcea7ef317783024e726571756573745f7374617475738301830258204b77e3e74aa91e7bf50f8cf1acc0cb1dbafa4ad45c050e2abcc5d910317862a383018302457265706c79820359032d4449444c0c6b02bc8a0101c5fed201096c02efcee7800402e29fdcc806046c02aeaeb1cc0503d880c6d007716e766b02d9e5b0980405fcdfd79a0f716c01c4d6b4ea0b066d076c01ffbb87a807086d716b04d1c4987c0aa3f2efe6020b9a8597e6030be3c581900f0b6c02fc91f4f80571c498b1b50d7d6c01fc91f4f805710100000002656e01a4052320417574686f72697a6520616e6f74686572206164647265737320746f2077697468647261772066726f6d20796f7572206163636f756e740a0a2a2a54686520666f6c6c6f77696e67206164647265737320697320616c6c6f77656420746f2077697468647261772066726f6d20796f7572206163636f756e743a2a2a0a72646d78362d6a616161612d61616161612d61616164712d6361690a0a2a2a596f7572207375626163636f756e743a2a2a0a303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030300a0a2a2a526571756573746564207769746864726177616c20616c6c6f77616e63653a2a2a0a3130204943500ae29aa02054686520616c6c6f77616e63652077696c6c2062652073657420746f2031302049435020696e646570656e64656e746c79206f6620616e792070726576696f757320616c6c6f77616e63652e20556e74696c2074686973207472616e73616374696f6e20686173206265656e20657865637574656420746865207370656e6465722063616e207374696c6c206578657263697365207468652070726576696f757320616c6c6f77616e63652028696620616e792920746f20697427732066756c6c20616d6f756e742e0a0a2a2a45787069726174696f6e20646174653a2a2a0a4e6f2065787069726174696f6e2e0a0a2a2a417070726f76616c206665653a2a2a0a302e30303031204943500a0a2a2a5472616e73616374696f6e206665657320746f206265207061696420627920796f7572207375626163636f756e743a2a2a0a303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030308302467374617475738203477265706c696564820458203c45af0e5805729f1d07fbce20047198017873e787c5a27744ca20c71ad357b8830182045820d4fdd3b69be601a88a9412234c7940446d55dbc09bd3f28eb96d8548a2fe885283024474696d65820349889893d986e3d58218697369676e61747572655830b60f55093dc0835939589c2a3dcb5313248b13b5d965f3019fb9f5f3d34503443100b5103386a9d2df229fa82fe0440c"; + const ROOT_KEY: &str = + "b354faa40626ebc91ed7e55b2307feff70d119ef37f89915bd4561a1ed8c5c26c8c2cb8c4711eec681bf213a75cb988008fb1f4d7aa278cd4fad6f295c83bab04b8cabcb32640cf926083daf865551f9f3b76fd800dac027a583858b9d1d3f64"; + + use std::vec::Vec; + + fn extract_bls_from_der(der_bytes: &[u8]) -> Option> { + // The BLS public key in this DER format comes after: + // - SEQUENCE (0x30) + // - Length of entire sequence (0x81 0x82) + // - Inner SEQUENCE (0x30) + // - Inner sequence length (0x1d) + // - Two OID structures + // - BIT STRING marker (0x03) + // - BIT STRING length (0x61) + // - Padding bit (0x00) + + // We can look for the bit string marker and length + let mut i = 0; + while i < der_bytes.len() { + if der_bytes[i] == 0x03 && der_bytes[i + 1] == 0x61 && der_bytes[i + 2] == 0x00 { + // Found the start of our key data + // Skip the 0x03 0x61 0x00 markers and return the rest + return Some(der_bytes[i + 3..].to_vec()); + } + i += 1; + } + None + } #[test] fn parse_cert() { @@ -343,34 +381,25 @@ mod test_certificate { } #[test] - fn verify_cert() { + fn verify_certificate() { let data = hex::decode(REAL_CERT).unwrap(); let cert = Certificate::from_bytes(&data).unwrap(); + let cert_signature = hex::encode(cert.signature()); let root_hash = hex::encode(cert.hash().unwrap()); // Verify delegation.cert root_hash - let del_cert = cert.delegation.unwrap().cert(); - let del_cert_hash = hex::encode(del_cert.hash().unwrap()); + assert!(cert.delegation.is_none()); // compare our root hash with the hash icp library computes let icp_cert: IcpCertificate = serde_cbor::from_slice(&data).unwrap(); let icp_tree = icp_cert.tree; let icp_hash = icp_tree.digest(); let icp_hash = hex::encode(icp_hash); + let icp_signature = hex::encode(icp_cert.signature); assert_eq!(root_hash, icp_hash); - assert_eq!(root_hash, CERT_HASH); - - // compare our root hash with the hash icp library computes - let icp_cert: IcpCertificate = serde_cbor::from_slice(&data).unwrap(); - let delegation = icp_cert.delegation.unwrap(); - let del_cert: IcpCertificate = serde_cbor::from_slice(&delegation.certificate).unwrap(); - let icp_hash = hex::encode(del_cert.tree.digest()); - - assert_eq!(del_cert_hash, icp_hash); - assert_eq!(del_cert_hash, DEL_CERT_HASH); + assert_eq!(cert_signature, icp_signature); - // verify certificate signatures - let root_key = hex::decode(CANISTER_ROOT_KEY).unwrap(); + let root_key = hex::decode(ROOT_KEY).unwrap(); assert!(cert.verify(&root_key).unwrap()); // verify certificate expiry time @@ -381,7 +410,12 @@ mod test_certificate { fn check_canister_ranges() { let data = hex::decode(REAL_CERT).unwrap(); let cert = Certificate::from_bytes(&data).unwrap(); - let ranges = cert.canister_ranges().unwrap(); + + let Some(ranges) = cert.canister_ranges() else { + // No ranges in this data + return; + }; + let mut num_ranges = 0; for (i, r) in ranges.iter().enumerate() { std::println!("Range{}: {:?}", i, r); @@ -407,4 +441,14 @@ mod test_certificate { let found = HashTree::lookup_path(&request_id[..].into(), tree).unwrap(); assert!(found.raw_value().is_some()); } + + #[test] + fn error_generic_display() { + let data = hex::decode(CERT_GENERIC_DISPLAY).unwrap(); + let cert = Certificate::from_bytes(&data).unwrap(); + + // Check we parse the message(reply field) + let msg = cert.msg().unwrap(); + assert_eq!(msg.response_type(), ResponseType::Ok); + } } diff --git a/app/rust/src/parser/certificate/signature.rs b/app/rust/src/parser/certificate/signature.rs index f34e40d1..59696607 100644 --- a/app/rust/src/parser/certificate/signature.rs +++ b/app/rust/src/parser/certificate/signature.rs @@ -47,6 +47,8 @@ impl<'a> FromBytes<'a> for Signature<'a> { out: &mut core::mem::MaybeUninit, ) -> Result<&'a [u8], crate::error::ParserError> { zlog("Signature::from_bytes\x00"); + #[cfg(test)] + std::println!("Encoded Signature: {}", hex::encode(&input[..50])); let mut d = Decoder::new(input); let out = out.as_mut_ptr(); diff --git a/app/rust/src/parser/consent_message/msg.rs b/app/rust/src/parser/consent_message/msg.rs index 7afaf97e..005d96ce 100644 --- a/app/rust/src/parser/consent_message/msg.rs +++ b/app/rust/src/parser/consent_message/msg.rs @@ -15,22 +15,19 @@ ********************************************************************************/ use core::ptr::addr_of_mut; +use nom::bytes::complete::take; + use crate::{ + candid_header::CandidHeader, candid_utils::parse_text, constants::{MAX_CHARS_PER_LINE, MAX_LINES}, error::{ParserError, ViewError}, utils::{decompress_leb128, handle_ui_message}, - DisplayableItem, FromBytes, + DisplayableItem, FromCandidHeader, }; -// We got this after printing the type table -// using candid_utils::print_type_table function -const LINE_DISPLAY_MESSAGE_HASH: u64 = 1124872921; -const GENERIC_DISPLAY_MESSAGE_HASH: u64 = 4082495484; - #[repr(C)] struct GenericDisplayMessageVariant<'a>(MessageType, &'a str); - #[repr(C)] struct LineDisplayMessageVariant<'a>(MessageType, &'a [u8]); @@ -41,8 +38,6 @@ pub enum MessageType { LineDisplayMessage, } -#[derive(Debug)] -#[repr(C)] // ( // record { // arg = blob "\44\49\44\4c\00\01\71\04\74\6f\62\69"; @@ -58,16 +53,31 @@ pub enum MessageType { // }; // }, // ) +// or: +//device_spec: [ +// { +// LineDisplay: { +// characters_per_line: 35, +// lines_per_page: 3, +// }, +// }, +// ], +#[derive(Debug)] +#[repr(u8)] // Important: same representation as MessageType pub enum ConsentMessage<'a, const PAGES: usize, const LINES: usize> { GenericDisplayMessage(&'a str), - // Assuming max 4 pages with 5 lines each - // also lazy parsing, although we ensure - // we parse every page and line, lets store - // this as bytes LineDisplayMessage(&'a [u8]), } impl<'a, const PAGES: usize, const LINES: usize> ConsentMessage<'a, PAGES, LINES> { + // Hashes de los campos + const LINE_DISPLAY_MESSAGE_HASH: u32 = 1124872921; + const GENERIC_DISPLAY_MESSAGE_HASH: u32 = 4082495484; + const PAGES_FIELD_HASH: u32 = 3175951172; // hash del campo pages en el record + const LINES_FIELD_HASH: u32 = 1963056639; // hash del campo lines en el record de page + // We got this after printing the type table + // using candid_utils::print_type_table function + // TODO: Check that this holds true // the idea snprintf(buffer, "%s\n%s\n", line1, line2) // but in bytes plus null terminator @@ -78,71 +88,39 @@ impl<'a, const PAGES: usize, const LINES: usize> ConsentMessage<'a, PAGES, LINES fn format_page_content( &self, - page: &Page<'_, LINES>, + page: &ScreenPage<'_, LINES>, output: &mut [u8], ) -> Result { crate::zlog("ConsentMessage::format_page_content\x00"); - let mut output_idx = 0; let mut current_line = 0; - let mut is_new_line = true; - for line in page.lines.iter().take(page.num_lines) { - let input = line.as_bytes(); - let mut char_idx = 0; - - while char_idx < input.len() && current_line < MAX_LINES { - let remaining_space = MAX_CHARS_PER_LINE.min(output.len() - output_idx); - if remaining_space == 0 { - break; - } - - // Add a space if this is a new line and we're not at the start of the output - if !is_new_line && output_idx > 0 && output_idx < output.len() - 1 { - output[output_idx] = b' '; - output_idx += 1; - } - - let copy_len = remaining_space.min(input.len() - char_idx); - output[output_idx..output_idx + copy_len] - .copy_from_slice(&input[char_idx..char_idx + copy_len]); - - char_idx += copy_len; - output_idx += copy_len; + // Process each segment (which is already pre-formatted to screen width) + for segment in page.segments.iter().take(page.num_segments) { + if current_line >= MAX_LINES { + break; + } - // Add hyphen if word is split and we're not at the end of the line - if char_idx < input.len() - && output_idx < output.len() - 1 - && output_idx % MAX_CHARS_PER_LINE != 0 - { - output[output_idx] = b'-'; - output_idx += 1; - } + let input = segment.as_bytes(); + if input.is_empty() { + continue; + } - // Move to next line if we've filled the current line - if output_idx % MAX_CHARS_PER_LINE == 0 { - if current_line < MAX_LINES - 1 && output_idx < output.len() - 1 { - output[output_idx] = b'\n'; - output_idx += 1; - current_line += 1; - is_new_line = true; - } else { - break; - } - } else { - is_new_line = false; - } + // Copy the pre-formatted segment + let remaining_space = MAX_CHARS_PER_LINE.min(output.len() - output_idx); + if remaining_space == 0 { + break; } - // Move to next line after processing each input line if there's space - if current_line < MAX_LINES - 1 - && output_idx < output.len() - 1 - && char_idx >= input.len() - { + let copy_len = remaining_space.min(input.len()); + output[output_idx..output_idx + copy_len].copy_from_slice(&input[..copy_len]); + output_idx += copy_len; + + // Add newline if we're not at the last line + if current_line < MAX_LINES - 1 && output_idx < output.len() - 1 { output[output_idx] = b'\n'; output_idx += 1; current_line += 1; - is_new_line = true; } } @@ -191,70 +169,172 @@ struct LineDisplayIterator<'b, const L: usize> { current: &'b [u8], page_idx: usize, page_count: u64, + screen_width: usize, + current_line_offset: usize, + current_line_in_page: usize, + current_line_count: usize, + current_line: &'b str, // Store current line being processed +} + +#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] +pub struct ScreenPage<'b, const L: usize> { + segments: [&'b str; L], + num_segments: usize, } impl<'b, const L: usize> LineDisplayIterator<'b, L> { - fn new(data: &'b [u8]) -> Self { - // get page count - // Safe to unwrap, when this is invoked ConsentMessage was fully parsed + fn new(data: &'b [u8], screen_width: usize) -> Self { let (rem, page_count) = decompress_leb128(data).unwrap(); - Self { current: rem, page_idx: 0, page_count, + screen_width, + current_line_offset: 0, + current_line_in_page: 0, + current_line_count: 0, + current_line: "", + } + } + + fn get_line_segment<'a>(&self, line: &'a str) -> Option<&'a str> { + let start = self.current_line_offset * self.screen_width; + if start >= line.len() { + return None; } + let end = (start + self.screen_width).min(line.len()); + Some(&line[start..end]) } } impl<'b, const L: usize> Iterator for LineDisplayIterator<'b, L> { - type Item = Page<'b, L>; + type Item = ScreenPage<'b, L>; + fn next(&mut self) -> Option { if self.page_idx >= self.page_count as usize { return None; } - let mut lines = [""; L]; + let mut screen_segments = [""; L]; + let mut segment_count = 0; - // item_n indicates the page number we want to show - // but we need to parse everything before reaching the - // requested page. - let (mut rem, line_count) = decompress_leb128(self.current).ok()?; - - // just double check - if line_count > L as u64 { - return None; + // If we're starting a new page, read its line count + if self.current_line_in_page == 0 { + let (rem, line_count) = decompress_leb128(self.current).ok()?; + self.current = rem; + self.current_line_count = line_count as usize; + if line_count > L as u64 { + return None; + } } - for l in lines.iter_mut().take(line_count as usize) { - let (new_rem, line) = parse_text(rem).ok()?; - // Copy page data(two lines) into our buffer - // only if we are at the requested page, otherwise - // just pass through the data - *l = line; - rem = new_rem; - } + // Process lines in the current page + while self.current_line_in_page < self.current_line_count { + // Parse the line text if we're at offset 0 + if self.current_line_offset == 0 { + let (new_rem, line) = parse_text(self.current).ok()?; + self.current = new_rem; + self.current_line = line; + } + + // Get the current segment of this line + if let Some(segment) = self.get_line_segment(self.current_line) { + screen_segments[segment_count] = segment; + segment_count += 1; + + let line_finished = + (self.current_line_offset + 1) * self.screen_width >= self.current_line.len(); + + if segment_count >= L || line_finished { + // If line is finished, move to next line + if line_finished { + self.current_line_offset = 0; + self.current_line_in_page += 1; + } else { + // Otherwise continue with next segment of current line + self.current_line_offset += 1; + } - self.current = rem; - self.page_idx += 1; + // Return screen if full + if segment_count >= L { + // Check if we need to move to next page + if self.current_line_in_page >= self.current_line_count + && self.current_line_offset == 0 + { + self.page_idx += 1; + self.current_line_in_page = 0; + } + return Some(ScreenPage { + segments: screen_segments, + num_segments: segment_count, + }); + } + } else { + // Move to next segment of current line + self.current_line_offset += 1; + } + } + } - Some(Page { - lines, - num_lines: line_count as usize, - }) + // Return any remaining segments if we finished the page + if segment_count > 0 { + self.page_idx += 1; + self.current_line_in_page = 0; + self.current_line_offset = 0; + Some(ScreenPage { + segments: screen_segments, + num_segments: segment_count, + }) + } else { + None + } } } impl<'a, const PAGES: usize, const LINES: usize> ConsentMessage<'a, PAGES, LINES> { - /// Creates an iterator over the pages in - /// the message - pub fn pages_iter(&self) -> Option>> { + pub fn pages_iter( + &self, + screen_width: usize, + ) -> Option>> { + crate::zlog("ConsentMessage::pages_iter\x00"); if let ConsentMessage::LineDisplayMessage(data) = self { - Some(LineDisplayIterator::new(data)) + Some(LineDisplayIterator::new(data, screen_width)) } else { None } } + + #[inline(never)] + fn render_item( + &self, + item_n: u8, + title: &mut [u8], + message: &mut [u8], + page: u8, + ) -> Result { + // Title handling remains the same + let title_bytes = b"ConsentMsg"; + let title_len = title_bytes.len().min(title.len() - 1); + title[..title_len].copy_from_slice(&title_bytes[..title_len]); + title[title_len] = 0; + + match self { + ConsentMessage::GenericDisplayMessage(content) => { + let msg = content.as_bytes(); + handle_ui_message(msg, message, page) + } + ConsentMessage::LineDisplayMessage(bytes) => { + // Use screen width of 35 characters + let mut pages = LineDisplayIterator::new(bytes, 35); + let current_page = pages.nth(item_n as usize).ok_or(ViewError::NoData)?; + let mut output = Self::render_buffer(); + + // Format the screen page content + self.format_page_content(¤t_page, &mut output)?; + handle_ui_message(&output, message, page) + } + } + } } impl TryFrom for MessageType { @@ -270,75 +350,104 @@ impl TryFrom for MessageType { } } -impl<'a, const PAGES: usize, const LINES: usize> FromBytes<'a> +impl<'a, const PAGES: usize, const LINES: usize> FromCandidHeader<'a> for ConsentMessage<'a, PAGES, LINES> { - fn from_bytes_into( + fn from_candid_header( input: &'a [u8], out: &mut core::mem::MaybeUninit, + header: &CandidHeader, ) -> Result<&'a [u8], ParserError> { - let (rem, variant) = decompress_leb128(input).map_err(|_| ParserError::UnexpectedError)?; + crate::zlog("ConsentMessage::from_candid_header\x00"); - let message_type = MessageType::try_from(variant)?; + // Read variant index + let (rem, variant_index) = decompress_leb128(input)?; - match message_type { - MessageType::GenericDisplayMessage => { - let out = out.as_mut_ptr() as *mut GenericDisplayMessageVariant; - let (rem, message) = parse_text(rem)?; - unsafe { - addr_of_mut!((*out).0).write(message_type); - addr_of_mut!((*out).1).write(message); - } - Ok(rem) - } - MessageType::LineDisplayMessage => { + // Get type info from table + let type_entry = header + .type_table + .find_type_entry(4) + .ok_or(ParserError::UnexpectedType)?; + + if variant_index >= type_entry.field_count as u64 { + return Err(ParserError::UnexpectedType); + } + + // Get field hash and verify + let (field_hash, _) = type_entry.fields[variant_index as usize]; + + match field_hash { + hash if hash == Self::LINE_DISPLAY_MESSAGE_HASH => { + // Start of the content message + // pointing to the page count let start = rem; - let out = out.as_mut_ptr() as *mut LineDisplayMessageVariant; - unsafe { - addr_of_mut!((*out).0).write(message_type); - } - // get page count - let (mut rem, page_count) = decompress_leb128(rem)?; - // we do not probably need to limit number of pages + // Get record type entry (type 5) + let record_entry = header + .type_table + .find_type_entry(5) + .ok_or(ParserError::UnexpectedType)?; + + // Verify pages field hash + let _ = record_entry + .fields + .iter() + .find(|(hash, _)| *hash == Self::PAGES_FIELD_HASH) + .ok_or(ParserError::UnexpectedType)?; + + // Vector of pages + let (rem, page_count) = decompress_leb128(rem)?; + if page_count as usize > PAGES { return Err(ParserError::ValueOutOfRange); } - // now iterate over each page to parse the line they contain - // ensure data integrity at this level at parsing, so we do not - // have to worried about in the UI part - for _ in 0..page_count as usize { - let (new_rem, lines_count) = decompress_leb128(rem)?; - // update our slice pointer - rem = new_rem; - - if lines_count as usize > LINES { + // Debug: intenta leer la primera página + let mut raw_line = rem; + for _page in 0..page_count { + let (rem, line_count) = decompress_leb128(raw_line)?; + if line_count as usize > LINES { return Err(ParserError::ValueOutOfRange); } - for _ in 0..lines_count as usize { - let (new_rem, _) = parse_text(rem)?; - // update our slice pointer - rem = new_rem; + #[cfg(test)] + std::println!("Page: {_page} line count: {line_count}"); + let mut current = rem; + for _i in 0..line_count { + if let Ok((new_rem, _text)) = parse_text(current) { + #[cfg(test)] + std::println!("Line {}: {:?}", _i, _text); + current = new_rem; + } } + raw_line = current; } + // Store raw bytes for lazy parsing + let data_len = start.len() - raw_line.len(); + let (rem, data) = take(data_len)(start)?; + #[cfg(test)] + std::println!("LineDisplayContent****\n {}", hex::encode(data)); - let read = rem.as_ptr() as usize - start.as_ptr() as usize; - if read > start.len() { - return Err(ParserError::UnexpectedBufferEnd); - } - - // Copy Line variant data, removing the other boilerplate for parsing message - // response - let data = &start[0..read]; - + let out = out.as_mut_ptr() as *mut LineDisplayMessageVariant; unsafe { + // Write the variant tag first + addr_of_mut!((*out).0).write(MessageType::LineDisplayMessage); + // Then write the data addr_of_mut!((*out).1).write(data); } Ok(rem) } + hash if hash == Self::GENERIC_DISPLAY_MESSAGE_HASH => { + let out = out.as_mut_ptr() as *mut GenericDisplayMessageVariant; + let (rem, text) = parse_text(rem)?; + unsafe { + addr_of_mut!((*out).0).write(MessageType::GenericDisplayMessage); + addr_of_mut!((*out).1).write(text); + } + Ok(rem) + } + _ => Err(ParserError::UnexpectedType), } } } @@ -348,11 +457,20 @@ impl<'a, const PAGES: usize, const LINES: usize> DisplayableItem { #[inline(never)] fn num_items(&self) -> Result { + crate::zlog("ConsentMessage::num_items\x00"); match self { ConsentMessage::GenericDisplayMessage(_) => Ok(1), - ConsentMessage::LineDisplayMessage(bytes) => { - let (_, page_count) = decompress_leb128(bytes).map_err(|_| ViewError::NoData)?; - Ok(page_count as u8) + ConsentMessage::LineDisplayMessage(_) => { + // Get an iterator using our standard screen width + if let Some(mut pages) = self.pages_iter(MAX_CHARS_PER_LINE) { + let mut total_screens = 0u8; + while pages.next().is_some() { + total_screens = total_screens.saturating_add(1); + } + Ok(total_screens) + } else { + Err(ViewError::NoData) + } } } } @@ -376,14 +494,15 @@ impl<'a, const PAGES: usize, const LINES: usize> DisplayableItem let msg = content.as_bytes(); handle_ui_message(msg, message, page) } - ConsentMessage::LineDisplayMessage(bytes) => { - let mut pages: LineDisplayIterator<'_, LINES> = LineDisplayIterator::new(bytes); - let current_page = pages.nth(item_n as usize).ok_or(ViewError::NoData)?; - + ConsentMessage::LineDisplayMessage(_) => { + // Use the existing pages_iter method with our standard screen width + let mut pages = self + .pages_iter(MAX_CHARS_PER_LINE) + .ok_or(ViewError::NoData)?; + let current_screen = pages.nth(item_n as usize).ok_or(ViewError::NoData)?; let mut output = Self::render_buffer(); - // Use a Rust version of the C function to format the message - self.format_page_content(¤t_page, &mut output)?; + self.format_page_content(¤t_screen, &mut output)?; handle_ui_message(&output, message, page) } } @@ -391,32 +510,168 @@ impl<'a, const PAGES: usize, const LINES: usize> DisplayableItem } #[cfg(test)] -mod test_consent_message { +mod tests_msg_display { use super::*; - - // Data taken from the provided certificate - const LINE_DISPLAY_MSG: &[u8] = &[ - 1, 2, 30, 80, 114, 111, 100, 117, 99, 101, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, - 119, 105, 110, 103, 32, 103, 114, 101, 101, 116, 105, 110, 103, 20, 116, 101, 120, 116, 58, - 32, 34, 72, 101, 108, 108, 111, 44, 32, 116, 111, 98, 105, 33, 34, + use std::string::String; + + const SCREEN_WIDTH: usize = 35; + const SMALL_SCREEN_WIDTH: usize = 15; + const LINES: usize = 3; // Each page has exactly 3 lines + const MSG_DATA: &str = "07031e2320417574686f72697a6520616e6f74686572206164647265737320746f2077697468647261772066726f6d20796f7572206163636f756e74202a2a5468651f666f6c6c6f77696e67206164647265737320697320616c6c6f77656420746f031d77697468647261772066726f6d20796f7572206163636f756e743a2a2a2272646d78362d6a616161612d61616161612d61616164712d636169202a2a596f75720d7375626163636f756e743a2a2a032330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030232a2a526571756573746564207769746864726177616c20616c6c6f77616e63653a2a2a032031302049435020e29aa02054686520616c6c6f77616e63652077696c6c2062652273657420746f2031302049435020696e646570656e64656e746c79206f6620616e791e70726576696f757320616c6c6f77616e63652e20556e74696c207468697303217472616e73616374696f6e20686173206265656e206578656375746564207468651e7370656e6465722063616e207374696c6c206578657263697365207468652370726576696f757320616c6c6f77616e63652028696620616e792920746f2069742773032166756c6c20616d6f756e742e202a2a45787069726174696f6e20646174653a2a2a204e6f2065787069726174696f6e2e202a2a417070726f76616c206665653a2a2a23302e3030303120494350202a2a5472616e73616374696f6e206665657320746f206265031a7061696420627920796f7572207375626163636f756e743a2a2a2330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030"; + // Expected content per page + const EXPECTED_PAGES: &[&[&str]] = &[ + &[ + "# Authorize another address to", + "withdraw from your account **The", + "following address is allowed to", + ], + &[ + "withdraw from your account:**", + "rdmx6-jaaaa-aaaaa-aaadq-cai **Your", + "subaccount:**", + ], + &[ + "00000000000000000000000000000000000", + "00000000000000000000000000000", + "**Requested withdrawal allowance:**", + ], + &[ + "10 ICP ⚠ The allowance will be", + "set to 10 ICP independently of any", + "previous allowance. Until this", + ], + &[ + "transaction has been executed the", + "spender can still exercise the", + "previous allowance (if any) to it's", + ], + &[ + "full amount. **Expiration date:**", + "No expiration. **Approval fee:**", + "0.0001 ICP **Transaction fees to be", + ], + &[ + "paid by your subaccount:**", + "00000000000000000000000000000000000", + "00000000000000000000000000000", + ], ]; - const NUM_PAGES: u64 = 1; - const PAGES: [&str; 2] = ["Produce the following greeting", "text: \"Hello, tobi!\""]; + #[test] + fn test_line_display_iterator() { + let msg_bytes = hex::decode(MSG_DATA).unwrap(); + + // Create iterator + let mut iterator = LineDisplayIterator::::new(&msg_bytes, SCREEN_WIDTH); + + // Track which page we're on + let mut page_idx = 0; + + // Test each page + while let Some(screen_page) = iterator.next() { + assert!(page_idx < EXPECTED_PAGES.len(), "Too many pages produced"); + + // Verify number of segments matches expected + assert_eq!( + screen_page.num_segments, + EXPECTED_PAGES[page_idx].len(), + "Wrong number of segments on page {}", + page_idx + ); + + // Verify each line matches expected + for (i, segment) in screen_page + .segments + .iter() + .take(screen_page.num_segments) + .enumerate() + { + assert_eq!( + *segment, EXPECTED_PAGES[page_idx][i], + "Mismatch on page {}, line {}", + page_idx, i + ); + + // Verify line length is within screen width + assert!( + segment.len() <= SCREEN_WIDTH, + "Line exceeds screen width on page {}, line {}", + page_idx, + i + ); + } + + page_idx += 1; + } + + // Verify we got all expected pages + assert_eq!( + page_idx, + EXPECTED_PAGES.len(), + "Wrong number of pages produced" + ); + } #[test] - fn test_iterator() { - // use a dummy number of lines(4) per page - let mut iter: LineDisplayIterator<'_, 4> = LineDisplayIterator::new(LINE_DISPLAY_MSG); - let page = iter.next().unwrap(); - let different = page - .lines() - .iter() - .zip(PAGES.iter()) - .any(|(pl, cl)| pl != cl); - - assert!(!different); - - assert!(iter.next().is_none()); + fn test_line_segments_within_width() { + let msg_bytes = hex::decode(MSG_DATA).unwrap(); + let mut iterator = LineDisplayIterator::::new(&msg_bytes, SMALL_SCREEN_WIDTH); + let mut page_idx = 0; + + let mut current_page = 0; + let mut current_line = 0; + let mut accumulated = String::new(); + + while let Some(screen_page) = iterator.next() { + std::println!( + "Page {} with {} segments:", + page_idx, + screen_page.num_segments + ); + + for segment in screen_page.segments.iter().take(screen_page.num_segments) { + std::println!(" Segment: '{}' (len: {})", segment, segment.len()); + + // Core requirements check + assert!( + segment.len() <= SMALL_SCREEN_WIDTH, + "Found segment exceeding screen width: '{}' (length: {})", + segment, + segment.len() + ); + + assert!( + screen_page.num_segments <= LINES, + "Too many segments on page {}: {}", + page_idx, + screen_page.num_segments + ); + + // Optional: Verify segments reconstruct original content + accumulated.push_str(segment); + + // If this was the last segment of a line, verify it matches original + if accumulated.len() >= EXPECTED_PAGES[current_page][current_line].len() { + assert!( + EXPECTED_PAGES[current_page][current_line].contains(&accumulated), + "Reconstructed line doesn't match original.\nOriginal: '{}'\nReconstructed: '{}'", + EXPECTED_PAGES[current_page][current_line], + accumulated + ); + + accumulated.clear(); + current_line += 1; + if current_line >= EXPECTED_PAGES[current_page].len() { + current_page += 1; + current_line = 0; + } + } + } + + page_idx += 1; + } + + // Verify all content was processed + assert!(current_page > 0, "No pages were processed"); } } diff --git a/app/rust/src/parser/consent_message/msg_error.rs b/app/rust/src/parser/consent_message/msg_error.rs index c3632d3e..d110621b 100644 --- a/app/rust/src/parser/consent_message/msg_error.rs +++ b/app/rust/src/parser/consent_message/msg_error.rs @@ -1,4 +1,3 @@ -use crate::candid_utils::parse_text; /******************************************************************************* * (c) 2018 - 2024 Zondax AG * @@ -14,9 +13,13 @@ use crate::candid_utils::parse_text; * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ +use crate::candid_header::CandidHeader; +use crate::candid_types::IDLTypes; +use crate::candid_utils::parse_text; use crate::error::{ParserError, ViewError}; +use crate::type_table::FieldType; use crate::utils::{decompress_leb128, handle_ui_message}; -use crate::{DisplayableItem, FromBytes}; +use crate::{DisplayableItem, FromBytes, FromCandidHeader}; use core::ptr::addr_of_mut; #[derive(Clone, Copy, PartialEq, Eq)] @@ -47,6 +50,42 @@ pub struct ErrorInfo<'a> { pub description: &'a str, } +impl<'a> ErrorInfo<'a> { + pub const DESCRIPTION: u32 = 1595738364; // hash of "description" +} + +impl<'a> FromCandidHeader<'a> for ErrorInfo<'a> { + fn from_candid_header( + input: &'a [u8], + out: &mut core::mem::MaybeUninit, + header: &CandidHeader, + ) -> Result<&'a [u8], ParserError> { + // Get the type entry for ErrorInfo (type 11 based on your table) + let type_entry = header + .type_table + .find_type_entry(11) + .ok_or(ParserError::UnexpectedType)?; + + // Verify it's a record with one field + if type_entry.field_count != 1 { + return Err(ParserError::UnexpectedType); + } + + // Verify the field is description and it's a text type + let description_field = type_entry.find_field_type(Self::DESCRIPTION)?; + if !matches!(description_field, FieldType::Primitive(IDLTypes::Text)) { + return Err(ParserError::UnexpectedType); + } + + // Parse the text as before + let (rem, description) = parse_text(input)?; + unsafe { + addr_of_mut!((*out.as_mut_ptr()).description).write(description); + } + Ok(rem) + } +} + impl<'a> FromBytes<'a> for ErrorInfo<'a> { fn from_bytes_into( input: &'a [u8], @@ -111,6 +150,114 @@ pub enum Error<'a> { }, } +impl<'a> Error<'a> { + pub const UNSUPPORTED_CANISTER_CALL: u32 = 260448849; + pub const CONSENT_MESSAGE_UNAVAILABLE: u32 = 752613667; + pub const INSUFFICIENT_PAYMENT: u32 = 1019593370; + pub const GENERIC_ERROR: u32 = 4060111587; + + // For GenericError fields + pub const ERROR_CODE: u32 = 1595738364; // hash of "error_code" + pub const DESCRIPTION: u32 = 3601615940; // hash of "description" +} + +impl<'a> FromCandidHeader<'a> for Error<'a> { + fn from_candid_header( + input: &'a [u8], + out: &mut core::mem::MaybeUninit, + header: &CandidHeader, + ) -> Result<&'a [u8], ParserError> { + // Get the variant index + let (rem, variant_index) = + decompress_leb128(input).map_err(|_| ParserError::UnexpectedError)?; + + // Get the type entry for Error (type 9 based on your table) + let type_entry = header + .type_table + .find_type_entry(9) + .ok_or(ParserError::UnexpectedType)?; + + // Find the matching variant based on the type table + match variant_index { + idx if idx + == type_entry + .find_field_type(Self::UNSUPPORTED_CANISTER_CALL)? + .as_index() + .unwrap() as u64 => + { + let mut error_info = core::mem::MaybeUninit::uninit(); + let rem = ErrorInfo::from_candid_header(rem, &mut error_info, header)?; + let out = out.as_mut_ptr() as *mut UnsupportedCanisterCallVariant; + unsafe { + addr_of_mut!((*out).0).write(ErrorType::UnsupportedCanisterCall); + addr_of_mut!((*out).1).write(error_info.assume_init()); + } + Ok(rem) + } + idx if idx + == type_entry + .find_field_type(Self::CONSENT_MESSAGE_UNAVAILABLE)? + .as_index() + .unwrap() as u64 => + { + let mut error_info = core::mem::MaybeUninit::uninit(); + let rem = ErrorInfo::from_candid_header(rem, &mut error_info, header)?; + let out = out.as_mut_ptr() as *mut ConsentMessageUnavailableVariant; + unsafe { + addr_of_mut!((*out).0).write(ErrorType::ConsentMessageUnavailable); + addr_of_mut!((*out).1).write(error_info.assume_init()); + } + Ok(rem) + } + idx if idx + == type_entry + .find_field_type(Self::INSUFFICIENT_PAYMENT)? + .as_index() + .unwrap() as u64 => + { + let mut error_info = core::mem::MaybeUninit::uninit(); + let rem = ErrorInfo::from_candid_header(rem, &mut error_info, header)?; + let out = out.as_mut_ptr() as *mut InsufficientPaymentVariant; + unsafe { + addr_of_mut!((*out).0).write(ErrorType::InsufficientPayment); + addr_of_mut!((*out).1).write(error_info.assume_init()); + } + Ok(rem) + } + idx if idx + == type_entry + .find_field_type(Self::GENERIC_ERROR)? + .as_index() + .unwrap() as u64 => + { + // For GenericError, we need to verify the field order from the type table + let _generic_type_entry = header + .type_table + .find_type_entry( + type_entry + .find_field_type(Self::GENERIC_ERROR)? + .as_index() + .unwrap(), + ) + .ok_or(ParserError::UnexpectedType)?; + + let (rem, error_code) = + decompress_leb128(rem).map_err(|_| ParserError::UnexpectedError)?; + let (rem, description) = parse_text(rem)?; + + let out = out.as_mut_ptr() as *mut GenericErrorVariant; + unsafe { + addr_of_mut!((*out).0).write(ErrorType::GenericError); + addr_of_mut!((*out).1).write(error_code as u32); + addr_of_mut!((*out).2).write(description); + } + Ok(rem) + } + _ => Err(ParserError::UnexpectedType), + } + } +} + impl<'a> FromBytes<'a> for Error<'a> { fn from_bytes_into( input: &'a [u8], diff --git a/app/rust/src/parser/consent_message/msg_info.rs b/app/rust/src/parser/consent_message/msg_info.rs index 94267e3e..80ef7836 100644 --- a/app/rust/src/parser/consent_message/msg_info.rs +++ b/app/rust/src/parser/consent_message/msg_info.rs @@ -14,9 +14,11 @@ * limitations under the License. ********************************************************************************/ use crate::{ + candid_header::CandidHeader, constants::{MAX_LINES, MAX_PAGES}, error::{ParserError, ViewError}, - DisplayableItem, FromBytes, + type_table::FieldType, + DisplayableItem, FromCandidHeader, }; use core::{mem::MaybeUninit, ptr::addr_of_mut}; @@ -29,20 +31,46 @@ pub struct ConsentInfo<'a> { pub metadata: ConsentMessageMetadata<'a>, } -impl<'a> FromBytes<'a> for ConsentInfo<'a> { - fn from_bytes_into( +impl<'a> ConsentInfo<'a> { + pub const METADATA: u32 = 1075439471; + pub const MESSAGE: u32 = 1763119074; +} + +impl<'a> FromCandidHeader<'a> for ConsentInfo<'a> { + fn from_candid_header( input: &'a [u8], out: &mut core::mem::MaybeUninit, + header: &CandidHeader, ) -> Result<&'a [u8], ParserError> { + crate::zlog("ConsentInfo::from_table_into\n"); + let out = out.as_mut_ptr(); - // Field with hash 1075439471 points to type 2 the metadata + + // Get the type entry for ConsentInfo (type 1) + let type_entry = header + .type_table + .find_type_entry(1) + .ok_or(ParserError::UnexpectedType)?; + + // We know METADATA has lower hash than MESSAGE, so it comes first in memory + // No need for sorting or vectors, just check the order is correct in the type table + let metadata_idx = type_entry.find_field_type(Self::METADATA)?; + let message_idx = type_entry.find_field_type(Self::MESSAGE)?; + + // Verify we have both fields + if !matches!(metadata_idx, FieldType::Compound(_)) + || !matches!(message_idx, FieldType::Compound(_)) + { + return Err(ParserError::UnexpectedType); + } + + // Parse in memory order (metadata first, then message) let metadata = unsafe { &mut *addr_of_mut!((*out).metadata).cast() }; - let rem = ConsentMessageMetadata::from_bytes_into(input, metadata)?; + let mut rem = ConsentMessageMetadata::from_candid_header(input, metadata, header)?; - // Field with hash 1763119074 points to type 3 which is the consent messagees let message: &mut MaybeUninit> = unsafe { &mut *addr_of_mut!((*out).message).cast() }; - let rem = ConsentMessage::from_bytes_into(rem, message)?; + rem = ConsentMessage::from_candid_header(rem, message, header)?; Ok(rem) } @@ -51,6 +79,7 @@ impl<'a> FromBytes<'a> for ConsentInfo<'a> { impl<'a> DisplayableItem for ConsentInfo<'a> { #[inline(never)] fn num_items(&self) -> Result { + crate::zlog("ConsentInfo::num_items\x00"); self.message.num_items() } diff --git a/app/rust/src/parser/consent_message/msg_metadata.rs b/app/rust/src/parser/consent_message/msg_metadata.rs index f2d4c305..aac3f9b0 100644 --- a/app/rust/src/parser/consent_message/msg_metadata.rs +++ b/app/rust/src/parser/consent_message/msg_metadata.rs @@ -16,10 +16,10 @@ use core::ptr::addr_of_mut; use crate::{ + candid_header::CandidHeader, candid_utils::{parse_opt_i16, parse_text}, error::ParserError, - utils::decompress_leb128, - FromBytes, + FromCandidHeader, }; #[repr(C)] @@ -30,27 +30,37 @@ pub struct ConsentMessageMetadata<'a> { pub utc_offset: Option, } -impl<'a> FromBytes<'a> for ConsentMessageMetadata<'a> { - fn from_bytes_into( +impl<'a> ConsentMessageMetadata<'a> { + pub const LANGUAGE: u32 = 2047967320; // hash of "language" + pub const UTC_OFFSET: u32 = 1502369582; +} + +impl<'a> FromCandidHeader<'a> for ConsentMessageMetadata<'a> { + fn from_candid_header( input: &'a [u8], out: &mut core::mem::MaybeUninit, + _header: &CandidHeader, ) -> Result<&'a [u8], ParserError> { - let (rem, _) = decompress_leb128(input).map_err(|_| ParserError::UnexpectedError)?; + crate::zlog("ConsentMessageMetadata::from_table_info\n"); + + // Then parse fields in hash order + // UTC_OFFSET (1502369582) comes first + let (rem, utc_offset) = parse_opt_i16(input)?; - // read first the utc_offset, its hash is < than the language hash - let (rem, utc_offset) = parse_opt_i16(rem)?; + // Then LANGUAGE (2047967320) let (rem, language) = parse_text(rem)?; if language.is_empty() || language != "en" { return Err(ParserError::InvalidLanguage); } + // Write values to output let out = out.as_mut_ptr(); - unsafe { addr_of_mut!((*out).language).write(language); addr_of_mut!((*out).utc_offset).write(utc_offset); } + Ok(rem) } } diff --git a/app/rust/src/parser/consent_message/msg_response.rs b/app/rust/src/parser/consent_message/msg_response.rs index 5024d061..fb94303f 100644 --- a/app/rust/src/parser/consent_message/msg_response.rs +++ b/app/rust/src/parser/consent_message/msg_response.rs @@ -16,21 +16,18 @@ use core::ptr::addr_of_mut; use crate::{ + candid_header::parse_candid_header, + constants::{MAX_ARGS, MAX_TABLE_FIELDS}, error::{ParserError, ViewError}, - type_table::parse_type_table, utils::decompress_leb128, - DisplayableItem, FromBytes, + DisplayableItem, FromBytes, FromCandidHeader, }; use super::{msg_error::Error, msg_info::ConsentInfo}; -// Defines the minimum number of elements -// in our candid type table in order -// to parse the type using it -const MAX_TABLE_FIELDS: usize = 11; - #[derive(Clone, Copy, PartialEq, Eq)] #[repr(u8)] +#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] pub enum ResponseType { Ok, Err, @@ -61,6 +58,9 @@ pub enum ConsentMessageResponse<'a> { } impl<'a> ConsentMessageResponse<'a> { + pub const OK_HASH: u32 = 17724; // hash of "Ok" + pub const ERR_HASH: u32 = 3456837; // hash of Err + pub fn response_type(&self) -> ResponseType { match self { Self::Ok(_) => ResponseType::Ok, @@ -83,36 +83,43 @@ impl<'a> FromBytes<'a> for ConsentMessageResponse<'a> { ) -> Result<&'a [u8], ParserError> { crate::zlog("ConsentMessageResponse::from_bytes_into"); - // 1. Read the "DIDL" magic number - let (rem, _) = nom::bytes::complete::tag("DIDL")(input) - .map_err(|_: nom::Err| ParserError::UnexpectedError)?; + #[cfg(test)] + std::println!("*********MsgResponse*********:\n {}", hex::encode(input)); + + // Parse Candid header + let (rem, header) = parse_candid_header::(input)?; - // 2. Parse the type table - let (rem, _table) = parse_type_table::(rem)?; + // Read the variant index + let (rem, variant_index) = decompress_leb128(rem)?; + + // Get the root variant type (type 0) + let root_entry = header + .type_table + .find_type_entry(0) + .ok_or(ParserError::UnexpectedType)?; + + // Verificar que el índice es válido + if variant_index >= root_entry.field_count as u64 { + return Err(ParserError::UnexpectedType); + } - // 3. Read the variant index (M part) - let (rem, variant_index) = - decompress_leb128(rem).map_err(|_| ParserError::UnexpectedError)?; + // Obtener el hash del campo seleccionado + let (field_hash, _) = root_entry.fields[variant_index as usize]; - // after inspecting the type table - // we know that ok index is 1, and 8 for error - // 0: variant {17724: 1, 3456837: 8} - match variant_index { - 1 => { - // Ok variant + match field_hash { + Self::OK_HASH => { let out = out.as_mut_ptr() as *mut OkVariant; let data = unsafe { &mut *addr_of_mut!((*out).1).cast() }; - let rem = ConsentInfo::from_bytes_into(rem, data)?; + let rem = ConsentInfo::from_candid_header(rem, data, &header)?; unsafe { addr_of_mut!((*out).0).write(ResponseType::Ok); } Ok(rem) } - 8 => { - // Err variant + Self::ERR_HASH => { let out = out.as_mut_ptr() as *mut ErrVariant; let data = unsafe { &mut *addr_of_mut!((*out).1).cast() }; - let rem = Error::from_bytes_into(rem, data)?; + let rem = Error::from_candid_header(rem, data, &header)?; unsafe { addr_of_mut!((*out).0).write(ResponseType::Err); } @@ -151,26 +158,25 @@ impl<'a> DisplayableItem for ConsentMessageResponse<'a> { #[cfg(test)] mod msg_response_test { + use serde::{Deserialize, Serialize}; + use std::string::String; + use std::vec::Vec; use zemu_sys::Viewable; - use crate::parser::snapshots_common::ReducedPage; + use crate::{parser::snapshots_common::ReducedPage, test_ui::with_leaked}; use super::*; - const MSG_DATA: &[u8] = &[ - 68, 73, 68, 76, 11, 107, 2, 188, 138, 1, 1, 197, 254, 210, 1, 8, 108, 2, 239, 206, 231, - 128, 4, 2, 226, 159, 220, 200, 6, 3, 108, 1, 216, 128, 198, 208, 7, 113, 107, 2, 217, 229, - 176, 152, 4, 4, 252, 223, 215, 154, 15, 113, 108, 1, 196, 214, 180, 234, 11, 5, 109, 6, - 108, 1, 255, 187, 135, 168, 7, 7, 109, 113, 107, 4, 209, 196, 152, 124, 9, 163, 242, 239, - 230, 2, 10, 154, 133, 151, 230, 3, 10, 227, 197, 129, 144, 15, 10, 108, 2, 252, 145, 244, - 248, 5, 113, 196, 152, 177, 181, 13, 125, 108, 1, 252, 145, 244, 248, 5, 113, 1, 0, 0, 2, - 101, 110, 0, 1, 2, 30, 80, 114, 111, 100, 117, 99, 101, 32, 116, 104, 101, 32, 102, 111, - 108, 108, 111, 119, 105, 110, 103, 32, 103, 114, 101, 101, 116, 105, 110, 103, 20, 116, - 101, 120, 116, 58, 32, 34, 72, 101, 108, 108, 111, 44, 32, 116, 111, 98, 105, 33, 34, - ]; + const MSG_DATA: &str = "4449444c0c6b02bc8a0101c5fed201096c02efcee7800402e29fdcc806046c02aeaeb1cc0503d880c6d007716e766b02d9e5b0980405fcdfd79a0f716c01c4d6b4ea0b066d076c01ffbb87a807086d716b04d1c4987c0aa3f2efe6020b9a8597e6030be3c581900f0b6c02fc91f4f80571c498b1b50d7d6c01fc91f4f805710100000002656e0007031e2320417574686f72697a6520616e6f74686572206164647265737320746f2077697468647261772066726f6d20796f7572206163636f756e74202a2a5468651f666f6c6c6f77696e67206164647265737320697320616c6c6f77656420746f031d77697468647261772066726f6d20796f7572206163636f756e743a2a2a2272646d78362d6a616161612d61616161612d61616164712d636169202a2a596f75720d7375626163636f756e743a2a2a032330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030232a2a526571756573746564207769746864726177616c20616c6c6f77616e63653a2a2a032031302049435020e29aa02054686520616c6c6f77616e63652077696c6c2062652273657420746f2031302049435020696e646570656e64656e746c79206f6620616e791e70726576696f757320616c6c6f77616e63652e20556e74696c207468697303217472616e73616374696f6e20686173206265656e206578656375746564207468651e7370656e6465722063616e207374696c6c206578657263697365207468652370726576696f757320616c6c6f77616e63652028696620616e792920746f2069742773032166756c6c20616d6f756e742e202a2a45787069726174696f6e20646174653a2a2a204e6f2065787069726174696f6e2e202a2a417070726f76616c206665653a2a2a23302e3030303120494350202a2a5472616e73616374696f6e206665657320746f206265031a7061696420627920796f7572207375626163636f756e743a2a2a2330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030"; + + #[derive(Serialize, Deserialize, Debug)] + struct TransactionData { + response: String, + } + /// This is only to be used for testing, hence why /// it's present inside the `mod test` block only - impl Viewable for ConsentMessageResponse<'static> { + impl<'a> Viewable for ConsentMessageResponse<'a> { fn num_items(&mut self) -> Result { DisplayableItem::num_items(&*self).map_err(|_| zemu_sys::ViewError::Unknown) } @@ -197,23 +203,38 @@ mod msg_response_test { #[test] fn parse_msg_response() { - let resp = ConsentMessageResponse::from_bytes(MSG_DATA).unwrap(); - std::println!("{:?}", resp); + let data = hex::decode(MSG_DATA).unwrap(); + let _ = ConsentMessageResponse::from_bytes(&data[..]).unwrap(); } + #[cfg_attr(miri, ignore)] #[test] fn test_ui() { - let resp = ConsentMessageResponse::from_bytes(MSG_DATA).unwrap(); - let mut driver = zuit::MockDriver::<_, 18, 1024>::new(resp); - driver.drive(); + insta::glob!("testvectors/*.json", |path| { + let file = std::fs::File::open(path) + .unwrap_or_else(|e| panic!("Unable to open file {:?}: {:?}", path, e)); + let input: TransactionData = serde_json::from_reader(file) + .unwrap_or_else(|e| panic!("Unable to read file {:?} as json: {:?}", path, e)); + + let test = |data| { + let resp = ConsentMessageResponse::from_bytes(data).expect("Fail parsing"); + + let mut driver = zuit::MockDriver::<_, 18, 1024>::new(resp); + driver.drive(); + + let ui = driver.out_ui(); + + let reduced = ui + .iter() + .flat_map(|item| item.iter().map(ReducedPage::from)) + .collect::>(); - let ui = driver.out_ui(); + insta::assert_debug_snapshot!(reduced); + }; - let reduced = ui - .iter() - .flat_map(|item| item.iter().map(ReducedPage::from)) - .collect::>(); + let data = hex::decode(input.response).unwrap(); - std::println!("{:?}", reduced); + unsafe { with_leaked(data, test) }; + }); } } diff --git a/app/rust/src/parser/consent_message/snapshots/rslib__parser__consent_message__msg_response__msg_response_test__ui.snap b/app/rust/src/parser/consent_message/snapshots/rslib__parser__consent_message__msg_response__msg_response_test__ui.snap new file mode 100644 index 00000000..84dcef8d --- /dev/null +++ b/app/rust/src/parser/consent_message/snapshots/rslib__parser__consent_message__msg_response__msg_response_test__ui.snap @@ -0,0 +1,21 @@ +--- +source: src/parser/consent_message/msg_response.rs +expression: reduced +input_file: src/parser/consent_message/testvectors/ui_data.json +--- +[ + "ConsentMsg": "# Authorize another addre\nss to\nwithdraw from your accoun", + "ConsentMsg": "t **The\nfollowing address is allo\nwed to", + "ConsentMsg": "withdraw from your accoun\nt:**\nrdmx6-jaaaa-aaaaa-aaadq-c", + "ConsentMsg": "ai **Your\nsubaccount:**\n", + "ConsentMsg": "0000000000000000000000000\n0000000000\n0000000000000000000000000", + "ConsentMsg": "0000\n**Requested withdrawal al\nlowance:**", + "ConsentMsg": "10 ICP ⚠ The allowance \nwill be\nset to 10 ICP independent", + "ConsentMsg": "ly of any\nprevious allowance. Until\n this", + "ConsentMsg": "transaction has been exec\nuted the\nspender can still exercis", + "ConsentMsg": "e the\nprevious allowance (if an\ny) to it's", + "ConsentMsg": "full amount. **Expiration\n date:**\nNo expiration. **Approval", + "ConsentMsg": " fee:**\n0.0001 ICP **Transaction \nfees to be", + "ConsentMsg": "paid by your subaccount:*\n*\n0000000000000000000000000", + "ConsentMsg": "0000000000\n0000000000000000000000000\n0000", +] diff --git a/app/rust/src/parser/consent_message/testvectors/ui_data.json b/app/rust/src/parser/consent_message/testvectors/ui_data.json new file mode 100644 index 00000000..f4c14f86 --- /dev/null +++ b/app/rust/src/parser/consent_message/testvectors/ui_data.json @@ -0,0 +1,3 @@ +{ + "response": "4449444c0c6b02bc8a0101c5fed201096c02efcee7800402e29fdcc806046c02aeaeb1cc0503d880c6d007716e766b02d9e5b0980405fcdfd79a0f716c01c4d6b4ea0b066d076c01ffbb87a807086d716b04d1c4987c0aa3f2efe6020b9a8597e6030be3c581900f0b6c02fc91f4f80571c498b1b50d7d6c01fc91f4f805710100000002656e0007031e2320417574686f72697a6520616e6f74686572206164647265737320746f2077697468647261772066726f6d20796f7572206163636f756e74202a2a5468651f666f6c6c6f77696e67206164647265737320697320616c6c6f77656420746f031d77697468647261772066726f6d20796f7572206163636f756e743a2a2a2272646d78362d6a616161612d61616161612d61616164712d636169202a2a596f75720d7375626163636f756e743a2a2a032330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030232a2a526571756573746564207769746864726177616c20616c6c6f77616e63653a2a2a032031302049435020e29aa02054686520616c6c6f77616e63652077696c6c2062652273657420746f2031302049435020696e646570656e64656e746c79206f6620616e791e70726576696f757320616c6c6f77616e63652e20556e74696c207468697303217472616e73616374696f6e20686173206265656e206578656375746564207468651e7370656e6465722063616e207374696c6c206578657263697365207468652370726576696f757320616c6c6f77616e63652028696620616e792920746f2069742773032166756c6c20616d6f756e742e202a2a45787069726174696f6e20646174653a2a2a204e6f2065787069726174696f6e2e202a2a417070726f76616c206665653a2a2a23302e3030303120494350202a2a5472616e73616374696f6e206665657320746f206265031a7061696420627920796f7572207375626163636f756e743a2a2a2330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030" +} diff --git a/app/rust/src/parser/testvectors/ui_data.json b/app/rust/src/parser/testvectors/ui_data.json new file mode 100644 index 00000000..f4c14f86 --- /dev/null +++ b/app/rust/src/parser/testvectors/ui_data.json @@ -0,0 +1,3 @@ +{ + "response": "4449444c0c6b02bc8a0101c5fed201096c02efcee7800402e29fdcc806046c02aeaeb1cc0503d880c6d007716e766b02d9e5b0980405fcdfd79a0f716c01c4d6b4ea0b066d076c01ffbb87a807086d716b04d1c4987c0aa3f2efe6020b9a8597e6030be3c581900f0b6c02fc91f4f80571c498b1b50d7d6c01fc91f4f805710100000002656e0007031e2320417574686f72697a6520616e6f74686572206164647265737320746f2077697468647261772066726f6d20796f7572206163636f756e74202a2a5468651f666f6c6c6f77696e67206164647265737320697320616c6c6f77656420746f031d77697468647261772066726f6d20796f7572206163636f756e743a2a2a2272646d78362d6a616161612d61616161612d61616164712d636169202a2a596f75720d7375626163636f756e743a2a2a032330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030232a2a526571756573746564207769746864726177616c20616c6c6f77616e63653a2a2a032031302049435020e29aa02054686520616c6c6f77616e63652077696c6c2062652273657420746f2031302049435020696e646570656e64656e746c79206f6620616e791e70726576696f757320616c6c6f77616e63652e20556e74696c207468697303217472616e73616374696f6e20686173206265656e206578656375746564207468651e7370656e6465722063616e207374696c6c206578657263697365207468652370726576696f757320616c6c6f77616e63652028696620616e792920746f2069742773032166756c6c20616d6f756e742e202a2a45787069726174696f6e20646174653a2a2a204e6f2065787069726174696f6e2e202a2a417070726f76616c206665653a2a2a23302e3030303120494350202a2a5472616e73616374696f6e206665657320746f206265031a7061696420627920796f7572207375626163636f756e743a2a2a2330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030" +} diff --git a/app/rust/src/test_ui.rs b/app/rust/src/test_ui.rs new file mode 100644 index 00000000..475a4162 --- /dev/null +++ b/app/rust/src/test_ui.rs @@ -0,0 +1,286 @@ +#![cfg(test)] +use std::fmt::Debug; +use std::fmt::Display; +use std::println; +use std::vec::Vec; + +use std::boxed::Box; + +use std::vec; + +use crate::error::ParserError; + +pub trait Viewable: Sized { + /// Returns the number of items to display + fn num_items(&self) -> Result; + + /// This is invoked when a given page is to be displayed + /// + /// `item_n` is the item of the operation to display; + /// guarantee: 0 <= item_n < self.num_items() + /// `title` is the title of the item + /// `message` is the contents of the item + /// `page` is what page we are supposed to display, this is used to split big messages + /// + /// returns the total number of pages on success + /// + /// It's a good idea to always put `#[inline(never)]` on top of this + /// function's implementation + //#[inline(never)] + fn render_item( + &self, + item_n: u8, + title: &mut [u8], + message: &mut [u8], + page: u8, + ) -> Result; + + fn accept(&mut self, _: &mut [u8]) -> (usize, u16) { + (0, 0) + } + + fn reject(&mut self, _: &mut [u8]) -> (usize, u16) { + (0, 0) + } +} + +#[derive(Clone, PartialEq, Eq)] +pub struct Page { + pub title: [u8; T], + pub message: [u8; M], +} + +impl Default for Page { + fn default() -> Self { + Self { + title: [0; T], + message: [0; M], + } + } +} + +/// This struct will render each item and each page of an item of a given `viewable` +pub struct MockDriver { + viewable: Box, + print: bool, + + //[item][page] .title .message + out: Vec>>, +} + +impl MockDriver { + pub fn new(viewable: V) -> Self { + Self { + viewable: Box::new(viewable), + print: true, + out: Default::default(), + } + } + + pub fn out_ui(&self) -> &[Vec>] { + self.out.as_slice() + } + + pub fn with_print(&mut self, print: bool) { + self.print = print + } +} + +impl MockDriver { + /// This function allows `callback` to be invoked for each page of each item + /// that the inner `Viewable` has to offer + /// + /// It will also `drive` if there's no data to pass to the callback + /// + /// The callback is passed 4 arguments: the item id, the page number, the title and the message + pub fn verify_with(&mut self, mut callback: F) -> Result<(), Vec> + where + F: FnMut(usize, usize, &[u8; T], &[u8; M]) -> Result<(), E>, + { + if self.out.is_empty() { + self.drive(); + } + + let mut errors = vec![]; + for (i, item) in self.out.iter().enumerate() { + for (j, page) in item.iter().enumerate() { + if let Err(e) = callback(i, j, &page.title, &page.message) { + errors.push(e) + } + } + } + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } + + /// This function will go thru each page of each item and collect + /// all the outputs of the viewable + pub fn drive(&mut self) { + let num_items = self + .viewable + .num_items() + .expect("unable to retrieve num_items"); + + //render each item + for item_n in 0..num_items { + //create new containers for this item's pages + self.out.push(Vec::new()); + let pages = self.out.last_mut().unwrap(); + + let mut page_n = 0; + //set an initial max_page to 255 to make sure we get at least 1 page + let mut max_pages = 255; + + //render each page of an item + while page_n < max_pages { + let mut page = Page::default(); + + max_pages = self + .viewable + .render_item(item_n, &mut page.title[..], &mut page.message[..], page_n) + .unwrap_or_else(|e| { + panic!( + "Error when rendering item #{}, page #{}/#{}; err: {:?}", + item_n, page_n, max_pages, e + ) + }); + + if self.print { + let title = std::str::from_utf8(&page.title[..]).unwrap_or_else(|e| { + panic!( + "title was not UTF-8; item #{}, page #{}/#{}; err : {:?}", + item_n, page_n, max_pages, e + ) + }); + let message = std::str::from_utf8(&page.message[..]).unwrap_or_else(|e| { + panic!( + "message was not UTF-8; item #{}, page #{}/#{}; err : {:?}", + item_n, page_n, max_pages, e + ) + }); + + println!("{} | {} : {}", item_n, title, message) + } + + //store page + pages.push(page); + + //increase counter for next page + page_n += 1; + } + } + } + + pub fn accept(&mut self, out: &mut [u8]) -> (usize, u16) { + self.viewable.accept(out) + } + + pub fn reject(&mut self, out: &mut [u8]) -> (usize, u16) { + self.viewable.reject(out) + } +} + +/// Executes the provided closure passing in the provided data +/// as a &'static [T]. +/// +/// This is really only useful to construct a type as `'static` for the purpose +/// of satisfying a bound like the one in `Viewable` +/// +/// # Safety +/// `f` shouldn't store the data or rely on it being _actually_ available for the entire +/// duration of the program, but rather only have it valid for the call to the closure itself +pub unsafe fn with_leaked<'a, T: 'static, U: 'a>( + data: std::vec::Vec, + mut f: impl FnMut(&'static [T]) -> U, +) -> U { + //this way we also drop the excess capacity + let data = data.into_boxed_slice(); + + let ptr = Box::into_raw(data); + + //it's fine to unwrap here, the pointer is aligned + // and everything... + let r = f(ptr.as_ref().unwrap_unchecked()); + + //reclaim the box an drop it + // this is the "unsafe" part of the function + // because if `f` stored the data somewhere now it would be freed + // and this isn't good, but that's why we have #Safety + let _ = Box::from_raw(ptr); + + r +} + +/// This struct is useful to have more concise output a certain page +/// +/// By default, to construct this you'd use `from` and the implementation will +/// try to parse the title and message of the page as UTF8 to display those +/// +/// The Debug impl is based on Display and is of the format `"{title}": "{message}"` +pub struct ReducedPage<'b> { + title: &'b str, + message: &'b str, +} + +impl<'b, const T: usize, const M: usize> From<&'b Page> for ReducedPage<'b> { + fn from(page: &'b Page) -> Self { + let tlen = strlen(&page.title); + let title = std::str::from_utf8(&page.title[..tlen]).expect("title was not valid utf8"); + + let mlen = strlen(&page.message); + let message = + std::str::from_utf8(&page.message[..mlen]).expect("message was not valid utf8"); + + ReducedPage { title, message } + } +} + +impl<'b> Debug for ReducedPage<'b> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + Display::fmt(self, f) + } +} +impl<'b> Display for ReducedPage<'b> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:?}: {:?}", self.title, self.message) + } +} + +pub fn handle_ui_message(item: &[u8], out: &mut [u8], page: u8) -> Result { + let m_len = out.len() - 1; //null byte terminator + if m_len < 1 { + return Err(ParserError::UnexpectedBufferEnd); + } + if m_len <= item.len() { + let chunk = item + .chunks(m_len) //divide in non-overlapping chunks + .nth(page as usize) //get the nth chunk + .ok_or(ParserError::UnexpectedValue)?; + + out[..chunk.len()].copy_from_slice(chunk); + out[chunk.len()] = 0; //null terminate + + let n_pages = item.len() / m_len; + Ok(1 + n_pages as u8) + } else { + out[..item.len()].copy_from_slice(item); + out[item.len()] = 0; //null terminate + Ok(1) + } +} + +pub fn strlen(s: &[u8]) -> usize { + let mut count = 0; + while let Some(&c) = s.get(count) { + if c == 0 { + return count; + } + count += 1; + } + + panic!("byte slice did not terminate with null byte, s: {:x?}", s) +} diff --git a/app/rust/src/type_table.rs b/app/rust/src/type_table.rs index 9ef65edf..4d833b53 100644 --- a/app/rust/src/type_table.rs +++ b/app/rust/src/type_table.rs @@ -56,6 +56,15 @@ impl TypeTable { pub fn find_type_entry(&self, type_index: usize) -> Option<&TypeTableEntry> { self.entries.get(type_index) } + pub fn find_variant(&self, field_hash: u32) -> Result { + // Get the root variant entry (type 0) + let root_entry = self.find_type_entry(0).ok_or(ParserError::UnexpectedType)?; + + match root_entry.find_field_type(field_hash)? { + FieldType::Compound(idx) => Ok(idx as u64), + _ => Err(ParserError::UnexpectedType), + } + } } pub fn parse_type_table( @@ -128,8 +137,7 @@ pub fn parse_type_table( } #[cfg(test)] -pub fn print_type_table(type_table: &TypeTable<20>) { - println!("type_count: {}", type_table.entry_count); +pub fn print_type_table(type_table: &TypeTable) { println!("Type table:"); for (i, entry) in type_table .entries diff --git a/deps/ledger-zxlib b/deps/ledger-zxlib index a69b91db..eebf9d53 160000 --- a/deps/ledger-zxlib +++ b/deps/ledger-zxlib @@ -1 +1 @@ -Subproject commit a69b91dbe387f39e8d37a252673d0d0bb42361a2 +Subproject commit eebf9d533a497f01e60f7a6f8bd5465b1379882c diff --git a/tests_zemu/package.json b/tests_zemu/package.json index 15cc69ab..ee1406e3 100644 --- a/tests_zemu/package.json +++ b/tests_zemu/package.json @@ -23,7 +23,7 @@ "@ledgerhq/hw-transport-node-hid": "^6.29.3", "@ledgerhq/logs": "^6.12.0", "@zondax/ledger-icp": "link:../js", - "@zondax/zemu": "^0.50.2" + "@zondax/zemu": "^0.52.0" }, "devDependencies": { "@matteoh2o1999/github-actions-jest-reporter": "^3.0.0", diff --git a/tests_zemu/snapshots/fl-candid_auto_stake_maturity/00000.png b/tests_zemu/snapshots/fl-candid_auto_stake_maturity/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-candid_auto_stake_maturity/00000.png and b/tests_zemu/snapshots/fl-candid_auto_stake_maturity/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_auto_stake_maturity/00001.png b/tests_zemu/snapshots/fl-candid_auto_stake_maturity/00001.png index c827d7d9..62079a55 100644 Binary files a/tests_zemu/snapshots/fl-candid_auto_stake_maturity/00001.png and b/tests_zemu/snapshots/fl-candid_auto_stake_maturity/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_disburse/00000.png b/tests_zemu/snapshots/fl-candid_disburse/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-candid_disburse/00000.png and b/tests_zemu/snapshots/fl-candid_disburse/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_disburse/00001.png b/tests_zemu/snapshots/fl-candid_disburse/00001.png index eb9e7921..421dfb6a 100644 Binary files a/tests_zemu/snapshots/fl-candid_disburse/00001.png and b/tests_zemu/snapshots/fl-candid_disburse/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_follow/00000.png b/tests_zemu/snapshots/fl-candid_follow/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-candid_follow/00000.png and b/tests_zemu/snapshots/fl-candid_follow/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_follow/00001.png b/tests_zemu/snapshots/fl-candid_follow/00001.png index f5dae901..bfe862be 100644 Binary files a/tests_zemu/snapshots/fl-candid_follow/00001.png and b/tests_zemu/snapshots/fl-candid_follow/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_increase_dissolve_delay/00000.png b/tests_zemu/snapshots/fl-candid_increase_dissolve_delay/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-candid_increase_dissolve_delay/00000.png and b/tests_zemu/snapshots/fl-candid_increase_dissolve_delay/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_increase_dissolve_delay/00001.png b/tests_zemu/snapshots/fl-candid_increase_dissolve_delay/00001.png index 97a676c1..3f5d9c99 100644 Binary files a/tests_zemu/snapshots/fl-candid_increase_dissolve_delay/00001.png and b/tests_zemu/snapshots/fl-candid_increase_dissolve_delay/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_list_neurons/00000.png b/tests_zemu/snapshots/fl-candid_list_neurons/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-candid_list_neurons/00000.png and b/tests_zemu/snapshots/fl-candid_list_neurons/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_list_neurons/00001.png b/tests_zemu/snapshots/fl-candid_list_neurons/00001.png index eb7628f6..fa579a75 100644 Binary files a/tests_zemu/snapshots/fl-candid_list_neurons/00001.png and b/tests_zemu/snapshots/fl-candid_list_neurons/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_merge_neuron/00000.png b/tests_zemu/snapshots/fl-candid_merge_neuron/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-candid_merge_neuron/00000.png and b/tests_zemu/snapshots/fl-candid_merge_neuron/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_merge_neuron/00001.png b/tests_zemu/snapshots/fl-candid_merge_neuron/00001.png index 008cf6aa..4bc06131 100644 Binary files a/tests_zemu/snapshots/fl-candid_merge_neuron/00001.png and b/tests_zemu/snapshots/fl-candid_merge_neuron/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_register_vote/00000.png b/tests_zemu/snapshots/fl-candid_register_vote/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-candid_register_vote/00000.png and b/tests_zemu/snapshots/fl-candid_register_vote/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_register_vote/00001.png b/tests_zemu/snapshots/fl-candid_register_vote/00001.png index 9c1fffb7..78cd9eee 100644 Binary files a/tests_zemu/snapshots/fl-candid_register_vote/00001.png and b/tests_zemu/snapshots/fl-candid_register_vote/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_send_icp/00002.png b/tests_zemu/snapshots/fl-candid_send_icp/00002.png index 4e62ef14..c671adb4 100644 Binary files a/tests_zemu/snapshots/fl-candid_send_icp/00002.png and b/tests_zemu/snapshots/fl-candid_send_icp/00002.png differ diff --git a/tests_zemu/snapshots/fl-candid_send_icp/00003.png b/tests_zemu/snapshots/fl-candid_send_icp/00003.png index 1bdf1658..039f2a7f 100644 Binary files a/tests_zemu/snapshots/fl-candid_send_icp/00003.png and b/tests_zemu/snapshots/fl-candid_send_icp/00003.png differ diff --git a/tests_zemu/snapshots/fl-candid_set_dissolve_delay/00000.png b/tests_zemu/snapshots/fl-candid_set_dissolve_delay/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-candid_set_dissolve_delay/00000.png and b/tests_zemu/snapshots/fl-candid_set_dissolve_delay/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_set_dissolve_delay/00001.png b/tests_zemu/snapshots/fl-candid_set_dissolve_delay/00001.png index 4fb2b1d3..35c3ae48 100644 Binary files a/tests_zemu/snapshots/fl-candid_set_dissolve_delay/00001.png and b/tests_zemu/snapshots/fl-candid_set_dissolve_delay/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_spawn_neuron/00000.png b/tests_zemu/snapshots/fl-candid_spawn_neuron/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-candid_spawn_neuron/00000.png and b/tests_zemu/snapshots/fl-candid_spawn_neuron/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_spawn_neuron/00001.png b/tests_zemu/snapshots/fl-candid_spawn_neuron/00001.png index 97ff4260..a993e8d4 100644 Binary files a/tests_zemu/snapshots/fl-candid_spawn_neuron/00001.png and b/tests_zemu/snapshots/fl-candid_spawn_neuron/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_split_neuron/00000.png b/tests_zemu/snapshots/fl-candid_split_neuron/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-candid_split_neuron/00000.png and b/tests_zemu/snapshots/fl-candid_split_neuron/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_split_neuron/00001.png b/tests_zemu/snapshots/fl-candid_split_neuron/00001.png index d126dc34..66211e39 100644 Binary files a/tests_zemu/snapshots/fl-candid_split_neuron/00001.png and b/tests_zemu/snapshots/fl-candid_split_neuron/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_stake_icrc_expert/00000.png b/tests_zemu/snapshots/fl-candid_stake_icrc_expert/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-candid_stake_icrc_expert/00000.png and b/tests_zemu/snapshots/fl-candid_stake_icrc_expert/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_stake_icrc_expert/00001.png b/tests_zemu/snapshots/fl-candid_stake_icrc_expert/00001.png index 9d0b9730..01f2673b 100644 Binary files a/tests_zemu/snapshots/fl-candid_stake_icrc_expert/00001.png and b/tests_zemu/snapshots/fl-candid_stake_icrc_expert/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_stake_maturity/00000.png b/tests_zemu/snapshots/fl-candid_stake_maturity/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-candid_stake_maturity/00000.png and b/tests_zemu/snapshots/fl-candid_stake_maturity/00000.png differ diff --git a/tests_zemu/snapshots/fl-candid_stake_maturity/00001.png b/tests_zemu/snapshots/fl-candid_stake_maturity/00001.png index 408cd476..401dd79c 100644 Binary files a/tests_zemu/snapshots/fl-candid_stake_maturity/00001.png and b/tests_zemu/snapshots/fl-candid_stake_maturity/00001.png differ diff --git a/tests_zemu/snapshots/fl-candid_stake_neuron_expert/00002.png b/tests_zemu/snapshots/fl-candid_stake_neuron_expert/00002.png index 0628f405..86d5261e 100644 Binary files a/tests_zemu/snapshots/fl-candid_stake_neuron_expert/00002.png and b/tests_zemu/snapshots/fl-candid_stake_neuron_expert/00002.png differ diff --git a/tests_zemu/snapshots/fl-candid_stake_neuron_expert/00003.png b/tests_zemu/snapshots/fl-candid_stake_neuron_expert/00003.png index b21c3fe5..484b1dff 100644 Binary files a/tests_zemu/snapshots/fl-candid_stake_neuron_expert/00003.png and b/tests_zemu/snapshots/fl-candid_stake_neuron_expert/00003.png differ diff --git a/tests_zemu/snapshots/fl-icrc_transfer/00002.png b/tests_zemu/snapshots/fl-icrc_transfer/00002.png index 6dc2c779..d9f2cdc8 100644 Binary files a/tests_zemu/snapshots/fl-icrc_transfer/00002.png and b/tests_zemu/snapshots/fl-icrc_transfer/00002.png differ diff --git a/tests_zemu/snapshots/fl-icrc_transfer/00003.png b/tests_zemu/snapshots/fl-icrc_transfer/00003.png index ffdc1c33..bb12f8cd 100644 Binary files a/tests_zemu/snapshots/fl-icrc_transfer/00003.png and b/tests_zemu/snapshots/fl-icrc_transfer/00003.png differ diff --git a/tests_zemu/snapshots/fl-mainmenu/00001.png b/tests_zemu/snapshots/fl-mainmenu/00001.png index 19bf0f0e..283dc831 100644 Binary files a/tests_zemu/snapshots/fl-mainmenu/00001.png and b/tests_zemu/snapshots/fl-mainmenu/00001.png differ diff --git a/tests_zemu/snapshots/fl-mainmenu/00002.png b/tests_zemu/snapshots/fl-mainmenu/00002.png index 2e30ccd6..c5f64be2 100644 Binary files a/tests_zemu/snapshots/fl-mainmenu/00002.png and b/tests_zemu/snapshots/fl-mainmenu/00002.png differ diff --git a/tests_zemu/snapshots/fl-mainmenu/00003.png b/tests_zemu/snapshots/fl-mainmenu/00003.png index 19bf0f0e..283dc831 100644 Binary files a/tests_zemu/snapshots/fl-mainmenu/00003.png and b/tests_zemu/snapshots/fl-mainmenu/00003.png differ diff --git a/tests_zemu/snapshots/fl-mainmenu/00004.png b/tests_zemu/snapshots/fl-mainmenu/00004.png index 5856cb8c..ef300557 100644 Binary files a/tests_zemu/snapshots/fl-mainmenu/00004.png and b/tests_zemu/snapshots/fl-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/fl-pb_add_hotkey/00000.png b/tests_zemu/snapshots/fl-pb_add_hotkey/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_add_hotkey/00000.png and b/tests_zemu/snapshots/fl-pb_add_hotkey/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_add_hotkey/00001.png b/tests_zemu/snapshots/fl-pb_add_hotkey/00001.png index 6595f08f..8150dbc7 100644 Binary files a/tests_zemu/snapshots/fl-pb_add_hotkey/00001.png and b/tests_zemu/snapshots/fl-pb_add_hotkey/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_check_status/00000.png b/tests_zemu/snapshots/fl-pb_check_status/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_check_status/00000.png and b/tests_zemu/snapshots/fl-pb_check_status/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_check_status/00001.png b/tests_zemu/snapshots/fl-pb_check_status/00001.png index e3f5fda9..b60fdfa8 100644 Binary files a/tests_zemu/snapshots/fl-pb_check_status/00001.png and b/tests_zemu/snapshots/fl-pb_check_status/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_claim_neurons/00000.png b/tests_zemu/snapshots/fl-pb_claim_neurons/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_claim_neurons/00000.png and b/tests_zemu/snapshots/fl-pb_claim_neurons/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_claim_neurons/00001.png b/tests_zemu/snapshots/fl-pb_claim_neurons/00001.png index f6696442..57dca825 100644 Binary files a/tests_zemu/snapshots/fl-pb_claim_neurons/00001.png and b/tests_zemu/snapshots/fl-pb_claim_neurons/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_disburse/00000.png b/tests_zemu/snapshots/fl-pb_disburse/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-pb_disburse/00000.png and b/tests_zemu/snapshots/fl-pb_disburse/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_disburse/00001.png b/tests_zemu/snapshots/fl-pb_disburse/00001.png index f3eadee2..10304046 100644 Binary files a/tests_zemu/snapshots/fl-pb_disburse/00001.png and b/tests_zemu/snapshots/fl-pb_disburse/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_follow/00000.png b/tests_zemu/snapshots/fl-pb_follow/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-pb_follow/00000.png and b/tests_zemu/snapshots/fl-pb_follow/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_follow/00001.png b/tests_zemu/snapshots/fl-pb_follow/00001.png index aad8de50..a8a4753c 100644 Binary files a/tests_zemu/snapshots/fl-pb_follow/00001.png and b/tests_zemu/snapshots/fl-pb_follow/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_increase_dissolve_delay/00000.png b/tests_zemu/snapshots/fl-pb_increase_dissolve_delay/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_increase_dissolve_delay/00000.png and b/tests_zemu/snapshots/fl-pb_increase_dissolve_delay/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_increase_dissolve_delay/00001.png b/tests_zemu/snapshots/fl-pb_increase_dissolve_delay/00001.png index c2f47026..4d8f1091 100644 Binary files a/tests_zemu/snapshots/fl-pb_increase_dissolve_delay/00001.png and b/tests_zemu/snapshots/fl-pb_increase_dissolve_delay/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_join_community_fund/00000.png b/tests_zemu/snapshots/fl-pb_join_community_fund/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_join_community_fund/00000.png and b/tests_zemu/snapshots/fl-pb_join_community_fund/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_join_community_fund/00001.png b/tests_zemu/snapshots/fl-pb_join_community_fund/00001.png index 4e2114ea..6052d566 100644 Binary files a/tests_zemu/snapshots/fl-pb_join_community_fund/00001.png and b/tests_zemu/snapshots/fl-pb_join_community_fund/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_list_neurons/00000.png b/tests_zemu/snapshots/fl-pb_list_neurons/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_list_neurons/00000.png and b/tests_zemu/snapshots/fl-pb_list_neurons/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_list_neurons/00001.png b/tests_zemu/snapshots/fl-pb_list_neurons/00001.png index 2a52e5c6..ccd3cada 100644 Binary files a/tests_zemu/snapshots/fl-pb_list_neurons/00001.png and b/tests_zemu/snapshots/fl-pb_list_neurons/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_register_vote/00000.png b/tests_zemu/snapshots/fl-pb_register_vote/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_register_vote/00000.png and b/tests_zemu/snapshots/fl-pb_register_vote/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_register_vote/00001.png b/tests_zemu/snapshots/fl-pb_register_vote/00001.png index d47a97d5..342fc488 100644 Binary files a/tests_zemu/snapshots/fl-pb_register_vote/00001.png and b/tests_zemu/snapshots/fl-pb_register_vote/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_remove_hotkey/00000.png b/tests_zemu/snapshots/fl-pb_remove_hotkey/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_remove_hotkey/00000.png and b/tests_zemu/snapshots/fl-pb_remove_hotkey/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_remove_hotkey/00001.png b/tests_zemu/snapshots/fl-pb_remove_hotkey/00001.png index fceeb16d..0dc6ee0a 100644 Binary files a/tests_zemu/snapshots/fl-pb_remove_hotkey/00001.png and b/tests_zemu/snapshots/fl-pb_remove_hotkey/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_sign_update_call/00002.png b/tests_zemu/snapshots/fl-pb_sign_update_call/00002.png index 493d3ee8..12b986f8 100644 Binary files a/tests_zemu/snapshots/fl-pb_sign_update_call/00002.png and b/tests_zemu/snapshots/fl-pb_sign_update_call/00002.png differ diff --git a/tests_zemu/snapshots/fl-pb_sign_update_call/00003.png b/tests_zemu/snapshots/fl-pb_sign_update_call/00003.png index 3fc290ad..ff8e3fae 100644 Binary files a/tests_zemu/snapshots/fl-pb_sign_update_call/00003.png and b/tests_zemu/snapshots/fl-pb_sign_update_call/00003.png differ diff --git a/tests_zemu/snapshots/fl-pb_spawn_neuron/00000.png b/tests_zemu/snapshots/fl-pb_spawn_neuron/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_spawn_neuron/00000.png and b/tests_zemu/snapshots/fl-pb_spawn_neuron/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_spawn_neuron/00001.png b/tests_zemu/snapshots/fl-pb_spawn_neuron/00001.png index c6a277a1..cb1d668a 100644 Binary files a/tests_zemu/snapshots/fl-pb_spawn_neuron/00001.png and b/tests_zemu/snapshots/fl-pb_spawn_neuron/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_stake_neuron/00000.png b/tests_zemu/snapshots/fl-pb_stake_neuron/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-pb_stake_neuron/00000.png and b/tests_zemu/snapshots/fl-pb_stake_neuron/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_stake_neuron/00001.png b/tests_zemu/snapshots/fl-pb_stake_neuron/00001.png index cd7f8fd4..a9f720ec 100644 Binary files a/tests_zemu/snapshots/fl-pb_stake_neuron/00001.png and b/tests_zemu/snapshots/fl-pb_stake_neuron/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_start_dissolve/00000.png b/tests_zemu/snapshots/fl-pb_start_dissolve/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_start_dissolve/00000.png and b/tests_zemu/snapshots/fl-pb_start_dissolve/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_start_dissolve/00001.png b/tests_zemu/snapshots/fl-pb_start_dissolve/00001.png index 08b60541..15ae4dcc 100644 Binary files a/tests_zemu/snapshots/fl-pb_start_dissolve/00001.png and b/tests_zemu/snapshots/fl-pb_start_dissolve/00001.png differ diff --git a/tests_zemu/snapshots/fl-pb_stop_dissolve/00000.png b/tests_zemu/snapshots/fl-pb_stop_dissolve/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-pb_stop_dissolve/00000.png and b/tests_zemu/snapshots/fl-pb_stop_dissolve/00000.png differ diff --git a/tests_zemu/snapshots/fl-pb_stop_dissolve/00001.png b/tests_zemu/snapshots/fl-pb_stop_dissolve/00001.png index 35b5207c..cb7e931f 100644 Binary files a/tests_zemu/snapshots/fl-pb_stop_dissolve/00001.png and b/tests_zemu/snapshots/fl-pb_stop_dissolve/00001.png differ diff --git a/tests_zemu/snapshots/fl-show_address/00000.png b/tests_zemu/snapshots/fl-show_address/00000.png index 0147b014..157ee8f7 100644 Binary files a/tests_zemu/snapshots/fl-show_address/00000.png and b/tests_zemu/snapshots/fl-show_address/00000.png differ diff --git a/tests_zemu/snapshots/fl-show_address/00001.png b/tests_zemu/snapshots/fl-show_address/00001.png index 6c7ed2af..492caded 100644 Binary files a/tests_zemu/snapshots/fl-show_address/00001.png and b/tests_zemu/snapshots/fl-show_address/00001.png differ diff --git a/tests_zemu/snapshots/fl-show_address_reject/00000.png b/tests_zemu/snapshots/fl-show_address_reject/00000.png index 0147b014..157ee8f7 100644 Binary files a/tests_zemu/snapshots/fl-show_address_reject/00000.png and b/tests_zemu/snapshots/fl-show_address_reject/00000.png differ diff --git a/tests_zemu/snapshots/fl-show_address_reject/00001.png b/tests_zemu/snapshots/fl-show_address_reject/00001.png index 6c7ed2af..492caded 100644 Binary files a/tests_zemu/snapshots/fl-show_address_reject/00001.png and b/tests_zemu/snapshots/fl-show_address_reject/00001.png differ diff --git a/tests_zemu/snapshots/fl-sns_add_permissions/00000.png b/tests_zemu/snapshots/fl-sns_add_permissions/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-sns_add_permissions/00000.png and b/tests_zemu/snapshots/fl-sns_add_permissions/00000.png differ diff --git a/tests_zemu/snapshots/fl-sns_add_permissions/00001.png b/tests_zemu/snapshots/fl-sns_add_permissions/00001.png index 71f0ceaa..6dad8a39 100644 Binary files a/tests_zemu/snapshots/fl-sns_add_permissions/00001.png and b/tests_zemu/snapshots/fl-sns_add_permissions/00001.png differ diff --git a/tests_zemu/snapshots/fl-sns_disburse/00002.png b/tests_zemu/snapshots/fl-sns_disburse/00002.png index 0c27a593..9dcaa4da 100644 Binary files a/tests_zemu/snapshots/fl-sns_disburse/00002.png and b/tests_zemu/snapshots/fl-sns_disburse/00002.png differ diff --git a/tests_zemu/snapshots/fl-sns_disburse/00003.png b/tests_zemu/snapshots/fl-sns_disburse/00003.png index 5f417cdd..b28dca81 100644 Binary files a/tests_zemu/snapshots/fl-sns_disburse/00003.png and b/tests_zemu/snapshots/fl-sns_disburse/00003.png differ diff --git a/tests_zemu/snapshots/fl-sns_remove_permissions/00000.png b/tests_zemu/snapshots/fl-sns_remove_permissions/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-sns_remove_permissions/00000.png and b/tests_zemu/snapshots/fl-sns_remove_permissions/00000.png differ diff --git a/tests_zemu/snapshots/fl-sns_remove_permissions/00001.png b/tests_zemu/snapshots/fl-sns_remove_permissions/00001.png index 11fa9db0..84c3b5e5 100644 Binary files a/tests_zemu/snapshots/fl-sns_remove_permissions/00001.png and b/tests_zemu/snapshots/fl-sns_remove_permissions/00001.png differ diff --git a/tests_zemu/snapshots/fl-sns_stake_maturity/00000.png b/tests_zemu/snapshots/fl-sns_stake_maturity/00000.png index c46ba3d3..1ba6dc74 100644 Binary files a/tests_zemu/snapshots/fl-sns_stake_maturity/00000.png and b/tests_zemu/snapshots/fl-sns_stake_maturity/00000.png differ diff --git a/tests_zemu/snapshots/fl-sns_stake_maturity/00001.png b/tests_zemu/snapshots/fl-sns_stake_maturity/00001.png index e2183509..c1b8d2c7 100644 Binary files a/tests_zemu/snapshots/fl-sns_stake_maturity/00001.png and b/tests_zemu/snapshots/fl-sns_stake_maturity/00001.png differ diff --git a/tests_zemu/snapshots/fl-sns_start_dissolve/00000.png b/tests_zemu/snapshots/fl-sns_start_dissolve/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-sns_start_dissolve/00000.png and b/tests_zemu/snapshots/fl-sns_start_dissolve/00000.png differ diff --git a/tests_zemu/snapshots/fl-sns_start_dissolve/00001.png b/tests_zemu/snapshots/fl-sns_start_dissolve/00001.png index 1facb9e9..2a2d41eb 100644 Binary files a/tests_zemu/snapshots/fl-sns_start_dissolve/00001.png and b/tests_zemu/snapshots/fl-sns_start_dissolve/00001.png differ diff --git a/tests_zemu/snapshots/fl-sns_stop_dissolve/00000.png b/tests_zemu/snapshots/fl-sns_stop_dissolve/00000.png index 74936fef..397c98d9 100644 Binary files a/tests_zemu/snapshots/fl-sns_stop_dissolve/00000.png and b/tests_zemu/snapshots/fl-sns_stop_dissolve/00000.png differ diff --git a/tests_zemu/snapshots/fl-sns_stop_dissolve/00001.png b/tests_zemu/snapshots/fl-sns_stop_dissolve/00001.png index 3e1f7156..47fddd20 100644 Binary files a/tests_zemu/snapshots/fl-sns_stop_dissolve/00001.png and b/tests_zemu/snapshots/fl-sns_stop_dissolve/00001.png differ diff --git a/tests_zemu/snapshots/s-mainmenu/00004.png b/tests_zemu/snapshots/s-mainmenu/00004.png index 54b2530b..f253ad1c 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00004.png and b/tests_zemu/snapshots/s-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/s-mainmenu/00010.png b/tests_zemu/snapshots/s-mainmenu/00010.png index 54b2530b..f253ad1c 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00010.png and b/tests_zemu/snapshots/s-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/sp-mainmenu/00004.png b/tests_zemu/snapshots/sp-mainmenu/00004.png index 09c01fee..5e352af9 100644 Binary files a/tests_zemu/snapshots/sp-mainmenu/00004.png and b/tests_zemu/snapshots/sp-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/sp-mainmenu/00010.png b/tests_zemu/snapshots/sp-mainmenu/00010.png index 09c01fee..5e352af9 100644 Binary files a/tests_zemu/snapshots/sp-mainmenu/00010.png and b/tests_zemu/snapshots/sp-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/st-candid_follow/00001.png b/tests_zemu/snapshots/st-candid_follow/00001.png index 55d2dc26..89a146de 100644 Binary files a/tests_zemu/snapshots/st-candid_follow/00001.png and b/tests_zemu/snapshots/st-candid_follow/00001.png differ diff --git a/tests_zemu/snapshots/st-mainmenu/00001.png b/tests_zemu/snapshots/st-mainmenu/00001.png index f6682071..bfbcf946 100644 Binary files a/tests_zemu/snapshots/st-mainmenu/00001.png and b/tests_zemu/snapshots/st-mainmenu/00001.png differ diff --git a/tests_zemu/snapshots/st-mainmenu/00002.png b/tests_zemu/snapshots/st-mainmenu/00002.png index d7665cd1..e2f3b141 100644 Binary files a/tests_zemu/snapshots/st-mainmenu/00002.png and b/tests_zemu/snapshots/st-mainmenu/00002.png differ diff --git a/tests_zemu/snapshots/st-mainmenu/00003.png b/tests_zemu/snapshots/st-mainmenu/00003.png index f6682071..bfbcf946 100644 Binary files a/tests_zemu/snapshots/st-mainmenu/00003.png and b/tests_zemu/snapshots/st-mainmenu/00003.png differ diff --git a/tests_zemu/snapshots/st-mainmenu/00004.png b/tests_zemu/snapshots/st-mainmenu/00004.png index 47ba37c9..1dd486f2 100644 Binary files a/tests_zemu/snapshots/st-mainmenu/00004.png and b/tests_zemu/snapshots/st-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/st-pb_follow/00001.png b/tests_zemu/snapshots/st-pb_follow/00001.png index b747c129..6643c2b3 100644 Binary files a/tests_zemu/snapshots/st-pb_follow/00001.png and b/tests_zemu/snapshots/st-pb_follow/00001.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00004.png b/tests_zemu/snapshots/x-mainmenu/00004.png index 09c01fee..5e352af9 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00004.png and b/tests_zemu/snapshots/x-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00010.png b/tests_zemu/snapshots/x-mainmenu/00010.png index 09c01fee..5e352af9 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00010.png and b/tests_zemu/snapshots/x-mainmenu/00010.png differ diff --git a/tests_zemu/tests/bls.test.ts b/tests_zemu/tests/bls.test.ts index 3cff3ae7..b870c513 100644 --- a/tests_zemu/tests/bls.test.ts +++ b/tests_zemu/tests/bls.test.ts @@ -20,7 +20,7 @@ import { DEFAULT_OPTIONS, DEVICE_MODELS_BLS } from './common' jest.setTimeout(180000) -describe('Bls', function () { +describe.skip('Bls', function () { test.concurrent.each(DEVICE_MODELS_BLS)('verify_with_custom_key', async function (m) { const sim = new Zemu(m.path) try { @@ -28,14 +28,14 @@ describe('Bls', function () { const app = new InternetComputerApp(sim.getTransport()) let consent_request = - 'd9d9f7a167636f6e74656e74a763617267586b4449444c076d7b6c01d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d026e036c02efcee7800401c4fbf2db05046c03d6fca70200e1edeb4a7184f7fee80a0501060c4449444c00017104746f626905677265657402656e01011e0003006b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49d49c5a920806b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e636550a3788c1805553fb69b20f08e87e23b136c726571756573745f747970656463616c6c6673656e6465724104' + 'd9d9f7a167636f6e74656e74a76361726758d84449444c086d7b6e766c02aeaeb1cc0501d880c6d007716c02cbaeb581017ab183e7f1077a6b028beabfc2067f8ef1c1ee0d036e046c02efcee7800402c4fbf2db05056c03d6fca70200e1edeb4a7184f7fee80a060107684449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e04010501904e0000008094ebdc030000010a00000000000000070101000d69637263325f617070726f76650002656e0101230003006b63616e69737465725f69644a000000000000000201016e696e67726573735f6578706972791b18072a6f7894d0006b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e636550369f1914fd64438f5e6329fcb66b1d4d6c726571756573745f747970656463616c6c6673656e6465724104' let canister_call = - 'd9d9f7a167636f6e74656e74a6636172674c4449444c00017104746f62696b63616e69737465725f69644a00000000006000fd01016e696e67726573735f657870697279c24817c49d610e2008806b6d6574686f645f6e616d656567726565746c726571756573745f747970656571756572796673656e6465724104' + 'd9d9f7a167636f6e74656e74a76361726758684449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e04010501904e0000008094ebdc030000010a00000000000000070101006b63616e69737465725f69644a000000000000000201016e696e67726573735f6578706972791b18072a6f7894d0006b6d6574686f645f6e616d656d69637263325f617070726f7665656e6f6e6365506b99f1c2338b4543152aae206d5286726c726571756573745f747970656463616c6c6673656e646572581d052c5f6f270fc4a3a882a8075732cba90ad4bd25d30bd2cf7b0bfe7c02' let certificate = - 'd9d9f7a3647472656583018301820458200bbcc71092da3ce262b8154d398b9a6114bee87f1c0b72e16912757aa023626a8301820458200628a8e00432e8657ad99c4d1bf167dd54ace9199609bfc5d57d89f48d97565f83024e726571756573745f737461747573830258204ea057c46292fedb573d35319dd1ccab3fb5d6a2b106b785d1f7757cfa5a254283018302457265706c79820358b44449444c0b6b02bc8a0101c5fed201086c02efcee7800402e29fdcc806036c01d880c6d007716b02d9e5b0980404fcdfd79a0f716c01c4d6b4ea0b056d066c01ffbb87a807076d716b04d1c4987c09a3f2efe6020a9a8597e6030ae3c581900f0a6c02fc91f4f80571c498b1b50d7d6c01fc91f4f8057101000002656e0001021e50726f647563652074686520666f6c6c6f77696e67206772656574696e6714746578743a202248656c6c6f2c20746f626921228302467374617475738203477265706c696564830182045820891af3e8982f1ac3d295c29b9fdfedc52301c03fbd4979676c01059184060b0583024474696d65820349cbf7dd8ca1a2a7e217697369676e6174757265583088078c6fe75f32594bf4e322b14d47e5c849cf24a370e3bab0cab5daffb7ab6a2c49de18b7f2d631893217d0c716cd656a64656c65676174696f6ea2697375626e65745f6964581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd026b6365727469666963617465590294d9d9f7a264747265658301820458200b0d62dc7b9d7de735bb9a6393b59c9f32ff7c4d2aacdfc9e6ffc70e341fb6f783018301820458204468514ca4af8224c055c386e3f7b0bfe018c2d9cfd5837e427b43e1ab0934f98302467375626e65748301830183018301820458208739fbbedd3dedaa8fef41870367c0905bde376b63dd37e2b176fb08b582052f830182045820f8c3eae0377ee00859223bf1c6202f5885c4dcdc8fd13b1d48c3c838688919bc83018302581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd02830183024f63616e69737465725f72616e67657382035832d9d9f782824a000000000060000001014a00000000006000ae0101824a00000000006000b001014a00000000006fffff010183024a7075626c69635f6b657982035885308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c0503020103610090075120778eb21a530a02bcc763e7f4a192933506966af7b54c10a4d2b24de6a86b200e3440bae6267bf4c488d9a11d0472c38c1b6221198f98e4e6882ba38a5a4e3aa5afce899b7f825ed95adfa12629688073556f2747527213e8d73e40ce8204582036f3cd257d90fb38e42597f193a5e031dbd585b6292793bb04db4794803ce06e82045820028fc5e5f70868254e7215e7fc630dbd29eefc3619af17ce231909e1faf97e9582045820696179fceb777eaed283265dd690241999eb3ede594091748b24456160edc1278204582081398069f9684da260cfb002eac42211d0dbf22c62d49aee61617d62650e793183024474696d65820349a5948992aaa195e217697369676e6174757265583094e5f544a7681b0c2c3c5dbf97950c96fd837f2d19342f1050d94d3068371b0a95a5ee20c36c4395c2dbb4204f2b4742' + 'd9d9f7a264747265658301830182045820d4cff6a25570a56ac14e743e694daa2aa88b80a4bea761116471b56e4945ed6d830182045820f26d51d511039fcb5058441e6204fec42a0da824541f57d0c1c3468a13e16cbf83024e726571756573745f737461747573830182045820198df32f6757100316e899c7a3afec26f6933c06bf5d2f6233f6b0f14ac5b96f83025820ea37fdc5229d7273d500dc8ae3c009f0421049c1f02cc5ad85ea838ae7dfc04583018302457265706c7982035903304449444c0c6b02bc8a0101c5fed201096c02efcee7800402e29fdcc806046c02aeaeb1cc0503d880c6d007716e766b02d9e5b0980405fcdfd79a0f716c01c4d6b4ea0b066d076c01ffbb87a807086d716b04d1c4987c0aa3f2efe6020b9a8597e6030be3c581900f0b6c02fc91f4f80571c498b1b50d7d6c01fc91f4f805710100000002656e0007031e2320417574686f72697a6520616e6f74686572206164647265737320746f2077697468647261772066726f6d20796f7572206163636f756e74202a2a5468651f666f6c6c6f77696e67206164647265737320697320616c6c6f77656420746f031d77697468647261772066726f6d20796f7572206163636f756e743a2a2a2272646d78362d6a616161612d61616161612d61616164712d636169202a2a596f75720d7375626163636f756e743a2a2a032330303030303030303030303030303030303030303030303030303030303030303030301d3030303030303030303030303030303030303030303030303030303030232a2a526571756573746564207769746864726177616c20616c6c6f77616e63653a2a2a032031302049435020e29aa02054686520616c6c6f77616e63652077696c6c2062652273657420746f2031302049435020696e646570656e64656e746c79206f6620616e791e70726576696f757320616c6c6f77616e63652e20556e74696c207468697303217472616e73616374696f6e20686173206265656e206578656375746564207468651e7370656e6465722063616e207374696c6c206578657263697365207468652370726576696f757320616c6c6f77616e63652028696620616e792920746f2069742773032166756c6c20616d6f756e742e202a2a45787069726174696f6e20646174653a2a2a204e6f2065787069726174696f6e2e202a2a417070726f76616c206665653a2a2a23302e3030303120494350202a2a5472616e73616374696f6e206665657320746f206265031a7061696420627920796f7572207375626163636f756e743a2a2a2330303030303030303030303030303030303030303030303030303030303030303030301d30303030303030303030303030303030303030303030303030303030308302467374617475738203477265706c6965648301820458206d8327eb52806a887c0e4f444261e7bde20005e64d9b31479b9af72f8f89886083024474696d65820349c8ccbcfea4c8ca8318697369676e61747572655830b9eb03718c42aa1926bab9956dcef37432045ba1122baf120b8fc3e9fb56f75df8eee419e4e6488e60db79dcaba8c153' let root_key = - '814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae' + 'b354faa40626ebc91ed7e55b2307feff70d119ef37f89915bd4561a1ed8c5c26c8c2cb8c4711eec681bf213a75cb988008fb1f4d7aa278cd4fad6f295c83bab04b8cabcb32640cf926083daf865551f9f3b76fd800dac027a583858b9d1d3f64' const respCert = app.signBls("m/44'/223'/0'/0/0", consent_request, canister_call, certificate, root_key)