Skip to content

Commit 78c4861

Browse files
committed
asn1: Add support for SEQUENCE OF
Signed-off-by: Facundo Tuesca <[email protected]>
1 parent 0212c66 commit 78c4861

File tree

6 files changed

+77
-1
lines changed

6 files changed

+77
-1
lines changed

src/cryptography/hazmat/asn1/asn1.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from __future__ import annotations
66

7+
import builtins
78
import dataclasses
89
import sys
910
import types
@@ -127,6 +128,11 @@ def _normalize_field_type(
127128
raise TypeError(
128129
"union types other than `X | None` are currently not supported"
129130
)
131+
elif get_type_origin(field_type) is builtins.list:
132+
inner_type = _normalize_field_type(
133+
get_type_args(field_type)[0], field_name
134+
)
135+
rust_field_type = declarative_asn1.Type.SequenceOf(inner_type)
130136
else:
131137
rust_field_type = declarative_asn1.non_root_python_to_rust(field_type)
132138

src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def non_root_python_to_rust(cls: type) -> Type: ...
1313
# annotations like this:
1414
class Type:
1515
Sequence: typing.ClassVar[type]
16+
SequenceOf: typing.ClassVar[type]
1617
Option: typing.ClassVar[type]
1718
PyBool: typing.ClassVar[type]
1819
PyInt: typing.ClassVar[type]

src/rust/src/declarative_asn1/decode.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use asn1::Parser;
66
use pyo3::types::PyAnyMethods;
7+
use pyo3::types::PyListMethods;
78

89
use crate::asn1::big_byte_slice_to_py_int;
910
use crate::declarative_asn1::types::{
@@ -151,6 +152,19 @@ pub(crate) fn decode_annotated_type<'a>(
151152
Ok(val)
152153
})?
153154
}
155+
Type::SequenceOf(cls) => {
156+
let seqof_parse_result = read_value::<asn1::Sequence<'_>>(parser, encoding)?;
157+
158+
seqof_parse_result.parse(|d| -> ParseResult<pyo3::Bound<'a, pyo3::PyAny>> {
159+
let inner_ann_type = cls.get();
160+
let list = pyo3::types::PyList::empty(py);
161+
while !d.is_empty() {
162+
let val = decode_annotated_type(py, d, inner_ann_type)?;
163+
list.append(val)?;
164+
}
165+
Ok(list.into_any())
166+
})?
167+
}
154168
Type::Option(cls) => {
155169
let inner_tag = type_to_tag(cls.get().inner.get(), encoding);
156170
match parser.peek_tag() {

src/rust/src/declarative_asn1/encode.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use asn1::{SimpleAsn1Writable, Writer};
66
use pyo3::types::PyAnyMethods;
7+
use pyo3::types::PyListMethods;
78

89
use crate::declarative_asn1::types::{
910
AnnotatedType, AnnotatedTypeObject, Encoding, GeneralizedTime, PrintableString, Type, UtcTime,
@@ -70,6 +71,18 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> {
7071
}),
7172
encoding,
7273
),
74+
Type::SequenceOf(cls) => {
75+
let values: Vec<AnnotatedTypeObject<'_>> = value
76+
.cast::<pyo3::types::PyList>()
77+
.map_err(|_| asn1::WriteError::AllocationError)?
78+
.iter()
79+
.map(|e| AnnotatedTypeObject {
80+
annotated_type: cls.get(),
81+
value: e,
82+
})
83+
.collect();
84+
write_value(writer, &asn1::SequenceOfWriter::new(values), encoding)
85+
}
7386
Type::Option(cls) => {
7487
if !value.is_none() {
7588
let inner_object = AnnotatedTypeObject {

src/rust/src/declarative_asn1/types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub enum Type {
1919
/// The first element is the Python class that represents the sequence,
2020
/// the second element is a dict of the (already converted) fields of the class.
2121
Sequence(pyo3::Py<pyo3::types::PyType>, pyo3::Py<pyo3::types::PyDict>),
22+
/// SEQUENCEOF (`list[`T`]`)
23+
SequenceOf(pyo3::Py<AnnotatedType>),
2224
/// OPTIONAL (`T | None`)
2325
Option(pyo3::Py<AnnotatedType>),
2426

@@ -285,6 +287,7 @@ pub(crate) fn python_class_to_annotated<'p>(
285287
pub(crate) fn type_to_tag(t: &Type, encoding: &Option<pyo3::Py<Encoding>>) -> asn1::Tag {
286288
let inner_tag = match t {
287289
Type::Sequence(_, _) => asn1::Sequence::TAG,
290+
Type::SequenceOf(_) => asn1::Sequence::TAG,
288291
Type::Option(t) => type_to_tag(t.get().inner.get(), encoding),
289292
Type::PyBool() => bool::TAG,
290293
Type::PyInt() => asn1::BigInt::TAG,

tests/hazmat/asn1/test_serialization.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,42 @@ class Example:
277277
]
278278
)
279279

280+
def test_ok_sequenceof_simple_type(self) -> None:
281+
@asn1.sequence
282+
@_comparable_dataclass
283+
class Example:
284+
a: typing.List[int]
285+
286+
assert_roundtrips(
287+
[
288+
(
289+
Example(a=[1, 2, 3, 4]),
290+
b"\x30\x0e\x30\x0c\x02\x01\x01\x02\x01\x02\x02\x01\x03\x02\x01\x04",
291+
)
292+
]
293+
)
294+
295+
def test_ok_sequenceof_user_defined_type(self) -> None:
296+
@asn1.sequence
297+
@_comparable_dataclass
298+
class MyType:
299+
a: int
300+
b: bool
301+
302+
@asn1.sequence
303+
@_comparable_dataclass
304+
class Example:
305+
a: typing.List[MyType]
306+
307+
assert_roundtrips(
308+
[
309+
(
310+
Example(a=[MyType(a=1, b=True), MyType(a=2, b=False)]),
311+
b"\x30\x12\x30\x10\x30\x06\x02\x01\x01\x01\x01\xff\x30\x06\x02\x01\x02\x01\x01\x00",
312+
)
313+
]
314+
)
315+
280316
def test_ok_sequence_with_optionals(self) -> None:
281317
@asn1.sequence
282318
@_comparable_dataclass
@@ -350,11 +386,14 @@ class Example:
350386
d: typing.Union[asn1.PrintableString, None]
351387
e: typing.Union[asn1.UtcTime, None]
352388
f: typing.Union[asn1.GeneralizedTime, None]
389+
g: typing.Union[typing.List[int], None]
353390

354391
assert_roundtrips(
355392
[
356393
(
357-
Example(a=None, b=None, c=None, d=None, e=None, f=None),
394+
Example(
395+
a=None, b=None, c=None, d=None, e=None, f=None, g=None
396+
),
358397
b"\x30\x00",
359398
)
360399
]

0 commit comments

Comments
 (0)