Skip to content

Commit 19bbd42

Browse files
committed
Add support for OCTET STRING and OBJECT IDENTIFIER to asn1
1 parent be37095 commit 19bbd42

File tree

13 files changed

+346
-71
lines changed

13 files changed

+346
-71
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

facet-asn1/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
- added facet-asn1
11+
- added support for OCTET STRING and OBJECT IDENTIFIER

facet-asn1/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ categories = ["encoding", "parsing", "data-structures"]
1313
# TODO features for different encoding rules
1414
std = ["alloc", "facet-core/std", "facet-reflect/std"]
1515
alloc = ["facet-core/alloc", "facet-reflect/alloc"]
16-
default = ["std"]
16+
default = ["std", "const-oid"]
17+
const-oid = ["facet-core/const-oid", "dep:const-oid"]
1718

1819
[dependencies]
1920
facet-core = { version = "0.27.12", path = "../facet-core", default-features = false }
2021
facet-reflect = { version = "0.27.12", path = "../facet-reflect", default-features = false }
22+
const-oid = { version = "0.10.1", optional = true }
2123

2224
[dev-dependencies]
2325
facet = { path = "../facet" }
2426
facet-testhelpers = { path = "../facet-testhelpers" }
27+
const-oid = { version = "0.10.1", features = ["db"] }

facet-asn1/README.md

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,32 +45,54 @@ Thanks to all individual and corporate sponsors, without whom this work could no
4545
</picture>
4646
</a> </p>
4747

48-
# facet-xdr
49-
50-
An XDR serializer and deserializer based on facet
51-
52-
## Reference
53-
54-
| XDR IDL | Rust |
55-
|----------------------------|-------------------------------|
56-
| `int` | `i32` |
57-
| `unsigned int` | `u32` |
58-
| `enum` | Unit `enum` |
59-
| `bool` | `bool` |
60-
| `hyper` | `i64` |
61-
| `unsigned hyper` | `u64` |
62-
| `float` | `f32` |
63-
| `double` | `f64` |
64-
| `quadruple` | Not currently supported |
65-
| `opaque [n]` | `[u8; N]` |
66-
| `opaque<>` | `Vec<u8>` or `&[u8]` |
67-
| `string<>` | `String` |
68-
| Fixed length array `[n]` | `[T; N]` |
69-
| Variable length array `<>` | `Vec<T>` or `&[T]` |
70-
| `struct` | `struct` |
71-
| `union` | `enum` |
72-
| `void` | Unit `struct` or unit variant |
73-
| `*` (optional-data) | `Option` |
48+
# facet-asn1
49+
50+
A `#![no_std]` ASN.1 serializer and deserializer based on facet
51+
52+
Currently supports Distinguished Encoding Rules (DER) only
53+
54+
## Basic Types
55+
56+
| ASN.1 Type | Rust |
57+
|-------------------|----------------------------------------------------------------------|
58+
| BOOLEAN | `bool` |
59+
| INTEGER | `i8`, `i16`, `i32`, or `i64` |
60+
| OCTET STRING | `Vec<u8>` |
61+
| NULL | Any unit struct |
62+
| OBJECT IDENTIFIER | `const_oid::ObjectIdentifier` (with the `const-oid` feature enabled) |
63+
| REAL | `f32` or `f64` |
64+
| UTF8String | `String` |
65+
| CHOICE | `enum` |
66+
| SEQUENCE | `struct` |
67+
68+
## Other ASN.1 Types
69+
70+
Newtype structs using the `facet::Shape::type_tag` property can be used to create other basic types without any content validation:
71+
72+
```rust
73+
#[derive(Debug, Clone, Facet, PartialEq, Eq)]
74+
#[facet(type_tag = "IA5String", transparent)]
75+
struct IA5String(String);
76+
```
77+
78+
## Context Specific Type Tags
79+
80+
You can also set context specific BER/DER tags to a given number. Implicit tags must be set as transparent.
81+
82+
```rust
83+
// ImplicitString ::= [5] IMPLICIT UTF8String
84+
#[derive(Debug, Facet, PartialEq, Eq)]
85+
#[facet(type_tag = "5", transparent)]
86+
struct ImplicitString(String);
87+
88+
// ExplciitString ::= [5] EXPLICIT UTF8String
89+
#[derive(Debug, Facet, PartialEq, Eq)]
90+
#[facet(type_tag = "5")]
91+
struct ExplicitString(String);
92+
```
93+
94+
The tag classes `UNIVERSAL`, `APPLICATION`, and `PRIVATE` are also supported in `type_tag`s for greater flexibility.
95+
7496

