]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
Ability to set values during Sequence initialization
[pyderasn.git] / tests / test_pyderasn.py
index fdc03b58369cb8bcd7607f5a9a5c4d7b56661ea8..dfea4300e78a6e4ccc8c17cd05457e6a3bc07fb6 100644 (file)
@@ -1,6 +1,6 @@
 # coding: utf-8
 # PyDERASN -- Python ASN.1 DER codec with abstract structures
-# Copyright (C) 2017 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
@@ -18,6 +18,7 @@
 
 from datetime import datetime
 from string import ascii_letters
+from string import digits
 from string import printable
 from string import whitespace
 from unittest import TestCase
@@ -48,8 +49,10 @@ from six import int2byte
 from six import iterbytes
 from six import PY2
 from six import text_type
+from six import unichr as six_unichr
 
 from pyderasn import _pp
+from pyderasn import abs_decode_path
 from pyderasn import Any
 from pyderasn import BitString
 from pyderasn import BMPString
@@ -57,6 +60,7 @@ from pyderasn import Boolean
 from pyderasn import BoundsError
 from pyderasn import Choice
 from pyderasn import DecodeError
+from pyderasn import DecodePathDefBy
 from pyderasn import Enumerated
 from pyderasn import GeneralizedTime
 from pyderasn import GeneralString
@@ -85,6 +89,7 @@ from pyderasn import SequenceOf
 from pyderasn import Set
 from pyderasn import SetOf
 from pyderasn import tag_ctxc
+from pyderasn import tag_ctxp
 from pyderasn import tag_decode
 from pyderasn import tag_encode
 from pyderasn import tag_strip
@@ -103,11 +108,11 @@ from pyderasn import VideotexString
 from pyderasn import VisibleString
 
 
