]> Cypherpunks.ru repositories - pyderasn.git/commitdiff
Do not shadow TagMismatch error during Choice/Set decoding 3.5
authorSergey Matveev <stargrave@stargrave.org>
Wed, 18 Apr 2018 11:42:44 +0000 (14:42 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Wed, 18 Apr 2018 11:59:42 +0000 (14:59 +0300)
VERSION
doc/news.rst
pyderasn.py
tests/test_pyderasn.py

diff --git a/VERSION b/VERSION
index 2f4b60750dc3500b0e4cf08f316a960a7ca42b40..5a958026daa3208cb329c403433beb56abd5c036 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.4
+3.5
index 8e821205dafc40701f860e62f10c015c2a978ac2..fd7551b0a8c3e0f673af5a368d49731225ee96e7 100644 (file)
@@ -1,6 +1,14 @@
 News
 ====
 
+.. _release3.5:
+
+3.5
+---
+* Fix TagMismatch exception completeness during Choice and Set decoding.
+  Previously we will loose offset and decode_path information about
+  concrete TagMismatched entity.
+
 .. _release3.4:
 
 3.4
index 0c7812c8d4a70eb9cbc1ca18d77158160133c830..04f8ed5a21cbbaa69123786ca9d595564836f7a9 100755 (executable)
@@ -912,7 +912,7 @@ class Obj(object):
     def _encode(self):  # pragma: no cover
         raise NotImplementedError()
 
-    def _decode(self, tlv, offset, decode_path, ctx):  # pragma: no cover
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):  # pragma: no cover
         raise NotImplementedError()
 
     def encode(self):
@@ -921,7 +921,15 @@ class Obj(object):
             return raw
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
-    def decode(self, data, offset=0, leavemm=False, decode_path=(), ctx=None):
+    def decode(
+            self,
+            data,
+            offset=0,
+            leavemm=False,
+            decode_path=(),
+            ctx=None,
+            tag_only=False,
+    ):
         """Decode the data
 
         :param data: either binary or memoryview
@@ -929,18 +937,25 @@ class Obj(object):
         :param bool leavemm: do we need to leave memoryview of remaining
                     data as is, or convert it to bytes otherwise
         :param ctx: optional :ref:`context <ctx>` governing decoding process.
+        :param tag_only: decode only the tag, without length and contents
+                         (used only in Choice and Set structures, trying to
+                         determine if tag satisfies the scheme)
         :returns: (Obj, remaining data)
         """
         if ctx is None:
             ctx = {}
         tlv = memoryview(data)
         if self._expl is None:
