]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
Fix CHOICE's tlvlen with explicitly tagged value
[pyderasn.git] / tests / test_pyderasn.py
index a07c495ebd5d838219b6b2be69cc011911f734cf..864c66bbbc847b6ed2ae573bedb4ce67e9d4ee66 100644 (file)
@@ -1,5 +1,5 @@
 # coding: utf-8
-# PyDERASN -- Python ASN.1 DER codec with abstract structures
+# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
 # Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
@@ -77,6 +77,7 @@ from pyderasn import InvalidOID
 from pyderasn import InvalidValueType
 from pyderasn import len_decode
 from pyderasn import len_encode
+from pyderasn import LENINDEF
 from pyderasn import NotEnoughData
 from pyderasn import Null
 from pyderasn import NumericString
@@ -113,7 +114,6 @@ from pyderasn import VisibleString
 
 settings.register_profile("local", settings(
     deadline=5000,
-    perform_health_check=False,
 ))
 settings.load_profile("local")
 LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4
@@ -125,6 +125,9 @@ tag_classes = sampled_from((
     TagClassUniversal,
 ))
 tag_forms = sampled_from((TagFormConstructed, TagFormPrimitive))
+decode_path_strat = lists(integers(), max_size=3).map(
+    lambda decode_path: tuple(str(dp) for dp in decode_path)
+)
 
 
 class TestHex(TestCase):