7597
## License
7698

facet-asn1/README.md.in

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,48 @@
1-
# facet-xdr
2-
3-
An XDR serializer and deserializer based on facet
4-
5-
## Reference
6-
7-
| XDR IDL | Rust |
8-
|----------------------------|-------------------------------|
9-
| `int` | `i32` |
10-
| `unsigned int` | `u32` |
11-
| `enum` | Unit `enum` |
12-
| `bool` | `bool` |
13-
| `hyper` | `i64` |
14-
| `unsigned hyper` | `u64` |
15-
| `float` | `f32` |
16-
| `double` | `f64` |
17-
| `quadruple` | Not currently supported |
18-
| `opaque [n]` | `[u8; N]` |
19-
| `opaque<>` | `Vec<u8>` or `&[u8]` |
20-
| `string<>` | `String` |
21-
| Fixed length array `[n]` | `[T; N]` |
22-
| Variable length array `<>` | `Vec<T>` or `&[T]` |
23-
| `struct` | `struct` |
24-
| `union` | `enum` |
25-
| `void` | Unit `struct` or unit variant |
26-
| `*` (optional-data) | `Option` |
1+
# facet-asn1
2+
3+
A `#![no_std]` ASN.1 serializer and deserializer based on facet
4+
5+
Currently supports Distinguished Encoding Rules (DER) only
6+
7+
## Basic Types
8+
9+
| ASN.1 Type | Rust |
10+
|-------------------|----------------------------------------------------------------------|
11+
| BOOLEAN | `bool` |
12+
| INTEGER | `i8`, `i16`, `i32`, or `i64` |
13+
| OCTET STRING | `Vec<u8>` |
14+
| NULL | Any unit struct |
15+
| OBJECT IDENTIFIER | `const_oid::ObjectIdentifier` (with the `const-oid` feature enabled) |
16+
| REAL | `f32` or `f64` |
17+
| UTF8String | `String` |
18+
| CHOICE | `enum` |
19+
| SEQUENCE | `struct` |
20+
21+
## Other ASN.1 Types
22+
23+
Newtype structs using the `facet::Shape::type_tag` property can be used to create other basic types without any content validation:
24+
25+
```rust
26+
#[derive(Debug, Clone, Facet, PartialEq, Eq)]
27+
#[facet(type_tag = "IA5String", transparent)]
28+
struct IA5String(String);
29+
```
30+
31+
## Context Specific Type Tags
32+
33+
You can also set context specific BER/DER tags to a given number. Implicit tags must be set as transparent.
34+
35+
```rust
36+
// ImplicitString ::= [5] IMPLICIT UTF8String
37+
#[derive(Debug, Facet, PartialEq, Eq)]
38+
#[facet(type_tag = "5", transparent)]
39+
struct ImplicitString(String);
40+
41+
// ExplciitString ::= [5] EXPLICIT UTF8String
42+
#[derive(Debug, Facet, PartialEq, Eq)]
43+
#[facet(type_tag = "5")]
44+
struct ExplicitString(String);
45+
```
46+
47+
The tag classes `UNIVERSAL`, `APPLICATION`, and `PRIVATE` are also supported in `type_tag`s for greater flexibility.
48+

facet-asn1/src/lib.rs

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ use alloc::{
1212
};
1313
use core::{f64, num::FpCategory, str::FromStr};
1414

