diff --git a/Cargo.lock b/Cargo.lock index 5c4675a7..cb57b652 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,9 +40,9 @@ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "asn1-rs" @@ -152,9 +152,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -200,9 +203,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.15" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", ] @@ -339,7 +342,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -395,9 +398,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -413,9 +416,9 @@ checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", @@ -439,9 +442,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -480,9 +483,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "log" @@ -870,29 +873,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.206" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.206" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", "memchr", @@ -900,6 +903,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "snafu" version = "0.7.5" @@ -936,9 +945,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -980,7 +989,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1047,9 +1056,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "uuid" @@ -1078,34 +1087,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1113,28 +1123,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 902731de..2e7a2370 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,12 @@ name = "iai" path = "benches/iai.rs" harness = false +[[bench]] +name = "criterion_integer" +path = "benches/integer.rs" +harness = false +test = true + [dependencies] nom = { version = "7.1.3", default-features = false, features = ["alloc"] } num-bigint = { version = "0.4.3", default-features = false } @@ -59,11 +65,11 @@ nom-bitvec = { package = "bitvec-nom2", version = "0.2.0" } arrayvec = { version = "0.7.4", default-features = false } either = { version = "1.9.0", default-features = false } once_cell = { version = "1.18.0", default-features = false, features = [ - "race", - "alloc", + "race", + "alloc", ] } num-integer = { version = "0.1.45", default-features = false, features = [ - "i128", + "i128", ] } jzon = { version = "0.12.5", optional = true } diff --git a/README.md b/README.md index c7bd4fa8..d941d4c8 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ type TestTypeB = bool; type TestTypeA = TestTypeB; ``` ```rust -// or +// or use rasn::prelude::*; #[derive(AsnType, Decode, Encode)] @@ -621,11 +621,11 @@ Test-type-a ::= CHOICE { raisin OCTET STRING } -Test-type-b ::= CHOICE { - juice INTEGER (0..3,...), +Test-type-b ::= CHOICE { + juice INTEGER (0..3,...), wine OCTET STRING, ..., - grappa INTEGER + grappa INTEGER } ``` @@ -663,8 +663,8 @@ enum TestTypeB { ```asn -Test-type-a ::= SEQUENCE { - juice INTEGER (0..3,...), +Test-type-a ::= SEQUENCE { + juice INTEGER (0..3,...), wine OCTET STRING, ..., grappa INTEGER OPTIONAL, @@ -699,7 +699,7 @@ struct TestTypeA { ```asn -Test-type-a ::= SET { +Test-type-a ::= SET { seed NULL, grape BOOLEAN, raisin INTEGER @@ -711,7 +711,7 @@ Test-type-a ::= SET { ```rust use rasn::prelude::*; -/// the SET declaration is basically identical to a SEQUENCE declaration, +/// the SET declaration is basically identical to a SEQUENCE declaration, /// except for the `set` annotation #[derive(AsnType, Decode, Encode)] #[rasn(set, automatic_tags)] @@ -730,7 +730,7 @@ struct TestTypeA { ```asn -Test-type-a ::= SEQUENCE { +Test-type-a ::= SEQUENCE { notQuiteRustCase INTEGER } ``` @@ -757,7 +757,7 @@ struct TestTypeA { ```asn -Test-type-a ::= SEQUENCE { +Test-type-a ::= SEQUENCE { seed BOOLEAN DEFAULT TRUE, grape INTEGER OPTIONAL, raisin INTEGER DEFAULT 1 @@ -773,7 +773,7 @@ use rasn::prelude::*; #[derive(AsnType, Decode, Encode)] #[rasn(automatic_tags)] struct TestTypeA { - #[rasn(default = "default_seed")] + #[rasn(default = "default_seed")] seed: bool, grape: Option, #[rasn(default = "default_raisin")] diff --git a/benches/integer.rs b/benches/integer.rs new file mode 100644 index 00000000..1c7d8b0e --- /dev/null +++ b/benches/integer.rs @@ -0,0 +1,143 @@ +// Based on https://github.com/dudycz/asn1_codecs_bench under Apache License Version 2.0 License +// +// Modifications: +// Adds other rasn codecs to the benchmark + +use criterion::{criterion_group, criterion_main, Criterion}; +use rasn::{ber, oer, uper}; + +#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, unused)] +pub mod world3d { + extern crate alloc; + use core::borrow::Borrow; + use rasn::prelude::*; + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq)] + #[rasn(automatic_tags)] + pub struct Color { + #[rasn(value("0..=255"))] + pub r: u8, + #[rasn(value("0..=255"))] + pub g: u8, + #[rasn(value("0..=255"))] + pub b: u8, + #[rasn(value("0..=65335"))] + pub a: u16, + } + impl Color { + pub fn new(r: u8, g: u8, b: u8, a: u16) -> Self { + Self { r, g, b, a } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq)] + #[rasn(automatic_tags)] + pub struct Column { + #[rasn(size("10"))] + pub elements: SequenceOf, + } + impl Column { + pub fn new(elements: SequenceOf) -> Self { + Self { elements } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq)] + #[rasn(automatic_tags)] + pub struct Plane { + #[rasn(size("10"))] + pub rows: SequenceOf, + } + impl Plane { + pub fn new(rows: SequenceOf) -> Self { + Self { rows } + } + } + #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq)] + #[rasn(automatic_tags)] + pub struct World { + #[rasn(size("10"))] + pub depth: SequenceOf, + } + impl World { + pub fn new(depth: SequenceOf) -> Self { + Self { depth } + } + } +} + +pub fn build_sample_rasn() -> world3d::World { + use world3d::*; + let color = Color::new(42, 128, 77, 12312); + let elements = (0..10).map(|_| color.clone()).collect::>(); + let column = Column { elements }; + let rows = (0..10).map(|_| column.clone()).collect::>(); + let plane = Plane { rows }; + let depth = (0..10).map(|_| plane.clone()).collect::>(); + + World { depth } +} + +fn uper_rasn_enc(c: &mut Criterion) { + c.bench_function("RASN/encode UPER - sample.asn", |b| { + b.iter(|| { + let w = build_sample_rasn(); + let _ = uper::encode(&w).unwrap(); + }) + }); +} +fn oer_rasn_enc(c: &mut Criterion) { + c.bench_function("RASN/encode OER - sample.asn", |b| { + b.iter(|| { + let w = build_sample_rasn(); + let _ = oer::encode(&w).unwrap(); + }) + }); +} +fn ber_rasn_enc(c: &mut Criterion) { + c.bench_function("RASN/encode BER - sample.asn", |b| { + b.iter(|| { + let w = build_sample_rasn(); + let _ = ber::encode(&w).unwrap(); + }) + }); +} + +fn uper_rasn_dec(c: &mut Criterion) { + let w = build_sample_rasn(); + let encoded = uper::encode(&w).unwrap(); + + c.bench_function("RASN/decode UPER - sample.asn", |b| { + b.iter(|| { + let _ = uper::decode::(&encoded).unwrap(); + }) + }); +} +fn oer_rasn_dec(c: &mut Criterion) { + let w = build_sample_rasn(); + let encoded = oer::encode(&w).unwrap(); + + c.bench_function("RASN/decode OER - sample.asn", |b| { + b.iter(|| { + let _ = oer::decode::(&encoded).unwrap(); + }) + }); +} +fn ber_rasn_dec(c: &mut Criterion) { + let w = build_sample_rasn(); + let encoded = ber::encode(&w).unwrap(); + + c.bench_function("RASN/decode BER - sample.asn", |b| { + b.iter(|| { + let _ = ber::decode::(&encoded).unwrap(); + }) + }); +} + +criterion_group!( + benches, + uper_rasn_enc, + uper_rasn_dec, + oer_rasn_enc, + oer_rasn_dec, + ber_rasn_enc, + ber_rasn_dec +); +criterion_main!(benches); diff --git a/src/ber/enc.rs b/src/ber/enc.rs index 25956959..b2f2e53d 100644 --- a/src/ber/enc.rs +++ b/src/ber/enc.rs @@ -11,7 +11,7 @@ use crate::{ types::{ self, oid::{MAX_OID_FIRST_OCTET, MAX_OID_SECOND_OCTET}, - Constraints, Enumerated, Tag, + Constraints, Enumerated, IntegerType, Tag, }, Codec, Encode, }; @@ -377,16 +377,17 @@ impl crate::Encoder for Encoder { value: &E, ) -> Result { let value = E::discriminant(value); - self.encode_integer(tag, <_>::default(), &value.into()) + self.encode_integer(tag, <_>::default(), &value) } - fn encode_integer( + fn encode_integer( &mut self, tag: Tag, _constraints: Constraints, - value: &num_bigint::BigInt, + value: &I, ) -> Result { - self.encode_primitive(tag, &value.to_signed_bytes_be()); + let (bytes, needed) = value.to_signed_bytes_be(); + self.encode_primitive(tag, &bytes.as_ref()[..needed]); Ok(()) } diff --git a/src/bits.rs b/src/bits.rs index f7ee9af4..124a19b1 100644 --- a/src/bits.rs +++ b/src/bits.rs @@ -1,9 +1,7 @@ //! Module for different bit modification functions which are used in the library. -use core::cmp::Ordering; - use alloc::vec::Vec; -use num_traits::{Signed, Zero}; +use core::cmp::Ordering; pub(crate) fn range_from_len(bit_length: u32) -> i128 { 2i128.pow(bit_length) - 1 @@ -34,13 +32,3 @@ pub(crate) fn octet_string_ascending(a: &Vec, b: &Vec) -> Ordering { } a.len().cmp(&b.len()) } - -pub fn integer_to_bytes(value: &crate::prelude::Integer, signed: bool) -> Option> { - if signed { - Some(value.to_signed_bytes_be()) - } else if !signed && (value.is_positive() || value.is_zero()) { - Some(value.to_biguint()?.to_bytes_be()) - } else { - None - } -} diff --git a/src/de.rs b/src/de.rs index faacf5ce..61ba5478 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,6 +1,7 @@ //! Generic ASN.1 decoding framework. use alloc::{boxed::Box, vec::Vec}; +use num_bigint::BigInt; use crate::error::DecodeError; use crate::types::{self, AsnType, Constraints, Enumerated, Tag}; @@ -396,12 +397,16 @@ impl_integers! { i16, i32, i64, + i128, isize, u8, u16, u32, u64, + // TODO cannot support u128 as it is constrained type by default and current constraints uses i128 for bounds + // u128, usize, + BigInt } impl Decode for types::ConstrainedInteger { @@ -422,7 +427,7 @@ impl Decode for types::Integer { tag: Tag, constraints: Constraints, ) -> Result { - decoder.decode_integer(tag, constraints) + decoder.decode_integer::(tag, constraints) } } diff --git a/src/enc.rs b/src/enc.rs index c47c7be7..07bfe3b0 100644 --- a/src/enc.rs +++ b/src/enc.rs @@ -1,7 +1,8 @@ //! Generic ASN.1 encoding framework. -use crate::types::{self, AsnType, Constraints, Enumerated, Tag}; +use crate::types::{self, AsnType, Constraints, Enumerated, IntegerType, Tag}; +use num_bigint::BigInt; pub use rasn_derive::Encode; /// A **data type** that can be encoded to a ASN.1 data format. @@ -80,11 +81,11 @@ pub trait Encoder { ) -> Result; /// Encode a `INTEGER` value. - fn encode_integer( + fn encode_integer( &mut self, tag: Tag, constraints: Constraints, - value: &num_bigint::BigInt, + value: &I, ) -> Result; /// Encode a `NULL` value. @@ -461,7 +462,7 @@ macro_rules! impl_integers { encoder.encode_integer( tag, constraints, - &(*self).into() + self ).map(drop) } } @@ -474,15 +475,18 @@ impl_integers! { i16, i32, i64, + i128, isize, u8, u16, u32, u64, + // TODO cannot support u128 as it is constrained type by default and current constraints uses i128 for bounds + u128, usize } -impl Encode for types::ConstrainedInteger { +impl Encode for BigInt { fn encode_with_tag_and_constraints( &self, encoder: &mut E, @@ -493,6 +497,17 @@ impl Encode for types::ConstrainedInteger Encode for types::ConstrainedInteger { + fn encode_with_tag_and_constraints( + &self, + encoder: &mut E, + tag: Tag, + constraints: Constraints, + ) -> Result<(), E::Error> { + encoder.encode_integer(tag, constraints, &**self).map(drop) + } +} + impl Encode for types::Integer { fn encode_with_tag_and_constraints( &self, diff --git a/src/error/decode.rs b/src/error/decode.rs index c468fb86..1486e59c 100644 --- a/src/error/decode.rs +++ b/src/error/decode.rs @@ -11,8 +11,9 @@ use snafu::Snafu; use snafu::{Backtrace, GenerateImplicitData}; use crate::de::Error; -use crate::types::{constraints::Bounded, variants::Variants, Integer, Tag}; +use crate::types::{constraints::Bounded, variants::Variants, Tag}; use crate::Codec; +use num_bigint::BigInt; /// Variants for every codec-specific `DecodeError` kind. #[derive(Debug)] @@ -172,7 +173,7 @@ impl DecodeError { } #[must_use] pub fn value_constraint_not_satisfied( - value: Integer, + value: BigInt, expected: Bounded, codec: Codec, ) -> Self { @@ -267,7 +268,11 @@ impl DecodeError { ) } #[must_use] - pub fn choice_index_exceeds_platform_width(needed: u32, present: u64, codec: Codec) -> Self { + pub fn choice_index_exceeds_platform_width( + needed: u32, + present: DecodeError, + codec: Codec, + ) -> Self { Self::from_kind( DecodeErrorKind::ChoiceIndexExceedsPlatformWidth { needed, present }, codec, @@ -372,7 +377,7 @@ pub enum DecodeErrorKind { #[snafu(display("Value constraint not satisfied: expected: {expected}; actual: {value}"))] ValueConstraintNotSatisfied { /// Actual value of the data - value: Integer, + value: BigInt, /// Expected value by the constraint expected: Bounded, }, @@ -401,8 +406,8 @@ pub enum DecodeErrorKind { ChoiceIndexExceedsPlatformWidth { /// Amount of bytes needed. needed: u32, - /// Amount of bytes needed. - present: u64, + /// Inner error + present: DecodeError, }, #[snafu(display("Custom: {}", msg))] Custom { diff --git a/src/error/encode.rs b/src/error/encode.rs index d2560213..8fe8ffe2 100644 --- a/src/error/encode.rs +++ b/src/error/encode.rs @@ -1,5 +1,5 @@ -use crate::prelude::Integer; use crate::types::constraints::{Bounded, Size}; +use num_bigint::BigInt; use snafu::Snafu; #[cfg(feature = "backtraces")] use snafu::{Backtrace, GenerateImplicitData}; @@ -147,7 +147,7 @@ impl EncodeError { } #[must_use] pub fn value_constraint_not_satisfied( - value: Integer, + value: BigInt, expected: &Bounded, codec: crate::Codec, ) -> Self { @@ -258,7 +258,7 @@ pub enum EncodeErrorKind { #[snafu(display("Value constraint not satisfied: expected: {expected}; actual: {value}"))] ValueConstraintNotSatisfied { /// Actual value of the data - value: Integer, + value: BigInt, /// Expected value by the constraint expected: Bounded, }, @@ -322,7 +322,7 @@ pub enum JerEncodeErrorKind { #[snafu(display("Exceeds supported integer range -2^63..2^63 ({:?}).", value))] ExceedsSupportedIntSize { /// value failed to encode - value: num_bigint::BigInt, + value: BigInt, }, #[snafu(display("Invalid character: {:?}", error))] InvalidCharacter { diff --git a/src/jer/enc.rs b/src/jer/enc.rs index 9bfce7ae..ec5bae4a 100644 --- a/src/jer/enc.rs +++ b/src/jer/enc.rs @@ -5,7 +5,7 @@ use jzon::{object::Object, JsonValue}; use crate::{ enc::Error, error::{EncodeError, JerEncodeErrorKind}, - types::{fields::Fields, variants}, + types::{fields::Fields, variants, IntegerType}, }; pub struct Encoder { @@ -131,19 +131,20 @@ impl crate::Encoder for Encoder { )) } - fn encode_integer( + fn encode_integer( &mut self, _t: crate::Tag, _c: crate::types::Constraints, - value: &num_bigint::BigInt, + value: &I, ) -> Result { - let as_i64: i64 = - value - .try_into() - .map_err(|_| JerEncodeErrorKind::ExceedsSupportedIntSize { - value: value.clone(), - })?; - self.update_root_or_constructed(JsonValue::Number(as_i64.into())) + if let Some(as_i64) = value.to_i64() { + self.update_root_or_constructed(JsonValue::Number(as_i64.into())) + } else { + Err(JerEncodeErrorKind::ExceedsSupportedIntSize { + value: value.to_bigint().unwrap_or_default().into(), + } + .into()) + } } fn encode_null(&mut self, _: crate::Tag) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 4e92c54c..fbecb798 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,7 +166,7 @@ mod tests { } } - codecs!(uper, aper); + codecs!(uper, aper, oer, coer, ber); } #[test] @@ -204,20 +204,23 @@ mod tests { i16, i32, i64, + // i128, TODO i128 does not work for UPER/APER isize, u8, u16, u32, u64, + // TODO cannot support u128 as it is constrained type by default and current constraints uses i128 for bounds + // u128, usize } #[test] fn integer() { - round_trip(&Integer::from(89)); - round_trip(&Integer::from(256)); - round_trip(&Integer::from(u64::MAX)); - round_trip(&Integer::from(i64::MIN)); + round_trip(&89); + round_trip(&256); + round_trip(&u64::MAX); + round_trip(&i64::MIN); } #[test] @@ -241,7 +244,7 @@ mod tests { constraints: Constraints, ) -> Result<(), E::Error> { encoder - .encode_integer(tag, constraints, &self.0.into()) + .encode_integer::(tag, constraints, &self.0.into()) .map(drop) } } diff --git a/src/oer/de.rs b/src/oer/de.rs index dacd8463..a636c796 100644 --- a/src/oer/de.rs +++ b/src/oer/de.rs @@ -248,21 +248,20 @@ impl<'input> Decoder<'input> { if let Some(value) = constraints.value() { ranges::determine_integer_size_and_sign(&value, self.input, |_, sign, octets| { let integer = self.decode_integer_from_bytes::(sign, octets.map(usize::from))?; - // if the value is too large for a i128, the constraint isn't satisfied - let constraint_integer = integer.clone().try_into().map_err(|_| { - DecodeError::value_constraint_not_satisfied( - integer.clone().into(), - value.constraint.0, - self.codec(), - ) - })?; - - if value.constraint.contains(&constraint_integer) { - Ok(integer) + if let Some(constraint_integer) = integer.to_i128() { + if value.constraint.contains(&constraint_integer) { + Ok(integer) + } else { + Err(DecodeError::value_constraint_not_satisfied( + integer.to_bigint().unwrap_or_default(), + value.constraint.0, + self.codec(), + )) + } } else { Err(DecodeError::value_constraint_not_satisfied( - integer.into(), + integer.to_bigint().unwrap_or_default(), value.constraint.0, self.codec(), )) diff --git a/src/oer/enc.rs b/src/oer/enc.rs index 05bebf9a..ef891cd9 100644 --- a/src/oer/enc.rs +++ b/src/oer/enc.rs @@ -1,16 +1,16 @@ -use alloc::{string::ToString, vec::Vec}; +use alloc::vec::Vec; use crate::oer::ranges; use crate::prelude::{ Any, BitStr, BmpString, Choice, Constructed, Enumerated, GeneralString, GeneralizedTime, Ia5String, NumericString, PrintableString, SetOf, TeletexString, UtcTime, VisibleString, }; +use crate::types::IntegerType; use crate::Codec; use bitvec::prelude::*; -use num_traits::{Signed, ToPrimitive}; +use num_traits::ToPrimitive; -use crate::types; -use crate::types::{fields::FieldPresence, BitString, Constraints, Integer}; +use crate::types::{fields::FieldPresence, BitString, Constraints, Date}; use crate::{Encode, Tag}; /// ITU-T X.696 (02/2021) version of (C)OER encoding @@ -219,14 +219,8 @@ impl Encoder { buffer: &mut Vec, value: isize, ) -> Result<(), EncodeError> { - let bytes = - crate::bits::integer_to_bytes(&Integer::from(value), true).ok_or_else(|| { - EncodeError::integer_type_conversion_failed( - "Unconstrained enumerated index conversion failed".to_string(), - self.codec(), - ) - })?; - let mut length = u8::try_from(bytes.len()).map_err(|err| { + let (bytes, needed) = value.to_signed_bytes_be(); + let mut length = u8::try_from(needed).map_err(|err| { EncodeError::integer_type_conversion_failed( alloc::format!( "Length of length conversion failed when encoding enumerated index.\ @@ -239,7 +233,7 @@ impl Encoder { // There seems to be an error in standard. It states that enumerated index can be // between –2^1015 and 2^1015 – 1, but then it limits the amount of subsequent bytes to 127 return Err(CoerEncodeErrorKind::TooLongValue { - length: bytes.len() as u128, + length: needed as u128, } .into()); } @@ -247,7 +241,7 @@ impl Encoder { // It is always zero by default with u8 type when value being < 128 length |= 0b_1000_0000; buffer.extend(&length.to_be_bytes()); - buffer.extend(&bytes); + buffer.extend(&bytes.as_ref()[..needed]); Ok(()) } /// Encode the length of the value to output. @@ -255,20 +249,13 @@ impl Encoder { /// /// COER tries to use the shortest possible encoding and avoids leading zeros. fn encode_length(&mut self, buffer: &mut Vec, length: usize) -> Result<(), EncodeError> { - let bytes = - crate::bits::integer_to_bytes(&Integer::from(length), false).ok_or_else(|| { - EncodeError::integer_type_conversion_failed( - "For unknown reason, length conversion failed when encoding length".to_string(), - self.codec(), - ) - })?; - + let (bytes, needed) = length.to_unsigned_bytes_be(); if length < 128 { // First bit should be always zero when below 128: ITU-T X.696 8.6.4 - buffer.extend(&bytes); + buffer.extend(&bytes.as_ref()[..needed]); return Ok(()); } - let mut length_of_length = u8::try_from(bytes.len()).map_err(|err| { + let mut length_of_length = u8::try_from(needed).map_err(|err| { EncodeError::integer_type_conversion_failed( alloc::format!("Length of length conversion failed: {err}"), self.codec(), @@ -284,27 +271,27 @@ impl Encoder { // It is always zero by default with u8 type when value being < 128 length_of_length |= 0b_1000_0000; buffer.extend(&length_of_length.to_be_bytes()); - buffer.extend(&bytes); + buffer.extend(&bytes.as_ref()[..needed]); Ok(()) } /// Encode integer `value_to_enc` with length determinant /// Either as signed or unsigned, set by `signed` - fn encode_unconstrained_integer( + fn encode_unconstrained_integer( &mut self, - value_to_enc: &Integer, + buffer: &mut Vec, + value_to_enc: &I, signed: bool, - ) -> Result, EncodeError> { - let mut buffer = Vec::new(); - let bytes = crate::bits::integer_to_bytes(value_to_enc, signed).ok_or_else(|| { - EncodeError::integer_type_conversion_failed( - "Negative integer value has been provided to be converted into unsigned bytes" - .to_string(), - self.codec(), - ) - })?; - self.encode_length(&mut buffer, bytes.len())?; - buffer.extend(bytes); - Ok(buffer) + ) -> Result<(), EncodeError> { + if signed { + let (bytes, needed) = value_to_enc.to_signed_bytes_be(); + self.encode_length(buffer, needed)?; + buffer.extend(&bytes.as_ref()[..needed]); + } else { + let (bytes, needed) = value_to_enc.to_unsigned_bytes_be(); + self.encode_length(buffer, needed)?; + buffer.extend(&bytes.as_ref()[..needed]); + }; + Ok(()) } /// Encode an integer value with constraints. @@ -317,18 +304,18 @@ impl Encoder { /// type with an extensible OER-visible constraint. Such a type is encoded as an integer type with no bounds. /// /// If the Integer is not bound or outside of range, we encode with the smallest number of octets possible. - fn encode_integer_with_constraints( + fn encode_integer_with_constraints( &mut self, tag: Tag, constraints: &Constraints, - value_to_enc: &Integer, + value_to_enc: &I, ) -> Result<(), EncodeError> { - let mut buffer = Vec::new(); + let mut buffer = Vec::with_capacity(8); if let Some(value) = constraints.value() { - if !value.constraint.0.bigint_contains(value_to_enc) && value.extensible.is_none() { + if !value.constraint.0.in_bound(value_to_enc) && value.extensible.is_none() { return Err(EncodeError::value_constraint_not_satisfied( - value_to_enc.clone(), + value_to_enc.to_bigint().unwrap_or_default(), &value.constraint.0, self.codec(), )); @@ -337,23 +324,21 @@ impl Encoder { &value, value_to_enc, |value_to_enc, sign, octets| -> Result<(), EncodeError> { - let bytes: Vec; if let Some(octets) = octets { - bytes = self.encode_constrained_integer_with_padding( + self.encode_constrained_integer_with_padding( + &mut buffer, usize::from(octets), value_to_enc, sign, )?; } else { - bytes = self.encode_unconstrained_integer(value_to_enc, sign)?; + self.encode_unconstrained_integer(&mut buffer, value_to_enc, sign)?; } - buffer.extend(bytes); Ok(()) }, )?; } else { - let bytes = self.encode_unconstrained_integer(value_to_enc, true)?; - buffer.extend(bytes); + self.encode_unconstrained_integer(&mut buffer, value_to_enc, true)?; } self.extend(tag, buffer)?; Ok(()) @@ -361,36 +346,40 @@ impl Encoder { /// When range constraints are present, the integer is encoded as a fixed-size number. /// This means that the zero padding is possible even with COER encoding. - fn encode_constrained_integer_with_padding( + fn encode_constrained_integer_with_padding( &mut self, + buffer: &mut Vec, octets: usize, - value: &Integer, + value: &I, signed: bool, - ) -> Result, EncodeError> { + ) -> Result<(), EncodeError> { use core::cmp::Ordering; if octets > 8 { return Err(CoerEncodeErrorKind::InvalidConstrainedIntegerOctetSize.into()); } + let signed_ref; + let unsigned_ref; + let needed: usize; let bytes = if signed { - value.to_signed_bytes_be() + (signed_ref, needed) = value.to_signed_bytes_be(); + signed_ref.as_ref() } else { - value.to_biguint().unwrap().to_bytes_be() + (unsigned_ref, needed) = value.to_unsigned_bytes_be(); + unsigned_ref.as_ref() }; - let mut buffer: Vec = Vec::new(); - - match octets.cmp(&bytes.len()) { + match octets.cmp(&needed) { Ordering::Greater => { if signed && value.is_negative() { // 2's complement - buffer.extend(core::iter::repeat(0xff).take(octets - bytes.len())); + buffer.extend(core::iter::repeat(0xff).take(octets - needed)); } else { - buffer.extend(core::iter::repeat(0x00).take(octets - bytes.len())); + buffer.extend(core::iter::repeat(0x00).take(octets - needed)); } } Ordering::Less => { return Err(EncodeError::from_kind( EncodeErrorKind::MoreBytesThanExpected { - value: bytes.len(), + value: needed, expected: octets, }, self.codec(), @@ -399,8 +388,8 @@ impl Encoder { // As is Ordering::Equal => {} }; - buffer.extend(bytes); - Ok(buffer) + buffer.extend(&bytes[..needed]); + Ok(()) } fn check_fixed_size_constraint( &self, @@ -661,8 +650,7 @@ impl crate::Encoder for Encoder { let number = value.discriminant(); let mut buffer = Vec::new(); if 0isize <= number && number <= i8::MAX.into() { - let bytes = self.encode_constrained_integer_with_padding(1, &number.into(), false)?; - buffer.extend(bytes); + self.encode_constrained_integer_with_padding(&mut buffer, 1, &number, false)?; } else { // Value is signed here as defined in section 11.4 // Long form but different from regular length determinant encoding @@ -687,11 +675,11 @@ impl crate::Encoder for Encoder { Ok(()) } - fn encode_integer( + fn encode_integer( &mut self, tag: Tag, constraints: Constraints, - value: &Integer, + value: &I, ) -> Result { self.set_bit(tag, true); self.encode_integer_with_constraints(tag, &constraints, value) @@ -829,7 +817,7 @@ impl crate::Encoder for Encoder { ) } - fn encode_date(&mut self, tag: Tag, value: &types::Date) -> Result { + fn encode_date(&mut self, tag: Tag, value: &Date) -> Result { self.set_bit(tag, true); self.encode_octet_string( tag, @@ -874,8 +862,7 @@ impl crate::Encoder for Encoder { // It seems that constraints here are not C/OER visible? No mention in standard... self.set_bit(tag, true); let mut buffer = Vec::new(); - let value_len_bytes = self.encode_unconstrained_integer(&value.len().into(), false)?; - buffer.extend(value_len_bytes); + self.encode_unconstrained_integer(&mut buffer, &value.len(), false)?; for one in value { let mut encoder = Self::new(self.options); E::encode(one, &mut encoder)?; @@ -1029,8 +1016,7 @@ mod tests { let value_range = &[Constraint::Value(Extensible::new(Value::new(range_bound)))]; let consts = Constraints::new(value_range); let mut encoder = Encoder::default(); - let result = - encoder.encode_integer_with_constraints(Tag::INTEGER, &consts, &BigInt::from(244)); + let result = encoder.encode_integer_with_constraints(Tag::INTEGER, &consts, &244); assert!(result.is_ok()); let v = vec![244u8]; assert_eq!(encoder.output, v); @@ -1066,7 +1052,7 @@ mod tests { // Signed integer with byte length of 128 // Needs long form to represent - let number = BigInt::from(256).pow(127) - 1; + let number: BigInt = BigInt::from(256).pow(127) - 1; let result = encoder.encode_integer_with_constraints(Tag::INTEGER, &constraints, &number); assert!(result.is_ok()); let vc = [ @@ -1086,6 +1072,7 @@ mod tests { #[test] fn test_choice() { use crate as rasn; + use crate::types::Integer; #[derive(AsnType, Decode, Debug, Encode, PartialEq)] #[rasn(choice, automatic_tags)] #[non_exhaustive] diff --git a/src/per/de.rs b/src/per/de.rs index b4d953c3..de25b493 100644 --- a/src/per/de.rs +++ b/src/per/de.rs @@ -2,6 +2,7 @@ use alloc::{collections::VecDeque, string::ToString, vec::Vec}; use bitvec::field::BitField; use super::{FOURTY_EIGHT_K, SIXTEEN_K, SIXTY_FOUR_K, THIRTY_TWO_K}; +use crate::types::IntegerType; use crate::{ de::Error as _, types::{ @@ -345,15 +346,14 @@ impl<'input> Decoder<'input> { Ok(boolean[0]) } - fn parse_normally_small_integer(&mut self) -> Result { + fn parse_normally_small_integer(&mut self) -> Result { let is_large = self.parse_one_bit()?; let constraints = if is_large { constraints::Value::new(constraints::Bounded::start_from(0)).into() } else { constraints::Value::new(constraints::Bounded::new(0, 63)).into() }; - - self.parse_integer(Constraints::new(&[constraints])) + self.parse_integer::(Constraints::new(&[constraints])) } fn parse_non_negative_binary_integer( @@ -383,8 +383,7 @@ impl<'input> Decoder<'input> { vec_bytes } }; - - I::try_from_unsigned_bytes(&data.as_raw_slice(), self.codec()) + I::try_from_unsigned_bytes(data.as_raw_slice(), self.codec()) } fn parse_integer(&mut self, constraints: Constraints) -> Result { @@ -399,22 +398,23 @@ impl<'input> Decoder<'input> { const K64: i128 = SIXTY_FOUR_K as i128; const OVER_K64: i128 = K64 + 1; + // Doing .map_error here causes 5% performance regression for unknown reason + // It would make code cleaner though + let minimum: I = match value_constraint.constraint.minimum().try_into() { + Ok(value) => value, + Err(_) => return Err(DecodeError::integer_overflow(I::WIDTH, self.codec())), + }; + let number = if let Some(range) = value_constraint.constraint.range() { match (self.options.aligned, range) { - (_, 0) => { - return value_constraint - .constraint - .minimum() - .try_into() - .map_err(|_| DecodeError::integer_overflow(I::WIDTH, self.codec())) - } + (_, 0) => return Ok(minimum), (true, 256) => { self.input = self.parse_padding(self.input)?; - self.parse_non_negative_binary_integer(range)? + self.parse_non_negative_binary_integer::(range)? } (true, 257..=K64) => { self.input = self.parse_padding(self.input)?; - self.parse_non_negative_binary_integer(K64)? + self.parse_non_negative_binary_integer::(K64)? } (true, OVER_K64..) => { let range_len_in_bytes = @@ -430,27 +430,25 @@ impl<'input> Decoder<'input> { .ok_or_else(|| { DecodeError::exceeds_max_length(u32::MAX.into(), self.codec()) })?; - self.parse_non_negative_binary_integer(crate::bits::range_from_len(range))? + self.parse_non_negative_binary_integer::( + crate::bits::range_from_len(range), + )? } - (_, _) => self.parse_non_negative_binary_integer(range)?, + (_, _) => self.parse_non_negative_binary_integer::(range)?, } } else { let bytes = &self.decode_octets()?; - - value_constraint + let number = value_constraint .constraint .as_start() - .map(|_| I::try_from_unsigned_bytes(&bytes.as_raw_slice(), self.codec())) - .unwrap_or_else(|| I::try_from_signed_bytes(&bytes.as_raw_slice(), self.codec()))? - }; - - let minimum: I = value_constraint - .constraint - .minimum() - .try_into() - .map_err(|_| DecodeError::integer_overflow(I::WIDTH, self.codec()))?; + .map(|_| I::try_from_unsigned_bytes(bytes.as_raw_slice(), self.codec())) + .unwrap_or_else(|| I::try_from_signed_bytes(bytes.as_raw_slice(), self.codec()))?; - Ok(minimum.wrapping_add(number)) + return minimum + .checked_add(&number) + .ok_or_else(|| DecodeError::integer_overflow(I::WIDTH, self.codec())); + }; + Ok(minimum.wrapping_unsigned_add(number)) } fn parse_extension_header(&mut self) -> Result { @@ -461,13 +459,8 @@ impl<'input> Decoder<'input> { } // The length bitfield has a lower bound of `1..` - let extensions_length = self.parse_normally_small_integer()? + 1; - let (input, bitfield) = - nom::bytes::streaming::take(usize::try_from(extensions_length).map_err( - |e: num_bigint::TryFromBigIntError| { - DecodeError::integer_type_conversion_failed(e.to_string(), self.codec()) - }, - )?)(self.input) + let extensions_length = self.parse_normally_small_integer::()? + 1; + let (input, bitfield) = nom::bytes::streaming::take(extensions_length)(self.input) .map_err(|e| DecodeError::map_nom_err(e, self.codec()))?; self.input = input; @@ -665,11 +658,7 @@ impl<'input> crate::Decoder for Decoder<'input> { .unwrap_or_default(); if extensible { - let index: usize = self.parse_normally_small_integer()?.try_into().map_err( - |e: num_bigint::TryFromBigIntError| { - DecodeError::integer_type_conversion_failed(e.to_string(), self.codec()) - }, - )?; + let index: usize = self.parse_normally_small_integer()?; E::from_extended_enumeration_index(index) .ok_or_else(|| DecodeError::enumeration_index_not_found(index, true, self.codec())) } else { @@ -1006,8 +995,15 @@ impl<'input> crate::Decoder for Decoder<'input> { }); let index = if variants.len() != 1 || is_extensible { - usize::try_from(if is_extensible { - self.parse_normally_small_integer()? + if is_extensible { + self.parse_normally_small_integer::() + .map_err(|error| { + DecodeError::choice_index_exceeds_platform_width( + usize::BITS, + error, + self.codec(), + ) + })? } else { let variance = variants.len(); debug_assert!(variance > 0); @@ -1016,15 +1012,15 @@ impl<'input> crate::Decoder for Decoder<'input> { let choice_range = constraints::Value::new(constraints::Bounded::new(0, (variance - 1) as i128)) .into(); - self.parse_integer(Constraints::new(&[choice_range]))? - }) - .map_err(|error| { - DecodeError::choice_index_exceeds_platform_width( - usize::BITS, - error.into_original().bits(), - self.codec(), - ) - })? + self.parse_integer(Constraints::new(&[choice_range])) + .map_err(|error| { + DecodeError::choice_index_exceeds_platform_width( + usize::BITS, + error, + self.codec(), + ) + })? + } } else { 0 }; diff --git a/src/per/enc.rs b/src/per/enc.rs index bd832803..96963e66 100644 --- a/src/per/enc.rs +++ b/src/per/enc.rs @@ -11,7 +11,7 @@ use crate::{ strings::{ should_be_indexed, BitStr, DynConstrainedCharacterString, StaticPermittedAlphabet, }, - BitString, Constraints, Enumerated, Tag, + BitString, Constraints, Enumerated, IntegerType, Tag, }, Encode, }; @@ -426,9 +426,9 @@ impl Encoder { constraints::Value::new(constraints::Bounded::new(0, 63)).into() }; - self.encode_integer_into_buffer( + self.encode_integer_into_buffer::( Constraints::new(&[size_constraints]), - &value.into(), + &value, buffer, ) } @@ -635,50 +635,69 @@ impl Encoder { Ok(()) } - fn encode_integer_into_buffer( + fn encode_integer_into_buffer( &mut self, constraints: Constraints, - value: &num_bigint::BigInt, + value: &I, buffer: &mut BitString, ) -> Result<()> { let is_extended_value = self.encode_extensible_bit(&constraints, buffer, || { constraints.value().map_or(false, |value_range| { - value_range.extensible.is_some() && value_range.constraint.bigint_contains(value) + value_range.extensible.is_some() && value_range.constraint.in_bound(value) }) }); let value_range = if is_extended_value || constraints.value().is_none() { - let bytes = value.to_signed_bytes_be(); - self.encode_length(buffer, bytes.len(), constraints.size(), |range| { - Ok(BitString::from_slice(&bytes[range])) + let (bytes, needed) = value.to_signed_bytes_be(); + self.encode_length(buffer, needed, constraints.size(), |range| { + Ok(BitString::from_slice(&bytes.as_ref()[..needed][range])) })?; return Ok(()); } else { - constraints.value().unwrap() + // Safe to unwrap because we checked for None above + constraints.value().unwrap_or_default() }; - if !value_range.constraint.bigint_contains(value) && !is_extended_value { + if !value_range.constraint.in_bound(value) && !is_extended_value { return Err(Error::value_constraint_not_satisfied( - value.clone(), + value.to_bigint().unwrap_or_default(), &value_range.constraint, self.codec(), )); } - let bytes = match value_range.constraint.effective_bigint_value(value.clone()) { - either::Left(offset) => offset.to_biguint().unwrap().to_bytes_be(), - either::Right(value) => value.to_signed_bytes_be(), - }; - - let effective_value: i128 = + let effective_range = value_range .constraint - .effective_value(value.try_into().map_err( - |e: num_bigint::TryFromBigIntError<()>| { - Error::integer_type_conversion_failed(e.to_string(), self.codec()) - }, - )?) - .either_into(); + .effective_integer_value(value.to_i128().ok_or_else(|| { + Error::integer_type_conversion_failed( + "Value too large for i128 type - outside of type constraint".to_string(), + self.codec(), + ) + })?); + let unsigned_ref; + let signed_ref; + let needed: usize; + let bytes = match &effective_range { + either::Left(offset) => { + (unsigned_ref, needed) = offset.to_unsigned_bytes_be(); + unsigned_ref.as_ref() + } + either::Right(value) => { + (signed_ref, needed) = value.to_signed_bytes_be(); + signed_ref.as_ref() + } + }; + + let effective_value: i128 = value_range + .constraint + .effective_value(value.to_i128().ok_or_else(|| { + Error::integer_type_conversion_failed( + "Value too large for i128 type - outside of type constraint".to_string(), + self.codec(), + ) + })?) + .either_into(); const K64: i128 = SIXTY_FOUR_K as i128; const OVER_K64: i128 = K64 + 1; @@ -687,11 +706,11 @@ impl Encoder { match (self.options.aligned, range) { (true, 256) => { self.pad_to_alignment(buffer); - self.encode_non_negative_binary_integer(buffer, range, &bytes) + self.encode_non_negative_binary_integer(buffer, range, &bytes[..needed]) } (true, 257..=K64) => { self.pad_to_alignment(buffer); - self.encode_non_negative_binary_integer(buffer, K64, &bytes); + self.encode_non_negative_binary_integer(buffer, K64, &bytes[..needed]); } (true, OVER_K64..) => { let range_len_in_bytes = @@ -704,7 +723,11 @@ impl Encoder { &[0], ); self.pad_to_alignment(&mut *buffer); - self.encode_non_negative_binary_integer(&mut *buffer, 255, &bytes); + self.encode_non_negative_binary_integer( + &mut *buffer, + 255, + &bytes[..needed], + ); } else { let range_value_in_bytes = num_integer::div_ceil(crate::num::log2(effective_value + 1), 8) as i128; @@ -717,15 +740,15 @@ impl Encoder { self.encode_non_negative_binary_integer( &mut *buffer, crate::bits::range_from_len(range_value_in_bytes as u32 * 8), - &bytes, + &bytes[..needed], ); } } - (_, _) => self.encode_non_negative_binary_integer(buffer, range, &bytes), + (_, _) => self.encode_non_negative_binary_integer(buffer, range, &bytes[..needed]), } } else { - self.encode_length(buffer, bytes.len(), <_>::default(), |range| { - Ok(BitString::from_slice(&bytes[range])) + self.encode_length(buffer, needed, <_>::default(), |range| { + Ok(BitString::from_slice(&bytes[..needed][range])) })?; } @@ -863,11 +886,11 @@ impl crate::Encoder for Encoder { Ok(()) } - fn encode_integer( + fn encode_integer( &mut self, tag: Tag, constraints: Constraints, - value: &num_bigint::BigInt, + value: &I, ) -> Result { self.set_bit(tag, true)?; let mut buffer = BitString::new(); @@ -1185,7 +1208,7 @@ impl crate::Encoder for Encoder { (variance - 1) as i128, )) .into()]; - self.encode_integer_into_buffer( + self.encode_integer_into_buffer::( Constraints::from(choice_range), &index.into(), &mut buffer, @@ -1398,7 +1421,7 @@ mod tests { constraints: Constraints, ) -> Result<(), E::Error> { encoder - .encode_integer(tag, constraints, &self.0.into()) + .encode_integer::(tag, constraints, &self.0.into()) .map(drop) } } @@ -1421,7 +1444,7 @@ mod tests { fn semi_constrained_integer() { let mut encoder = Encoder::new(EncoderOptions::unaligned()); encoder - .encode_integer( + .encode_integer::( Tag::INTEGER, Constraints::from(&[constraints::Value::from(constraints::Bounded::start_from( -1, @@ -1434,7 +1457,7 @@ mod tests { assert_eq!(&[2, 0b00010000, 1], &*encoder.output.clone().into_vec()); encoder.output.clear(); encoder - .encode_integer( + .encode_integer::( Tag::INTEGER, Constraints::from(&[ constraints::Value::from(constraints::Bounded::start_from(1)).into(), @@ -1445,7 +1468,7 @@ mod tests { assert_eq!(&[1, 0b01111110], &*encoder.output.clone().into_vec()); encoder.output.clear(); encoder - .encode_integer( + .encode_integer::( Tag::INTEGER, Constraints::from(&[ constraints::Value::from(constraints::Bounded::start_from(0)).into(), diff --git a/src/types.rs b/src/types.rs index 9e194a04..1445686d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -14,17 +14,18 @@ pub mod fields; pub mod variants; pub(crate) mod date; +pub(crate) mod integer; pub(crate) mod oid; pub(crate) mod strings; use alloc::boxed::Box; -use num_bigint::BigUint; pub use { self::{ any::Any, constraints::{Constraint, Constraints, Extensible}, instance::InstanceOf, + integer::{ConstrainedInteger, Integer, IntegerType}, oid::{ObjectIdentifier, Oid}, open::Open, prefix::{Explicit, Implicit}, @@ -35,7 +36,6 @@ pub use { }, tag::{Class, Tag, TagTree}, }, - num_bigint::BigInt as Integer, rasn_derive::AsnType, }; @@ -81,6 +81,7 @@ pub trait AsnType { /// The associated tag for the type. /// /// **Note** When implementing CHOICE types, this should be set to + /// [`Tag::EOC`] and instead set the [`Self::TAG_TREE`] constant to contain /// all variants. const TAG: Tag; @@ -242,34 +243,6 @@ pub trait Enumerated: Sized + 'static + PartialEq + Copy + core::fmt::Debug { } } -/// A integer which has encoded constraint range between `START` and `END`. -#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub struct ConstrainedInteger(pub(crate) Integer); - -impl AsnType for ConstrainedInteger { - const TAG: Tag = Tag::INTEGER; - const CONSTRAINTS: Constraints<'static> = - Constraints::new(&[constraints::Constraint::Value(Extensible::new( - constraints::Value::new(constraints::Bounded::const_new(START, END)), - ))]); -} - -impl core::ops::Deref for ConstrainedInteger { - type Target = Integer; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl, const START: i128, const END: i128> From - for ConstrainedInteger -{ - fn from(value: T) -> Self { - Self(value.into()) - } -} - macro_rules! asn_type { ($($name:ty: $value:ident),+) => { $( @@ -318,182 +291,11 @@ asn_integer_type! { u16, u32, u64, - u128, + u128, // TODO upper constraint truncated usize, } - -pub trait IntegerType: - Sized - + Clone - + core::fmt::Debug - + TryFrom - + TryFrom - + TryInto - + Into - + num_traits::Num - + num_traits::CheckedAdd -{ - const WIDTH: u32; - - fn try_from_bytes(input: &[u8], codec: crate::Codec) - -> Result; - - fn try_from_signed_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result; - - fn try_from_unsigned_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result; - - // `num_traits::WrappingAdd` is not implemented for `BigInt` - #[doc(hidden)] - fn wrapping_add(self, other: Self) -> Self; -} - -macro_rules! integer_type_decode { - ((signed $t1:ty, $t2:ty), $($ts:tt)*) => { - impl IntegerType for $t1 { - const WIDTH: u32 = <$t1>::BITS; - - fn try_from_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result { - Self::try_from_signed_bytes(input, codec) - } - - fn try_from_signed_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result { - const BYTE_SIZE: usize = (<$t1>::BITS / 8) as usize; - if input.is_empty() { - return Err(crate::error::DecodeError::unexpected_empty_input(codec)); - } - if input.len() > BYTE_SIZE { - return Err(crate::error::DecodeError::integer_overflow(<$t1>::BITS, codec)); - } - - let mut array = [0u8; BYTE_SIZE]; - let pad = if input[0] & 0x80 == 0 { 0 } else { 0xff }; - array[..BYTE_SIZE - input.len()].fill(pad); - array[BYTE_SIZE - input.len()..].copy_from_slice(input); - Ok(Self::from_be_bytes(array)) - } - - fn try_from_unsigned_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result { - Ok(<$t2>::try_from_bytes(input, codec)? as $t1) - } - - fn wrapping_add(self, other: Self) -> Self { - self.wrapping_add(other) - } - } - - integer_type_decode!($($ts)*); - }; - ((unsigned $t1:ty, $t2:ty), $($ts:tt)*) => { - impl IntegerType for $t1 { - const WIDTH: u32 = <$t1>::BITS; - - fn try_from_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result { - Self::try_from_unsigned_bytes(input, codec) - } - - fn try_from_signed_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result { - Ok(<$t2>::try_from_bytes(input, codec)? as $t1) - } - - fn try_from_unsigned_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result { - const BYTE_SIZE: usize = (<$t1>::BITS / 8) as usize; - if input.is_empty() { - return Err(crate::error::DecodeError::unexpected_empty_input(codec)); - } - if input.len() > BYTE_SIZE { - return Err(crate::error::DecodeError::integer_overflow(<$t1>::BITS, codec)); - } - - let mut array = [0u8; BYTE_SIZE]; - array[BYTE_SIZE - input.len()..].copy_from_slice(input); - Ok(Self::from_be_bytes(array)) - } - - fn wrapping_add(self, other: Self) -> Self { - self.wrapping_add(other) - } - } - - integer_type_decode!($($ts)*); - }; - (,) => {}; - () => {}; -} - -integer_type_decode!( - (unsigned u8, i8), - (signed i8, u8), - (unsigned u16, i16), - (signed i16, u16), - (unsigned u32, i32), - (signed i32, u32), - (unsigned u64, i64), - (signed i64, u64), - (unsigned u128, i128), - (signed i128, u128), - (unsigned usize, isize), - (signed isize, usize), -); - -impl IntegerType for Integer { - const WIDTH: u32 = u32::MAX; - - fn try_from_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result { - if input.is_empty() { - return Err(crate::error::DecodeError::unexpected_empty_input(codec)); - } - - Ok(Integer::from_signed_bytes_be(input)) - } - - fn try_from_signed_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result { - Self::try_from_bytes(input, codec) - } - - fn try_from_unsigned_bytes( - input: &[u8], - codec: crate::Codec, - ) -> Result { - if input.is_empty() { - return Err(crate::error::DecodeError::unexpected_empty_input(codec)); - } - - Ok(BigUint::from_bytes_be(input).into()) - } - - fn wrapping_add(self, other: Self) -> Self { - self + other - } +impl AsnType for num_bigint::BigInt { + const TAG: Tag = Tag::INTEGER; } impl AsnType for str { diff --git a/src/types/constraints.rs b/src/types/constraints.rs index 278d0029..ecfe3cc6 100644 --- a/src/types/constraints.rs +++ b/src/types/constraints.rs @@ -1,4 +1,7 @@ +use super::IntegerType; use alloc::borrow::Cow; +use num_bigint::BigInt; +use once_cell::race::OnceBox; #[derive(Debug, Default, Clone)] pub struct Constraints<'constraint>(pub Cow<'constraint, [Constraint]>); @@ -366,7 +369,7 @@ impl Bounded { } } -impl Bounded { +impl Bounded { pub fn as_minimum(&self) -> Option<&T> { match self { Self::Single(value) => Some(value), @@ -378,7 +381,7 @@ impl Bounded { } pub fn minimum(&self) -> T { - self.as_minimum().cloned().unwrap_or_default() + self.as_minimum().copied().unwrap_or_default() } } @@ -397,38 +400,42 @@ impl + num_traits::SaturatingAdd + core::fmt::Debug + Default + Clone + PartialOrd> Bounded { +impl + core::fmt::Debug + Default + core::cmp::PartialOrd> Bounded +where + for<'a> &'a T: core::ops::Sub, +{ /// Returns the effective value which is either the number, or the positive /// offset of that number from the start of the value range. `Either::Left` /// represents the positive offset, and `Either::Right` represents /// the number. pub fn effective_value(&self, value: T) -> either::Either { - match &self { + match self { Self::Range { start: Some(start), .. } => { debug_assert!(&value >= start); - either::Left(value - start.clone()) + either::Left(&value - start) } _ => either::Right(value), } } } -impl + core::fmt::Debug + Default + Clone + PartialOrd> Bounded +impl Bounded where - crate::types::Integer: From, + T: core::ops::Sub + core::fmt::Debug + Default + Clone + PartialOrd, { - /// The same as [`effective_value`] except using [`crate::types::Integer`]. - pub fn effective_bigint_value( - &self, - value: crate::types::Integer, - ) -> either::Either { + /// The same as [`effective_value`] except using [`crate::types::Integer`]. + pub fn effective_integer_value(&self, value: I) -> either::Either + where + I: IntegerType + core::ops::Sub, + I: From + PartialOrd, + { if let Bounded::Range { start: Some(start), .. } = self { - let start = crate::types::Integer::from(start.clone()); + let start = I::from(start.clone()); debug_assert!(value >= start); either::Left(value - start) } else { @@ -474,14 +481,35 @@ impl From> for Constraint { } impl Bounded { - pub fn bigint_contains(&self, element: &crate::types::Integer) -> bool { + /// Returns `true` if the given element is within the bounds of the constraint. + /// Constraint type is `i128` here, so we can make checks based on that. + pub fn in_bound(&self, element: &I) -> bool { match &self { - Self::Single(value) => crate::types::Integer::from(*value) == *element, + Self::Single(value) => { + if let Some(e) = element.to_i128() { + e == *value + } else { + false + } + } Self::Range { start, end } => { - start - .as_ref() - .map_or(true, |&start| element >= &start.into()) - && end.as_ref().map_or(true, |&end| element <= &end.into()) + start.as_ref().map_or(true, |&start| { + if let Some(e) = element.to_i128() { + e >= start + } else if let Some(e) = element.to_bigint() { + e >= BigInt::from(start) + } else { + false + } + }) && end.as_ref().map_or(true, |&end| { + if let Some(e) = element.to_i128() { + e <= end + } else if let Some(e) = element.to_bigint() { + e <= BigInt::from(end) + } else { + false + } + }) } Self::None => true, } diff --git a/src/types/integer.rs b/src/types/integer.rs new file mode 100644 index 00000000..c8d54482 --- /dev/null +++ b/src/types/integer.rs @@ -0,0 +1,849 @@ +use crate::types::{constraints, AsnType, Constraints, Extensible}; +use crate::Tag; +use alloc::boxed::Box; +use core::hash::Hash; +use num_bigint::{BigInt, BigUint, ToBigInt}; +use num_traits::{identities::Zero, Signed, ToBytes, ToPrimitive}; +use num_traits::{CheckedAdd, CheckedSub}; + +/// `Integer`` enum is variable-sized non-constrained integer type which uses `isize` for lower values to optimize performance. +#[derive(Debug, Clone, Ord, PartialOrd)] +pub enum Integer { + Primitive(isize), + Variable(Box), +} + +impl Default for Integer { + fn default() -> Self { + Self::Primitive(isize::default()) + } +} + +impl core::fmt::Display for Integer { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + Self::Primitive(value) => write!(f, "{}", value), + Self::Variable(value) => write!(f, "{}", value), + } + } +} + +impl PartialEq for Integer { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Primitive(lhs), Self::Primitive(rhs)) => lhs == rhs, + (Self::Variable(lhs), Self::Variable(rhs)) => lhs == rhs, + (Self::Primitive(lhs), Self::Variable(rhs)) => { + lhs.to_bigint().unwrap_or_default() == **rhs + } + (Self::Variable(lhs), Self::Primitive(rhs)) => { + **lhs == rhs.to_bigint().unwrap_or_default() + } + } + } +} +impl Eq for Integer {} + +impl Hash for Integer { + fn hash(&self, state: &mut H) { + match self { + Self::Primitive(value) => value.hash(state), + Self::Variable(value) => value.hash(state), + } + } +} + +impl num_traits::CheckedAdd for Integer { + fn checked_add(&self, other: &Self) -> Option { + match (self, other) { + (Self::Primitive(lhs), Self::Primitive(rhs)) => { + let value = lhs.checked_add(rhs); + if value.is_some() { + value.map(Integer::from) + } else { + Some(Self::Variable(Box::new(BigInt::from(*lhs) + *rhs))) + } + } + (Self::Primitive(lhs), Self::Variable(rhs)) => { + Some(Self::Variable(Box::new(&**rhs + lhs))) + } + (Self::Variable(lhs), Self::Primitive(rhs)) => { + Some(Self::Variable(Box::new(&**lhs + *rhs))) + } + (Self::Variable(lhs), Self::Variable(rhs)) => { + Some(Self::Variable(Box::new(&**lhs + &**rhs))) + } + } + } +} + +impl core::ops::Add for Integer { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + ::checked_add(&self, &rhs).unwrap_or_default() + } +} + +macro_rules! impl_ops_integer { + ($($t:ty),*) => { + $( + impl core::ops::Add<$t> for Integer { + type Output = Self; + fn add(self, rhs: $t) -> Self::Output { + match self { + Self::Primitive(lhs) => { + let result = lhs.checked_add(rhs as isize); + match result { + Some(value) => Self::Primitive(value), + None => Self::Variable(Box::new(BigInt::from(lhs) + rhs)), + } + } + Self::Variable(lhs) => { + Self::Variable(Box::new(*lhs + rhs)) + } + } + } + } + impl core::ops::Sub<$t> for Integer { + type Output = Self; + fn sub(self, rhs: $t) -> Self::Output { + match self { + Self::Primitive(lhs) => { + let result = lhs.checked_sub(rhs as isize); + match result { + Some(value) => Self::Primitive(value), + None => Self::Variable(Box::new(BigInt::from(lhs) - rhs)), + } + } + Self::Variable(lhs) => { + Self::Variable(Box::new(*lhs - rhs)) + } + } + } + } + )* + }; +} +macro_rules! impl_ops_integer_big { + ($($t:ty),*) => { + $( + impl core::ops::Add<$t> for Integer { + type Output = Self; + fn add(self, rhs: $t) -> Self::Output { + match self { + Self::Primitive(lhs) => { + let value = isize::try_from(rhs); + if let Ok(rhs) = value { + let result = lhs.checked_add(rhs); + match result { + Some(value) => Self::Primitive(value), + None => Self::Variable(Box::new(BigInt::from(lhs) + rhs)), + } + } else { + Self::Variable(Box::new(BigInt::from(lhs) + rhs)) + } + } + Self::Variable(lhs) => { + Self::Variable(Box::new(*lhs + rhs)) + } + } + } + } + impl core::ops::Sub<$t> for Integer { + type Output = Self; + fn sub(self, rhs: $t) -> Self::Output { + match self { + Self::Primitive(lhs) => { + let value = isize::try_from(rhs); + if let Ok(rhs) = value { + let result = lhs.checked_sub(rhs); + match result { + Some(value) => Self::Primitive(value), + None => Self::Variable(Box::new(BigInt::from(lhs) - rhs)), + } + } else { + Self::Variable(Box::new(BigInt::from(lhs) - rhs)) + } + } + Self::Variable(lhs) => { + Self::Variable(Box::new(*lhs - rhs)) + } + } + } + } + )* + }; +} + +// Safely casted to isize without truncation, used on 32-bit targets +#[cfg(target_pointer_width = "32")] +impl_ops_integer!(u8, u16, i8, i16, i32, isize); +#[cfg(target_pointer_width = "32")] +impl_ops_integer_big!(u32, i64); +// Safely casted to isize without truncation, used on 64-bit targets +#[cfg(target_pointer_width = "64")] +impl_ops_integer!(u8, u16, u32, i8, i16, i32, i64, isize); + +// Never safely casted for isize variant, used on all targets +impl_ops_integer_big!(u64, u128, usize, i128); + +impl num_traits::CheckedSub for Integer { + fn checked_sub(&self, other: &Self) -> Option { + match (self, other) { + (Self::Primitive(lhs), Self::Primitive(rhs)) => { + let value = lhs.checked_sub(rhs); + if value.is_some() { + value.map(Integer::from) + } else { + Some(Self::Variable(Box::new(BigInt::from(*lhs) - *rhs))) + } + } + (Self::Primitive(lhs), Self::Variable(rhs)) => { + Some(Self::Variable(Box::new(BigInt::from(*lhs) - &**rhs))) + } + (Self::Variable(lhs), Self::Primitive(rhs)) => { + Some(Self::Variable(Box::new(&**lhs - *rhs))) + } + (Self::Variable(lhs), Self::Variable(rhs)) => { + Some(Self::Variable(Box::new(&**lhs - &**rhs))) + } + } + } +} + +impl core::ops::Sub for Integer { + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + ::checked_sub(&self, &rhs).unwrap_or_default() + } +} + +impl ToPrimitive for Integer { + fn to_i64(&self) -> Option { + match self { + Self::Primitive(value) => Some(*value as i64), + Self::Variable(value) => value.to_i64(), + } + } + fn to_u64(&self) -> Option { + match self { + Self::Primitive(value) => { + if *value >= 0 { + Some(*value as u64) + } else { + None + } + } + Self::Variable(value) => value.to_u64(), + } + } + fn to_i128(&self) -> Option { + match self { + Self::Primitive(value) => Some(*value as i128), + Self::Variable(value) => value.to_i128(), + } + } +} + +macro_rules! impl_from_integer_as_prim { + ($($t:ty),*) => { + $( + impl From<$t> for Integer { + fn from(value: $t) -> Self { + Self::Primitive(value as isize) + } + } + )* + }; +} + +macro_rules! impl_from_integer_as_big { + ($($t:ty),*) => { + $( + impl From<$t> for Integer { + fn from(value: $t) -> Self { + if let Some(value) = value.to_isize() { + Self::Primitive(value) + } else { + Self::Variable(Box::new((BigInt::from(value)))) + } + } + } + )* + }; +} +#[cfg(target_pointer_width = "32")] +impl_from_integer_as_prim!(u8, u16, i8, i16, i32, isize); +#[cfg(target_pointer_width = "32")] +impl_from_integer_as_big!(u32, i64); +#[cfg(target_pointer_width = "64")] +impl_from_integer_as_prim!(u8, u16, u32, i8, i16, i32, i64, isize); +// Never fit for isize variant, used on all targets +impl_from_integer_as_big!(u64, u128, i128, usize, BigInt); + +impl From for BigInt { + fn from(value: Integer) -> Self { + match value { + Integer::Primitive(value) => value.to_bigint().unwrap_or_default(), + Integer::Variable(value) => *value, + } + } +} + +impl ToBigInt for Integer { + fn to_bigint(&self) -> Option { + match self { + Integer::Primitive(value) => Some(BigInt::from(*value)), + Integer::Variable(value) => Some(*value.clone()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TryFromIntegerError { + original: BigInt, +} + +impl TryFromIntegerError { + fn new(original: BigInt) -> Self { + TryFromIntegerError { original } + } + fn __description(&self) -> &str { + "out of range conversion regarding integer conversion attempted" + } + pub fn into_original(self) -> BigInt { + self.original + } +} + +impl alloc::fmt::Display for TryFromIntegerError { + fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { + self.__description().fmt(f) + } +} +macro_rules! impl_try_from_integer { + ($($t:ty),*) => { + $( + impl core::convert::TryFrom for $t { + type Error = TryFromIntegerError; + fn try_from(value: Integer) -> Result { + Self::try_from(&value) + } + } + impl core::convert::TryFrom<&Integer> for $t { + type Error = TryFromIntegerError; + fn try_from(value: &Integer) -> Result { + match value { + Integer::Primitive(value) => (*value).try_into().map_err(|_| TryFromIntegerError::new(value.to_bigint().unwrap_or_default())), + Integer::Variable(value) => (**value).clone().try_into().map_err(|_| TryFromIntegerError::new(*value.clone())), + } + } + } + )* + }; +} +impl_try_from_integer!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); + +/// An integer which has encoded constraint range between `START` and `END`. +#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct ConstrainedInteger(pub(crate) Integer); + +impl AsnType for ConstrainedInteger { + const TAG: Tag = Tag::INTEGER; + const CONSTRAINTS: Constraints<'static> = + Constraints::new(&[constraints::Constraint::Value(Extensible::new( + constraints::Value::new(constraints::Bounded::const_new(START, END)), + ))]); +} + +impl core::ops::Deref for ConstrainedInteger { + type Target = Integer; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl, const START: i128, const END: i128> From + for ConstrainedInteger +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} + +pub trait IntegerType: + Sized + + Clone + + core::fmt::Debug + + core::fmt::Display + + Default + + TryInto + + TryInto + + TryInto + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + Into + + ToBigInt + + num_traits::CheckedAdd + + num_traits::CheckedSub + + core::cmp::PartialOrd + + core::cmp::PartialEq + + num_traits::ToPrimitive +{ + const WIDTH: u32; + const BYTE_WIDTH: usize = Self::WIDTH as usize / 8; + /// `Self` as an unsigned type with the same width. + type UnsignedPair: IntegerType; + /// `Self` as a signed type with one type size larger to prevent truncation, in case `Self` is unsigned. (e.g. u8 -> i16) + /// Truncation would happen if unsigned type is converted to signed bytes with the same size. + type SignedPair: IntegerType; + + fn try_from_bytes(input: &[u8], codec: crate::Codec) + -> Result; + + fn try_from_signed_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result; + + fn try_from_unsigned_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result; + + /// Returns minimum number defined by `usize` of signed Big-endian bytes needed to present the the. + fn to_signed_bytes_be(&self) -> (impl AsRef<[u8]>, usize); + + /// Returns minimum number defined by `usize` of unsigned Big-endian bytes needed to present the the integer. + fn to_unsigned_bytes_be(&self) -> (impl AsRef<[u8]>, usize); + + /// Makes it possible to add unsigned integer to signed integer + /// This is mainly used on UPER, in order to add unsigned offset into signed constrained minimum to get the resulting value as correct type. + /// Casting will change the "value", but same width makes the result ending to be correct. + fn wrapping_unsigned_add(self, other: Self::UnsignedPair) -> Self; + /// Whether the integer value is negative or not. + fn is_negative(&self) -> bool; + /// Whether the integer type is signed or not. + fn is_signed(&self) -> bool; + /// Convert the underlying integer type into rasn ASN.1 `Integer` type. + fn to_integer(self) -> Integer; +} + +trait MinFixedSizeIntegerBytes: IntegerType + ToBytes { + /// Encode the given `N` sized integer as big-endian bytes and determine the number of bytes needed. + /// We know the maximum size of the integer bytes in compile time, but we don't know the actual size. + /// "Needed"" value defines unnecessary leading zeros or ones on runtime to provide useful integer byte presentation. + /// We can use the same returned value to use only required bytes for encoding. + #[inline(always)] + fn needed_as_be_bytes(&self, signed: bool) -> ([u8; N], usize) { + let bytes: [u8; N] = self.to_le_bytes().as_ref().try_into().unwrap_or([0; N]); + let needed = if signed { + self.signed_bytes_needed() + } else { + self.unsigned_bytes_needed() + }; + let mut slice_reversed: [u8; N] = [0; N]; + // About 2.5x speed when compared to `copy_from_slice` and `.reverse()`, since we don't need all bytes in most cases + for i in 0..needed { + slice_reversed[i] = bytes[needed - 1 - i]; + } + (slice_reversed, needed) + } + /// Finds the minimum number of bytes needed to present the unsigned integer. (in order to drop unecessary leading zeros or ones) + fn unsigned_bytes_needed(&self) -> usize; + + /// Finds the minimum number of bytes needed to present the signed integer. (in order to drop unecessary leading zeros or ones) + fn signed_bytes_needed(&self) -> usize; +} + +macro_rules! integer_type_impl { + ((signed $t1:ty, $t2:ty), $($ts:tt)*) => { + impl IntegerType for $t1 { + const WIDTH: u32 = <$t1>::BITS; + type UnsignedPair = $t2; + type SignedPair = $t1; + + #[inline(always)] + fn try_from_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + Self::try_from_signed_bytes(input, codec) + } + + #[inline(always)] + fn try_from_signed_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + const BYTE_SIZE: usize = (<$t1>::BITS / 8) as usize; + if input.is_empty() { + return Err(crate::error::DecodeError::unexpected_empty_input(codec)); + } + if input.len() > BYTE_SIZE { + return Err(crate::error::DecodeError::integer_overflow(<$t1>::BITS, codec)); + } + + let mut array = [0u8; BYTE_SIZE]; + let pad = if input[0] & 0x80 == 0 { 0 } else { 0xff }; + array[..BYTE_SIZE - input.len()].fill(pad); + array[BYTE_SIZE - input.len()..].copy_from_slice(input); + Ok(Self::from_be_bytes(array)) + } + + #[inline(always)] + fn try_from_unsigned_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + <$t1>::try_from(<$t2>::try_from_bytes(input, codec)?) + .map_err(|_| { + crate::error::DecodeError::integer_type_conversion_failed(alloc::format!("Failed to create signed integer from unsigned bytes, target bit-size {}, with bytes {:?}", <$t1>::BITS, input).into(), codec) + }) + + } + #[inline(always)] + fn to_signed_bytes_be(&self) -> (impl AsRef<[u8]>, usize) { + const N: usize = core::mem::size_of::<$t1>(); + self.needed_as_be_bytes::( true) + } + #[inline(always)] + fn to_unsigned_bytes_be(&self) -> (impl AsRef<[u8]>, usize) { + const N: usize = core::mem::size_of::<$t2>(); + (*self as $t2).needed_as_be_bytes::( false) + } + + fn wrapping_unsigned_add(self, other: $t2) -> Self { + self.wrapping_add(other as $t1) + } + fn is_negative(&self) -> bool { + ::is_negative(self) + } + fn is_signed(&self) -> bool { + true + } + + fn to_integer(self) -> Integer { + if let Some(value) = self.to_isize() { + Integer::Primitive(value) + } else { + Integer::Variable(Box::new(self.to_bigint().unwrap_or_default())) + } + } + } + impl MinFixedSizeIntegerBytes for $t1 { + + #[inline(always)] + fn unsigned_bytes_needed(&self) -> usize { + (*self as $t2).unsigned_bytes_needed() + } + #[inline(always)] + fn signed_bytes_needed(&self) -> usize { + let leading_bits = if Signed::is_negative(self) { + self.leading_ones() as usize + } else { + self.leading_zeros() as usize + }; + let full_bytes = Self::BYTE_WIDTH - leading_bits / 8; + let extra_byte = (leading_bits % 8 == 0) as usize; + full_bytes + extra_byte + + } + } + + integer_type_impl!($($ts)*); + }; + ((unsigned $t1:ty, $t2:ty), $($ts:tt)*) => { + + + + impl IntegerType for $t1 { + const WIDTH: u32 = <$t1>::BITS; + type UnsignedPair = $t1; + type SignedPair = $t2; + + #[inline(always)] + fn try_from_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + Self::try_from_unsigned_bytes(input, codec) + } + + #[inline(always)] + fn try_from_signed_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + <$t1>::try_from(<$t2>::try_from_bytes(input, codec)?) + .map_err(|_| { + crate::error::DecodeError::integer_type_conversion_failed(alloc::format!("Failed to create unsigned integer from signed bytes, target bit-size {}, with bytes: {:?}", <$t1>::BITS, input).into(), codec) + }) + } + + #[inline(always)] + fn try_from_unsigned_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + const BYTE_SIZE: usize = (<$t1>::BITS / 8) as usize; + if input.is_empty() { + return Err(crate::error::DecodeError::unexpected_empty_input(codec)); + } + if input.len() > BYTE_SIZE { + return Err(crate::error::DecodeError::integer_overflow(<$t1>::BITS, codec)); + } + + let mut array = [0u8; BYTE_SIZE]; + array[BYTE_SIZE - input.len()..].copy_from_slice(input); + Ok(Self::from_be_bytes(array)) + } + + // Getting signed bytes of an unsigned integer is challenging, because we don't want to truncate the value + // Signed bytes of u8::MAX for example takes more bytes than unsigned bytes. + // As a result, we cast the type to next fitting signed type if possible and use the size of its to define the sign bytes. + #[inline(always)] + fn to_signed_bytes_be(&self) -> (impl AsRef<[u8]>, usize) { + const N: usize = core::mem::size_of::<$t2>(); + (*self as $t2).needed_as_be_bytes::(true) + } + #[inline(always)] + fn to_unsigned_bytes_be(&self) -> (impl AsRef<[u8]>, usize) { + const N: usize = core::mem::size_of::<$t1>(); + self.needed_as_be_bytes::(false) + } + + fn wrapping_unsigned_add(self, other: $t1) -> Self { + self.wrapping_add(other) + } + fn is_negative(&self) -> bool { + false + } + fn is_signed(&self) -> bool { + false + } + fn to_integer(self) -> Integer { + if let Some(value) = self.to_isize() { + Integer::Primitive(value) + } else { + Integer::Variable(Box::new(self.to_bigint().unwrap_or_default())) + } + } + } + impl MinFixedSizeIntegerBytes for $t1 { + #[inline(always)] + fn unsigned_bytes_needed(&self) -> usize { + if self.is_zero() { + 1 + } else { + let significant_bits = Self::WIDTH as usize - self.leading_zeros() as usize; + (significant_bits + 7) / 8 + } + } + #[inline(always)] + fn signed_bytes_needed(&self) -> usize { + (*self as $t2).signed_bytes_needed() + } + } + + integer_type_impl!($($ts)*); + }; + (,) => {}; + () => {}; +} + +integer_type_impl!( + (unsigned u8, i16), + (signed i8, u8), + (unsigned u16, i32), + (signed i16, u16), + (unsigned u32, i64), + (signed i32, u32), + (unsigned u64, i128), + (signed i64, u64), + // Will truncate on i128 on large numbers + (unsigned u128, i128), + (signed i128, u128), + (unsigned usize, i128), + (signed isize, usize), +); + +impl IntegerType for BigInt { + const WIDTH: u32 = u32::MAX; + type UnsignedPair = Self; + type SignedPair = Self; + + #[inline(always)] + fn try_from_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + if input.is_empty() { + return Err(crate::error::DecodeError::unexpected_empty_input(codec)); + } + + Ok(BigInt::from_signed_bytes_be(input)) + } + + #[inline(always)] + fn try_from_signed_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + Self::try_from_bytes(input, codec) + } + + #[inline(always)] + fn try_from_unsigned_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + if input.is_empty() { + return Err(crate::error::DecodeError::unexpected_empty_input(codec)); + } + + Ok(BigUint::from_bytes_be(input).into()) + } + #[inline(always)] + fn to_signed_bytes_be(&self) -> (impl AsRef<[u8]>, usize) { + let bytes = self.to_signed_bytes_be(); + let len = bytes.len(); + (bytes, len) + } + #[inline(always)] + fn to_unsigned_bytes_be(&self) -> (impl AsRef<[u8]>, usize) { + let bytes = self.to_biguint().unwrap_or_default().to_bytes_be(); + let len = bytes.len(); + (bytes, len) + } + + fn wrapping_unsigned_add(self, other: Self) -> Self { + self + other + } + fn is_negative(&self) -> bool { + ::is_negative(self) + } + fn is_signed(&self) -> bool { + true + } + fn to_integer(self) -> Integer { + Integer::Variable(Box::new(self)) + } +} +/// We cannot use `impl AsRef<[u8]>` as return type for function to return variants' byte presentation +/// when enum variants are different opaque types, unless we use a wrapper. +/// Only needed for our custom `Integer` type. +enum IntegerBytesRef> { + Stack([u8; core::mem::size_of::()]), + Heap(T), +} + +impl> AsRef<[u8]> for IntegerBytesRef { + fn as_ref(&self) -> &[u8] { + match self { + IntegerBytesRef::Stack(arr) => arr, + IntegerBytesRef::Heap(slice) => slice.as_ref(), + } + } +} + +impl IntegerType for Integer { + const WIDTH: u32 = u32::MAX; + type UnsignedPair = Self; + type SignedPair = Self; + + #[inline(always)] + fn try_from_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + if input.is_empty() { + return Err(crate::error::DecodeError::unexpected_empty_input(codec)); + } + let value = isize::try_from_bytes(input, codec); + if let Ok(value) = value { + Ok(Integer::Primitive(value)) + } else { + Ok(Integer::Variable(Box::new(BigInt::try_from_bytes( + input, codec, + )?))) + } + } + + #[inline(always)] + fn try_from_signed_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + Self::try_from_bytes(input, codec) + } + + #[inline(always)] + fn try_from_unsigned_bytes( + input: &[u8], + codec: crate::Codec, + ) -> Result { + if input.is_empty() { + return Err(crate::error::DecodeError::unexpected_empty_input(codec)); + } + let value = isize::try_from_unsigned_bytes(input, codec); + if let Ok(value) = value { + Ok(Integer::Primitive(value)) + } else { + Ok(Integer::Variable(Box::new( + BigInt::try_from_unsigned_bytes(input, codec)?, + ))) + } + } + #[inline(always)] + fn to_signed_bytes_be(&self) -> (impl AsRef<[u8]>, usize) { + match self { + Integer::Primitive(value) => { + let (bytes, len) = ::to_signed_bytes_be(value); + ( + IntegerBytesRef::Stack(bytes.as_ref().try_into().unwrap_or_default()), + len, + ) + } + Integer::Variable(value) => { + let (bytes, len) = ::to_signed_bytes_be(value); + (IntegerBytesRef::Heap(bytes), len) + } + } + } + #[inline(always)] + fn to_unsigned_bytes_be(&self) -> (impl AsRef<[u8]>, usize) { + match self { + Integer::Primitive(value) => { + let (bytes, len) = ::to_unsigned_bytes_be(value); + ( + IntegerBytesRef::Stack(bytes.as_ref().try_into().unwrap_or_default()), + len, + ) + } + Integer::Variable(value) => { + let (bytes, len) = ::to_signed_bytes_be(value); + (IntegerBytesRef::Heap(bytes), len) + } + } + } + + fn wrapping_unsigned_add(self, other: Self) -> Self { + self + other + } + fn is_negative(&self) -> bool { + match self { + Integer::Primitive(value) => ::is_negative(value), + Integer::Variable(value) => ::is_negative(value), + } + } + fn is_signed(&self) -> bool { + true + } + fn to_integer(self) -> Integer { + self + } +} diff --git a/standards/pkix/tests/digicert.rs b/standards/pkix/tests/digicert.rs index 9375b261..86d2b899 100644 --- a/standards/pkix/tests/digicert.rs +++ b/standards/pkix/tests/digicert.rs @@ -24,7 +24,7 @@ fn extensions() { encoder.encode_sequence::(Tag::SEQUENCE, |encoder| { encoder.encode_bool(Tag::BOOL, true)?; - encoder.encode_integer(Tag::INTEGER, <_>::default(), &0u32.into())?; + encoder.encode_integer::(Tag::INTEGER, <_>::default(), &0u32)?; Ok(()) })?; diff --git a/tests/integer.rs b/tests/integer.rs new file mode 100644 index 00000000..ca1dbe2a --- /dev/null +++ b/tests/integer.rs @@ -0,0 +1,151 @@ +macro_rules! assert_integer_round_trip { + ($t:ty, $value:expr, $expected_unsigned:expr, $expected_signed:expr) => {{ + let value = <$t>::try_from($value).unwrap(); + + // Test unsigned bytes + let (unsigned_bytes, unsigned_needed) = value.to_unsigned_bytes_be(); + assert_eq!( + &unsigned_bytes.as_ref()[..unsigned_needed], + $expected_unsigned, + "Unsigned bytes mismatch for {}", + stringify!($value) + ); + + // Only test unsigned round-trip for non-negative values or unsigned types + #[allow(unused_comparisons)] + if $value >= 0 || stringify!($t).starts_with('u') { + assert_eq!( + <$t>::try_from_unsigned_bytes( + &unsigned_bytes.as_ref()[..unsigned_needed], + rasn::Codec::Oer + ) + .ok(), + Some(value), + "Round-trip failed for unsigned bytes of {}", + stringify!($value) + ); + } + + // Test signed bytes + let (signed_bytes, signed_needed) = value.to_signed_bytes_be(); + assert_eq!( + &signed_bytes.as_ref()[..signed_needed], + $expected_signed, + "Signed bytes mismatch for {}", + stringify!($value) + ); + + // Always test signed round-trip + assert_eq!( + <$t>::try_from_signed_bytes(&signed_bytes.as_ref()[..signed_needed], rasn::Codec::Oer) + .ok(), + Some(value), + "Round-trip failed for signed bytes of {}", + stringify!($value) + ); + // Round trip with Integer type should work for any type with all values + let integer = Integer::from($value); + let (bytes, needed) = integer.to_signed_bytes_be(); + assert_eq!( + Integer::try_from_signed_bytes(&bytes.as_ref()[..needed], rasn::Codec::Oer).ok(), + Some($value.into()), + "Round-trip failed for Integer({})", + stringify!($value) + ); + // Check that encoding matches the signed expected bytes for Integer type + assert_eq!( + &bytes.as_ref()[..needed], + $expected_signed, + "Signed bytes mismatch for Integer({})", + stringify!($value) + ); + }}; +} + +macro_rules! test_integer_conversions_and_operations { + ($($t:ident),*) => { + #[cfg(test)] + mod tests { + use rasn::prelude::*; + + $( + #[test] + fn $t() { + let min = <$t>::MIN as i128; + let max = <$t>::MAX as u128; + + if min >= isize::MIN as i128 { + assert!(matches!(min.into(), Integer::Primitive(_))); + } else { + assert!(matches!(min.into(), Integer::Variable(_))); + } + if max <= isize::MAX as u128 { + assert!(matches!(max.into(), Integer::Primitive(_))); + } else { + assert!(matches!(max.into(), Integer::Variable(_))); + } + + // Test positive values + assert_integer_round_trip!($t, 1, &[1], &[1]); + + // Test some signed maximum values, should be identical to unsigned + if <$t>::MAX as u128 >= i8::MAX as u128 { + assert_integer_round_trip!($t, i8::MAX as $t, &[127], &[127]); + } + // Even if the type is wider than 2 bytes, the value remains the same + if <$t>::MAX as u128 >= i16::MAX as u128 { + assert_integer_round_trip!($t, i16::MAX as $t, &[127, 255], &[127, 255]); + // 127 + 1 results to leading zero for signed types + assert_integer_round_trip!($t, 128, &[128], &[0, 128]); + } + if <$t>::MAX as u128 >= i32::MAX as u128 { + assert_integer_round_trip!($t, i32::MAX as $t, &[127, 255, 255, 255], &[127, 255, 255, 255]); + // 32_767 + 1 results to leading zero for signed types + assert_integer_round_trip!($t, (i16::MAX as $t + 1), &[128, 0], &[0, 128, 0]); + } + if <$t>::MAX as u128 >= i64::MAX as u128 { + assert_integer_round_trip!($t, i64::MAX as $t, &[127, 255, 255, 255, 255, 255, 255, 255], &[127, 255, 255, 255, 255, 255, 255, 255]); + } + + + // Test negative values for signed types + match stringify!($t) { + "i8" => { + assert_integer_round_trip!($t, -1, &[255], &[255]); + }, + "i16" => { + assert_integer_round_trip!($t, -1, &[255, 255], &[255]); + }, + "i32" => { + assert_integer_round_trip!($t, -1, &[255, 255, 255, 255], &[255]); + }, + "i64" => { + assert_integer_round_trip!($t, -1, &[255, 255, 255, 255, 255, 255, 255, 255], &[255]); + }, + _ => {}, + } + } + )* + + #[test] + fn test_overflow_addition() { + let max = Integer::Primitive(isize::MAX); + let one = Integer::Primitive(1); + let result = max + one; + assert!(matches!(result, Integer::Variable(_))); + } + + #[test] + fn test_overflow_subtraction() { + let min = Integer::Primitive(isize::MIN); + let one = Integer::Primitive(1); + let result = min - one; + assert!(matches!(result, Integer::Variable(_))); + } + } + }; +} + +test_integer_conversions_and_operations!( + i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize +);