Skip to content

Commit

Permalink
Fix PER encoding of empty octet strings missing the length determinant
Browse files Browse the repository at this point in the history
  • Loading branch information
decryphe committed Jan 2, 2025
1 parent 6f708c4 commit d083c89
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 3 deletions.
4 changes: 2 additions & 2 deletions src/per/enc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,8 @@ impl<const RCL: usize, const ECL: usize> Encoder<RCL, ECL> {
self.encode_length(buffer, value.len(), <_>::default(), |range| {
Ok(BitString::from_slice(&value[range]))
})?;
} else if 0 == size.constraint.effective_value(value.len()).into_inner() {
// NO-OP
} else if Some(0) == size.constraint.range() {
// ITU-T X.691 (02/2021) §11.9.3.3: If "n" is zero there shall be no further addition to the field-list.
} else if size.constraint.range() == Some(1) && size.constraint.as_start() <= Some(&2) {
// ITU-T X.691 (02/2021) §17 NOTE: Octet strings of fixed length less than or equal to two octets are not octet-aligned.
// All other octet strings are octet-aligned in the ALIGNED variant.
Expand Down
195 changes: 195 additions & 0 deletions src/uper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1044,4 +1044,199 @@ mod tests {
&[0b11000000, 0x20, 0x20, 0x60, 0x20, 0x40, 0x60, 0x20, 0x80, 0x60, 0x20, 0x40, 0x60]
);
}

