Skip to content

Commit

Permalink
Many improvements and add UI for MessageResponse type and full parser…
Browse files Browse the repository at this point in the history
… along with some unit tests
  • Loading branch information
neithanmo committed Jul 16, 2024
1 parent 37a7b23 commit 1d936b6
Show file tree
Hide file tree
Showing 13 changed files with 411 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use crate::{
error::ParserError,
utils::{decompress_leb128, decompress_sleb128},
};

// Only for debug information in testing
#[cfg(test)]
use std::{string::String, vec::Vec};
use std::vec::Vec;

#[repr(i32)]
enum IDLTypes {
Expand Down Expand Up @@ -70,6 +72,59 @@ impl TryFrom<i64> for IDLTypes {
}
}

/// Use to read out the bytes that are part of the candid encoded type table.
/// in candid encoded data we have first T(type table) followed by the actual data
/// in the M(memory section), and finaly a R(reference) section.
/// for more documentation about encoding/decoding:
/// https://github.com/dfinity/candid/blob/master/spec/Candid.md#binary-format
pub fn parse_type_table(input: &[u8]) -> Result<&[u8], ParserError> {
let (rem, type_count) = decompress_leb128(input).map_err(|_| ParserError::UnexpectedError)?;
let mut current = rem;

for _ in 0..type_count {
let (new_rem, type_code) = decompress_sleb128(current)?;
let type_code = IDLTypes::try_from(type_code).map_err(|_| ParserError::UnexpectedType)?;
current = new_rem;

match type_code {
IDLTypes::Opt => {
let (new_rem, _) = decompress_sleb128(current)?;
current = new_rem;
}
IDLTypes::Vector => {
let (new_rem, _) = decompress_sleb128(current)?;
current = new_rem;
}
IDLTypes::Record => {
let (new_rem, field_count) =
decompress_leb128(current).map_err(|_| ParserError::UnexpectedError)?;
current = new_rem;
for _ in 0..field_count {
let (new_rem, _) =
decompress_leb128(current).map_err(|_| ParserError::UnexpectedError)?;
let (new_rem, _) = decompress_sleb128(new_rem)?;
current = new_rem;
}
}
IDLTypes::Variant => {
let (new_rem, field_count) =
decompress_leb128(current).map_err(|_| ParserError::UnexpectedError)?;
current = new_rem;
for _ in 0..field_count {
let (new_rem, _) =
decompress_leb128(current).map_err(|_| ParserError::UnexpectedError)?;
let (new_rem, _) = decompress_sleb128(new_rem)?;
current = new_rem;
}
}
_ => {}
};
}

Ok(current)
}

