1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-18 12:28:09 +00:00

feat(core): introduce OrderedMap into CBOR serialisation

Items of an OrderedMap are included in CBOR as they come without sorting them in any way.
This commit is contained in:
gabrielkerekes 2021-06-15 23:11:27 +02:00
parent b8b0ae09d9
commit 3cd2182b69
2 changed files with 54 additions and 2 deletions

View File

@ -10,10 +10,16 @@ from trezor import log, utils
from . import readers from . import readers
if False: if False:
from typing import Any, Union, Iterator, Tuple from typing import Any, Generic, Iterator, Tuple, TypeVar, Union
K = TypeVar("K")
V = TypeVar("V")
Value = Any Value = Any
CborSequence = Union[list[Value], Tuple[Value, ...]] CborSequence = Union[list[Value], Tuple[Value, ...]]
else:
# mypy cheat: Generic[K, V] will be `object` which is a valid parent type
Generic = {(0, 0): object} # type: ignore
K = V = 0 # type: ignore
_CBOR_TYPE_MASK = const(0xE0) _CBOR_TYPE_MASK = const(0xE0)
_CBOR_INFO_BITS = const(0x1F) _CBOR_INFO_BITS = const(0x1F)
@ -82,6 +88,11 @@ def _cbor_encode(value: Value) -> Iterator[bytes]:
for k, v in sorted_map: for k, v in sorted_map:
yield k yield k
yield from _cbor_encode(v) yield from _cbor_encode(v)
elif isinstance(value, OrderedMap):
yield _header(_CBOR_MAP, len(value))
for k, v in value:
yield encode(k)
yield from _cbor_encode(v)
elif isinstance(value, Tagged): elif isinstance(value, Tagged):
yield _header(_CBOR_TAG, value.tag) yield _header(_CBOR_TAG, value.tag)
yield from _cbor_encode(value.value) yield from _cbor_encode(value.value)
@ -226,6 +237,26 @@ class IndefiniteLengthArray:
return False return False
class OrderedMap(Generic[K, V]):
"""
Items of an OrderedMap are included in CBOR as they are added without sorting them in any way. We also allow
duplicates since CBOR is also somewhat lenient in not allowing them. It is thus up to the client to make sure no
duplicates are inserted if it's desired.
"""
def __init__(self) -> None:
self._internal_list: list[tuple[K, V]] = []
def __setitem__(self, key: K, value: V) -> None:
self._internal_list.append((key, value))
def __iter__(self) -> Iterator:
yield from self._internal_list
def __len__(self) -> int:
return len(self._internal_list)
def encode(value: Value) -> bytes: def encode(value: Value) -> bytes:
return b"".join(_cbor_encode(value)) return b"".join(_cbor_encode(value))

View File

@ -3,8 +3,9 @@ import math
from common import * from common import *
from apps.common.cbor import ( from apps.common.cbor import (
Tagged,
IndefiniteLengthArray, IndefiniteLengthArray,
OrderedMap,
Tagged,
decode, decode,
encode, encode,
encode_chunked, encode_chunked,
@ -59,6 +60,7 @@ class TestCardanoCbor(unittest.TestCase):
# maps # maps
({}, 'a0'), ({}, 'a0'),
({1: 2, 3: 4}, 'a201020304'), ({1: 2, 3: 4}, 'a201020304'),
({3: 4, 1: 2}, 'a201020304'),
# indefinite # indefinite
(IndefiniteLengthArray([]), '9fff'), (IndefiniteLengthArray([]), '9fff'),
@ -94,6 +96,25 @@ class TestCardanoCbor(unittest.TestCase):
self.assertEqual(encode(value_tuple), encoded) self.assertEqual(encode(value_tuple), encoded)
self.assertEqual(decode(encoded), val) self.assertEqual(decode(encoded), val)
def test_cbor_ordered_map(self):
"""
OrderedMaps should be encoded as maps without any ordering and decoded back as dicts.
"""
test_vectors = [
({}, 'a0'),
([[1, 2], [3, 4]], 'a201020304'),
([[3, 4], [1, 2]], 'a203040102'),
]
for val, encoded_hex in test_vectors:
ordered_map = OrderedMap()
for key, value in val:
ordered_map[key] = value
encoded = unhexlify(encoded_hex)
self.assertEqual(encode(ordered_map), encoded)
self.assertEqual(decode(encoded), {k: v for k, v in val})
def test_encode_streamed(self): def test_encode_streamed(self):
large_dict = {i: i for i in range(100)} large_dict = {i: i for i in range(100)}
encoded = encode(large_dict) encoded = encode(large_dict)