/// Tests that unaligned OctetStrings are encoded and decoded correctly (UPER).
#[test]
fn test_unaligned_sequence_with_octet_string() {
use crate as rasn;
#[derive(AsnType, Clone, Debug, Default, Decode, Encode, PartialEq)]
#[rasn(automatic_tags)]
struct Unaligned {
#[rasn(value("0..=7"))]
pub offset_bits: u8,
#[rasn(size("0..=255"))]
pub the_string: OctetString,
}
/// Describes the encodings of a given string (first array in a tuple) into its
/// UPER representation (second array in a tuple).
const UPER_UNALIGNED_CASES: &[(&[u8], &[u8])] = &[
(&[], &[0xe0, 0x00]), // The minimum encoding contains 3 + 8 bits.
(&[0x00; 1], &[0xe0, 0x20, 0x00]),
(&[0xF0; 1], &[0xe0, 0x3e, 0x00]),
(&[0xFF; 1], &[0xe0, 0x3f, 0xe0]),
(&[0x00; 4], &[0xe0, 0x80, 0x00, 0x00, 0x00, 0x00]),
(&[0xFF; 4], &[0xe0, 0x9f, 0xff, 0xff, 0xff, 0xe0]),
(
&[0x00; 10],
&[
0xe1, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
),
(
&[0xFF; 10],
&[
0xe1, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0,
],
),
(
&[0x00; 100],
&[
0xec, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
),
(
&[0x00; 127],
&[
0xef, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
),
(
&[0x00; 128],
&[
0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
),
(
&[0x00; 200],
&[
0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
),
(
&[0x00; 255],
&[
0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
),
];
for (case, expected) in UPER_UNALIGNED_CASES {
round_trip!(
uper,
Unaligned,
Unaligned {
offset_bits: 7,
the_string: OctetString::from_static(case)
},
expected
);
}
}

#[test]
fn test_encoding_of_zero_size_octet_string() {
use crate as rasn;

#[derive(AsnType, Clone, Debug, Default, Decode, Encode, PartialEq)]
#[rasn(automatic_tags)]
struct Unaligned {
#[rasn(value("0..=7"))]
pub offset_bits: u8,
#[rasn(size("0..=255"))]
pub the_string: OctetString,
}

round_trip!(
uper,
Unaligned,
Unaligned {
offset_bits: 7,
the_string: OctetString::from_static(&[])
},
&[0b11100000, 0b00000000]
);

#[derive(AsnType, Clone, Debug, Default, Decode, Encode, PartialEq)]
#[rasn(automatic_tags)]
struct UnalignedZeroLength {
#[rasn(value("0..=7"))]
pub offset_bits: u8,
#[rasn(size("0"))]
pub the_string: OctetString,
}

round_trip!(
uper,
UnalignedZeroLength,
UnalignedZeroLength {
offset_bits: 7,
the_string: OctetString::from_static(&[])
},
&[0b11100000]
);

#[derive(AsnType, Clone, Debug, Default, Decode, Encode, PartialEq)]
#[rasn(automatic_tags)]
struct AlignedZeroLength {
#[rasn(size("0"))]
pub the_string: OctetString,
}

round_trip!(
uper,
AlignedZeroLength,
AlignedZeroLength {
the_string: OctetString::from_static(&[])
},
&[]
);
}
}
131 changes: 130 additions & 1 deletion tests/strings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bitvec::prelude::*;
use rasn::prelude::*;
use rasn::{ber, jer, oer, uper};
use rasn::{aper, ber, jer, oer, uper};

#[derive(AsnType, Decode, Encode, Debug, Clone, PartialEq)]
#[rasn(automatic_tags)]
Expand Down Expand Up @@ -327,3 +327,132 @@ fn test_jer_octetstring_dec() {
}
}
}

// Tests that OctetStrings are encoded and decoded correctly (APER, UPER).
const BYTE_ARRAYS: &[&[u8]] = &[
&[],
&[0x00; 1],
&[0x00; 2],
&[0x0F; 1],
&[0xFF; 1],
&[0xFF; 2],
&[0x00; 10],
&[0x00; 100],
&[0x00; 128],
&[0x00; 200],
&[0x01, 0x23],
&[0xAB, 0xCD],
&[0xAB, 0xCD, 0xEF],
&[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF],
&[0x00; 255],
&[0x0F; 255],
&[0xFF; 255],
&[0x00; 256],
&[0x0F; 256],
&[0xFF; 256],
&[0x00; 16383],
&[0x0F; 16383],
&[0xFF; 16383],
];

#[test]
fn test_per_encode_octet_string() {
for case in BYTE_ARRAYS {
let length = case.len(); // number of bytes
let mut buf_expected: Vec<u8> = Vec::new();
if length < 128 {
// X.691, 11.9.a)
buf_expected.push(length as u8);
} else if length < 16384 {
// X.691, 11.9.b)
let length = (length as u16).to_be_bytes();
buf_expected.push(0b10000000 | length[0]);
buf_expected.push(length[1]);
} else {
// X.691, 11.9.c)
todo!("implement chunk generation");
}
buf_expected.extend_from_slice(case);

let bytes = OctetString::copy_from_slice(case);
assert_eq!(buf_expected, aper::encode::<OctetString>(&bytes).unwrap());
assert_eq!(buf_expected, uper::encode::<OctetString>(&bytes).unwrap());

assert_eq!(*case, aper::decode::<OctetString>(&buf_expected).unwrap());
assert_eq!(*case, uper::decode::<OctetString>(&buf_expected).unwrap());
}
}

// Tests that UTF8Strings are encoded and decoded correctly (APER, UPER).
const UTF8_STRINGS: &[&str] = &[
"",
"Hello World!",
"Hello World! 🌍",
"こんにちは世界!",
"你好世界!",
"안녕하세요!",
"مرحبا بالعالم!",
"હેલો વિશ્વ!",
"привіт світ!",
" ",
"!",
"0",
" 0",
"0 ",
"0!",
" ",
"000",
"Œ",
"ŒŒ",
"ŒŒŒ",
"ABCDEFG",
" ABCDEF",
"åäö",
"\0",
"\n",
"\r\nASN.1",
"\u{0000}",
"\u{0001}",
"\u{FFFF}",
"\u{0123}",
"\u{30}",
"\\u0030",
"\\u202E\\u0030\\u0030",
"⣐⡄",
"😎",
"🙈🙉🙊",
"👭👩🏻‍🤝‍👨🏾🧑🏿‍🤝‍🧑🏼",
];

#[test]
fn test_per_encode_utf8_string() {
for case in UTF8_STRINGS {
let case = case.to_string();
// The X.691 spec, chapter 11.9, says determinant should be the number
// of characters, but for UTF-8 this is a non-trivial operation and
// dependant on the supported Unicode release. Actual implementations
// seem to interpret the spec as "number of octets" instead - which is
// reasonable (see `asn1tools` for example).
let length = case.len(); // number of bytes
let mut buf_expected: Vec<u8> = Vec::new();
if length < 128 {
// X.691, 11.9.a)
buf_expected.push(length as u8);
} else if length < 16384 {
// X.691, 11.9.b)
let length = length.to_le_bytes();
buf_expected.push(0b10000000 | length[1]);
buf_expected.push(length[0]);
} else {
// X.691, 11.9.c)
todo!("implement chunk generation");
}
buf_expected.extend_from_slice(case.as_bytes());

assert_eq!(buf_expected, aper::encode::<Utf8String>(&case).unwrap());
assert_eq!(buf_expected, uper::encode::<Utf8String>(&case).unwrap());

assert_eq!(case, aper::decode::<Utf8String>(&buf_expected).unwrap());
assert_eq!(case, uper::decode::<Utf8String>(&buf_expected).unwrap());
}
}

0 comments on commit d083c89

Please sign in to comment.