-settings.register_profile('local', settings(
+settings.register_profile("local", settings(
     deadline=5000,
     perform_health_check=False,
 ))
-settings.load_profile('local')
+settings.load_profile("local")
 LONG_TEST_MAX_EXAMPLES = settings().max_examples * 4
 
 tag_classes = sampled_from((
@@ -526,8 +531,9 @@ class TestBoolean(CommonMixin, TestCase):
         booleans(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (Boolean, BooleanInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -545,10 +551,13 @@ class TestBoolean(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(bool(obj_decoded), bool(obj_expled))
@@ -901,8 +910,9 @@ class TestInteger(CommonMixin, TestCase):
         integers(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (Integer, IntegerInherited):
             _, _, _, _, default, optional, _, _decoded = values
             obj = klass(
@@ -920,10 +930,13 @@ class TestInteger(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(int(obj_decoded), int(obj_expled))
@@ -1276,6 +1289,7 @@ class TestBitString(CommonMixin, TestCase):
             optional,
             _decoded,
         ) = d.draw(bit_string_values_strategy(value_required=True))
+        tail_junk = d.draw(binary(max_size=5))
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
         for klass in (BitString, BitStringInherited):
@@ -1296,10 +1310,13 @@ class TestBitString(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
@@ -1634,8 +1651,9 @@ class TestOctetString(CommonMixin, TestCase):
         binary(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (OctetString, OctetStringInherited):
             _, _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -1653,10 +1671,13 @@ class TestOctetString(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
@@ -1813,8 +1834,9 @@ class TestNull(CommonMixin, TestCase):
         null_values_strategy(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, tag_expl, offset):
+    def test_symmetric(self, values, tag_expl, offset, tail_junk):
         for klass in (Null, NullInherited):
             _, _, optional, _decoded = values
             obj = klass(optional=optional, _decoded=_decoded)
@@ -1827,10 +1849,13 @@ class TestNull(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
@@ -1953,12 +1978,12 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 _decoded_initial,
             ) = d.draw(oid_values_strategy())
             obj_initial = klass(
-                value_initial,
-                impl_initial,
-                expl_initial,
-                default_initial,
-                optional_initial or False,
-                _decoded_initial,
+                value=value_initial,
+                impl=impl_initial,
+                expl=expl_initial,
+                default=default_initial,
+                optional=optional_initial or False,
+                _decoded=_decoded_initial,
             )
             (
                 value,
@@ -1968,7 +1993,13 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 optional,
                 _decoded,
             ) = d.draw(oid_values_strategy(do_expl=impl_initial is None))
-            obj = obj_initial(value, impl, expl, default, optional)
+            obj = obj_initial(
+                value=value,
+                impl=impl,
+                expl=expl,
+                default=default,
+                optional=optional,
+            )
             if obj.ready:
                 value_expected = default if value is None else value
                 value_expected = (
@@ -1992,7 +2023,22 @@ class TestObjectIdentifier(CommonMixin, TestCase):
     @given(oid_values_strategy())
     def test_copy(self, values):
         for klass in (ObjectIdentifier, ObjectIdentifierInherited):
-            obj = klass(*values)
+            (
+                value,
+                impl,
+                expl,
+                default,
+                optional,
+                _decoded,
+            ) = values
+            obj = klass(
+                value=value,
+                impl=impl,
+                expl=expl,
+                default=default,
+                optional=optional,
+                _decoded=_decoded,
+            )
             obj_copied = obj.copy()
             self.assert_copied_basic_fields(obj, obj_copied)
             self.assertEqual(obj._value, obj_copied._value)
@@ -2112,8 +2158,9 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         oid_strategy(),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (ObjectIdentifier, ObjectIdentifierInherited):
             _, _, _, default, optional, _decoded = values
             obj = klass(
@@ -2131,10 +2178,13 @@ class TestObjectIdentifier(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertNotEqual(obj_decoded, obj)
             self.assertEqual(tuple(obj_decoded), tuple(obj_expled))
@@ -2389,6 +2439,7 @@ class TestEnumerated(CommonMixin, TestCase):
         tag_expl = d.draw(integers(min_value=1).map(tag_ctxc))
         offset = d.draw(integers(min_value=0))
         value = d.draw(sampled_from(sorted([v for _, v in schema_input])))
+        tail_junk = d.draw(binary(max_size=5))
 
         class E(Enumerated):
             schema = schema_input
@@ -2407,10 +2458,13 @@ class TestEnumerated(CommonMixin, TestCase):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertNotEqual(obj_decoded, obj)
         self.assertEqual(int(obj_decoded), int(obj_expled))
@@ -2700,6 +2754,7 @@ class StringMixin(object):
         value = d.draw(text(alphabet=self.text_alphabet()))
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
+        tail_junk = d.draw(binary(max_size=5))
         _, _, _, _, default, optional, _decoded = values
         obj = self.base_klass(
             value=value,
@@ -2716,10 +2771,13 @@ class StringMixin(object):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertNotEqual(obj_decoded, obj)
         self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
@@ -2746,35 +2804,110 @@ class TestUTF8String(StringMixin, CommonMixin, TestCase):
     base_klass = UTF8String
 
 
+class UnicodeDecodeErrorMixin(object):
+    @given(text(
+        alphabet="".join(six_unichr(i) for i in list(range(0x0410, 0x044f + 1))),
+        min_size=1,
+        max_size=5,
+    ))
+    def test_unicode_decode_error(self, cyrillic_text):
+        with self.assertRaises(DecodeError):
+            self.base_klass(cyrillic_text)
+
+
 class TestNumericString(StringMixin, CommonMixin, TestCase):
     base_klass = NumericString
 
+    def text_alphabet(self):
+        return digits
+
+    @given(text(alphabet=ascii_letters, min_size=1, max_size=5))
+    def test_non_numeric(self, cyrillic_text):
+        with assertRaisesRegex(self, DecodeError, "non-numeric"):
+            self.base_klass(cyrillic_text)
+
+    @given(
+        sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
+        integers(min_value=0),
+        lists(integers()),
+    )
+    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):
+            bounds = (bound_min, bound_min)
+        with self.assertRaises(DecodeError) as err:
+            String().decode(
+                self.base_klass(b"1" * value).encode(),
+                offset=offset,
+                decode_path=decode_path,
+            )
+        repr(err.exception)
+        self.assertEqual(err.exception.offset, offset)
+        self.assertEqual(err.exception.decode_path, decode_path)
+
 
-class TestPrintableString(StringMixin, CommonMixin, TestCase):
+class TestPrintableString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = PrintableString
 
 
-class TestTeletexString(StringMixin, CommonMixin, TestCase):
+class TestTeletexString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = TeletexString
 
 
-class TestVideotexString(StringMixin, CommonMixin, TestCase):
+class TestVideotexString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = VideotexString
 
 
-class TestIA5String(StringMixin, CommonMixin, TestCase):
+class TestIA5String(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = IA5String
 
 
-class TestGraphicString(StringMixin, CommonMixin, TestCase):
+class TestGraphicString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = GraphicString
 
 
-class TestVisibleString(StringMixin, CommonMixin, TestCase):
+class TestVisibleString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = VisibleString
 
 
-class TestGeneralString(StringMixin, CommonMixin, TestCase):
+class TestGeneralString(
+        UnicodeDecodeErrorMixin,
+        StringMixin,
+        CommonMixin,
+        TestCase,
+):
     base_klass = GeneralString
 
 
@@ -2984,6 +3117,7 @@ class TimeMixin(object):
         ))
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
+        tail_junk = d.draw(binary(max_size=5))
         _, _, _, default, optional, _decoded = values
         obj = self.base_klass(
             value=value,
@@ -3000,10 +3134,13 @@ class TimeMixin(object):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertEqual(obj_decoded.todatetime(), obj_expled.todatetime())
         self.assertEqual(obj_decoded.todatetime(), obj.todatetime())
@@ -3302,8 +3439,9 @@ class TestAny(CommonMixin, TestCase):
         integers().map(lambda x: Integer(x).encode()),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         for klass in (Any, AnyInherited):
             _, _, optional, _decoded = values
             obj = klass(value=value, optional=optional, _decoded=_decoded)
@@ -3316,10 +3454,13 @@ class TestAny(CommonMixin, TestCase):
             repr(obj_expled)
             pprint(obj_expled)
             obj_expled_encoded = obj_expled.encode()
-            obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+            obj_decoded, tail = obj_expled.decode(
+                obj_expled_encoded + tail_junk,
+                offset=offset,
+            )
             repr(obj_decoded)
             pprint(obj_decoded)
-            self.assertEqual(tail, b"")
+            self.assertEqual(tail, tail_junk)
             self.assertEqual(obj_decoded, obj_expled)
             self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
             self.assertEqual(bytes(obj_decoded), bytes(obj))
@@ -3546,6 +3687,7 @@ class TestChoice(CommonMixin, TestCase):
         )
         tag_expl = tag_ctxc(d.draw(integers(min_value=1)))
         offset = d.draw(integers(min_value=0))
+        tail_junk = d.draw(binary(max_size=5))
 
         class Wahl(self.base_klass):
             schema = _schema
@@ -3564,10 +3706,13 @@ class TestChoice(CommonMixin, TestCase):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self.assertEqual(obj_decoded, obj_expled)
         self.assertEqual(obj_decoded.choice, obj_expled.choice)
         self.assertEqual(obj_decoded.value, obj_expled.value)
@@ -3627,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):
@@ -3802,7 +3967,7 @@ def sequences_strategy(draw, seq_klass):
 class SeqMixing(object):
     def test_invalid_value_type(self):
         with self.assertRaises(InvalidValueType) as err:
-            self.base_klass((1, 2, 3))
+            self.base_klass(123)
         repr(err.exception)
 
     def test_invalid_value_type_set(self):
@@ -4033,14 +4198,15 @@ class SeqMixing(object):
     @given(data_strategy())
     def test_symmetric(self, d):
         seq, expects = d.draw(sequence_strategy(seq_klass=self.base_klass))
+        tail_junk = d.draw(binary(max_size=5))
         self.assertTrue(seq.ready)
         self.assertFalse(seq.decoded)
         self._assert_expects(seq, expects)
         repr(seq)
         pprint(seq)
         seq_encoded = seq.encode()
-        seq_decoded, tail = seq.decode(seq_encoded)
-        self.assertEqual(tail, b"")
+        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)
@@ -4568,8 +4734,9 @@ class SeqOfMixing(object):
         lists(integers().map(Integer)),
         integers(min_value=1).map(tag_ctxc),
         integers(min_value=0),
+        binary(max_size=5),
     )
-    def test_symmetric(self, values, value, tag_expl, offset):
+    def test_symmetric(self, values, value, tag_expl, offset, tail_junk):
         _, _, _, _, _, default, optional, _decoded = values
 
         class SeqOf(self.base_klass):
@@ -4589,10 +4756,13 @@ class SeqOfMixing(object):
         repr(obj_expled)
         pprint(obj_expled)
         obj_expled_encoded = obj_expled.encode()
-        obj_decoded, tail = obj_expled.decode(obj_expled_encoded, offset=offset)
+        obj_decoded, tail = obj_expled.decode(
+            obj_expled_encoded + tail_junk,
+            offset=offset,
+        )
         repr(obj_decoded)
         pprint(obj_decoded)
-        self.assertEqual(tail, b"")
+        self.assertEqual(tail, tail_junk)
         self._test_symmetric_compare_objs(obj_decoded, obj_expled)
         self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
         self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
@@ -4861,3 +5031,257 @@ class TestAutoAddSlots(TestCase):
         with self.assertRaises(AttributeError):
             inher = Inher()
             inher.unexistent = "whatever"
+
+
+class TestOIDDefines(TestCase):
+    @given(data_strategy())
+    def runTest(self, d):
+        value_names = list(d.draw(sets(text_letters(), min_size=1, max_size=10)))
+        value_name_chosen = d.draw(sampled_from(value_names))
+        oids = [
+            ObjectIdentifier(oid)
+            for oid in d.draw(sets(oid_strategy(), min_size=2, max_size=10))
+        ]
+        oid_chosen = d.draw(sampled_from(oids))
+        values = d.draw(lists(
+            integers(),
+            min_size=len(value_names),
+            max_size=len(value_names),
+        ))
+        _schema = [
+            ("type", ObjectIdentifier(defines=(((value_name_chosen,), {
+                oid: Integer() for oid in oids[:-1]
+            }),))),
+        ]
+        for i, value_name in enumerate(value_names):
+            _schema.append((value_name, Any(expl=tag_ctxp(i))))
+
+        class Seq(Sequence):
+            schema = _schema
+        seq = Seq()
+        for value_name, value in zip(value_names, values):
+            seq[value_name] = Any(Integer(value).encode())
+        seq["type"] = oid_chosen
+        seq, _ = Seq().decode(seq.encode())
+        for value_name in value_names:
+            if value_name == value_name_chosen:
+                continue
+            self.assertIsNone(seq[value_name].defined)
+        if value_name_chosen in oids[:-1]:
+            self.assertIsNotNone(seq[value_name_chosen].defined)
+            self.assertEqual(seq[value_name_chosen].defined[0], oid_chosen)
+            self.assertIsInstance(seq[value_name_chosen].defined[1], Integer)
+
+
+class TestDefinesByPath(TestCase):
+    def test_generated(self):
+        class Seq(Sequence):
+            schema = (
+                ("type", ObjectIdentifier()),
+                ("value", OctetString(expl=tag_ctxc(123))),
+            )
+
+        class SeqInner(Sequence):
+            schema = (
+                ("typeInner", ObjectIdentifier()),
+                ("valueInner", Any()),
+            )
+
+        class PairValue(SetOf):
+            schema = Any()
+
+        class Pair(Sequence):
+            schema = (
+                ("type", ObjectIdentifier()),
+                ("value", PairValue()),
+            )
+
+        class Pairs(SequenceOf):
+            schema = Pair()
+
+        (
+            type_integered,
+            type_sequenced,
+            type_innered,
+            type_octet_stringed,
+        ) = [
+            ObjectIdentifier(oid)
+            for oid in sets(oid_strategy(), min_size=4, max_size=4).example()
+        ]
+        seq_integered = Seq()
+        seq_integered["type"] = type_integered
+        seq_integered["value"] = OctetString(Integer(123).encode())
+        seq_integered_raw = seq_integered.encode()
+
+        pairs = Pairs()
+        pairs_input = (
+            (type_octet_stringed, OctetString(b"whatever")),
+            (type_integered, Integer(123)),
+            (type_octet_stringed, OctetString(b"whenever")),
+            (type_integered, Integer(234)),
+        )
+        for t, v in pairs_input:
+            pair = Pair()
+            pair["type"] = t
+            pair["value"] = PairValue((Any(v),))
+            pairs.append(pair)
+        seq_inner = SeqInner()
+        seq_inner["typeInner"] = type_innered
+        seq_inner["valueInner"] = Any(pairs)
+        seq_sequenced = Seq()
+        seq_sequenced["type"] = type_sequenced
+        seq_sequenced["value"] = OctetString(seq_inner.encode())
+        seq_sequenced_raw = seq_sequenced.encode()
+
+        defines_by_path = []
+        seq_integered, _ = Seq().decode(seq_integered_raw)
+        self.assertIsNone(seq_integered["value"].defined)
+        defines_by_path.append(
+            (("type",), ((("value",), {
+                type_integered: Integer(),
+                type_sequenced: SeqInner(),
+            }),))
+        )
+        seq_integered, _ = Seq().decode(
+            seq_integered_raw,
+            ctx={"defines_by_path": defines_by_path},
+        )
+        self.assertIsNotNone(seq_integered["value"].defined)
+        self.assertEqual(seq_integered["value"].defined[0], type_integered)
+        self.assertEqual(seq_integered["value"].defined[1], Integer(123))
+        self.assertTrue(seq_integered_raw[
+            seq_integered["value"].defined[1].offset:
+        ].startswith(Integer(123).encode()))
+
+        seq_sequenced, _ = Seq().decode(
+            seq_sequenced_raw,
+            ctx={"defines_by_path": defines_by_path},
+        )
+        self.assertIsNotNone(seq_sequenced["value"].defined)
+        self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
+        seq_inner = seq_sequenced["value"].defined[1]
+        self.assertIsNone(seq_inner["valueInner"].defined)
+
+        defines_by_path.append((
+            ("value", DecodePathDefBy(type_sequenced), "typeInner"),
+            ((("valueInner",), {type_innered: Pairs()}),),
+        ))
+        seq_sequenced, _ = Seq().decode(
+            seq_sequenced_raw,
+            ctx={"defines_by_path": defines_by_path},
+        )
+        self.assertIsNotNone(seq_sequenced["value"].defined)
+        self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
+        seq_inner = seq_sequenced["value"].defined[1]
+        self.assertIsNotNone(seq_inner["valueInner"].defined)
+        self.assertEqual(seq_inner["valueInner"].defined[0], type_innered)
+        pairs = seq_inner["valueInner"].defined[1]
+        for pair in pairs:
+            self.assertIsNone(pair["value"][0].defined)
+
+        defines_by_path.append((
+            (
+                "value",
+                DecodePathDefBy(type_sequenced),
+                "valueInner",
+                DecodePathDefBy(type_innered),
+                any,
+                "type",
+            ),
+            ((("value",), {
+                type_integered: Integer(),
+                type_octet_stringed: OctetString(),
+            }),),
+        ))
+        seq_sequenced, _ = Seq().decode(
+            seq_sequenced_raw,
+            ctx={"defines_by_path": defines_by_path},
+        )
+        self.assertIsNotNone(seq_sequenced["value"].defined)
+        self.assertEqual(seq_sequenced["value"].defined[0], type_sequenced)
+        seq_inner = seq_sequenced["value"].defined[1]
+        self.assertIsNotNone(seq_inner["valueInner"].defined)
+        self.assertEqual(seq_inner["valueInner"].defined[0], type_innered)
+        pairs_got = seq_inner["valueInner"].defined[1]
+        for pair_input, pair_got in zip(pairs_input, pairs_got):
+            self.assertEqual(pair_got["value"][0].defined[0], pair_input[0])
+            self.assertEqual(pair_got["value"][0].defined[1], pair_input[1])
+
+    @given(oid_strategy(), integers())
+    def test_simple(self, oid, tgt):
+        class Inner(Sequence):
+            schema = (
+                ("oid", ObjectIdentifier(defines=((("..", "tgt"), {
+                    ObjectIdentifier(oid): Integer(),
+                }),))),
+            )
+
+        class Outer(Sequence):
+            schema = (
+                ("inner", Inner()),
+                ("tgt", OctetString()),
+            )
+
+        inner = Inner()
+        inner["oid"] = ObjectIdentifier(oid)
+        outer = Outer()
+        outer["inner"] = inner
+        outer["tgt"] = OctetString(Integer(tgt).encode())
+        decoded, _ = Outer().decode(outer.encode())
+        self.assertEqual(decoded["tgt"].defined[1], Integer(tgt))
+
+
+class TestAbsDecodePath(TestCase):
+    @given(
+        lists(text(alphabet=ascii_letters, min_size=1)).map(tuple),
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple),
+    )
+    def test_concat(self, decode_path, rel_path):
+        self.assertSequenceEqual(
+            abs_decode_path(decode_path, rel_path),
+            decode_path + rel_path,
+        )
+
+    @given(
+        lists(text(alphabet=ascii_letters, min_size=1)).map(tuple),
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple),
+    )
+    def test_abs(self, decode_path, rel_path):
+        self.assertSequenceEqual(
+            abs_decode_path(decode_path, ("/",) + rel_path),
+            rel_path,
+        )
+
+    @given(
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=5).map(tuple),
+        integers(min_value=1, max_value=3),
+        lists(text(alphabet=ascii_letters, min_size=1), min_size=1).map(tuple),
+    )
+    def test_dots(self, decode_path, number_of_dots, rel_path):
+        self.assertSequenceEqual(
+            abs_decode_path(decode_path, tuple([".."] * number_of_dots) + rel_path),
+            decode_path[:-number_of_dots] + rel_path,
+        )
+
+
+class TestStrictDefaultExistence(TestCase):
+    @given(data_strategy())
+    def runTest(self, d):
+        count = d.draw(integers(min_value=1, max_value=10))
+        chosen = d.draw(integers(min_value=0, max_value=count - 1))
+        _schema = [
+            ("int%d" % i, Integer(expl=tag_ctxc(i + 1)))
+            for i in range(count)
+        ]
+
+        class Seq(Sequence):
+            schema = _schema
+        seq = Seq()
+        for i in range(count):
+            seq["int%d" % i] = Integer(123)
+        raw = seq.encode()
+        chosen = "int%d" % chosen
+        seq.specs[chosen] = seq.specs[chosen](default=123)
+        seq.decode(raw)
+        with assertRaisesRegex(self, DecodeError, "DEFAULT value met"):
+            seq.decode(raw, ctx={"strict_default_existence": True})