Skip to content

Commit

Permalink
Add basic support for REAL using f32/f64 with JER/OER rules
Browse files Browse the repository at this point in the history
  • Loading branch information
kattenkorven committed Jan 9, 2025
1 parent f028633 commit ddd785e
Show file tree
Hide file tree
Showing 17 changed files with 391 additions and 2 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ chrono = { version = "0.4.38", default-features = false, features = ["alloc"] }
bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] }

[features]
default = ["f32", "f64"]
std = []
f32 = []
f64 = []
backtraces = ["std", "snafu/backtrace"]
compiler = ["rasn-compiler"]

Expand Down
9 changes: 9 additions & 0 deletions src/ber/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,15 @@ impl<'input> crate::Decoder for Decoder<'input> {
}
}

#[cfg(any(feature = "f32", feature = "f64"))]
fn decode_real<R: types::RealType>(
&mut self,
_: Tag,
_: Constraints,
) -> Result<R, Self::Error> {
Err(DecodeError::real_not_supported(self.codec()))
}

fn decode_octet_string<'b, T: From<&'b [u8]> + From<Vec<u8>>>(
&'b mut self,
tag: Tag,
Expand Down
10 changes: 10 additions & 0 deletions src/ber/enc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,16 @@ impl crate::Encoder<'_> for Encoder {
Ok(())
}

#[cfg(any(feature = "f32", feature = "f64"))]
fn encode_real<R: types::RealType>(
&mut self,
_: Tag,
_: Constraints,
_: &R,
) -> Result<Self::Ok, Self::Error> {
Err(EncodeError::real_not_supported(self.codec()))
}

fn encode_null(&mut self, tag: Tag) -> Result<Self::Ok, Self::Error> {
self.encode_primitive(tag, &[]);
Ok(())
Expand Down
31 changes: 31 additions & 0 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ pub trait Decoder<const RCL: usize = 0, const ECL: usize = 0>: Sized {
tag: Tag,
constraints: Constraints,
) -> Result<I, Self::Error>;

/// Decode a `REAL` identified by `tag` from the available input.
#[cfg(any(feature = "f32", feature = "f64"))]
fn decode_real<R: types::RealType>(
&mut self,
tag: Tag,
constraints: Constraints,
) -> Result<R, Self::Error>;

/// Decode `NULL` identified by `tag` from the available input.
fn decode_null(&mut self, tag: Tag) -> Result<(), Self::Error>;
/// Decode a `OBJECT IDENTIFIER` identified by `tag` from the available input.
Expand Down Expand Up @@ -534,6 +543,28 @@ impl Decode for types::Integer {
}
}

#[cfg(feature = "f32")]
impl Decode for f32 {
fn decode_with_tag_and_constraints<D: Decoder>(
decoder: &mut D,
tag: Tag,
_: Constraints,
) -> Result<Self, D::Error> {
decoder.decode_real::<f32>(tag, Constraints::default())
}
}

#[cfg(feature = "f64")]
impl Decode for f64 {
fn decode_with_tag_and_constraints<D: Decoder>(
decoder: &mut D,
tag: Tag,
_: Constraints,
) -> Result<Self, D::Error> {
decoder.decode_real::<f64>(tag, Constraints::default())
}
}

impl<T: Decode> Decode for Box<T> {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, D::Error> {
T::decode(decoder).map(Box::new)
Expand Down
36 changes: 36 additions & 0 deletions src/enc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
use crate::types::{self, AsnType, Constraints, Enumerated, IntegerType, SetOf, Tag};

#[cfg(any(feature = "f32", feature = "f64"))]
use crate::types::RealType;

use num_bigint::BigInt;
pub use rasn_derive::Encode;

Expand Down Expand Up @@ -117,6 +120,15 @@ pub trait Encoder<'encoder, const RCL: usize = 0, const ECL: usize = 0> {
value: &I,
) -> Result<Self::Ok, Self::Error>;

/// Encode a `REAL` value.
#[cfg(any(feature = "f32", feature = "f64"))]
fn encode_real<R: RealType>(
&mut self,
tag: Tag,
constraints: Constraints,
value: &R,
) -> Result<Self::Ok, Self::Error>;

/// Encode a `NULL` value.
fn encode_null(&mut self, tag: Tag) -> Result<Self::Ok, Self::Error>;

Expand Down Expand Up @@ -576,6 +588,30 @@ impl Encode for types::Integer {
}
}

