Skip to content

Commit b9165d8

Browse files
authored
fix(per): encode empty extension addition group as absent per X.691 §… (#543)
* fix(per): encode empty extension addition group as absent per X.691 §19.9 * style: fix rustfmt formatting * ci: trigger actions * fix(per/tests): fix clippy warnings in issue505 test
1 parent 5ab3f5f commit b9165d8

3 files changed

Lines changed: 99 additions & 6 deletions

File tree

macros/macros_impl/src/encode.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ pub fn derive_struct_impl(
5151

5252
if config.tag.as_ref().is_some_and(|tag| tag.is_explicit()) {
5353
// Note: encoder must be aware if the field is optional and present, so we should not do the presence check on this level
54-
quote!(encoder
55-
.encode_explicit_prefix(tag, &self.0, identifier.or(Self::IDENTIFIER))
56-
.map(drop))
54+
quote!(
55+
encoder
56+
.encode_explicit_prefix(tag, &self.0, identifier.or(Self::IDENTIFIER))
57+
.map(drop)
58+
)
5759
} else {
5860
// NOTE: AsnType trait already implements correct delegate constraints, and those are passed here
5961
// We don't need to do double intersection here!

src/per/enc.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,10 +1344,25 @@ impl<const RFC: usize, const EFC: usize> crate::Encoder<'_> for Encoder<RFC, EFC
13441344
encoder.is_extension_sequence = true;
13451345
encoder.number_optional_default_fields = E::FIELDS.number_of_optional_and_default_fields();
13461346
value.encode(&mut encoder)?;
1347-
let output = encoder.output();
1347+
let out = encoder.output();
13481348

1349-
self.extension_fields[self.extension_bitfield.0] = Some(output);
1350-
self.set_extension_presence(true);
1349+
let all_absent = if E::FIELDS.has_required_field() {
1350+
false
1351+
} else if encoder.root_bitfield.0 > 0 {
1352+
encoder.root_bitfield.1[..encoder.root_bitfield.0]
1353+
.iter()
1354+
.all(|(present, _)| !present)
1355+
} else {
1356+
out.iter().all(|&b| b == 0)
1357+
};
1358+
1359+
if all_absent {
1360+
self.extension_fields[self.extension_bitfield.0] = None;
1361+
self.set_extension_presence(false);
1362+
} else {
1363+
self.extension_fields[self.extension_bitfield.0] = Some(out);
1364+
self.set_extension_presence(true);
1365+
}
13511366
Ok(())
13521367
}
13531368
}

tests/issue505.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use rasn::prelude::*;
2+
3+
#[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq)]
4+
#[rasn(tag(0))]
5+
pub struct S1ExtGroupB2 {
6+
pub b2: Option<bool>,
7+
}
8+
9+
#[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq)]
10+
#[rasn(tag(1))]
11+
pub struct S1ExtGroupB3 {
12+
pub b3: Option<bool>,
13+
}
14+
15+
#[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq)]
16+
#[non_exhaustive]
17+
pub struct S1 {
18+
pub b1: bool,
19+
#[rasn(extension_addition_group)]
20+
pub ext_group_b2: Option<S1ExtGroupB2>,
21+
#[rasn(extension_addition_group)]
22+
pub ext_group_b3: Option<S1ExtGroupB3>,
23+
}
24+
25+
#[test]
26+
fn uper_issue505_empty_extension_group_encoded_as_absent() {
27+
let value_with_empty_some = S1 {
28+
b1: true,
29+
ext_group_b2: Some(S1ExtGroupB2 { b2: None }),
30+
ext_group_b3: Some(S1ExtGroupB3 { b3: Some(true) }),
31+
};
32+
let value_with_none = S1 {
33+
b1: true,
34+
ext_group_b2: None,
35+
ext_group_b3: Some(S1ExtGroupB3 { b3: Some(true) }),
36+
};
37+
38+
let enc_empty_some =
39+
rasn::uper::encode(&value_with_empty_some).expect("encode Some({all None}) failed");
40+
let enc_none = rasn::uper::encode(&value_with_none).expect("encode None failed");
41+
42+
assert_eq!(
43+
enc_empty_some, enc_none,
44+
"issue #505: Some({{all None}}) encoded as {:02X?} but None encoded as {:02X?} — \
45+
empty extension group must be treated as absent per X.691 §19.9",
46+
enc_empty_some, enc_none
47+
);
48+
49+
let decoded: S1 = rasn::uper::decode(&enc_none).expect("decode failed");
50+
assert!(decoded.b1);
51+
assert_eq!(decoded.ext_group_b3, Some(S1ExtGroupB3 { b3: Some(true) }));
52+
}
53+
54+
#[test]
55+
fn uper_issue505_some_all_none_equals_none() {
56+
let with_empty_some = S1 {
57+
b1: true,
58+
ext_group_b2: Some(S1ExtGroupB2 { b2: None }),
59+
ext_group_b3: None,
60+
};
61+
let with_none = S1 {
62+
b1: true,
63+
ext_group_b2: None,
64+
ext_group_b3: None,
65+
};
66+
67+
let enc_some = rasn::uper::encode(&with_empty_some).expect("encode failed");
68+
let enc_none = rasn::uper::encode(&with_none).expect("encode failed");
69+
70+
assert_eq!(
71+
enc_some, enc_none,
72+
"Some({{all None}}) must encode identically to None per X.691 §19.9, \
73+
got {:02X?} vs {:02X?}",
74+
enc_some, enc_none
75+
);
76+
}

0 commit comments

Comments
 (0)