-            obj, tail = self._decode(
+            result = self._decode(
                 tlv,
                 offset,
                 decode_path=decode_path,
                 ctx=ctx,
+                tag_only=tag_only,
             )
+            if tag_only:
+                return
+            obj, tail = result
         else:
             try:
                 t, tlen, lv = tag_strip(tlv)
@@ -973,12 +988,16 @@ class Obj(object):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            obj, tail = self._decode(
+            result = self._decode(
                 v,
                 offset=offset + tlen + llen,
                 decode_path=decode_path,
                 ctx=ctx,
+                tag_only=tag_only,
             )
+            if tag_only:
+                return
+            obj, tail = result
         return obj, (tail if leavemm else tail.tobytes())
 
     @property
@@ -1337,7 +1356,7 @@ class Boolean(Obj):
             (b"\xFF" if self._value else b"\x00"),
         ))
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1353,6 +1372,8 @@ class Boolean(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -1630,7 +1651,7 @@ class Integer(Obj):
                     break
         return b"".join((self.tag, len_encode(len(octets)), octets))
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1646,6 +1667,8 @@ class Integer(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -1964,7 +1987,7 @@ class BitString(Obj):
             octets,
         ))
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -1980,6 +2003,8 @@ class BitString(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -2216,7 +2241,7 @@ class OctetString(Obj):
             self._value,
         ))
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2232,6 +2257,8 @@ class OctetString(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -2372,7 +2399,7 @@ class Null(Obj):
     def _encode(self):
         return self.tag + len_encode(0)
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2388,6 +2415,8 @@ class Null(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -2617,7 +2646,7 @@ class ObjectIdentifier(Obj):
         v = b"".join(octets)
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, _, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -2633,6 +2662,8 @@ class ObjectIdentifier(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -3394,32 +3425,45 @@ class Choice(Obj):
         self._assert_ready()
         return self._value[1].encode()
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         for choice, spec in self.specs.items():
+            sub_decode_path = decode_path + (choice,)
             try:
-                value, tail = spec.decode(
+                spec.decode(
                     tlv,
                     offset=offset,
                     leavemm=True,
-                    decode_path=decode_path + (choice,),
+                    decode_path=sub_decode_path,
                     ctx=ctx,
+                    tag_only=True,
                 )
             except TagMismatch:
                 continue
-            obj = self.__class__(
-                schema=self.specs,
-                expl=self._expl,
-                default=self.default,
-                optional=self.optional,
-                _decoded=(offset, 0, value.tlvlen),
+            break
+        else:
+            raise TagMismatch(
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
             )
-            obj._value = (choice, value)
-            return obj, tail
-        raise TagMismatch(
-            klass=self.__class__,
-            decode_path=decode_path,
+        if tag_only:
+            return
+        value, tail = spec.decode(
+            tlv,
             offset=offset,
+            leavemm=True,
+            decode_path=sub_decode_path,
+            ctx=ctx,
         )
+        obj = self.__class__(
+            schema=self.specs,
+            expl=self._expl,
+            default=self.default,
+            optional=self.optional,
+            _decoded=(offset, 0, value.tlvlen),
+        )
+        obj._value = (choice, value)
+        return obj, tail
 
     def __repr__(self):
         value = pp_console_row(next(self.pps()))
@@ -3569,7 +3613,7 @@ class Any(Obj):
         self._assert_ready()
         return self._value
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
             l, llen, v = len_decode(lv)
@@ -3890,7 +3934,7 @@ class Sequence(Obj):
         v = b"".join(self._encoded_values())
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -3906,6 +3950,8 @@ class Sequence(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -4084,7 +4130,7 @@ class Set(Sequence):
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -4100,6 +4146,8 @@ class Set(Sequence):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -4121,23 +4169,18 @@ class Set(Sequence):
         specs_items = self.specs.items
         while len(v) > 0:
             for name, spec in specs_items():
+                sub_decode_path = decode_path + (name,)
                 try:
-                    value, v_tail = spec.decode(
+                    spec.decode(
                         v,
                         sub_offset,
                         leavemm=True,
-                        decode_path=decode_path + (name,),
+                        decode_path=sub_decode_path,
                         ctx=ctx,
+                        tag_only=True,
                     )
                 except TagMismatch:
                     continue
-                sub_offset += (
-                    value.expl_tlvlen if value.expled else value.tlvlen
-                )
-                v = v_tail
-                if spec.default is None or value != spec.default:  # pragma: no cover
-                    # SeqMixing.test_encoded_default_accepted covers that place
-                    values[name] = value
                 break
             else:
                 raise TagMismatch(
@@ -4145,6 +4188,20 @@ class Set(Sequence):
                     decode_path=decode_path,
                     offset=offset,
                 )
+            value, v_tail = spec.decode(
+                v,
+                sub_offset,
+                leavemm=True,
+                decode_path=sub_decode_path,
+                ctx=ctx,
+            )
+            sub_offset += (
+                value.expl_tlvlen if value.expled else value.tlvlen
+            )
+            v = v_tail
+            if spec.default is None or value != spec.default:  # pragma: no cover
+                # SeqMixing.test_encoded_default_accepted covers that place
+                values[name] = value
         obj = self.__class__(
             schema=self.specs,
             impl=self.tag,
@@ -4337,7 +4394,7 @@ class SequenceOf(Obj):
         v = b"".join(self._encoded_values())
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -4353,6 +4410,8 @@ class SequenceOf(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
+        if tag_only:
+            return
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
index 401d6237222ead8c0308e9f2fe05292bedfcd3c3..723020c4820802e2970d87712bbb879a36146ece 100644 (file)
@@ -3772,6 +3772,26 @@ class TestChoice(CommonMixin, TestCase):
         with self.assertRaises(TagMismatch):
             obj.decode(int_encoded)
 
+    def test_tag_mismatch_underlying(self):
+        class SeqOfBoolean(SequenceOf):
+            schema = Boolean()
+
+        class SeqOfInteger(SequenceOf):
+            schema = Integer()
+
+        class Wahl(Choice):
+            schema = (
+                ("erste", SeqOfBoolean()),
+            )
+
+        int_encoded = SeqOfInteger((Integer(123),)).encode()
+        bool_encoded = SeqOfBoolean((Boolean(False),)).encode()
+        obj = Wahl()
+        obj.decode(bool_encoded)
+        with self.assertRaises(TagMismatch) as err:
+            obj.decode(int_encoded)
+        self.assertEqual(err.exception.decode_path, ("erste", "0"))
+
 
 @composite
 def seq_values_strategy(draw, seq_klass, do_expl=False):