15+
#[cfg(feature = "const-oid")]
16+
use const_oid::ObjectIdentifier;
17+
1518
use facet_core::{
1619
Def, Facet, NumberBits, ScalarAffinity, Shape, ShapeAttribute, StructKind, Type, UserType,
1720
};
@@ -21,7 +24,9 @@ mod tag;
2124

2225
const ASN1_TYPE_TAG_BOOLEAN: u8 = 0x01;
2326
const ASN1_TYPE_TAG_INTEGER: u8 = 0x02;
27+
const ASN1_TYPE_TAG_OCTET_STRING: u8 = 0x04;
2428
const ASN1_TYPE_TAG_NULL: u8 = 0x05;
29+
const ASN1_TYPE_TAG_OBJECT_IDENTIFIER: u8 = 0x06;
2530
const ASN1_TYPE_TAG_REAL: u8 = 0x09;
2631
const ASN1_TYPE_TAG_UTF8STRING: u8 = 0x0C;
2732
const ASN1_TYPE_TAG_SEQUENCE: u8 = 0x10;
@@ -272,19 +277,35 @@ fn serialize_der_recursive<'shape, 'w, W: Asn1Write>(
272277
serializer.serialize_str(tag.unwrap_or(ASN1_TYPE_TAG_UTF8STRING), value);
273278
Ok(())
274279
}
280+
#[cfg(feature = "const-oid")]
281+
ScalarAffinity::OID(_) => {
282+
let value = pv.get::<ObjectIdentifier>().unwrap();
283+
serializer.serialize_tlv(
284+
tag.unwrap_or(ASN1_TYPE_TAG_OBJECT_IDENTIFIER),
285+
value.as_bytes(),
286+
);
287+
Ok(())
288+
}
275289
_ => Err(Asn1SerError::UnsupportedShape),
276290
},
277-
(Def::List(_), _) => {
278-
let pv = pv.into_list().unwrap();
279-
let mut value = Vec::new();
280-
for pv in pv.iter() {
281-
let mut inner_serializer = DerSerializer { writer: &mut value };
282-
serialize_der_recursive(pv, &mut inner_serializer, None)?;
291+
(Def::List(ld), _) => {
292+
if ld.t().is_type::<u8>() && shape.is_type::<Vec<u8>>() {
293+
serializer.serialize_tlv(
294+
tag.unwrap_or(ASN1_TYPE_TAG_OCTET_STRING),
295+
pv.get::<Vec<u8>>().unwrap(),
296+
);
297+
} else {
298+
let pv = pv.into_list().unwrap();
299+
let mut value = Vec::new();
300+
for pv in pv.iter() {
301+
let mut inner_serializer = DerSerializer { writer: &mut value };
302+
serialize_der_recursive(pv, &mut inner_serializer, None)?;
303+
}
304+
serializer.serialize_tlv(
305+
tag.unwrap_or(ASN1_TYPE_TAG_SEQUENCE | ASN1_FORM_CONSTRUCTED),
306+
&value,
307+
);
283308
}
284-
serializer.serialize_tlv(
285-
tag.unwrap_or(ASN1_TYPE_TAG_SEQUENCE | ASN1_FORM_CONSTRUCTED),
286-
&value,
287-
);
288309
Ok(())
289310
}
290311
(Def::Option(_), _) => {
@@ -409,6 +430,14 @@ pub enum Asn1DeserError {
409430
/// Underlying UTF-8 error
410431
source: core::str::Utf8Error,
411432
},
433+
#[cfg(feature = "const-oid")]
434+
/// Invalid OID
435+
InvalidOid {
436+
/// Position of this error in bytes
437+
position: usize,
438+
/// Underlying const-oid error
439+
source: const_oid::Error,
440+
},
412441
/// Sequence length didn't match content length
413442
SequenceSizeMismatch {
414443
/// Position of the end of the sequence in bytes
@@ -446,6 +475,10 @@ impl core::fmt::Display for Asn1DeserError {
446475
Asn1DeserError::InvalidString { position, .. } => {
447476
write!(f, "Invalid string at byte {}", position)
448477
}
478+
#[cfg(feature = "const-oid")]
479+
Asn1DeserError::InvalidOid { position, .. } => {
480+
write!(f, "Invalid OID at byte {}", position)
481+
}
449482
Asn1DeserError::SequenceSizeMismatch {
450483
sequence_end,
451484
content_end,
@@ -465,6 +498,8 @@ impl core::error::Error for Asn1DeserError {
465498
match self {
466499
Asn1DeserError::TypeTag(source) => Some(source),
467500
Asn1DeserError::InvalidString { source, .. } => Some(source),
501+
#[cfg(feature = "const-oid")]
502+
Asn1DeserError::InvalidOid { source, .. } => Some(source),
468503
_ => None,
469504
}
470505
}
@@ -518,11 +553,18 @@ fn ber_tag_for_shape(shape: &Shape<'_>) -> Result<Option<u8>, Asn1DeserError> {
518553
_ => Err(Asn1DeserError::UnsupportedShape),
519554
},
520555
ScalarAffinity::String(_) => Ok(Some(type_tag.unwrap_or(ASN1_TYPE_TAG_UTF8STRING))),
556+
ScalarAffinity::OID(_) => Ok(Some(type_tag.unwrap_or(ASN1_TYPE_TAG_OBJECT_IDENTIFIER))),
521557
_ => Err(Asn1DeserError::UnsupportedShape),
522558
},
523-
(Def::List(_), _) => Ok(Some(
524-
type_tag.unwrap_or(ASN1_TYPE_TAG_SEQUENCE) | ASN1_FORM_CONSTRUCTED,
525-
)),
559+
(Def::List(ld), _) => {
560+
if ld.t().is_type::<u8>() && shape.is_type::<Vec<u8>>() {
561+
Ok(Some(type_tag.unwrap_or(ASN1_TYPE_TAG_OCTET_STRING)))
562+
} else {
563+
Ok(Some(
564+
type_tag.unwrap_or(ASN1_TYPE_TAG_SEQUENCE) | ASN1_FORM_CONSTRUCTED,
565+
))
566+
}
567+
}
526568
(Def::Option(od), _) => Ok(type_tag.or(ber_tag_for_shape(od.t)?)),
527569
(_, Type::User(ut)) => match ut {
528570
UserType::Struct(st) => match st.kind {
@@ -730,14 +772,31 @@ impl<'shape, 'input> Asn1DeserializerStack<'input> {
730772
wip.set(value.to_string()).unwrap();
731773
Ok(wip)
732774
}
775+
#[cfg(feature = "const-oid")]
776+
ScalarAffinity::OID(_) => {
777+
let bytes = self.next_tlv(tag_for_shape.unwrap())?;
778+
let value = ObjectIdentifier::from_bytes(bytes).map_err(|source| {
779+
Asn1DeserError::InvalidOid {
780+
position: self.pos,
781+
source,
782+
}
783+
})?;
784+
wip.set(value).unwrap();
785+
Ok(wip)
786+
}
733787
_ => Err(Asn1DeserError::UnsupportedShape),
734788
},
735789
(Def::List(_), _) => {
736-
let len = self.next_tl(tag_for_shape.unwrap())?;
737-
self.stack.push(DeserializeTask::Pop(PopReason::ListVal {
738-
end: self.pos + len,
739-
}));
740-
self.stack.push(DeserializeTask::Value { with_tag: None });
790+
if shape.is_type::<Vec<u8>>() {
791+
let bytes = self.next_tlv(tag_for_shape.unwrap())?;
792+
wip.set(bytes.to_vec()).unwrap();
793+
} else {
794+
let len = self.next_tl(tag_for_shape.unwrap())?;
795+
self.stack.push(DeserializeTask::Pop(PopReason::ListVal {
796+
end: self.pos + len,
797+
}));
798+
self.stack.push(DeserializeTask::Value { with_tag: None });
799+
}
741800
Ok(wip)
742801
}
743802
(Def::Option(od), _) => {

0 commit comments

Comments
 (0)