#[cfg(feature = "f32")]
impl Encode for f32 {
fn encode_with_tag_and_constraints<'b, E: Encoder<'b>>(
&self,
encoder: &mut E,
tag: Tag,
constraints: Constraints,
) -> Result<(), E::Error> {
encoder.encode_real(tag, constraints, self).map(drop)
}
}

#[cfg(feature = "f64")]
impl Encode for f64 {
fn encode_with_tag_and_constraints<'b, E: Encoder<'b>>(
&self,
encoder: &mut E,
tag: Tag,
constraints: Constraints,
) -> Result<(), E::Error> {
encoder.encode_real(tag, constraints, self).map(drop)
}
}

impl Encode for types::OctetString {
fn encode_with_tag_and_constraints<'b, E: Encoder<'b>>(
&self,
Expand Down
14 changes: 14 additions & 0 deletions src/error/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ impl DecodeError {
DecodeError::from_kind(DecodeErrorKind::Parser { msg }, codec)
}

/// Creates a wrapper around using `REAL` with unsupported codecs.
#[must_use]
pub fn real_not_supported(codec: Codec) -> Self {
DecodeError::from_kind(DecodeErrorKind::RealNotSupported, codec)
}

/// Creates a wrapper around a missing required extension error from a given codec.
#[must_use]
pub fn required_extension_not_present(tag: Tag, codec: Codec) -> Self {
Expand Down Expand Up @@ -553,6 +559,14 @@ pub enum DecodeErrorKind {
msg: alloc::string::String,
},

/// Real conversion failure.
#[snafu(display("Invalid real encoding"))]
InvalidRealEncoding,

/// Decoder doesn't support REAL
#[snafu(display("Decoder doesn't support REAL types"))]
RealNotSupported,

/// BitString contains an invalid amount of unused bits.
#[snafu(display("BitString contains an invalid amount of unused bits: {}", bits))]
InvalidBitString {
Expand Down
14 changes: 14 additions & 0 deletions src/error/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ impl EncodeError {
Self::from_kind(EncodeErrorKind::VariantNotInChoice, codec)
}

/// Returns an encode error when the encoder doesn't support `REAL` type.
#[must_use]
pub fn real_not_supported(codec: crate::Codec) -> Self {
Self::from_kind(EncodeErrorKind::RealNotSuppored, codec)
}

/// A helper function to construct an `EncodeError` from the given `kind` and `codec`.
#[must_use]
pub fn from_kind(kind: EncodeErrorKind, codec: crate::Codec) -> Self {
Expand Down Expand Up @@ -328,6 +334,11 @@ pub enum EncodeErrorKind {
/// Error when the selected variant is not found in the choice.
#[snafu(display("Selected Variant not found from Choice"))]
VariantNotInChoice,

/// Error when we try to encode a `REAL` type with an unspported codec.
#[snafu(display("Encoder doesn't support `REAL` type"))]
RealNotSuppored,

}
/// `EncodeError` kinds of `Kind::CodecSpecific` which are specific for BER.
#[derive(Snafu, Debug)]
Expand Down Expand Up @@ -392,6 +403,9 @@ pub enum JerEncodeErrorKind {
/// value failed to encode
value: BigInt,
},
/// Error to be thrown when encoding real values that exceed the supported range
#[snafu(display("Exceeds supported real value range"))]
ExceedsSupportedRealRange,
/// Error to be thrown when some character from the input data is not valid UTF-8
#[snafu(display("Invalid character: {:?}", error))]
InvalidCharacter {
Expand Down
40 changes: 38 additions & 2 deletions src/jer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ pub fn encode<T: crate::Encode>(
#[cfg(test)]
mod tests {
macro_rules! round_trip_jer {
($typ:ty, $value:expr, $expected:expr) => {{
let value: $typ = $value;
pretty_assertions::assert_eq!(value, round_trip_value!($typ, $value, $expected));
}};
}

macro_rules! round_trip_value {
($typ:ty, $value:expr, $expected:expr) => {{
let value: $typ = $value;
let expected: &'static str = $expected;
Expand All @@ -32,8 +39,7 @@ mod tests {
pretty_assertions::assert_eq!(expected, &*actual_encoding);

let decoded_value: $typ = crate::jer::decode(&actual_encoding).unwrap();

pretty_assertions::assert_eq!(value, decoded_value);
decoded_value
}};
}

Expand Down Expand Up @@ -187,6 +193,36 @@ mod tests {
round_trip_jer!(ConstrainedInt, ConstrainedInt(1.into()), "1");
}

#[test]
#[cfg(feature = "f32")]
fn real_f32() {
round_trip_jer!(f32, 0.0, "0.0");
round_trip_jer!(f32, -0.0, "\"-0\"");

round_trip_jer!(f32, f32::INFINITY, "\"INF\"");
round_trip_jer!(f32, f32::NEG_INFINITY, "\"-INF\"");

assert!(round_trip_value!(f32, f32::NAN, "\"NAN\"").is_nan());

round_trip_jer!(f32, 1.0, "1.0");
round_trip_jer!(f32, -1.0, "-1.0");
}

#[test]
#[cfg(feature = "f64")]
fn real_f64() {
round_trip_jer!(f64, 0.0, "0.0");
round_trip_jer!(f64, -0.0, "\"-0\"");

round_trip_jer!(f64, f64::INFINITY, "\"INF\"");
round_trip_jer!(f64, f64::NEG_INFINITY, "\"-INF\"");

assert!(round_trip_value!(f64, f64::NAN, "\"NAN\"").is_nan());

round_trip_jer!(f64, 1.0, "1.0");
round_trip_jer!(f64, -1.0, "-1.0");
}

#[test]
fn bit_string() {
round_trip_jer!(
Expand Down
38 changes: 38 additions & 0 deletions src/jer/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ impl crate::Decoder for Decoder {
decode_jer_value!(Self::integer_from_value::<I>, self.stack)
}

#[cfg(any(feature = "f32", feature = "f64"))]
fn decode_real<R: crate::types::RealType>(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<R, Self::Error> {
decode_jer_value!(Self::real_from_value::<R>, self.stack)
}

fn decode_null(&mut self, _t: Tag) -> Result<(), Self::Error> {
decode_jer_value!(Self::null_from_value, self.stack)
}
Expand Down Expand Up @@ -496,6 +505,35 @@ impl Decoder {
.map_err(|_| DecodeError::integer_overflow(I::WIDTH, crate::Codec::Jer))
}

#[cfg(feature = "f64")]
fn real_from_value<R: crate::types::RealType>(
value: Value
) -> Result<R, DecodeError> {
if let Some(as_f64) = value.as_f64() {
return R::try_from_float(as_f64)
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "number (double precision floating point)",
found: alloc::format!("{value}"),
}.into());
}

value
.as_str()
.and_then(|s|
match s {
"-0" => R::try_from_float(-0.0),
"INF" => R::try_from_float(f64::INFINITY),
"-INF" => R::try_from_float(f64::NEG_INFINITY),
"NAN" => R::try_from_float(f64::NAN),
_ => None
}
)
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "number (double precision floating point)",
found: alloc::format!("{value}"),
}.into())
}

fn null_from_value(value: Value) -> Result<(), DecodeError> {
Ok(value
.is_null()
Expand Down
31 changes: 31 additions & 0 deletions src/jer/enc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use crate::{
types::{variants, Constraints, IntegerType, Tag},
};

#[cfg(any(feature = "f32", feature = "f64"))]
use crate::types::RealType;

/// Encodes Rust structures into JSON Encoding Rules data.
pub struct Encoder {
stack: alloc::vec::Vec<&'static str>,
Expand Down Expand Up @@ -149,6 +152,34 @@ impl crate::Encoder<'_> for Encoder {
}
}

#[cfg(feature = "f64")]
fn encode_real<R: RealType>(
&mut self,
_t: Tag,
_c: Constraints,
value: &R,
) -> Result<Self::Ok, Self::Error> {
use num_traits::{Float, ToPrimitive, Zero};

let as_float = value.try_to_float().ok_or(JerEncodeErrorKind::ExceedsSupportedRealRange)?;

if as_float.is_infinite() {
if as_float.is_sign_positive() {
self.update_root_or_constructed(Value::String("INF".into()))
} else {
self.update_root_or_constructed(Value::String("-INF".into()))
}
} else if as_float.is_nan() {
self.update_root_or_constructed(Value::String("NAN".into()))
} else if as_float.is_zero() && as_float.is_sign_negative() {
self.update_root_or_constructed(Value::String("-0".into()))
} else if let Some(number) = as_float.to_f64().and_then(serde_json::Number::from_f64) {
self.update_root_or_constructed(number.into())
} else {
Err(JerEncodeErrorKind::ExceedsSupportedRealRange.into())
}
}

fn encode_null(&mut self, _: Tag) -> Result<Self::Ok, Self::Error> {
self.update_root_or_constructed(Value::Null)
}
Expand Down
Loading

0 comments on commit ddd785e

Please sign in to comment.