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.
Files changed (41) hide show
  1. pcapkit/__init__.py +1 -1
  2. pcapkit/const/pcapng/option_type.py +25 -1
  3. pcapkit/const/reg/apptype.py +25 -1
  4. pcapkit/corekit/fields/collections.py +4 -5
  5. pcapkit/corekit/fields/field.py +33 -8
  6. pcapkit/corekit/fields/ipaddress.py +0 -1
  7. pcapkit/corekit/fields/misc.py +14 -19
  8. pcapkit/corekit/fields/numbers.py +2 -3
  9. pcapkit/corekit/fields/strings.py +2 -3
  10. pcapkit/corekit/infoclass.py +59 -6
  11. pcapkit/corekit/protochain.py +8 -1
  12. pcapkit/foundation/registry/protocols.py +30 -51
  13. pcapkit/protocols/data/__init__.py +4 -4
  14. pcapkit/protocols/data/internet/__init__.py +4 -4
  15. pcapkit/protocols/data/internet/mh.py +4 -4
  16. pcapkit/protocols/data/transport/__init__.py +4 -4
  17. pcapkit/protocols/data/transport/tcp.py +4 -4
  18. pcapkit/protocols/internet/mh.py +20 -20
  19. pcapkit/protocols/protocol.py +3 -2
  20. pcapkit/protocols/schema/__init__.py +4 -4
  21. pcapkit/protocols/schema/application/httpv2.py +16 -29
  22. pcapkit/protocols/schema/internet/__init__.py +4 -4
  23. pcapkit/protocols/schema/internet/hip.py +62 -111
  24. pcapkit/protocols/schema/internet/hopopt.py +46 -48
  25. pcapkit/protocols/schema/internet/ipv4.py +36 -41
  26. pcapkit/protocols/schema/internet/ipv6_opts.py +48 -52
  27. pcapkit/protocols/schema/internet/ipv6_route.py +9 -20
  28. pcapkit/protocols/schema/internet/mh.py +49 -84
  29. pcapkit/protocols/schema/misc/pcapng.py +178 -195
  30. pcapkit/protocols/schema/schema.py +252 -53
  31. pcapkit/protocols/schema/transport/__init__.py +4 -4
  32. pcapkit/protocols/schema/transport/tcp.py +52 -83
  33. pcapkit/protocols/transport/tcp.py +14 -14
  34. pcapkit/vendor/pcapng/option_type.py +25 -1
  35. pcapkit/vendor/reg/apptype.py +25 -1
  36. {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/METADATA +1 -1
  37. {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/RECORD +41 -41
  38. {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/LICENSE +0 -0
  39. {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/WHEEL +0 -0
  40. {pypcapkit-1.0.3.post3.dist-info → pypcapkit-1.1.0.post1.dist-info}/entry_points.txt +0 -0
  41. {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
- from typing import TYPE_CHECKING, Generic, TypeVar, cast, final
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 typing import IO, Any, Iterable, Iterator, Optional, Type
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_ = [] # type: list[str]
69
- dict_ = [] # type: list[str]
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 Schema(Mapping[str, VT], Generic[VT]):
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
- PartialOrderConnectionProfile as TCP_PartialOrderConnectionProfile
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 Timestamp as TCP_Timestamp
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', 'TCP_Timestamp', 'TCP_PartialOrderConnectionPermitted', # pylint: disable=line-too-long
58
- 'TCP_PartialOrderConnectionProfile', 'TCP_CC', 'TCP_CCNew', 'TCP_CCEcho', 'TCP_AlternateChecksumRequest',
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',