pypcapkit 1.0.3.post3__pp39-none-any.whl → 1.1.0.post1__pp39-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pcapkit/__init__.py +1 -1
- pcapkit/const/pcapng/option_type.py +25 -1
- pcapkit/const/reg/apptype.py +25 -1
- pcapkit/corekit/fields/collections.py +4 -5
- pcapkit/corekit/fields/field.py +33 -8
- pcapkit/corekit/fields/ipaddress.py +0 -1
- pcapkit/corekit/fields/misc.py +14 -19
- pcapkit/corekit/fields/numbers.py +2 -3
- pcapkit/corekit/fields/strings.py +2 -3
- pcapkit/corekit/infoclass.py +59 -6
- pcapkit/corekit/protochain.py +8 -1
- pcapkit/foundation/registry/protocols.py +30 -51
- pcapkit/protocols/data/__init__.py +4 -4
- pcapkit/protocols/data/internet/__init__.py +4 -4
- pcapkit/protocols/data/internet/mh.py +4 -4
- pcapkit/protocols/data/transport/__init__.py +4 -4
- pcapkit/protocols/data/transport/tcp.py +4 -4
- pcapkit/protocols/internet/mh.py +20 -20
- pcapkit/protocols/protocol.py +3 -2
- pcapkit/protocols/schema/__init__.py +4 -4
- pcapkit/protocols/schema/application/httpv2.py +16 -29
- pcapkit/protocols/schema/internet/__init__.py +4 -4
- pcapkit/protocols/schema/internet/hip.py +62 -111
- pcapkit/protocols/schema/internet/hopopt.py +46 -48
- pcapkit/protocols/schema/internet/ipv4.py +36 -41
- pcapkit/protocols/schema/internet/ipv6_opts.py +48 -52
- pcapkit/protocols/schema/internet/ipv6_route.py +9 -20
- pcapkit/protocols/schema/internet/mh.py +49 -84
- pcapkit/protocols/schema/misc/pcapng.py +178 -195
- pcapkit/protocols/schema/schema.py +252 -53
- pcapkit/protocols/schema/transport/__init__.py +4 -4
- pcapkit/protocols/schema/transport/tcp.py +52 -83
- pcapkit/protocols/transport/tcp.py +14 -14
- pcapkit/vendor/pcapng/option_type.py +25 -1
- pcapkit/vendor/reg/apptype.py +25 -1
- {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/METADATA +1 -1
- {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/RECORD +41 -41
- {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/LICENSE +0 -0
- {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/WHEEL +0 -0
- {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/entry_points.txt +0 -0
- {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,13 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
"""schema for protocol headers"""
|
3
3
|
|
4
|
+
import abc
|
4
5
|
import collections
|
5
6
|
import collections.abc
|
6
7
|
import io
|
7
8
|
import itertools
|
8
|
-
|
9
|
+
import sys
|
10
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast, final
|
9
11
|
|
10
12
|
from pcapkit.corekit.fields.collections import ListField, OptionField
|
11
13
|
from pcapkit.corekit.fields.field import NoValue, _Field
|
@@ -19,13 +21,15 @@ from pcapkit.utilities.warnings import SchemaWarning, UnknownFieldWarning, warn
|
|
19
21
|
|
20
22
|
if TYPE_CHECKING:
|
21
23
|
from collections import OrderedDict
|
22
|
-
from
|
24
|
+
from enum import Enum
|
25
|
+
from typing import IO, Any, Callable, DefaultDict, Iterable, Iterator, Optional, Type
|
23
26
|
|
24
27
|
from typing_extensions import Self
|
25
28
|
|
26
|
-
__all__ = ['Schema', 'schema_final']
|
29
|
+
__all__ = ['Schema', 'EnumSchema', 'schema_final']
|
27
30
|
|
28
31
|
VT = TypeVar('VT')
|
32
|
+
ET = TypeVar('ET', bound='Enum')
|
29
33
|
ST = TypeVar('ST', bound='Type[Schema]')
|
30
34
|
|
31
35
|
|
@@ -37,6 +41,11 @@ def schema_final(cls: 'ST', *, _finalised: 'bool' = True) -> 'ST':
|
|
37
41
|
class. It can be useful to reduce runtime generation
|
38
42
|
time as well as caching already generated attributes.
|
39
43
|
|
44
|
+
Notes:
|
45
|
+
The decorator should only be used on the *final*
|
46
|
+
class, otherwise, any subclasses derived from a
|
47
|
+
finalised schema class will not be re-finalised.
|
48
|
+
|
40
49
|
Args:
|
41
50
|
cls: Schema class.
|
42
51
|
_finalised: Whether to make the schema class finalised.
|
@@ -44,11 +53,6 @@ def schema_final(cls: 'ST', *, _finalised: 'bool' = True) -> 'ST':
|
|
44
53
|
Returns:
|
45
54
|
Finalised schema class.
|
46
55
|
|
47
|
-
Notes:
|
48
|
-
The decorator should only be used on the *final*
|
49
|
-
class, otherwise, any subclasses derived from a
|
50
|
-
finalised schema class will not be re-finalised.
|
51
|
-
|
52
56
|
:meta decorator:
|
53
57
|
"""
|
54
58
|
if cls.__finalised__ == FinalisedState.FINAL:
|
@@ -65,50 +69,8 @@ def schema_final(cls: 'ST', *, _finalised: 'bool' = True) -> 'ST':
|
|
65
69
|
cls.__builtin__ = set(temp)
|
66
70
|
cls.__excluded__.extend(cls.__builtin__)
|
67
71
|
|
68
|
-
args_ = [
|
69
|
-
dict_ = [
|
70
|
-
|
71
|
-
cls_fields = [] # type: list[tuple[str, _Field]]
|
72
|
-
for cls_ in cls.mro():
|
73
|
-
# NOTE: We skip the ``Schema`` class itself, to avoid superclass
|
74
|
-
# type annotations being considered.
|
75
|
-
if cls_ is Schema:
|
76
|
-
break
|
77
|
-
|
78
|
-
# NOTE: We iterate in reversed order to ensure that the type
|
79
|
-
# annotations of the superclasses are considered first.
|
80
|
-
for key in reversed(cls_.__dict__.keys()):
|
81
|
-
# NOTE: We skip duplicated annotations to avoid duplicate
|
82
|
-
# argument in function definition.
|
83
|
-
if key in args_:
|
84
|
-
continue
|
85
|
-
|
86
|
-
# NOTE: We only consider the fields that are instances of
|
87
|
-
# the ``_Field`` class.
|
88
|
-
field = cls_.__dict__[key]
|
89
|
-
if not isinstance(field, _Field):
|
90
|
-
continue
|
91
|
-
|
92
|
-
# NOTE: We need to consider the case where the field itself
|
93
|
-
# is optional, i.e., the field is not required to be present
|
94
|
-
# in the protocol header.
|
95
|
-
args_.append(f'{key}=NoValue')
|
96
|
-
dict_.append(f'{key}={key}')
|
97
|
-
|
98
|
-
field.name = key
|
99
|
-
cls_fields.append((key, field))
|
100
|
-
|
101
|
-
# NOTE: We reverse the two lists such that the order of the
|
102
|
-
# arguments is the same as the order of the field definition,
|
103
|
-
# i.e., from the most base class to the most derived class.
|
104
|
-
args_.reverse()
|
105
|
-
dict_.reverse()
|
106
|
-
|
107
|
-
# NOTE: We also reverse the field list such that the order
|
108
|
-
# can be preserved in the :class:`~collections.OrderedDict`
|
109
|
-
# instance.
|
110
|
-
cls_fields.reverse()
|
111
|
-
cls.__fields__ = collections.OrderedDict(cls_fields)
|
72
|
+
args_ = [f'{key}=NoValue' for key in cls.__fields__]
|
73
|
+
dict_ = [f'{key}={key}' for key in cls.__fields__]
|
112
74
|
|
113
75
|
# NOTE: We shall only attempt to generate ``__init__`` method
|
114
76
|
# if the class does not define such method.
|
@@ -148,7 +110,118 @@ def schema_final(cls: 'ST', *, _finalised: 'bool' = True) -> 'ST':
|
|
148
110
|
return final(cls)
|
149
111
|
|
150
112
|
|
151
|
-
class
|
113
|
+
class SchemaMeta(abc.ABCMeta):
|
114
|
+
"""Meta class to add dynamic support to :class:`Schema`.
|
115
|
+
|
116
|
+
This meta class is used to generate necessary attributes for the
|
117
|
+
:class:`Schema` class. It can be useful to reduce runtime generation
|
118
|
+
cost as well as caching already generated attributes.
|
119
|
+
|
120
|
+
* :attr:`Schema.__fields__` is a dictionary of field names and their
|
121
|
+
corresponding :class:`~pcapkit.corekit.fields.field.Field` objects,
|
122
|
+
which are used to define and parse the protocol headers. The field
|
123
|
+
dictionary will automatically be populated from the class attributes
|
124
|
+
of the :class:`Schema` class, and the field names will be the same
|
125
|
+
as the attribute names.
|
126
|
+
|
127
|
+
.. seealso::
|
128
|
+
|
129
|
+
This is implemented thru setting up the initial field dictionary
|
130
|
+
in the :meth:`__prepare__` method, and then inherit the field
|
131
|
+
dictionaries from the base classes.
|
132
|
+
|
133
|
+
Later, during the class creation, the :meth:`Field.__set_name__`
|
134
|
+
method will be called to set the field name for each field object,
|
135
|
+
as well as to add the field object to the field dictionary.
|
136
|
+
|
137
|
+
* :attr:`Schema.__additional__` and :attr:`Schema.__excluded__` are
|
138
|
+
lists of additional and excluded field names, which are used to
|
139
|
+
determine certain names to be included or excluded from the field
|
140
|
+
dictionary. They will be automatically populated from the class
|
141
|
+
attributes of the :class:`Schema` class and its base classes.
|
142
|
+
|
143
|
+
.. note::
|
144
|
+
|
145
|
+
This is implemented thru the :meth:`__new__` method, which will
|
146
|
+
inherit the additional and excluded field names from the base
|
147
|
+
classes, as well as populating the additional and excluded field
|
148
|
+
from the subclass attributes.
|
149
|
+
|
150
|
+
.. code-block:: python
|
151
|
+
|
152
|
+
class A(Schema):
|
153
|
+
__additional__ = ['a', 'b']
|
154
|
+
|
155
|
+
class B(A):
|
156
|
+
__additional__ = ['c', 'd']
|
157
|
+
|
158
|
+
class C(B):
|
159
|
+
__additional__ = ['e', 'f']
|
160
|
+
|
161
|
+
print(A.__additional__) # ['a', 'b']
|
162
|
+
print(B.__additional__) # ['a', 'b', 'c', 'd']
|
163
|
+
print(C.__additional__) # ['a', 'b', 'c', 'd', 'e', 'f']
|
164
|
+
|
165
|
+
Args:
|
166
|
+
name: Schema class name.
|
167
|
+
bases: Schema class bases.
|
168
|
+
attrs: Schema class attributes.
|
169
|
+
**kwargs: Arbitrary keyword arguments in class definition.
|
170
|
+
|
171
|
+
"""
|
172
|
+
|
173
|
+
@classmethod
|
174
|
+
def __prepare__(cls, name: 'str', bases: 'tuple[type, ...]', /, **kwds: 'Any') -> 'Mapping[str, object]':
|
175
|
+
"""Prepare the namespace for the schema class.
|
176
|
+
|
177
|
+
Args:
|
178
|
+
name: Name of the schema class.
|
179
|
+
bases: Base classes of the schema class.
|
180
|
+
**kwds: Additional keyword arguments at class definition.
|
181
|
+
|
182
|
+
This method is used to create the initial field dictionary
|
183
|
+
:attr:`~Schema.__fields__` for the schema class.
|
184
|
+
|
185
|
+
"""
|
186
|
+
fields = collections.OrderedDict()
|
187
|
+
for base in bases:
|
188
|
+
if hasattr(base, '__fields__'):
|
189
|
+
fields.update(base.__fields__)
|
190
|
+
return collections.OrderedDict(__fields__=fields)
|
191
|
+
|
192
|
+
def __new__(cls, name: 'str', bases: 'tuple[type, ...]', attrs: 'dict[str, Any]', **kwargs: 'Any') -> 'Type[Schema]':
|
193
|
+
"""Create the schema class.
|
194
|
+
|
195
|
+
Args:
|
196
|
+
name: Schema class name.
|
197
|
+
bases: Schema class bases.
|
198
|
+
attrs: Schema class attributes.
|
199
|
+
**kwargs: Arbitrary keyword arguments in class definition.
|
200
|
+
|
201
|
+
This method is used to inherit the :attr:`~Schema.__additional__` and
|
202
|
+
:attr:`~Schema.__excluded__` fields from the base classes, as well as
|
203
|
+
populating both fields from the subclass attributes.
|
204
|
+
|
205
|
+
"""
|
206
|
+
if '__additional__' not in attrs:
|
207
|
+
attrs['__additional__'] = []
|
208
|
+
if '__excluded__' not in attrs:
|
209
|
+
attrs['__excluded__'] = []
|
210
|
+
|
211
|
+
for base in bases:
|
212
|
+
if hasattr(base, '__additional__'):
|
213
|
+
attrs['__additional__'].extend(name for name in base.__additional__ if name not in attrs['__additional__'])
|
214
|
+
if hasattr(base, '__excluded__'):
|
215
|
+
attrs['__excluded__'].extend(name for name in base.__excluded__ if name not in attrs['__excluded__'])
|
216
|
+
|
217
|
+
# NOTE: for unknown reason, the following code will cause an error
|
218
|
+
# for duplicated keyword arguments in class definition.
|
219
|
+
if sys.version_info < (3, 11):
|
220
|
+
return type.__new__(cls, name, bases, attrs, **kwargs)
|
221
|
+
return super().__new__(cls, name, bases, attrs, **kwargs) # type: ignore[return-value]
|
222
|
+
|
223
|
+
|
224
|
+
class Schema(Mapping[str, VT], Generic[VT], metaclass=SchemaMeta):
|
152
225
|
"""Schema for protocol headers."""
|
153
226
|
|
154
227
|
if TYPE_CHECKING:
|
@@ -603,3 +676,129 @@ class Schema(Mapping[str, VT], Generic[VT]):
|
|
603
676
|
|
604
677
|
"""
|
605
678
|
return self
|
679
|
+
|
680
|
+
|
681
|
+
class EnumMeta(SchemaMeta):
|
682
|
+
"""Meta class to add dynamic support for :class:`EnumSchema`.
|
683
|
+
|
684
|
+
This meta class is used to generate necessary attributes for the
|
685
|
+
:class:`SchemaMeta` class. It can be useful to reduce runtime generation
|
686
|
+
cost as well as caching already generated attributes.
|
687
|
+
|
688
|
+
* :attr:`~EnumSchema.registry` is added to subclasses as an *immutable*
|
689
|
+
proxy (similar to :class:`property`, but on class variables) to the
|
690
|
+
:attr:`~EnumSchema.__enum__` mapping.
|
691
|
+
|
692
|
+
Args:
|
693
|
+
name: Schema class name.
|
694
|
+
bases: Schema class bases.
|
695
|
+
attrs: Schema class attributes.
|
696
|
+
**kwargs: Arbitrary keyword arguments in class definition.
|
697
|
+
|
698
|
+
"""
|
699
|
+
|
700
|
+
@property
|
701
|
+
def registry(cls) -> 'DefaultDict[ET, Type[EnumSchema]]':
|
702
|
+
"""Mapping of enumeration numbers to schemas."""
|
703
|
+
return cls.__enum__ # type: ignore[attr-defined]
|
704
|
+
|
705
|
+
|
706
|
+
class EnumSchema(Schema, Generic[ET], metaclass=EnumMeta):
|
707
|
+
""":class:`Schema` with enumeration mapping support.
|
708
|
+
|
709
|
+
Examples:
|
710
|
+
|
711
|
+
To create an enumeration mapping supported schema, simply
|
712
|
+
|
713
|
+
.. code-block:: python
|
714
|
+
|
715
|
+
class MySchema(EnumSchema[MyEnum]):
|
716
|
+
|
717
|
+
# optional, set the default schema for enumeration mapping
|
718
|
+
# if the enumeration number is not found in the mapping
|
719
|
+
__default__ = lambda: UnknownSchema # by default, None
|
720
|
+
|
721
|
+
then, you can use inheritance to create a list of schemas
|
722
|
+
for this given enumeration mapping:
|
723
|
+
|
724
|
+
.. code-block:: python
|
725
|
+
|
726
|
+
class OneSchema(MySchema, code=MyEnum.ONE):
|
727
|
+
...
|
728
|
+
|
729
|
+
class MultipleSchema(MySchema, code=[MyEnum.TWO, MyEnum.THREE]):
|
730
|
+
...
|
731
|
+
|
732
|
+
or optionally, using the :meth:`register` method to register a
|
733
|
+
schema to the enumeration mapping:
|
734
|
+
|
735
|
+
.. code-block:: python
|
736
|
+
|
737
|
+
MySchema.register(MyEnum.ZERO, ZeroSchema)
|
738
|
+
|
739
|
+
And now you can access the enumeration mapping via the :attr:`registry`
|
740
|
+
property (more specifically, class attribute):
|
741
|
+
|
742
|
+
.. code-block:: python
|
743
|
+
|
744
|
+
>>> MySchema.registry[MyEnum.ONE] # OneSchema
|
745
|
+
|
746
|
+
"""
|
747
|
+
|
748
|
+
__additional__ = ['__enum__', '__default__']
|
749
|
+
__excluded__ = ['__enum__', '__default__']
|
750
|
+
|
751
|
+
#: Callback to return the default schema for enumeration mapping,
|
752
|
+
#: by default is a ``lambda: None`` statement.
|
753
|
+
__default__: 'Callable[[], Type[Self]]' = lambda: None # type: ignore[assignment,return-value]
|
754
|
+
|
755
|
+
if TYPE_CHECKING:
|
756
|
+
#: Mapping of enumeration numbers to schemas (**internal use only**).
|
757
|
+
__enum__: 'DefaultDict[ET, Type[Self]]'
|
758
|
+
#: Mapping of enumeration numbers to schemas.
|
759
|
+
registry: 'DefaultDict[ET, Type[Self]]'
|
760
|
+
|
761
|
+
def __init_subclass__(cls, /, code: 'Optional[ET | Iterable[ET]]' = None, *args: 'Any', **kwargs: 'Any') -> 'None':
|
762
|
+
"""Register enumeration to :attr:`registry` mapping.
|
763
|
+
|
764
|
+
Args:
|
765
|
+
code: Enumeration code. It can be either a single enumeration
|
766
|
+
or a list of enumerations.
|
767
|
+
*args: Arbitrary positional arguments.
|
768
|
+
**kwargs: Arbitrary keyword arguments.
|
769
|
+
|
770
|
+
If ``code`` is provided, the subclass will be registered to the
|
771
|
+
:attr:`registry` mapping with the given ``code``. If ``code`` is
|
772
|
+
not given, the subclass will not be registered.
|
773
|
+
|
774
|
+
Notes:
|
775
|
+
If :attr:`__enum__` is not yet defined at function call, it will
|
776
|
+
automatically be defined as a :class:`collections.defaultdict`
|
777
|
+
object, with the default value set to :attr:`__default__`.
|
778
|
+
|
779
|
+
If intended to customise the :attr:`__enum__` mapping, it is
|
780
|
+
possible to override the :meth:`__init_subclass__` method and
|
781
|
+
define :attr:`__enum__` manually.
|
782
|
+
|
783
|
+
"""
|
784
|
+
if not hasattr(cls, '__enum__'):
|
785
|
+
cls.__enum__ = collections.defaultdict(cls.__default__)
|
786
|
+
|
787
|
+
if code is not None:
|
788
|
+
if isinstance(code, collections.abc.Iterable):
|
789
|
+
for _code in code:
|
790
|
+
cls.__enum__[_code] = cls
|
791
|
+
else:
|
792
|
+
cls.__enum__[code] = cls
|
793
|
+
super().__init_subclass__()
|
794
|
+
|
795
|
+
@classmethod
|
796
|
+
def register(cls, code: 'ET', schema: 'Type[Self]') -> 'None':
|
797
|
+
"""Register enumetaion to :attr:`__enum__` mapping.
|
798
|
+
|
799
|
+
Args:
|
800
|
+
code: Enumetaion code.
|
801
|
+
schema: Enumetaion schema.
|
802
|
+
|
803
|
+
"""
|
804
|
+
cls.__enum__[code] = schema
|
@@ -36,11 +36,11 @@ from pcapkit.protocols.schema.transport.tcp import Option as TCP_Option
|
|
36
36
|
from pcapkit.protocols.schema.transport.tcp import \
|
37
37
|
PartialOrderConnectionPermitted as TCP_PartialOrderConnectionPermitted
|
38
38
|
from pcapkit.protocols.schema.transport.tcp import \
|
39
|
-
|
39
|
+
PartialOrderServiceProfile as TCP_PartialOrderServiceProfile
|
40
40
|
from pcapkit.protocols.schema.transport.tcp import QuickStartResponse as TCP_QuickStartResponse
|
41
41
|
from pcapkit.protocols.schema.transport.tcp import SACKBlock as TCP_SACKBlock
|
42
42
|
from pcapkit.protocols.schema.transport.tcp import SACKPermitted as TCP_SACKPermitted
|
43
|
-
from pcapkit.protocols.schema.transport.tcp import
|
43
|
+
from pcapkit.protocols.schema.transport.tcp import Timestamps as TCP_Timestamps
|
44
44
|
from pcapkit.protocols.schema.transport.tcp import UnassignedOption as TCP_UnassignedOption
|
45
45
|
from pcapkit.protocols.schema.transport.tcp import UserTimeout as TCP_UserTimeout
|
46
46
|
from pcapkit.protocols.schema.transport.tcp import WindowScale as TCP_WindowScale
|
@@ -54,8 +54,8 @@ __all__ = [
|
|
54
54
|
'TCP_SACKBlock',
|
55
55
|
'TCP_Option',
|
56
56
|
'TCP_UnassignedOption', 'TCP_EndOfOptionList', 'TCP_NoOperation', 'TCP_MaximumSegmentSize', 'TCP_WindowScale',
|
57
|
-
'TCP_SACKPermitted', 'TCP_SACK', 'TCP_Echo', 'TCP_EchoReply', '
|
58
|
-
'
|
57
|
+
'TCP_SACKPermitted', 'TCP_SACK', 'TCP_Echo', 'TCP_EchoReply', 'TCP_Timestamps', 'TCP_PartialOrderConnectionPermitted', # pylint: disable=line-too-long
|
58
|
+
'TCP_PartialOrderServiceProfile', 'TCP_CC', 'TCP_CCNew', 'TCP_CCEcho', 'TCP_AlternateChecksumRequest',
|
59
59
|
'TCP_AlternateChecksumData', 'TCP_MD5Signature', 'TCP_QuickStartResponse', 'TCP_UserTimeout',
|
60
60
|
'TCP_Authentication', 'TCP_FastOpenCookie',
|
61
61
|
'TCP_MPTCP',
|