#[cfg(test)]
impl core::fmt::Display for IDLTypes {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let s = match self {
Expand Down Expand Up @@ -173,50 +228,3 @@ pub fn print_type_table(input: &[u8]) -> Result<&[u8], ParserError> {

Ok(current)
}

pub fn parse_type_table(input: &[u8]) -> Result<&[u8], ParserError> {
let (rem, type_count) = decompress_leb128(input).map_err(|_| ParserError::UnexpectedError)?;
let mut current = rem;

for _ in 0..type_count {
let (new_rem, type_code) = decompress_sleb128(current)?;
let type_code = IDLTypes::try_from(type_code).map_err(|_| ParserError::UnexpectedType)?;
current = new_rem;

match type_code {
IDLTypes::Opt => {
let (new_rem, _) = decompress_sleb128(current)?;
current = new_rem;
}
IDLTypes::Vector => {
let (new_rem, _) = decompress_sleb128(current)?;
current = new_rem;
}
IDLTypes::Record => {
let (new_rem, field_count) =
decompress_leb128(current).map_err(|_| ParserError::UnexpectedError)?;
current = new_rem;
for _ in 0..field_count {
let (new_rem, _) =
decompress_leb128(current).map_err(|_| ParserError::UnexpectedError)?;
let (new_rem, _) = decompress_sleb128(new_rem)?;
current = new_rem;
}
}
IDLTypes::Variant => {
let (new_rem, field_count) =
decompress_leb128(current).map_err(|_| ParserError::UnexpectedError)?;
current = new_rem;
for _ in 0..field_count {
let (new_rem, _) =
decompress_leb128(current).map_err(|_| ParserError::UnexpectedError)?;
let (new_rem, _) = decompress_sleb128(new_rem)?;
current = new_rem;
}
}
_ => {}
};
}

Ok(current)
}
3 changes: 2 additions & 1 deletion app/rust/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
pub const BLS_PUBLIC_KEY_SIZE: usize = 96;
pub const BLS_SIGNATURE_SIZE: usize = 48;
pub const CBOR_CERTIFICATE_TAG: u64 = 55799;
pub const MAX_LINES: usize = 4;
pub const MAX_LINES: usize = 2;
pub const MAX_PAGES: usize = 20;
pub const MAX_CHARS_PER_LINE: usize = 32;
8 changes: 8 additions & 0 deletions app/rust/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
use minicbor::decode::Error;
use nom::error::ErrorKind;

#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "derive-debug", derive(Debug))]
pub enum ViewError {
Unknown,
NoData,
Reject,
}

#[repr(u32)]
#[derive(Debug, PartialEq)]
pub enum ParserError {
Expand Down
1 change: 1 addition & 0 deletions app/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

extern crate no_std_compat as std;

pub mod candid_types;
mod constants;
mod error;
mod ffi;
Expand Down
48 changes: 27 additions & 21 deletions app/rust/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
********************************************************************************/
use core::mem::MaybeUninit;

use nom::bytes::complete::take;

use crate::{error::ParserError, utils::decompress_leb128};
use crate::error::ViewError;

pub mod candid_utils;
pub mod certificate;
pub mod consent_message;
pub mod delegation;
Expand Down Expand Up @@ -65,23 +64,30 @@ pub trait FromBytes<'b>: Sized {
) -> Result<&'b [u8], crate::error::ParserError>;
}

pub fn parse_text(input: &[u8]) -> Result<(&[u8], &str), nom::Err<ParserError>> {
let (rem, len) = crate::utils::decompress_leb128(input)?;
let (rem, bytes) = take(len as usize)(rem)?;
let s = core::str::from_utf8(bytes).map_err(|_| nom::Err::Error(ParserError::InvalidUtf8))?;

Ok((rem, s))
}
///This trait defines the interface useful in the UI context
/// so that all the different OperationTypes or other items can handle their own UI
pub trait DisplayableItem {
/// Returns the number of items to display
fn num_items(&self) -> Result<u8, ViewError>;

fn parse_opt_int16(input: &[u8]) -> Result<(&[u8], Option<i16>), ParserError> {
let (rem, opt_tag) = decompress_leb128(input).map_err(|_| ParserError::UnexpectedError)?;
match opt_tag {
0 => Ok((rem, None)),
1 => {
let (rem, value) = nom::number::complete::le_i16(rem)
.map_err(|_: nom::Err<ParserError>| ParserError::UnexpectedError)?;
Ok((rem, Some(value)))
}
_ => Err(ParserError::UnexpectedValue),
}
/// 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<u8, ViewError>;
}
43 changes: 43 additions & 0 deletions app/rust/src/parser/candid_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use nom::bytes::complete::take;

use crate::{error::ParserError, utils::decompress_leb128};

/// Parse a text from the candid encoded input
pub fn parse_text(input: &[u8]) -> Result<(&[u8], &str), nom::Err<ParserError>> {
let (rem, len) = crate::utils::decompress_leb128(input)?;
let (rem, bytes) = take(len as usize)(rem)?;
let s = core::str::from_utf8(bytes).map_err(|_| nom::Err::Error(ParserError::InvalidUtf8))?;

Ok((rem, s))
}

/// Generates parser functions for any Option<Number> from the candid encoded input
/// Number could be either a u8, ..., u64 or i8, ..., i64
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).map_err(|_| ParserError::UnexpectedError)?;
match opt_tag {
0 => Ok((rem, None)),
1 => {
let (rem, value) = nom::number::complete::$le_type(rem)
.map_err(|_: nom::Err<ParserError>| ParserError::UnexpectedError)?;
Ok((rem, Some(value)))
}
_ => Err(ParserError::UnexpectedValue),
}
}
};
}

// Generate functions for unsigned integers
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);
6 changes: 3 additions & 3 deletions app/rust/src/parser/consent_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
pub mod candid_types;
pub mod msg;
pub mod msg_error;
pub mod msg_info;
pub mod msg_metadata;
pub mod msg_response;

// refer to:
// https://github.com/dfinity/wg-identity-authentication/blob/main/topics/ICRC-21/ICRC-21.did#L134
//
// type icrc21_consent_info = record {
// consent_message: icrc21_consent_message,
// metadata: icrc21_consent_message_metadata
Expand All @@ -41,5 +43,3 @@ pub mod msg_response;
// language: text,
// utc_offset_minutes: opt int16
// };

// Custom error type
Loading

0 comments on commit 1d936b6

Please sign in to comment.