doc/news.rst | 2 +- pyderasn.py | 36 ++++++++++++++++++++++++++++++++---- tests/test_pyderasn.py | 31 +++++++++++++++++++++++++++++++ diff --git a/doc/news.rst b/doc/news.rst index fe96946b1ff1f768836a0cb672692050cd0c877c4b12a440e65eab978bebb206..ab25a953329d4e71ad18bdd8a9165f922787643be04709acb35ef94ac4fbee09 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -9,7 +9,7 @@ * Default value is checked also for Sets, not for Sequences only * **Incompatible** change: defaulted values in Sequence/Set are always strictly checked, unless ``allow_default_values`` context option is set. ``strict_default_existence`` option disappeared -* Strict Set's values ordering check +* Strict Set/Set Of's values ordering check .. _release3.14: diff --git a/pyderasn.py b/pyderasn.py index d5add7b2e62e72cc73134f35f97fe237b0a6863f1af02a9bf3ea9231f1ed183f..44469de0788f915aac1939b9349a5d0e9ae4b63562bc797dbb54215e5858311e 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -5050,7 +5050,7 @@ def _encode(self): v = b"".join(self._encoded_values()) return b"".join((self.tag, len_encode(len(v)), v)) - def _decode(self, tlv, offset, decode_path, ctx, tag_only): + def _decode(self, tlv, offset, decode_path, ctx, tag_only, ordering_check=False): try: t, tlen, lv = tag_strip(tlv) except DecodeError as err: @@ -5069,10 +5069,11 @@ ) if tag_only: return lenindef = False + ctx_bered = ctx.get("bered", False) try: l, llen, v = len_decode(lv) except LenIndefForm as err: - if not ctx.get("bered", False): + if not ctx_bered: raise err.__class__( msg=err.msg, klass=self.__class__, @@ -5100,22 +5101,38 @@ v, tail = v[:l], v[l:] vlen = 0 sub_offset = offset + tlen + llen _value = [] + ctx_allow_unordered_set = ctx.get("allow_unordered_set", False) + value_prev = memoryview(v[:0]) + bered = False spec = self.spec while len(v) > 0: if lenindef and v[:EOC_LEN].tobytes() == EOC: break + sub_decode_path = decode_path + (str(len(_value)),) value, v_tail = spec.decode( v, sub_offset, leavemm=True, - decode_path=decode_path + (str(len(_value)),), + decode_path=sub_decode_path, ctx=ctx, ) value_len = value.fulllen + if ordering_check: + if value_prev.tobytes() > v[:value_len].tobytes(): + if ctx_bered or ctx_allow_unordered_set: + bered = True + else: + raise DecodeError( + "unordered " + self.asn1_type_name, + klass=self.__class__, + decode_path=sub_decode_path, + offset=sub_offset, + ) + value_prev = v[:value_len] + _value.append(value) sub_offset += value_len vlen += value_len v = v_tail - _value.append(value) try: obj = self.__class__( value=_value, @@ -5144,6 +5161,7 @@ offset=offset, ) obj.lenindef = True tail = v[EOC_LEN:] + obj.bered = bered return obj, tail def __repr__(self): @@ -5192,6 +5210,16 @@ raws = self._encoded_values() raws.sort() v = b"".join(raws) return b"".join((self.tag, len_encode(len(v)), v)) + + def _decode(self, tlv, offset, decode_path, ctx, tag_only): + return super(SetOf, self)._decode( + tlv, + offset, + decode_path, + ctx, + tag_only, + ordering_check=True, + ) def obj_by_path(pypath): # pragma: no cover diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index c81ac44fb014362d6a83b6e2d12885a87fc83472a3616188f65dff94930898aa..8be1de9594ad7eba447dcc1be9e3822b2329733fc70651e4b571dc39c2c557c5 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -5457,6 +5457,37 @@ seq_encoded[seq_decoded.tlen + seq_decoded.llen:], b"".join(sorted([v.encode() for v in values])), ) + @settings(max_examples=LONG_TEST_MAX_EXAMPLES) + @given(data_strategy()) + def test_unsorted(self, d): + values = [OctetString(v).encode() for v in d.draw(sets( + binary(min_size=1, max_size=5), + min_size=2, + max_size=5, + ))] + values = d.draw(permutations(values)) + assume(values != sorted(values)) + encoded = b"".join(values) + seq_encoded = b"".join(( + SetOf.tag_default, + len_encode(len(encoded)), + encoded, + )) + + class Seq(SetOf): + schema = OctetString() + seq = Seq() + with assertRaisesRegex(self, DecodeError, "unordered SET OF"): + seq.decode(seq_encoded) + + for ctx in ({"bered": True}, {"allow_unordered_set": True}): + seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx) + self.assertTrue(seq_decoded.bered) + self.assertSequenceEqual( + [obj.encode() for obj in seq_decoded], + values, + ) + class TestGoMarshalVectors(TestCase): def runTest(self):