@@ -463,10 +466,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Boolean().decode(
                 tag_encode(tag)[:-1],
@@ -480,10 +482,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_expl_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Boolean(expl=Boolean.tag_default).decode(
                 tag_encode(tag)[:-1],
@@ -497,10 +498,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Boolean().decode(
                 Boolean.tag_default + len_encode(l)[:-1],
@@ -514,10 +514,9 @@ class TestBoolean(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_expl_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Boolean(expl=Boolean.tag_default).decode(
                 Boolean.tag_default + len_encode(l)[:-1],
@@ -609,6 +608,21 @@ class TestBoolean(CommonMixin, TestCase):
         self.assertTrue(obj.bered)
         self.assertFalse(obj.lenindef)
 
+    @given(
+        integers(min_value=1).map(tag_ctxc),
+        binary().filter(lambda x: not x.startswith(EOC)),
+    )
+    def test_ber_expl_no_eoc(self, expl, junk):
+        encoded = expl + LENINDEF + Boolean(False).encode()
+        with assertRaisesRegex(self, DecodeError, "no EOC"):
+            Boolean(expl=expl).decode(encoded + junk, ctx={"bered": True})
+        obj, tail = Boolean(expl=expl).decode(
+            encoded + EOC + junk,
+            ctx={"bered": True},
+        )
+        self.assertTrue(obj.expl_lenindef)
+        self.assertSequenceEqual(tail, junk)
+
     @given(
         integers(min_value=1).map(tag_ctxc),
         lists(
@@ -622,7 +636,7 @@ class TestBoolean(CommonMixin, TestCase):
         for value in values:
             encoded += (
                 expl +
-                b"\x80" +
+                LENINDEF +
                 Boolean(value).encode() +
                 EOC
             )
@@ -913,10 +927,9 @@ class TestInteger(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Integer().decode(
                 tag_encode(tag)[:-1],
@@ -930,10 +943,9 @@ class TestInteger(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Integer().decode(
                 Integer.tag_default + len_encode(l)[:-1],
@@ -947,10 +959,9 @@ class TestInteger(CommonMixin, TestCase):
     @given(
         sets(integers(), min_size=2, max_size=2),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_invalid_bounds_while_decoding(self, ints, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         value, bound_min = list(sorted(ints))
 
         class Int(Integer):
@@ -1307,10 +1318,9 @@ class TestBitString(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             BitString().decode(
                 tag_encode(tag)[:-1],
@@ -1324,10 +1334,9 @@ class TestBitString(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             BitString().decode(
                 BitString.tag_default + len_encode(l)[:-1],
@@ -1462,12 +1471,13 @@ class TestBitString(CommonMixin, TestCase):
             max_size=3,
         ),
         lists(booleans(), min_size=1),
+        binary(),
     )
-    def test_constructed(self, impl, chunk_inputs, chunk_last_bits):
+    def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk):
         def chunk_constructed(contents):
             return (
                 tag_encode(form=TagFormConstructed, num=3) +
-                b"\x80" +
+                LENINDEF +
                 b"".join(BitString(content).encode() for content in contents) +
                 EOC
             )
@@ -1491,7 +1501,7 @@ class TestBitString(CommonMixin, TestCase):
         bit_len_expected += chunk_last.bit_len
         encoded_indefinite = (
             tag_encode(form=TagFormConstructed, num=impl) +
-            b"\x80" +
+            LENINDEF +
             b"".join(chunks) +
             chunk_last.encode() +
             EOC
@@ -1509,15 +1519,132 @@ class TestBitString(CommonMixin, TestCase):
                 (False, encoded_definite),
         ):
             obj, tail = BitString(impl=tag_encode(impl)).decode(
-                encoded, ctx={"bered": True}
+                encoded + junk,
+                ctx={"bered": True},
             )
-            self.assertSequenceEqual(tail, b"")
+            self.assertSequenceEqual(tail, junk)
             self.assertEqual(obj.bit_len, bit_len_expected)
             self.assertSequenceEqual(bytes(obj), payload_expected)
             self.assertTrue(obj.bered)
             self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertEqual(len(encoded), obj.tlvlen)
 
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_definite_too_short(self, offset, decode_path):
+        with assertRaisesRegex(self, DecodeError, "longer than data") as err:
+            BitString().decode(
+                tag_encode(3, form=TagFormConstructed) + len_encode(1),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path)
+        self.assertEqual(err.exception.offset, offset)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_definite_no_data(self, offset, decode_path):
+        with assertRaisesRegex(self, DecodeError, "zero length") as err:
+            BitString().decode(
+                tag_encode(3, form=TagFormConstructed) + len_encode(0),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path)
+        self.assertEqual(err.exception.offset, offset)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+        integers(min_value=1, max_value=3),
+    )
+    def test_ber_indefinite_no_eoc(self, offset, decode_path, chunks):
+        bs = BitString(b"data").encode()
+        with self.assertRaises(NotEnoughData) as err:
+            BitString().decode(
+                tag_encode(3, form=TagFormConstructed) + LENINDEF + chunks * bs,
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+        integers(min_value=1, max_value=3),
+    )
+    def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks):
+        bs = BitString(b"data").encode()
+        bs_longer = BitString(b"data-longer").encode()
+        with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err:
+            BitString().decode(
+                (
+                    tag_encode(3, form=TagFormConstructed) +
+                    len_encode((chunks + 1) * len(bs)) +
+                    chunks * bs +
+                    bs_longer
+                ),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_indefinite_no_chunks(self, offset, decode_path):
+        with assertRaisesRegex(self, DecodeError, "no chunks") as err:
+            BitString().decode(
+                tag_encode(3, form=TagFormConstructed) + LENINDEF + EOC,
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path)
+        self.assertEqual(err.exception.offset, offset)
+
+    @given(data_strategy())
+    def test_ber_indefinite_not_multiple(self, d):
+        bs_short = BitString("'A'H").encode()
+        bs_full = BitString("'AA'H").encode()
+        chunks = [bs_full for _ in range(d.draw(integers(min_value=0, max_value=3)))]
+        chunks.append(bs_short)
+        d.draw(permutations(chunks))
+        chunks.append(bs_short)
+        offset = d.draw(integers(min_value=0))
+        decode_path = d.draw(decode_path_strat)
+        with assertRaisesRegex(self, DecodeError, "multiple of 8 bits") as err:
+            BitString().decode(
+                (
+                    tag_encode(3, form=TagFormConstructed) +
+                    LENINDEF +
+                    b"".join(chunks) +
+                    EOC
+                ),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(
+            err.exception.decode_path,
+            decode_path + (str(chunks.index(bs_short)),),
+        )
+        self.assertEqual(
+            err.exception.offset,
+            offset + 1 + 1 + chunks.index(bs_short) * len(bs_full),
+        )
+
     def test_x690_vector(self):
         vector = BitString("'0A3B5F291CD'H")
         obj, tail = BitString().decode(hexdec("0307040A3B5F291CD0"))
@@ -1739,10 +1866,9 @@ class TestOctetString(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             OctetString().decode(
                 tag_encode(tag)[:-1],
@@ -1756,10 +1882,9 @@ class TestOctetString(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             OctetString().decode(
                 OctetString.tag_default + len_encode(l)[:-1],
@@ -1773,10 +1898,9 @@ class TestOctetString(CommonMixin, TestCase):
     @given(
         sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_invalid_bounds_while_decoding(self, ints, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         value, bound_min = list(sorted(ints))
 
         class String(OctetString):
@@ -1857,12 +1981,13 @@ class TestOctetString(CommonMixin, TestCase):
             min_size=1,
             max_size=3,
         ),
+        binary(),
     )
-    def test_constructed(self, impl, chunk_inputs):
+    def test_constructed(self, impl, chunk_inputs, junk):
         def chunk_constructed(contents):
             return (
                 tag_encode(form=TagFormConstructed, num=4) +
-                b"\x80" +
+                LENINDEF +
                 b"".join(OctetString(content).encode() for content in contents) +
                 EOC
             )
@@ -1878,7 +2003,7 @@ class TestOctetString(CommonMixin, TestCase):
                 payload_expected += payload
         encoded_indefinite = (
             tag_encode(form=TagFormConstructed, num=impl) +
-            b"\x80" +
+            LENINDEF +
             b"".join(chunks) +
             EOC
         )
@@ -1894,14 +2019,70 @@ class TestOctetString(CommonMixin, TestCase):
                 (False, encoded_definite),
         ):
             obj, tail = OctetString(impl=tag_encode(impl)).decode(
-                encoded, ctx={"bered": True}
+                encoded + junk,
+                ctx={"bered": True},
             )
-            self.assertSequenceEqual(tail, b"")
+            self.assertSequenceEqual(tail, junk)
             self.assertSequenceEqual(bytes(obj), payload_expected)
             self.assertTrue(obj.bered)
             self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertEqual(len(encoded), obj.tlvlen)
 
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+    )
+    def test_ber_definite_too_short(self, offset, decode_path):
+        with assertRaisesRegex(self, DecodeError, "longer than data") as err:
+            OctetString().decode(
+                tag_encode(4, form=TagFormConstructed) + len_encode(1),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path)
+        self.assertEqual(err.exception.offset, offset)
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+        integers(min_value=1, max_value=3),
+    )
+    def test_ber_indefinite_no_eoc(self, offset, decode_path, chunks):
+        bs = OctetString(b"data").encode()
+        with self.assertRaises(NotEnoughData) as err:
+            OctetString().decode(
+                tag_encode(4, form=TagFormConstructed) + LENINDEF + chunks * bs,
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
+    @given(
+        integers(min_value=0),
+        decode_path_strat,
+        integers(min_value=1, max_value=3),
+    )
+    def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks):
+        bs = OctetString(b"data").encode()
+        bs_longer = OctetString(b"data-longer").encode()
+        with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err:
+            OctetString().decode(
+                (
+                    tag_encode(4, form=TagFormConstructed) +
+                    len_encode((chunks + 1) * len(bs)) +
+                    chunks * bs +
+                    bs_longer
+                ),
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
 
 @composite
 def null_values_strategy(draw, do_expl=False):
@@ -1998,10 +2179,9 @@ class TestNull(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Null().decode(
                 tag_encode(tag)[:-1],
@@ -2015,10 +2195,9 @@ class TestNull(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Null().decode(
                 Null.tag_default + len_encode(l)[:-1],
@@ -2270,10 +2449,9 @@ class TestObjectIdentifier(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             ObjectIdentifier().decode(
                 tag_encode(tag)[:-1],
@@ -2287,10 +2465,9 @@ class TestObjectIdentifier(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             ObjectIdentifier().decode(
                 ObjectIdentifier.tag_default + len_encode(l)[:-1],
@@ -2905,10 +3082,9 @@ class StringMixin(object):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 tag_encode(tag)[:-1],
@@ -2922,10 +3098,9 @@ class StringMixin(object):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 self.base_klass.tag_default + len_encode(l)[:-1],
@@ -2939,10 +3114,9 @@ class StringMixin(object):
     @given(
         sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_invalid_bounds_while_decoding(self, ints, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         value, bound_min = list(sorted(ints))
 
         class String(self.base_klass):
@@ -3040,10 +3214,9 @@ class TestNumericString(StringMixin, CommonMixin, TestCase):
     @given(
         sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_invalid_bounds_while_decoding(self, ints, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         value, bound_min = list(sorted(ints))
 
         class String(self.base_klass):
@@ -3638,10 +3811,9 @@ class TestAny(CommonMixin, TestCase):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Any().decode(
                 tag_encode(tag)[:-1],
@@ -3655,10 +3827,9 @@ class TestAny(CommonMixin, TestCase):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             Any().decode(
                 Any.tag_default + len_encode(l)[:-1],
@@ -3718,17 +3889,57 @@ class TestAny(CommonMixin, TestCase):
             self.assertEqual(obj_decoded.llen, 0)
             self.assertEqual(obj_decoded.vlen, len(value))
 
+    @given(
+        integers(min_value=1).map(tag_ctxc),
+        integers(min_value=0, max_value=3),
+        integers(min_value=0),
+        decode_path_strat,
+        binary(),
+    )
+    def test_indefinite(self, expl, chunks, offset, decode_path, junk):
+        chunk = Boolean(False, expl=expl).encode()
+        encoded = (
+            OctetString.tag_default +
+            LENINDEF +
+            b"".join([chunk] * chunks) +
+            EOC
+        )
+        obj, tail = Any().decode(
+            encoded + junk,
+            offset=offset,
+            decode_path=decode_path,
+            ctx={"bered": True},
+        )
+        self.assertSequenceEqual(tail, junk)
+        self.assertEqual(obj.offset, offset)
+        self.assertEqual(obj.tlvlen, len(encoded))
+        with self.assertRaises(NotEnoughData) as err:
+            Any().decode(
+                encoded[:-1],
+                offset=offset,
+                decode_path=decode_path,
+                ctx={"bered": True},
+            )
+        self.assertEqual(err.exception.offset, offset + 1 + 1 + len(chunk) * chunks)
+        self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+
 
 @composite
 def choice_values_strategy(draw, value_required=False, schema=None, do_expl=False):
     if schema is None:
         names = list(draw(sets(text_letters(), min_size=1, max_size=5)))
-        tags = [tag_encode(tag) for tag in draw(sets(
-            integers(min_value=0),
+        tags = [{tag_type: tag_value} for tag_type, tag_value in draw(sets(
+            one_of(
+                tuples(just("impl"), integers(min_value=0).map(tag_encode)),
+                tuples(just("expl"), integers(min_value=0).map(tag_ctxp)),
+            ),
             min_size=len(names),
             max_size=len(names),
         ))]
-        schema = [(name, Integer(impl=tag)) for name, tag in zip(names, tags)]
+        schema = [
+            (name, Integer(**tag_kwargs))
+            for name, tag_kwargs in zip(names, tags)
+        ]
     value = None
     if value_required or draw(booleans()):
         value = draw(tuples(
@@ -3970,8 +4181,8 @@ class TestChoice(CommonMixin, TestCase):
         self.assertEqual(obj_decoded.expl_offset, offset)
         self.assertSequenceEqual(
             obj_expled_encoded[
-                obj_decoded.value.offset - offset:
-                obj_decoded.value.offset + obj_decoded.value.tlvlen - offset
+                obj_decoded.value.fulloffset - offset:
+                obj_decoded.value.fulloffset + obj_decoded.value.fulllen - offset
             ],
             obj_encoded,
         )
@@ -4384,10 +4595,9 @@ class SeqMixing(object):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 tag_encode(tag)[:-1],
@@ -4401,10 +4611,9 @@ class SeqMixing(object):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 self.base_klass.tag_default + len_encode(l)[:-1],
@@ -4440,26 +4649,49 @@ class SeqMixing(object):
         self._assert_expects(seq, expects)
         repr(seq)
         pprint(seq)
+        self.assertTrue(seq.ready)
         seq_encoded = seq.encode()
         seq_decoded, tail = seq.decode(seq_encoded + tail_junk)
-        self.assertEqual(tail, tail_junk)
-        self.assertTrue(seq.ready)
-        self._assert_expects(seq_decoded, expects)
-        self.assertEqual(seq, seq_decoded)
-        self.assertEqual(seq_decoded.encode(), seq_encoded)
-        for expect in expects:
-            if not expect["presented"]:
-                self.assertNotIn(expect["name"], seq_decoded)
-                continue
-            self.assertIn(expect["name"], seq_decoded)
-            obj = seq_decoded[expect["name"]]
-            self.assertTrue(obj.decoded)
-            offset = obj.expl_offset if obj.expled else obj.offset
-            tlvlen = obj.expl_tlvlen if obj.expled else obj.tlvlen
-            self.assertSequenceEqual(
-                seq_encoded[offset:offset + tlvlen],
-                obj.encode(),
-            )
+        self.assertFalse(seq_decoded.lenindef)
+
+        t, _, lv = tag_strip(seq_encoded)
+        _, _, v = len_decode(lv)
+        seq_encoded_lenindef = t + LENINDEF + v + EOC
+        seq_decoded_lenindef, tail_lenindef = seq.decode(
+            seq_encoded_lenindef + tail_junk,
+            ctx={"bered": True},
+        )
+        self.assertTrue(seq_decoded_lenindef.lenindef)
+        with self.assertRaises(DecodeError):
+            seq.decode(seq_encoded_lenindef[:-1], ctx={"bered": True})
+        with self.assertRaises(DecodeError):
+            seq.decode(seq_encoded_lenindef[:-2], ctx={"bered": True})
+        repr(seq_decoded_lenindef)
+        pprint(seq_decoded_lenindef)
+        self.assertTrue(seq_decoded_lenindef.ready)
+
+        for decoded, decoded_tail, encoded in (
+                (seq_decoded, tail, seq_encoded),
+                (seq_decoded_lenindef, tail_lenindef, seq_encoded_lenindef),
+        ):
+            self.assertEqual(decoded_tail, tail_junk)
+            self._assert_expects(decoded, expects)
+            self.assertEqual(seq, decoded)
+            self.assertEqual(decoded.encode(), seq_encoded)
+            self.assertEqual(decoded.tlvlen, len(encoded))
+            for expect in expects:
+                if not expect["presented"]:
+                    self.assertNotIn(expect["name"], decoded)
+                    continue
+                self.assertIn(expect["name"], decoded)
+                obj = decoded[expect["name"]]
+                self.assertTrue(obj.decoded)
+                offset = obj.expl_offset if obj.expled else obj.offset
+                tlvlen = obj.expl_tlvlen if obj.expled else obj.tlvlen
+                self.assertSequenceEqual(
+                    seq_encoded[offset:offset + tlvlen],
+                    obj.encode(),
+                )
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
@@ -4937,10 +5169,9 @@ class SeqOfMixing(object):
     @given(
         integers(min_value=31),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_tag(self, tag, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 tag_encode(tag)[:-1],
@@ -4954,10 +5185,9 @@ class SeqOfMixing(object):
     @given(
         integers(min_value=128),
         integers(min_value=0),
-        lists(integers()),
+        decode_path_strat,
     )
     def test_bad_len(self, l, offset, decode_path):
-        decode_path = tuple(str(i) for i in decode_path)
         with self.assertRaises(DecodeError) as err:
             self.base_klass().decode(
                 self.base_klass.tag_default + len_encode(l)[:-1],
@@ -5034,6 +5264,22 @@ class SeqOfMixing(object):
                 ],
             )
 
+        t, _, lv = tag_strip(obj_encoded)
+        _, _, v = len_decode(lv)
+        obj_encoded_lenindef = t + LENINDEF + v + EOC
+        obj_decoded_lenindef, tail_lenindef = obj.decode(
+            obj_encoded_lenindef + tail_junk,
+            ctx={"bered": True},
+        )
+        self.assertTrue(obj_decoded_lenindef.lenindef)
+        repr(obj_decoded_lenindef)
+        pprint(obj_decoded_lenindef)
+        self.assertEqual(obj_decoded_lenindef.tlvlen, len(obj_encoded_lenindef))
+        with self.assertRaises(DecodeError):
+            obj.decode(obj_encoded_lenindef[:-1], ctx={"bered": True})
+        with self.assertRaises(DecodeError):
+            obj.decode(obj_encoded_lenindef[:-2], ctx={"bered": True})
+
 
 class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase):
     class SeqOf(SequenceOf):