Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/cryptography/hazmat/asn1/asn1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from __future__ import annotations

import builtins
import dataclasses
import sys
import types
Expand Down Expand Up @@ -127,6 +128,11 @@ def _normalize_field_type(
raise TypeError(
"union types other than `X | None` are currently not supported"
)
elif get_type_origin(field_type) is builtins.list:
inner_type = _normalize_field_type(
get_type_args(field_type)[0], field_name
)
rust_field_type = declarative_asn1.Type.SequenceOf(inner_type)
else:
rust_field_type = declarative_asn1.non_root_python_to_rust(field_type)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def non_root_python_to_rust(cls: type) -> Type: ...
# annotations like this:
class Type:
Sequence: typing.ClassVar[type]
SequenceOf: typing.ClassVar[type]
Option: typing.ClassVar[type]
PyBool: typing.ClassVar[type]
PyInt: typing.ClassVar[type]
Expand Down
14 changes: 14 additions & 0 deletions src/rust/src/declarative_asn1/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use asn1::Parser;
use pyo3::types::PyAnyMethods;
use pyo3::types::PyListMethods;

use crate::asn1::big_byte_slice_to_py_int;
use crate::declarative_asn1::types::{
Expand Down Expand Up @@ -151,6 +152,19 @@ pub(crate) fn decode_annotated_type<'a>(
Ok(val)
})?
}
Type::SequenceOf(cls) => {
let seqof_parse_result = read_value::<asn1::Sequence<'_>>(parser, encoding)?;

seqof_parse_result.parse(|d| -> ParseResult<pyo3::Bound<'a, pyo3::PyAny>> {
let inner_ann_type = cls.get();
let list = pyo3::types::PyList::empty(py);
while !d.is_empty() {
let val = decode_annotated_type(py, d, inner_ann_type)?;
list.append(val)?;
}
Ok(list.into_any())
})?
}
Type::Option(cls) => {
let inner_tag = type_to_tag(cls.get().inner.get(), encoding);
match parser.peek_tag() {
Expand Down
13 changes: 13 additions & 0 deletions src/rust/src/declarative_asn1/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use asn1::{SimpleAsn1Writable, Writer};
use pyo3::types::PyAnyMethods;
use pyo3::types::PyListMethods;

use crate::declarative_asn1::types::{
AnnotatedType, AnnotatedTypeObject, Encoding, GeneralizedTime, PrintableString, Type, UtcTime,
Expand Down Expand Up @@ -70,6 +71,18 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> {
}),
encoding,
),
Type::SequenceOf(cls) => {
let values: Vec<AnnotatedTypeObject<'_>> = value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it practical to not have to allocate a whole vec and instead just iterate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can that be done with the existing asn1::SequenceOf API?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm, probably not, no

.cast::<pyo3::types::PyList>()
.map_err(|_| asn1::WriteError::AllocationError)?
.iter()
.map(|e| AnnotatedTypeObject {
annotated_type: cls.get(),
value: e,
})
.collect();
write_value(writer, &asn1::SequenceOfWriter::new(values), encoding)
}
Type::Option(cls) => {
if !value.is_none() {
let inner_object = AnnotatedTypeObject {
Expand Down
3 changes: 3 additions & 0 deletions src/rust/src/declarative_asn1/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub enum Type {
/// The first element is the Python class that represents the sequence,
/// the second element is a dict of the (already converted) fields of the class.
Sequence(pyo3::Py<pyo3::types::PyType>, pyo3::Py<pyo3::types::PyDict>),
/// SEQUENCEOF (`list[`T`]`)
SequenceOf(pyo3::Py<AnnotatedType>),
/// OPTIONAL (`T | None`)
Option(pyo3::Py<AnnotatedType>),

Expand Down Expand Up @@ -285,6 +287,7 @@ pub(crate) fn python_class_to_annotated<'p>(
pub(crate) fn type_to_tag(t: &Type, encoding: &Option<pyo3::Py<Encoding>>) -> asn1::Tag {
let inner_tag = match t {
Type::Sequence(_, _) => asn1::Sequence::TAG,
Type::SequenceOf(_) => asn1::Sequence::TAG,
Type::Option(t) => type_to_tag(t.get().inner.get(), encoding),
Type::PyBool() => bool::TAG,
Type::PyInt() => asn1::BigInt::TAG,
Expand Down
3 changes: 3 additions & 0 deletions tests/hazmat/asn1/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ def test_fields_of_variant_type(self) -> None:
opt = declarative_asn1.Type.Option(ann_type)
assert opt._0 == ann_type

seq_of = declarative_asn1.Type.SequenceOf(ann_type)
assert seq_of._0 is ann_type

def test_fields_of_variant_encoding(self) -> None:
from cryptography.hazmat.bindings._rust import declarative_asn1

Expand Down
41 changes: 40 additions & 1 deletion tests/hazmat/asn1/test_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,42 @@ class Example:
]
)

def test_ok_sequenceof_simple_type(self) -> None:
@asn1.sequence
@_comparable_dataclass
class Example:
a: typing.List[int]

assert_roundtrips(
[
(
Example(a=[1, 2, 3, 4]),
b"\x30\x0e\x30\x0c\x02\x01\x01\x02\x01\x02\x02\x01\x03\x02\x01\x04",
)
]
)

def test_ok_sequenceof_user_defined_type(self) -> None:
@asn1.sequence
@_comparable_dataclass
class MyType:
a: int
b: bool

@asn1.sequence
@_comparable_dataclass
class Example:
a: typing.List[MyType]

assert_roundtrips(
[
(
Example(a=[MyType(a=1, b=True), MyType(a=2, b=False)]),
b"\x30\x12\x30\x10\x30\x06\x02\x01\x01\x01\x01\xff\x30\x06\x02\x01\x02\x01\x01\x00",
)
]
)

def test_ok_sequence_with_optionals(self) -> None:
@asn1.sequence
@_comparable_dataclass
Expand Down Expand Up @@ -350,11 +386,14 @@ class Example:
d: typing.Union[asn1.PrintableString, None]
e: typing.Union[asn1.UtcTime, None]
f: typing.Union[asn1.GeneralizedTime, None]
g: typing.Union[typing.List[int], None]

assert_roundtrips(
[
(
Example(a=None, b=None, c=None, d=None, e=None, f=None),
Example(
a=None, b=None, c=None, d=None, e=None, f=None, g=None
),
b"\x30\x00",
)
]
Expand Down