From 2dc06a21fd4bd1e52bbea125ef744d58fc0d17bf Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Mon, 10 Sep 2018 17:45:55 +0300 Subject: [PATCH] Defaulted values are checked by default --- VERSION | 2 +- doc/news.rst | 11 +++++--- pyderasn.py | 62 ++++++++++++++++++++++-------------------- tests/test_pyderasn.py | 22 ++++++++++----- 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/VERSION b/VERSION index 93a848f..5186d07 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.15 +4.0 diff --git a/doc/news.rst b/doc/news.rst index 4224b3d..d0f156f 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -1,11 +1,14 @@ News ==== -.. _release3.15: +.. _release4.0: -3.15 ----- -* DEFAULT-encoded value is checked also for Set-s, not for Sequences only +4.0 +--- +* 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 .. _release3.14: diff --git a/pyderasn.py b/pyderasn.py index 7edfc80..a773296 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -213,10 +213,10 @@ decoding process. Currently available context options: +* :ref:`allow_default_values ` * :ref:`allow_expl_oob ` * :ref:`bered ` * :ref:`defines_by_path ` -* :ref:`strict_default_existence ` .. _pprinting: @@ -383,7 +383,7 @@ constructed primitive types should be parsed successfully. * If object is encoded in BER form (not the DER one), then ``bered`` attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET - STRING`` can contain it. + STRING``, ``SEQUENCE``, ``SET`` can contain it. * If object has an indefinite length encoding, then its ``lenindef`` attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``, ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can @@ -4338,18 +4338,14 @@ class Sequence(Obj): All defaulted values are always optional. - .. _strict_default_existence_ctx: + .. _allow_default_values_ctx: - .. warning:: - - When decoded DER contains defaulted value inside, then - technically this is not valid DER encoding. But we allow and pass - it **by default**. Of course reencoding of that kind of DER will - result in different binary representation (validly without - defaulted value inside). You can enable strict defaulted values - existence validation by setting ``"strict_default_existence": - True`` :ref:`context ` option -- decoding process will raise - an exception if defaulted value is met. + DER prohibits default value encoding and will raise an error if + default value is unexpectedly met during decode. + If :ref:`bered ` context option is set, then no error + will be raised, but ``bered`` attribute set. You can disable strict + defaulted values existence validation by setting + ``"allow_default_values": True`` :ref:`context ` option. Two sequences are equal if they have equal specification (schema), implicit/explicit tagging and the same values. @@ -4509,10 +4505,11 @@ class Sequence(Obj): 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__, @@ -4540,6 +4537,8 @@ class Sequence(Obj): vlen = 0 sub_offset = offset + tlen + llen values = {} + bered = False + ctx_allow_default_values = ctx.get("allow_default_values", False) for name, spec in self.specs.items(): if spec.optional and ( (lenindef and v[:EOC_LEN].tobytes() == EOC) or @@ -4612,15 +4611,15 @@ class Sequence(Obj): sub_offset += value_len v = v_tail if spec.default is not None and value == spec.default: - if ctx.get("strict_default_existence", False): + if ctx_bered or ctx_allow_default_values: + bered = True + else: raise DecodeError( "DEFAULT value met", klass=self.__class__, decode_path=sub_decode_path, offset=sub_offset, ) - else: - continue values[name] = value spec_defines = getattr(spec, "defines", ()) @@ -4663,6 +4662,7 @@ class Sequence(Obj): ) obj._value = values obj.lenindef = lenindef + obj.bered = bered return obj, tail def __repr__(self): @@ -4738,10 +4738,11 @@ class Set(Sequence): 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__, @@ -4768,6 +4769,8 @@ class Set(Sequence): vlen = 0 sub_offset = offset + tlen + llen values = {} + bered = False + ctx_allow_default_values = ctx.get("allow_default_values", False) specs_items = self.specs.items while len(v) > 0: if lenindef and v[:EOC_LEN].tobytes() == EOC: @@ -4803,18 +4806,18 @@ class Set(Sequence): sub_offset += value_len vlen += value_len v = v_tail - if spec.default is None: - values[name] = value + if spec.default is None or value != spec.default: + pass + elif ctx_bered or ctx_allow_default_values: + bered = True else: - if value != spec.default: - values[name] = value - if ctx.get("strict_default_existence", False): - raise DecodeError( - "DEFAULT value met", - klass=self.__class__, - decode_path=sub_decode_path, - offset=sub_offset, - ) + raise DecodeError( + "DEFAULT value met", + klass=self.__class__, + decode_path=sub_decode_path, + offset=sub_offset, + ) + values[name] = value obj = self.__class__( schema=self.specs, impl=self.tag, @@ -4824,6 +4827,7 @@ class Set(Sequence): _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)), ) obj._value = values + obj.bered = bered if lenindef: if v[:EOC_LEN].tobytes() != EOC: raise DecodeError( diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 7b397f2..c8321bb 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -4749,6 +4749,7 @@ class SeqMixing(object): seq_encoded = seq.encode() seq_decoded, tail = seq.decode(seq_encoded + tail_junk) self.assertFalse(seq_decoded.lenindef) + self.assertFalse(seq_decoded.bered) t, _, lv = tag_strip(seq_encoded) _, _, v = len_decode(lv) @@ -4840,7 +4841,7 @@ class SeqMixing(object): self.assertSequenceEqual(seq.encode(), empty_seq) @given(data_strategy()) - def test_encoded_default_accepted(self, d): + def test_encoded_default_not_accepted(self, d): _schema = list(d.draw(dictionaries( text_letters(), integers(), @@ -4868,10 +4869,14 @@ class SeqMixing(object): for (n, v), t in zip(_schema, tags) ] seq_with_default = SeqWithDefault() - seq_decoded, _ = seq_with_default.decode(seq_encoded) - for name, value in _schema: - self.assertEqual(seq_decoded[name], seq_with_default[name]) - self.assertEqual(seq_decoded[name], value) + with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): + seq_with_default.decode(seq_encoded) + for ctx in ({"bered": True}, {"allow_default_values": True}): + seq_decoded, _ = seq_with_default.decode(seq_encoded, ctx=ctx) + self.assertTrue(seq_decoded.bered) + for name, value in _schema: + self.assertEqual(seq_decoded[name], seq_with_default[name]) + self.assertEqual(seq_decoded[name], value) @given(data_strategy()) def test_missing_from_spec(self, d): @@ -5880,9 +5885,12 @@ class TestStrictDefaultExistence(TestCase): raw = seq.encode() chosen_choice = "int%d" % chosen seq.specs[chosen_choice] = seq.specs[chosen_choice](default=123) - seq.decode(raw) with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): - seq.decode(raw, ctx={"strict_default_existence": True}) + seq.decode(raw) + decoded, _ = seq.decode(raw, ctx={"allow_default_values": True}) + self.assertTrue(decoded.bered) + decoded, _ = seq.decode(raw, ctx={"bered": True}) + self.assertTrue(decoded.bered) class TestX690PrefixedType(TestCase): -- 2.44.0