diff --git a/docs/source/contributing/conventions/protocol-member-order.rst b/docs/source/contributing/conventions/protocol-member-order.rst index 9d2748a80f..53bc2f880a 100644 --- a/docs/source/contributing/conventions/protocol-member-order.rst +++ b/docs/source/contributing/conventions/protocol-member-order.rst @@ -38,24 +38,37 @@ The Order ``_read_opt_unassigned()`` opens the readers of :class:`~pcapkit.protocols.internet.ipv4.protocol.IPv4`; the rest follow the enum value of the code or type each handles, not alphabetical order. Ruled on - :issue:`2490`. + :issue:`2490`. A class with several registries puts every registry's readers + before any registry's makers, as :class:`~pcapkit.protocols.internet.mh.protocol.MH` + and :class:`~pcapkit.protocols.misc.pcapng.protocol.PCAPNG` do, ruled on + :issue:`2772`. d. **Other helpers.** Every helper that ``read()``, ``make()`` and the registries do not reach goes last, including one that only another helper calls, such as ``_decode_text()`` in :class:`~pcapkit.protocols.application.httpv1.protocol.HTTPv1`. Ruled on :issue:`2490`. **Registry maps.** The registry maps themselves (``__proto__``, ``__option__``, -``__parameter__`` and similar) are ordered by their enum values, or alphabetically -by key where there is no enum. +``__parameter__`` and similar) are ordered by their enum values, by key value where +an :class:`int` key has no enum, and alphabetically where the key is a string, as the +namespaces of +:attr:`Option.__enum__ ` are. +:attr:`PCAPNG.__option__ `, +keyed by an option's namespace and value, is ordered by namespace, in the order +:class:`~pcapkit.const.pcapng.option_type.OptionType` declares them, then by value. +Ruled on :issue:`2772`. +:attr:`ProtocolBase.__registry__ ` +groups its built-in entries by layer under the comments ``# Link Layer``, +``# Internet Layer``, ``# Transport Layer``, ``# Application Layer`` and +``# Miscellaneous``, in that order, the last holding what belongs to no layer, and +is alphabetical within each group, ruled on :issue:`2773`. **The guard.** :file:`tests/project/test_protocol_member_order_2490_unit.py` holds every protocol class to the groups, ``_make_data()`` first, each ``_read_xxx()`` before its ``_make_xxx()``, subgroups b, c and d, and the order of the registry -maps, with a table of known exceptions (:issue:`2490`). It values a key that is an -enum member or an :class:`int`; a map with a key of another shape, such as the -computed keys of -:attr:`PCAPNG.__option__ `, -it leaves in its own order, which rule c then follows. +maps, layer groups included, with a table of known exceptions (:issue:`2490`). It +values a key that is an enum member, an :class:`int`, a string or a PCAP-NG option +key, and orders a string-keyed map within each group its comment lines open; a map +with a key of another shape it leaves in its own order, which rule c then follows. Example ~~~~~~~ diff --git a/docs/source/pcapkit/protocols/application/application.rst b/docs/source/pcapkit/protocols/application/application.rst index 2719bb438a..0a5ff51edf 100644 --- a/docs/source/pcapkit/protocols/application/application.rst +++ b/docs/source/pcapkit/protocols/application/application.rst @@ -22,13 +22,13 @@ which is a base class for application layer protocols, e.g. :no-members: :show-inheritance: - .. autoproperty:: layer - - .. automethod:: _decode_next_layer - .. automethod:: _import_next_layer - .. autoattribute:: __layer__ :no-value: + .. autoproperty:: layer + .. automethod:: __post_init__ .. automethod:: __index__ + + .. automethod:: _decode_next_layer + .. automethod:: _import_next_layer diff --git a/docs/source/pcapkit/protocols/application/http.rst b/docs/source/pcapkit/protocols/application/http.rst index 45fe3f8ba9..169d8a8930 100644 --- a/docs/source/pcapkit/protocols/application/http.rst +++ b/docs/source/pcapkit/protocols/application/http.rst @@ -37,29 +37,26 @@ which tells whether a payload opens with an HTTP/1.* start line. :no-members: :show-inheritance: + .. autoattribute:: _preface_length + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length .. autoproperty:: version .. automethod:: id - .. automethod:: read .. automethod:: make - - .. automethod:: _make_data + .. automethod:: from_data .. automethod:: __index__ .. automethod:: __init__ .. automethod:: __new__ + .. automethod:: _make_data .. automethod:: _guess_version - .. automethod:: _dispatch_version .. automethod:: _member_of_version - - .. autoattribute:: _preface_length - - .. automethod:: from_data + .. automethod:: _dispatch_version .. autofunction:: pcapkit.protocols.application.http.protocol.test_start_line diff --git a/docs/source/pcapkit/protocols/application/httpv1.rst b/docs/source/pcapkit/protocols/application/httpv1.rst index eb33c5c13a..f06bf01e23 100644 --- a/docs/source/pcapkit/protocols/application/httpv1.rst +++ b/docs/source/pcapkit/protocols/application/httpv1.rst @@ -51,13 +51,11 @@ structure of an HTTP/1.* message is described as below: .. autoproperty:: alias .. autoproperty:: version - .. automethod:: id - .. automethod:: read .. automethod:: make + .. automethod:: id .. automethod:: _make_data - .. automethod:: _read_http_header .. automethod:: _read_http_body diff --git a/docs/source/pcapkit/protocols/application/httpv2.rst b/docs/source/pcapkit/protocols/application/httpv2.rst index b6604b8bdd..76e79be0fc 100644 --- a/docs/source/pcapkit/protocols/application/httpv2.rst +++ b/docs/source/pcapkit/protocols/application/httpv2.rst @@ -66,18 +66,20 @@ exceptions: :no-members: :show-inheritance: + .. autoattribute:: __frame__ + :no-value: + .. autoproperty:: alias .. autoproperty:: length .. autoproperty:: version - .. automethod:: id - .. automethod:: register_frame - + .. automethod:: unpack .. automethod:: read .. automethod:: make + .. automethod:: id + .. automethod:: register_frame .. automethod:: _make_data - .. automethod:: _read_http_none .. automethod:: _read_http_data .. automethod:: _read_http_headers @@ -89,7 +91,6 @@ exceptions: .. automethod:: _read_http_goaway .. automethod:: _read_http_window_update .. automethod:: _read_http_continuation - .. automethod:: _make_http_none .. automethod:: _make_http_data .. automethod:: _make_http_headers @@ -102,11 +103,6 @@ exceptions: .. automethod:: _make_http_window_update .. automethod:: _make_http_continuation - .. autoattribute:: __frame__ - :no-value: - - .. automethod:: unpack - Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/application/ospf.rst b/docs/source/pcapkit/protocols/application/ospf.rst index 6b6a567d2c..f3760eb7aa 100644 --- a/docs/source/pcapkit/protocols/application/ospf.rst +++ b/docs/source/pcapkit/protocols/application/ospf.rst @@ -72,13 +72,11 @@ as below: .. automethod:: read .. automethod:: make - .. automethod:: _make_data + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_encrypt_auth .. automethod:: _make_encrypt_auth - - .. automethod:: __index__ - .. automethod:: _make_id_numbers Header Schemas diff --git a/docs/source/pcapkit/protocols/application/ssl.rst b/docs/source/pcapkit/protocols/application/ssl.rst index 0b99dd41e1..92c8832625 100644 --- a/docs/source/pcapkit/protocols/application/ssl.rst +++ b/docs/source/pcapkit/protocols/application/ssl.rst @@ -41,6 +41,9 @@ accepting bytes that cannot be a genuine record. :no-members: :show-inheritance: + .. autoattribute:: __extension__ + :no-value: + .. automethod:: id .. automethod:: register_extension @@ -52,7 +55,6 @@ accepting bytes that cannot be a genuine record. .. automethod:: _read_ext_supported_versions .. automethod:: _read_ext_key_share .. automethod:: _read_ext_connection_id - .. automethod:: _make_ext_unknown .. automethod:: _make_ext_server_name .. automethod:: _make_ext_supported_groups @@ -61,32 +63,21 @@ accepting bytes that cannot be a genuine record. .. automethod:: _make_ext_supported_versions .. automethod:: _make_ext_key_share .. automethod:: _make_ext_connection_id - .. automethod:: _validate_tls_record - .. automethod:: _tls_content_is_cleartext - .. automethod:: _read_tls_content - .. automethod:: _make_tls_content - .. automethod:: _read_tls_handshake_messages .. automethod:: _make_tls_handshake_messages - .. automethod:: _read_tls_client_hello - .. automethod:: _make_tls_client_hello - .. automethod:: _read_tls_server_hello - .. automethod:: _make_tls_server_hello - .. automethod:: _read_tls_encrypted_extensions - .. automethod:: _make_tls_encrypted_extensions - .. automethod:: _read_tls_extensions + .. automethod:: _make_tls_content + .. automethod:: _make_tls_client_hello + .. automethod:: _make_tls_server_hello + .. automethod:: _make_tls_encrypted_extensions .. automethod:: _make_tls_extensions - .. autoattribute:: __extension__ - :no-value: - Data Models ----------- diff --git a/docs/source/pcapkit/protocols/application/sslv2.rst b/docs/source/pcapkit/protocols/application/sslv2.rst index ef29e23775..680a4bc1aa 100644 --- a/docs/source/pcapkit/protocols/application/sslv2.rst +++ b/docs/source/pcapkit/protocols/application/sslv2.rst @@ -75,11 +75,10 @@ a ``CLIENT-MASTER-KEY`` reads ``SSLv2:SSLv2``. The exceptions: .. autoproperty:: alias .. autoproperty:: length + .. automethod:: id .. automethod:: read .. automethod:: make - .. automethod:: id - .. automethod:: _make_data .. automethod:: _make_sslv2_client_hello .. automethod:: _make_sslv2_server_hello diff --git a/docs/source/pcapkit/protocols/index.rst b/docs/source/pcapkit/protocols/index.rst index 530bd4c16f..6ab9752920 100644 --- a/docs/source/pcapkit/protocols/index.rst +++ b/docs/source/pcapkit/protocols/index.rst @@ -242,31 +242,27 @@ Header Schema :no-members: :show-inheritance: + .. autoattribute:: __fields__ + :no-value: + .. autoattribute:: __payload__ .. autoattribute:: __additional__ :no-value: + .. autoattribute:: __excluded__ :no-value: - .. automethod:: __init_subclass__ - .. automethod:: __new__ - + .. automethod:: from_dict + .. automethod:: to_dict + .. automethod:: to_bytes + .. automethod:: get_payload .. automethod:: pack .. automethod:: pre_pack - .. automethod:: unpack .. automethod:: pre_unpack - .. automethod:: post_process - .. automethod:: get_payload - - .. automethod:: from_dict - .. automethod:: to_dict - .. automethod:: to_bytes - - .. autoattribute:: __fields__ - :no-value: - + .. automethod:: __init_subclass__ + .. automethod:: __new__ .. automethod:: _cut_short_read .. autoclass:: pcapkit.protocols.schema.EnumSchema @@ -339,6 +335,9 @@ Data Model :members: :show-inheritance: + .. autoattribute:: __included__ + :no-value: + .. autoattribute:: __excluded__ :no-value: @@ -346,6 +345,3 @@ Data Model Please refer to :func:`Protocol._decode_next_layer ` for more information with the inserted names to be excluded. - - .. autoattribute:: __included__ - :no-value: diff --git a/docs/source/pcapkit/protocols/internet/ah.rst b/docs/source/pcapkit/protocols/internet/ah.rst index 900465ebd6..e1366a878a 100644 --- a/docs/source/pcapkit/protocols/internet/ah.rst +++ b/docs/source/pcapkit/protocols/internet/ah.rst @@ -32,16 +32,15 @@ Octets Bits Name Description .. autoproperty:: protocol .. autoproperty:: protochain - .. automethod:: id - .. automethod:: read .. automethod:: make - - .. automethod:: _make_data + .. automethod:: id .. automethod:: __post_init__ .. automethod:: __index__ + .. automethod:: _make_data + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/esp.rst b/docs/source/pcapkit/protocols/internet/esp.rst index ed7ff4b5aa..51323f2ec4 100644 --- a/docs/source/pcapkit/protocols/internet/esp.rst +++ b/docs/source/pcapkit/protocols/internet/esp.rst @@ -63,22 +63,21 @@ negotiated out of band. Therefore: .. autoproperty:: name .. autoproperty:: alias - .. autoproperty:: length .. autoproperty:: protocol - - .. automethod:: id + .. autoproperty:: length .. automethod:: read .. automethod:: make - - .. automethod:: _make_data - .. automethod:: _payload_bytes - .. automethod:: _read_trailer - .. automethod:: _make_opaque + .. automethod:: id .. automethod:: __post_init__ .. automethod:: __index__ + .. automethod:: _make_data + .. automethod:: _make_opaque + .. automethod:: _read_trailer + .. automethod:: _payload_bytes + Security Associations --------------------- @@ -117,9 +116,10 @@ in :mod:`pcapkit.corekit.context`: .. automethod:: decrypt .. automethod:: encrypt - .. automethod:: _split_key .. automethod:: __repr__ + .. automethod:: _split_key + .. autoclass:: pcapkit.protocols.internet.esp.protocol.ESPContext :no-members: :show-inheritance: diff --git a/docs/source/pcapkit/protocols/internet/hip.rst b/docs/source/pcapkit/protocols/internet/hip.rst index 912b4b5544..b48ef870a8 100644 --- a/docs/source/pcapkit/protocols/internet/hip.rst +++ b/docs/source/pcapkit/protocols/internet/hip.rst @@ -37,6 +37,9 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __parameter__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length @@ -44,16 +47,17 @@ Octets Bits Name Description .. autoproperty:: protocol .. autoproperty:: protochain - .. automethod:: id - - .. automethod:: register_parameter - .. automethod:: read .. automethod:: make + .. automethod:: register_parameter + .. automethod:: id - .. automethod:: _make_data + .. automethod:: __post_init__ + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_hip_param + .. automethod:: _make_hip_param .. automethod:: _read_param_unassigned .. automethod:: _read_param_esp_info .. automethod:: _read_param_r1_counter @@ -93,8 +97,8 @@ Octets Bits Name Description .. automethod:: _read_param_hip_mac_2 .. automethod:: _read_param_hip_signature_2 .. automethod:: _read_param_hip_signature - .. automethod:: _read_param_echo_request_unsigned .. automethod:: _read_param_echo_response_unsigned + .. automethod:: _read_param_echo_request_unsigned .. automethod:: _read_param_relay_from .. automethod:: _read_param_relay_to .. automethod:: _read_param_overlay_ttl @@ -103,9 +107,6 @@ Octets Bits Name Description .. automethod:: _read_param_rvs_hmac .. automethod:: _read_param_via_rvs .. automethod:: _read_param_relay_hmac - - .. automethod:: _make_hip_param - .. automethod:: _make_hip_param_data .. automethod:: _make_param_unassigned .. automethod:: _make_param_esp_info .. automethod:: _make_param_r1_counter @@ -145,8 +146,8 @@ Octets Bits Name Description .. automethod:: _make_param_hip_mac_2 .. automethod:: _make_param_hip_signature_2 .. automethod:: _make_param_hip_signature - .. automethod:: _make_param_echo_request_unsigned .. automethod:: _make_param_echo_response_unsigned + .. automethod:: _make_param_echo_request_unsigned .. automethod:: _make_param_relay_from .. automethod:: _make_param_relay_to .. automethod:: _make_param_overlay_ttl @@ -155,12 +156,7 @@ Octets Bits Name Description .. automethod:: _make_param_rvs_hmac .. automethod:: _make_param_via_rvs .. automethod:: _make_param_relay_hmac - - .. autoattribute:: __parameter__ - :no-value: - - .. automethod:: __post_init__ - .. automethod:: __index__ + .. automethod:: _make_hip_param_data Type Stubs ---------- diff --git a/docs/source/pcapkit/protocols/internet/hopopt.rst b/docs/source/pcapkit/protocols/internet/hopopt.rst index 17933ae8dc..b327689e9b 100644 --- a/docs/source/pcapkit/protocols/internet/hopopt.rst +++ b/docs/source/pcapkit/protocols/internet/hopopt.rst @@ -22,6 +22,9 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __option__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length @@ -29,16 +32,17 @@ Octets Bits Name Description .. autoproperty:: protocol .. autoproperty:: protochain - .. automethod:: id - - .. automethod:: register_option - .. automethod:: read .. automethod:: make + .. automethod:: register_option + .. automethod:: id - .. automethod:: _make_data + .. automethod:: __post_init__ + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_hopopt_options + .. automethod:: _make_hopopt_options .. automethod:: _read_opt_none .. automethod:: _read_opt_pad .. automethod:: _read_opt_tun @@ -46,16 +50,14 @@ Octets Bits Name Description .. automethod:: _read_opt_calipso .. automethod:: _read_opt_smf_dpd .. automethod:: _read_opt_pdm - .. automethod:: _read_opt_qs .. automethod:: _read_opt_rpl + .. automethod:: _read_opt_qs .. automethod:: _read_opt_mpl .. automethod:: _read_opt_ilnp .. automethod:: _read_opt_lio .. automethod:: _read_opt_jumbo .. automethod:: _read_opt_home .. automethod:: _read_opt_ip_dff - - .. automethod:: _make_hopopt_options .. automethod:: _make_opt_none .. automethod:: _make_opt_pad .. automethod:: _make_opt_tun @@ -63,8 +65,8 @@ Octets Bits Name Description .. automethod:: _make_opt_calipso .. automethod:: _make_opt_smf_dpd .. automethod:: _make_opt_pdm - .. automethod:: _make_opt_qs .. automethod:: _make_opt_rpl + .. automethod:: _make_opt_qs .. automethod:: _make_opt_mpl .. automethod:: _make_opt_ilnp .. automethod:: _make_opt_lio @@ -72,12 +74,6 @@ Octets Bits Name Description .. automethod:: _make_opt_home .. automethod:: _make_opt_ip_dff - .. autoattribute:: __option__ - :no-value: - - .. automethod:: __post_init__ - .. automethod:: __index__ - Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/icmpv4.rst b/docs/source/pcapkit/protocols/internet/icmpv4.rst index 2f5ad04f5e..a5913521c1 100644 --- a/docs/source/pcapkit/protocols/internet/icmpv4.rst +++ b/docs/source/pcapkit/protocols/internet/icmpv4.rst @@ -23,36 +23,33 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __type__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length .. automethod:: id - .. automethod:: register_type - .. automethod:: read .. automethod:: make + .. automethod:: register_type - .. automethod:: _make_data + .. automethod:: __length_hint__ + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_data_type_none .. automethod:: _read_data_type_echo .. automethod:: _read_data_type_destination_unreachable .. automethod:: _read_data_type_time_exceeded .. automethod:: _read_data_type_parameter_problem - .. automethod:: _make_data_type_none .. automethod:: _make_data_type_echo .. automethod:: _make_data_type_destination_unreachable .. automethod:: _make_data_type_time_exceeded .. automethod:: _make_data_type_parameter_problem - .. autoattribute:: __type__ - :no-value: - - .. automethod:: __length_hint__ - .. automethod:: __index__ - Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/icmpv6.rst b/docs/source/pcapkit/protocols/internet/icmpv6.rst index 0f74c0bad4..e30087eeba 100644 --- a/docs/source/pcapkit/protocols/internet/icmpv6.rst +++ b/docs/source/pcapkit/protocols/internet/icmpv6.rst @@ -23,38 +23,35 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __type__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length .. automethod:: id - .. automethod:: register_type - .. automethod:: read .. automethod:: make + .. automethod:: register_type - .. automethod:: _make_data - .. automethod:: _verify_pseudo_checksum + .. automethod:: __length_hint__ + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_data_type_none .. automethod:: _read_data_type_destination_unreachable .. automethod:: _read_data_type_packet_too_big .. automethod:: _read_data_type_time_exceeded .. automethod:: _read_data_type_parameter_problem .. automethod:: _read_data_type_echo - .. automethod:: _make_data_type_none .. automethod:: _make_data_type_destination_unreachable .. automethod:: _make_data_type_packet_too_big .. automethod:: _make_data_type_time_exceeded .. automethod:: _make_data_type_parameter_problem .. automethod:: _make_data_type_echo - - .. autoattribute:: __type__ - :no-value: - - .. automethod:: __length_hint__ - .. automethod:: __index__ + .. automethod:: _verify_pseudo_checksum Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/igmpv3.rst b/docs/source/pcapkit/protocols/internet/igmpv3.rst index 3bb0ae5cad..b62b23dc10 100644 --- a/docs/source/pcapkit/protocols/internet/igmpv3.rst +++ b/docs/source/pcapkit/protocols/internet/igmpv3.rst @@ -55,12 +55,11 @@ Octets Bits Name Description .. autoproperty:: length .. automethod:: id - .. automethod:: read .. automethod:: make - .. automethod:: _make_group_record .. automethod:: _make_data + .. automethod:: _make_group_record Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/internet.rst b/docs/source/pcapkit/protocols/internet/internet.rst index 3f4368b143..a9d6afe60c 100644 --- a/docs/source/pcapkit/protocols/internet/internet.rst +++ b/docs/source/pcapkit/protocols/internet/internet.rst @@ -12,26 +12,29 @@ which is a base class for internet layer protocols, e.g. :class:`~pcapkit.protoc :no-members: :show-inheritance: - .. autoproperty:: layer - - .. automethod:: register - .. automethod:: from_data - - .. automethod:: _decode_next_layer - .. automethod:: _import_next_layer - .. automethod:: _next_layer_class - .. automethod:: _next_exthdr_depth - .. automethod:: _make_payload - .. automethod:: _rebuilt_exthdr_depth - .. autoattribute:: __layer__ :no-value: + .. autoattribute:: __proto__ :no-value: + .. autoattribute:: __proto_key__ :no-value: + .. autoattribute:: _exthdr_depth + .. autoproperty:: layer + + .. automethod:: register + .. automethod:: from_data + + .. automethod:: _next_exthdr_depth + .. automethod:: _rebuilt_exthdr_depth + .. automethod:: _make_payload + .. automethod:: _next_layer_class + .. automethod:: _decode_next_layer + .. automethod:: _import_next_layer + .. autodata:: pcapkit.protocols.internet.internet._exthdr_rebuilt :no-value: diff --git a/docs/source/pcapkit/protocols/internet/ipv4.rst b/docs/source/pcapkit/protocols/internet/ipv4.rst index f8acfd3372..a9627d6fd8 100644 --- a/docs/source/pcapkit/protocols/internet/ipv4.rst +++ b/docs/source/pcapkit/protocols/internet/ipv4.rst @@ -38,60 +38,57 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __option__ + :no-value: + .. autoproperty:: name .. autoproperty:: length .. autoproperty:: protocol .. autoproperty:: src .. autoproperty:: dst - .. automethod:: id - .. automethod:: register_option - .. automethod:: read .. automethod:: make .. automethod:: unpack + .. automethod:: id + .. automethod:: register_option - .. automethod:: _make_data + .. automethod:: __index__ + .. automethod:: __post_init__ + .. automethod:: _make_data .. automethod:: _read_ipv4_options + .. automethod:: _make_ipv4_options .. automethod:: _read_opt_unassigned .. automethod:: _read_opt_eool .. automethod:: _read_opt_nop + .. automethod:: _read_opt_rr + .. automethod:: _read_opt_mtup + .. automethod:: _read_opt_mtur + .. automethod:: _read_opt_qs + .. automethod:: _read_opt_ts + .. automethod:: _read_opt_tr .. automethod:: _read_opt_sec .. automethod:: _read_opt_lsr - .. automethod:: _read_opt_ts .. automethod:: _read_opt_e_sec - .. automethod:: _read_opt_rr .. automethod:: _read_opt_sid .. automethod:: _read_opt_ssr - .. automethod:: _read_opt_mtup - .. automethod:: _read_opt_mtur - .. automethod:: _read_opt_tr .. automethod:: _read_opt_rtralt - .. automethod:: _read_opt_qs - - .. automethod:: _make_ipv4_options .. automethod:: _make_opt_unassigned .. automethod:: _make_opt_eool .. automethod:: _make_opt_nop + .. automethod:: _make_opt_rr + .. automethod:: _make_opt_mtup + .. automethod:: _make_opt_mtur + .. automethod:: _make_opt_qs + .. automethod:: _make_opt_ts + .. automethod:: _make_opt_tr .. automethod:: _make_opt_sec .. automethod:: _make_opt_lsr - .. automethod:: _make_opt_ts .. automethod:: _make_opt_e_sec - .. automethod:: _make_opt_rr .. automethod:: _make_opt_sid .. automethod:: _make_opt_ssr - .. automethod:: _make_opt_mtup - .. automethod:: _make_opt_mtur - .. automethod:: _make_opt_tr .. automethod:: _make_opt_rtralt - .. automethod:: _make_opt_qs - - .. autoattribute:: __option__ - :no-value: - - .. automethod:: __index__ - .. automethod:: __post_init__ Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/ipv6.rst b/docs/source/pcapkit/protocols/internet/ipv6.rst index f04da1ae49..d2ac866354 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6.rst @@ -41,21 +41,18 @@ Octets Bits Name Description .. autoproperty:: dst .. autoproperty:: extension_headers - .. automethod:: id - .. automethod:: read .. automethod:: make .. automethod:: unpack - - .. automethod:: _make_data - - .. automethod:: _decode_next_layer - .. automethod:: _import_next_layer + .. automethod:: id .. automethod:: __index__ .. automethod:: __post_init__ + .. automethod:: _make_data + .. automethod:: _decode_next_layer .. automethod:: _read_ip_hextet + .. automethod:: _import_next_layer Protocol Constants ------------------ diff --git a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst index dd3ff908df..8279940628 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6_ext.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6_ext.rst @@ -47,15 +47,14 @@ instead of clipping it. .. autoproperty:: protochain .. automethod:: id - .. automethod:: read .. automethod:: make - .. automethod:: _make_data - .. automethod:: __post_init__ .. automethod:: __index__ + .. automethod:: _make_data + .. _ipv6-ext-length-rules: Length Rules diff --git a/docs/source/pcapkit/protocols/internet/ipv6_frag.rst b/docs/source/pcapkit/protocols/internet/ipv6_frag.rst index 3c4223ab30..ef844f2624 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6_frag.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6_frag.rst @@ -32,16 +32,15 @@ Octets Bits Name Description .. autoproperty:: protocol .. autoproperty:: protochain - .. automethod:: id - .. automethod:: read .. automethod:: make - - .. automethod:: _make_data + .. automethod:: id .. automethod:: __post_init__ .. automethod:: __index__ + .. automethod:: _make_data + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/ipv6_opts.rst b/docs/source/pcapkit/protocols/internet/ipv6_opts.rst index 743fcb43e4..87cbaeeb0c 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6_opts.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6_opts.rst @@ -22,6 +22,9 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __option__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length @@ -29,16 +32,17 @@ Octets Bits Name Description .. autoproperty:: protocol .. autoproperty:: protochain - .. automethod:: id - - .. automethod:: register_option - .. automethod:: read .. automethod:: make + .. automethod:: register_option + .. automethod:: id - .. automethod:: _make_data + .. automethod:: __post_init__ + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_ipv6_opts + .. automethod:: _make_ipv6_opts .. automethod:: _read_opt_none .. automethod:: _read_opt_pad .. automethod:: _read_opt_tun @@ -46,16 +50,14 @@ Octets Bits Name Description .. automethod:: _read_opt_calipso .. automethod:: _read_opt_smf_dpd .. automethod:: _read_opt_pdm - .. automethod:: _read_opt_qs .. automethod:: _read_opt_rpl + .. automethod:: _read_opt_qs .. automethod:: _read_opt_mpl .. automethod:: _read_opt_ilnp .. automethod:: _read_opt_lio .. automethod:: _read_opt_jumbo .. automethod:: _read_opt_home .. automethod:: _read_opt_ip_dff - - .. automethod:: _make_ipv6_opts .. automethod:: _make_opt_none .. automethod:: _make_opt_pad .. automethod:: _make_opt_tun @@ -63,8 +65,8 @@ Octets Bits Name Description .. automethod:: _make_opt_calipso .. automethod:: _make_opt_smf_dpd .. automethod:: _make_opt_pdm - .. automethod:: _make_opt_qs .. automethod:: _make_opt_rpl + .. automethod:: _make_opt_qs .. automethod:: _make_opt_mpl .. automethod:: _make_opt_ilnp .. automethod:: _make_opt_lio @@ -72,12 +74,6 @@ Octets Bits Name Description .. automethod:: _make_opt_home .. automethod:: _make_opt_ip_dff - .. autoattribute:: __option__ - :no-value: - - .. automethod:: __post_init__ - .. automethod:: __index__ - Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/ipv6_route.rst b/docs/source/pcapkit/protocols/internet/ipv6_route.rst index e47f3e92b6..62ea49c5bd 100644 --- a/docs/source/pcapkit/protocols/internet/ipv6_route.rst +++ b/docs/source/pcapkit/protocols/internet/ipv6_route.rst @@ -23,6 +23,9 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __routing__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length @@ -30,32 +33,25 @@ Octets Bits Name Description .. autoproperty:: protocol .. autoproperty:: protochain - .. automethod:: id - - .. automethod:: register_routing - .. automethod:: read .. automethod:: make + .. automethod:: register_routing + .. automethod:: id + + .. automethod:: __post_init__ + .. automethod:: __index__ .. automethod:: _make_data .. automethod:: _make_hdr_ext_len - .. automethod:: _read_data_type_none .. automethod:: _read_data_type_src .. automethod:: _read_data_type_2 .. automethod:: _read_data_type_rpl - .. automethod:: _make_data_type_none .. automethod:: _make_data_type_src .. automethod:: _make_data_type_2 .. automethod:: _make_data_type_rpl - .. autoattribute:: __routing__ - :no-value: - - .. automethod:: __post_init__ - .. automethod:: __index__ - Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/ipx.rst b/docs/source/pcapkit/protocols/internet/ipx.rst index eada618aa8..2c73cb29f6 100644 --- a/docs/source/pcapkit/protocols/internet/ipx.rst +++ b/docs/source/pcapkit/protocols/internet/ipx.rst @@ -25,6 +25,12 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + .. autoproperty:: name .. autoproperty:: length .. autoproperty:: protocol @@ -35,15 +41,10 @@ Octets Bits Name Description .. automethod:: make .. automethod:: register - .. automethod:: _make_data - .. automethod:: _next_exthdr_depth - .. automethod:: __index__ - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: + .. automethod:: _make_data + .. automethod:: _next_exthdr_depth Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/internet/mh.rst b/docs/source/pcapkit/protocols/internet/mh.rst index e658990737..5f532bcff8 100644 --- a/docs/source/pcapkit/protocols/internet/mh.rst +++ b/docs/source/pcapkit/protocols/internet/mh.rst @@ -39,6 +39,15 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __message__ + :no-value: + + .. autoattribute:: __option__ + :no-value: + + .. autoattribute:: __extension__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length @@ -46,17 +55,17 @@ Octets Bits Name Description .. autoproperty:: protocol .. autoproperty:: protochain - .. automethod:: id - + .. automethod:: read + .. automethod:: make .. automethod:: register_message .. automethod:: register_option .. automethod:: register_extension + .. automethod:: id - .. automethod:: read - .. automethod:: make + .. automethod:: __post_init__ + .. automethod:: __index__ .. automethod:: _make_data - .. automethod:: _read_msg_unknown .. automethod:: _read_msg_brr .. automethod:: _read_msg_hoti @@ -70,50 +79,18 @@ Octets Bits Name Description .. automethod:: _read_msg_fback .. automethod:: _read_msg_fna .. automethod:: _read_msg_emh + .. automethod:: _read_msg_has + .. automethod:: _read_msg_hb .. automethod:: _read_msg_hi .. automethod:: _read_msg_hack .. automethod:: _read_msg_brm - .. automethod:: _read_msg_fbm - .. automethod:: _read_msg_has - .. automethod:: _read_msg_hb - .. automethod:: _read_msg_lra .. automethod:: _read_msg_lri + .. automethod:: _read_msg_lra + .. automethod:: _read_msg_upn + .. automethod:: _read_msg_upa + .. automethod:: _read_msg_fbm .. automethod:: _read_msg_sq .. automethod:: _read_msg_sr - .. automethod:: _read_msg_upa - .. automethod:: _read_msg_upn - - .. automethod:: _make_msg_unknown - .. automethod:: _make_msg_brr - .. automethod:: _make_msg_hoti - .. automethod:: _make_msg_coti - .. automethod:: _make_msg_hot - .. automethod:: _make_msg_cot - .. automethod:: _make_msg_bu - .. automethod:: _make_msg_ba - .. automethod:: _make_msg_be - .. automethod:: _make_msg_fbu - .. automethod:: _make_msg_fback - .. automethod:: _make_msg_fna - .. automethod:: _make_msg_emh - .. automethod:: _make_msg_hi - .. automethod:: _make_msg_hack - .. automethod:: _make_msg_brm - .. automethod:: _make_msg_fbm - .. automethod:: _make_msg_has - .. automethod:: _make_msg_hb - .. automethod:: _make_msg_lra - .. automethod:: _make_msg_lri - .. automethod:: _make_msg_sq - .. automethod:: _make_msg_sr - .. automethod:: _make_msg_upa - .. automethod:: _make_msg_upn - - .. automethod:: _read_mh_options - .. automethod:: _read_fid_suboptions - .. automethod:: _read_ani_suboptions - .. automethod:: _read_qos_attributes - .. automethod:: _read_lcmp_suboptions .. automethod:: _read_opt_none .. automethod:: _read_opt_pad .. automethod:: _read_opt_bra @@ -131,71 +108,88 @@ Octets Bits Name Description .. automethod:: _read_opt_phkt .. automethod:: _read_opt_ct_init .. automethod:: _read_opt_ct + .. automethod:: _read_opt_dns .. automethod:: _read_opt_exp + .. automethod:: _read_opt_vendor + .. automethod:: _read_opt_service .. automethod:: _read_opt_badf - .. automethod:: _read_opt_ipv6_ap - .. automethod:: _read_opt_alt_ipv4_coa - .. automethod:: _read_opt_ams4 - .. automethod:: _read_opt_ams6 - .. automethod:: _read_opt_anchored - .. automethod:: _read_opt_ani - .. automethod:: _read_opt_att - .. automethod:: _read_opt_bid - .. automethod:: _read_opt_cr - .. automethod:: _read_opt_dlif_lla - .. automethod:: _read_opt_dlif_lladdr - .. automethod:: _read_opt_dmnp - .. automethod:: _read_opt_dns - .. automethod:: _read_opt_fid - .. automethod:: _read_opt_fs - .. automethod:: _read_opt_gre - .. automethod:: _read_opt_hi .. automethod:: _read_opt_hnp + .. automethod:: _read_opt_hi + .. automethod:: _read_opt_att + .. automethod:: _read_opt_mn_lli + .. automethod:: _read_opt_lla_addr + .. automethod:: _read_opt_timestamp + .. automethod:: _read_opt_restart + .. automethod:: _read_opt_ipv4_hoa .. automethod:: _read_opt_ipv4_ack + .. automethod:: _read_opt_nat .. automethod:: _read_opt_ipv4_coa - .. automethod:: _read_opt_ipv4_dhcp - .. automethod:: _read_opt_ipv4_hoa - .. automethod:: _read_opt_ipv4_hoa_rep + .. automethod:: _read_opt_gre + .. automethod:: _read_opt_ipv6_ap + .. automethod:: _read_opt_bid .. automethod:: _read_opt_ipv4_hoa_req + .. automethod:: _read_opt_ipv4_hoa_rep .. automethod:: _read_opt_ipv4_router - .. automethod:: _read_opt_lcmp - .. automethod:: _read_opt_lla_addr - .. automethod:: _read_opt_lma_up + .. automethod:: _read_opt_ipv4_dhcp + .. automethod:: _read_opt_cr .. automethod:: _read_opt_lmaa + .. automethod:: _read_opt_mn_lla_iid + .. automethod:: _read_opt_transient + .. automethod:: _read_opt_fs + .. automethod:: _read_opt_fid + .. automethod:: _read_opt_rc + .. automethod:: _read_opt_redirect .. automethod:: _read_opt_load - .. automethod:: _read_opt_local_prefix + .. automethod:: _read_opt_alt_ipv4_coa + .. automethod:: _read_opt_mn_group .. automethod:: _read_opt_mag_addr - .. automethod:: _read_opt_mag_id - .. automethod:: _read_opt_mag_mp + .. automethod:: _read_opt_ani + .. automethod:: _read_opt_offload + .. automethod:: _read_opt_mcast_sel + .. automethod:: _read_opt_dmnp + .. automethod:: _read_opt_ams4 + .. automethod:: _read_opt_ams6 + .. automethod:: _read_opt_qos + .. automethod:: _read_opt_lma_up .. automethod:: _read_opt_mcast .. automethod:: _read_opt_mcast_ack - .. automethod:: _read_opt_mcast_sel - .. automethod:: _read_opt_mn_group - .. automethod:: _read_opt_mn_lla_iid - .. automethod:: _read_opt_mn_lli - .. automethod:: _read_opt_nat - .. automethod:: _read_opt_offload + .. automethod:: _read_opt_lcmp + .. automethod:: _read_opt_mag_mp + .. automethod:: _read_opt_mag_id + .. automethod:: _read_opt_anchored + .. automethod:: _read_opt_local_prefix .. automethod:: _read_opt_prev_maar - .. automethod:: _read_opt_qos - .. automethod:: _read_opt_rc - .. automethod:: _read_opt_redirect - .. automethod:: _read_opt_restart .. automethod:: _read_opt_serv_maar - .. automethod:: _read_opt_service - .. automethod:: _read_opt_timestamp - .. automethod:: _read_opt_transient - .. automethod:: _read_opt_vendor - - .. automethod:: _make_mh_options - .. automethod:: _make_fid_suboptions - .. automethod:: _make_fid_suboption - .. automethod:: _make_ani_suboptions - .. automethod:: _make_ani_suboption - .. automethod:: _make_qos_attributes - .. automethod:: _make_qos_attribute - .. automethod:: _make_lcmp_suboptions - .. automethod:: _make_lcmp_suboption - .. automethod:: _seconds + .. automethod:: _read_opt_dlif_lla + .. automethod:: _read_opt_dlif_lladdr + .. automethod:: _read_ext_none + .. automethod:: _read_ext_multiprefix + .. automethod:: _read_ext_exp + .. automethod:: _make_msg_unknown + .. automethod:: _make_msg_brr + .. automethod:: _make_msg_hoti + .. automethod:: _make_msg_coti + .. automethod:: _make_msg_hot + .. automethod:: _make_msg_cot + .. automethod:: _make_msg_bu + .. automethod:: _make_msg_ba + .. automethod:: _make_msg_be + .. automethod:: _make_msg_fbu + .. automethod:: _make_msg_fback + .. automethod:: _make_msg_fna + .. automethod:: _make_msg_emh + .. automethod:: _make_msg_has + .. automethod:: _make_msg_hb + .. automethod:: _make_msg_hi + .. automethod:: _make_msg_hack + .. automethod:: _make_msg_brm + .. automethod:: _make_msg_lri + .. automethod:: _make_msg_lra + .. automethod:: _make_msg_upn + .. automethod:: _make_msg_upa + .. automethod:: _make_msg_fbm + .. automethod:: _make_msg_sq + .. automethod:: _make_msg_sr .. automethod:: _make_opt_none .. automethod:: _make_opt_pad .. automethod:: _make_opt_bra @@ -213,80 +207,80 @@ Octets Bits Name Description .. automethod:: _make_opt_phkt .. automethod:: _make_opt_ct_init .. automethod:: _make_opt_ct + .. automethod:: _make_opt_dns .. automethod:: _make_opt_exp + .. automethod:: _make_opt_vendor + .. automethod:: _make_opt_service .. automethod:: _make_opt_badf - .. automethod:: _make_opt_ipv6_ap - .. automethod:: _make_opt_alt_ipv4_coa - .. automethod:: _make_opt_ams4 - .. automethod:: _make_opt_ams6 - .. automethod:: _make_opt_anchored - .. automethod:: _make_opt_ani - .. automethod:: _make_opt_att - .. automethod:: _make_opt_bid - .. automethod:: _make_opt_cr - .. automethod:: _make_opt_dlif_lla - .. automethod:: _make_opt_dlif_lladdr - .. automethod:: _make_opt_dmnp - .. automethod:: _make_opt_dns - .. automethod:: _make_opt_fid - .. automethod:: _make_opt_fs - .. automethod:: _make_opt_gre - .. automethod:: _make_opt_hi .. automethod:: _make_opt_hnp + .. automethod:: _make_opt_hi + .. automethod:: _make_opt_att + .. automethod:: _make_opt_mn_lli + .. automethod:: _make_opt_lla_addr + .. automethod:: _make_opt_timestamp + .. automethod:: _make_opt_restart + .. automethod:: _make_opt_ipv4_hoa .. automethod:: _make_opt_ipv4_ack + .. automethod:: _make_opt_nat .. automethod:: _make_opt_ipv4_coa - .. automethod:: _make_opt_ipv4_dhcp - .. automethod:: _make_opt_ipv4_hoa - .. automethod:: _make_opt_ipv4_hoa_rep + .. automethod:: _make_opt_gre + .. automethod:: _make_opt_ipv6_ap + .. automethod:: _make_opt_bid .. automethod:: _make_opt_ipv4_hoa_req + .. automethod:: _make_opt_ipv4_hoa_rep .. automethod:: _make_opt_ipv4_router - .. automethod:: _make_opt_lcmp - .. automethod:: _make_opt_lla_addr - .. automethod:: _make_opt_lma_up + .. automethod:: _make_opt_ipv4_dhcp + .. automethod:: _make_opt_cr .. automethod:: _make_opt_lmaa + .. automethod:: _make_opt_mn_lla_iid + .. automethod:: _make_opt_transient + .. automethod:: _make_opt_fs + .. automethod:: _make_opt_fid + .. automethod:: _make_opt_rc + .. automethod:: _make_opt_redirect .. automethod:: _make_opt_load - .. automethod:: _make_opt_local_prefix + .. automethod:: _make_opt_alt_ipv4_coa + .. automethod:: _make_opt_mn_group .. automethod:: _make_opt_mag_addr - .. automethod:: _make_opt_mag_id - .. automethod:: _make_opt_mag_mp + .. automethod:: _make_opt_ani + .. automethod:: _make_opt_offload + .. automethod:: _make_opt_mcast_sel + .. automethod:: _make_opt_dmnp + .. automethod:: _make_opt_ams4 + .. automethod:: _make_opt_ams6 + .. automethod:: _make_opt_qos + .. automethod:: _make_opt_lma_up .. automethod:: _make_opt_mcast .. automethod:: _make_opt_mcast_ack - .. automethod:: _make_opt_mcast_sel - .. automethod:: _make_opt_mn_group - .. automethod:: _make_opt_mn_lla_iid - .. automethod:: _make_opt_mn_lli - .. automethod:: _make_opt_nat - .. automethod:: _make_opt_offload + .. automethod:: _make_opt_lcmp + .. automethod:: _make_opt_mag_mp + .. automethod:: _make_opt_mag_id + .. automethod:: _make_opt_anchored + .. automethod:: _make_opt_local_prefix .. automethod:: _make_opt_prev_maar - .. automethod:: _make_opt_qos - .. automethod:: _make_opt_rc - .. automethod:: _make_opt_redirect - .. automethod:: _make_opt_restart .. automethod:: _make_opt_serv_maar - .. automethod:: _make_opt_service - .. automethod:: _make_opt_timestamp - .. automethod:: _make_opt_transient - .. automethod:: _make_opt_vendor - - .. automethod:: _read_cga_extensions - .. automethod:: _read_ext_none - .. automethod:: _read_ext_multiprefix - .. automethod:: _read_ext_exp - - .. automethod:: _make_cga_extensions + .. automethod:: _make_opt_dlif_lla + .. automethod:: _make_opt_dlif_lladdr .. automethod:: _make_ext_none .. automethod:: _make_ext_multiprefix .. automethod:: _make_ext_exp - - .. autoattribute:: __message__ - :no-value: - .. autoattribute:: __option__ - :no-value: - .. autoattribute:: __extension__ - :no-value: - - .. automethod:: __post_init__ - .. automethod:: __index__ + .. automethod:: _read_mh_options + .. automethod:: _read_fid_suboptions + .. automethod:: _read_ani_suboptions + .. automethod:: _read_qos_attributes + .. automethod:: _read_lcmp_suboptions + .. automethod:: _read_cga_extensions + .. automethod:: _make_mh_options + .. automethod:: _make_fid_suboptions + .. automethod:: _make_fid_suboption + .. automethod:: _make_ani_suboptions + .. automethod:: _make_ani_suboption + .. automethod:: _make_qos_attributes + .. automethod:: _make_qos_attribute + .. automethod:: _make_lcmp_suboptions + .. automethod:: _seconds + .. automethod:: _make_lcmp_suboption + .. automethod:: _make_cga_extensions Auxiliary Data -------------- diff --git a/docs/source/pcapkit/protocols/link/arp.rst b/docs/source/pcapkit/protocols/link/arp.rst index 1358a08d56..f2a4335c55 100644 --- a/docs/source/pcapkit/protocols/link/arp.rst +++ b/docs/source/pcapkit/protocols/link/arp.rst @@ -59,17 +59,15 @@ below: .. autoproperty:: type .. automethod:: id - .. automethod:: read .. automethod:: make - .. automethod:: _make_data - .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_addr_resolve - .. automethod:: _read_proto_resolve .. automethod:: _make_addr_resolve + .. automethod:: _read_proto_resolve .. automethod:: _make_proto_resolve Header Schemas diff --git a/docs/source/pcapkit/protocols/link/ethernet.rst b/docs/source/pcapkit/protocols/link/ethernet.rst index 8e10573643..211c575bc2 100644 --- a/docs/source/pcapkit/protocols/link/ethernet.rst +++ b/docs/source/pcapkit/protocols/link/ethernet.rst @@ -42,10 +42,10 @@ below: .. automethod:: read .. automethod:: make - .. automethod:: _make_data - .. automethod:: __index__ + .. automethod:: _make_data + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/link/frad.rst b/docs/source/pcapkit/protocols/link/frad.rst index 628cb15ef7..57df9a6fb6 100644 --- a/docs/source/pcapkit/protocols/link/frad.rst +++ b/docs/source/pcapkit/protocols/link/frad.rst @@ -30,13 +30,15 @@ An NLPID of ``0x80``, with or without the pad, is followed by a SNAP header, a :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length .. autoproperty:: protocol .. automethod:: id - .. automethod:: read .. automethod:: make .. automethod:: register @@ -47,9 +49,6 @@ An NLPID of ``0x80``, with or without the pad, is followed by a SNAP header, a .. automethod:: _make_data .. automethod:: _import_next_layer - .. autoattribute:: __proto__ - :no-value: - Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/link/l2tpv2.rst b/docs/source/pcapkit/protocols/link/l2tpv2.rst index 2cfe941f79..54b600b13e 100644 --- a/docs/source/pcapkit/protocols/link/l2tpv2.rst +++ b/docs/source/pcapkit/protocols/link/l2tpv2.rst @@ -68,14 +68,14 @@ reading ``2``. It is dispatched from .. autoproperty:: length .. autoproperty:: type - .. automethod:: id .. automethod:: read .. automethod:: make - - .. automethod:: _make_data + .. automethod:: id .. automethod:: __index__ + .. automethod:: _make_data + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/link/link.rst b/docs/source/pcapkit/protocols/link/link.rst index 9df1903fc7..848420242c 100644 --- a/docs/source/pcapkit/protocols/link/link.rst +++ b/docs/source/pcapkit/protocols/link/link.rst @@ -13,15 +13,16 @@ and :class:`~pcapkit.protocols.link.vlan.protocol.VLAN`. :no-members: :show-inheritance: + .. autoattribute:: __layer__ + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + .. autoproperty:: layer .. automethod:: register .. automethod:: from_data .. automethod:: __post_init__ - - .. autoattribute:: __layer__ - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: diff --git a/docs/source/pcapkit/protocols/link/linux_sll.rst b/docs/source/pcapkit/protocols/link/linux_sll.rst index cde332ad1a..03a296684f 100644 --- a/docs/source/pcapkit/protocols/link/linux_sll.rst +++ b/docs/source/pcapkit/protocols/link/linux_sll.rst @@ -31,20 +31,23 @@ whose data model it is handed (:issue:`2574`). :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + .. automethod:: id .. automethod:: register .. automethod:: from_data + .. automethod:: __index__ .. automethod:: __init__ .. automethod:: __new__ + .. automethod:: _import_next_layer .. automethod:: _member_of_version - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: - Auxiliary Functions ------------------- diff --git a/docs/source/pcapkit/protocols/link/linux_sll1.rst b/docs/source/pcapkit/protocols/link/linux_sll1.rst index 7c47d7fe4d..ee168251ee 100644 --- a/docs/source/pcapkit/protocols/link/linux_sll1.rst +++ b/docs/source/pcapkit/protocols/link/linux_sll1.rst @@ -49,10 +49,10 @@ see :ref:`linux-sll-labels`. .. automethod:: read .. automethod:: make - .. automethod:: _make_data - .. automethod:: __index__ + .. automethod:: _make_data + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/link/linux_sll2.rst b/docs/source/pcapkit/protocols/link/linux_sll2.rst index c3506b9b55..c5dc2a2fb9 100644 --- a/docs/source/pcapkit/protocols/link/linux_sll2.rst +++ b/docs/source/pcapkit/protocols/link/linux_sll2.rst @@ -53,10 +53,10 @@ see :ref:`linux-sll-labels`. .. automethod:: read .. automethod:: make - .. automethod:: _make_data - .. automethod:: __index__ + .. automethod:: _make_data + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/link/loopback.rst b/docs/source/pcapkit/protocols/link/loopback.rst index 4431467356..1844305f31 100644 --- a/docs/source/pcapkit/protocols/link/loopback.rst +++ b/docs/source/pcapkit/protocols/link/loopback.rst @@ -33,6 +33,9 @@ is kept as ``loopback.byteorder``. :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + .. autoproperty:: name .. autoproperty:: length .. autoproperty:: protocol @@ -41,12 +44,9 @@ is kept as ``loopback.byteorder``. .. automethod:: make .. automethod:: register - .. automethod:: _make_data - .. automethod:: __index__ - .. autoattribute:: __proto__ - :no-value: + .. automethod:: _make_data .. autofunction:: pcapkit.protocols.link.loopback.protocol.family_byteorder diff --git a/docs/source/pcapkit/protocols/link/netlink.rst b/docs/source/pcapkit/protocols/link/netlink.rst index 28127c78e0..ee933f3f70 100644 --- a/docs/source/pcapkit/protocols/link/netlink.rst +++ b/docs/source/pcapkit/protocols/link/netlink.rst @@ -35,13 +35,15 @@ format) and is guessed by :func:`nlmsg_byteorder`; either way it is kept as :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length .. autoproperty:: protocol .. automethod:: id - .. automethod:: read .. automethod:: make .. automethod:: register @@ -51,9 +53,6 @@ format) and is guessed by :func:`nlmsg_byteorder`; either way it is kept as .. automethod:: _make_data - .. autoattribute:: __proto__ - :no-value: - .. autofunction:: pcapkit.protocols.link.netlink.protocol.nlmsg_byteorder Header Schemas diff --git a/docs/source/pcapkit/protocols/link/radiotap.rst b/docs/source/pcapkit/protocols/link/radiotap.rst index 13c6756ec2..da7b4ce953 100644 --- a/docs/source/pcapkit/protocols/link/radiotap.rst +++ b/docs/source/pcapkit/protocols/link/radiotap.rst @@ -115,12 +115,20 @@ as ``fields`` and encodes them with zero padding. :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + + .. autoattribute:: __field__ + :no-value: + .. autoproperty:: name .. autoproperty:: alias .. autoproperty:: length .. automethod:: id - .. automethod:: read .. automethod:: make .. automethod:: register @@ -132,7 +140,6 @@ as ``fields`` and encodes them with zero padding. .. automethod:: _make_data .. automethod:: _read_radiotap_fields .. automethod:: _make_radiotap_fields - .. automethod:: _read_field_unknown .. automethod:: _read_field_tsft .. automethod:: _read_field_flags @@ -164,7 +171,6 @@ as ``fields`` and encodes them with zero padding. .. automethod:: _read_field_s1g .. automethod:: _read_field_usig .. automethod:: _read_field_eht - .. automethod:: _make_field_unknown .. automethod:: _make_field_tsft .. automethod:: _make_field_flags @@ -196,16 +202,8 @@ as ``fields`` and encodes them with zero padding. .. automethod:: _make_field_s1g .. automethod:: _make_field_usig .. automethod:: _make_field_eht - .. automethod:: _get_payload - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: - .. autoattribute:: __field__ - :no-value: - Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/link/vlan.rst b/docs/source/pcapkit/protocols/link/vlan.rst index bae4733f35..2b7601c8ce 100644 --- a/docs/source/pcapkit/protocols/link/vlan.rst +++ b/docs/source/pcapkit/protocols/link/vlan.rst @@ -74,10 +74,10 @@ declares no :meth:`~pcapkit.protocols.protocol.Protocol.__index__`, so its .. automethod:: read .. automethod:: make - .. automethod:: _make_data - .. automethod:: __index__ + .. automethod:: _make_data + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/misc/checksum.rst b/docs/source/pcapkit/protocols/misc/checksum.rst index 80e9c1fabf..0f425e0fde 100644 --- a/docs/source/pcapkit/protocols/misc/checksum.rst +++ b/docs/source/pcapkit/protocols/misc/checksum.rst @@ -72,15 +72,15 @@ in :mod:`pcapkit.corekit.checksum.internet`. .. automethod:: read .. automethod:: make + .. automethod:: __post_init__ + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _source .. automethod:: _destination .. automethod:: _upper_protocol .. automethod:: _upper_length - .. automethod:: __post_init__ - .. automethod:: __index__ - .. autofunction:: pcapkit.protocols.misc.checksum.ip_pseudo.protocol.pseudo_header .. autofunction:: pcapkit.protocols.misc.checksum.ip_pseudo.protocol.verify_upper diff --git a/docs/source/pcapkit/protocols/misc/pcap.rst b/docs/source/pcapkit/protocols/misc/pcap.rst index a2f5a1a077..2c7f739924 100644 --- a/docs/source/pcapkit/protocols/misc/pcap.rst +++ b/docs/source/pcapkit/protocols/misc/pcap.rst @@ -41,9 +41,6 @@ of PCAP, whose structure is described as below: :no-members: :show-inheritance: - .. automethod:: __post_init__ - .. automethod:: __index__ - .. autoproperty:: name .. autoproperty:: length .. autoproperty:: version @@ -56,8 +53,10 @@ of PCAP, whose structure is described as below: .. automethod:: read .. automethod:: make - .. automethod:: _make_data + .. automethod:: __post_init__ + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_protos Header Schemas @@ -135,30 +134,29 @@ whose structure is described as below: :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + .. autoproperty:: name .. autoproperty:: length .. autoproperty:: header .. automethod:: register .. automethod:: index - - .. automethod:: unpack .. automethod:: pack - + .. automethod:: unpack .. automethod:: read .. automethod:: make - .. automethod:: _make_data - .. automethod:: _dispatch_linktype - - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: - .. automethod:: __post_init__ .. automethod:: __index__ + .. automethod:: _make_data + .. automethod:: _dispatch_linktype + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/misc/pcapng.rst b/docs/source/pcapkit/protocols/misc/pcapng.rst index e073bbe9d3..563d92ca1c 100644 --- a/docs/source/pcapkit/protocols/misc/pcapng.rst +++ b/docs/source/pcapkit/protocols/misc/pcapng.rst @@ -45,8 +45,29 @@ which implements an extractor for PCAP-NG file format [*]_, and the :no-members: :show-inheritance: + .. autoattribute:: PACKET_TYPES + .. autoattribute:: SHORT_HEADER_TYPES + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + + .. autoattribute:: __block__ + :no-value: + + .. autoattribute:: __option__ + :no-value: + + .. autoattribute:: __record__ + :no-value: + + .. autoattribute:: __secrets__ + :no-value: + .. autoproperty:: name .. autoproperty:: length + .. autoproperty:: packet .. autoproperty:: section_context .. autoproperty:: byteorder .. autoproperty:: nanosecond @@ -55,41 +76,37 @@ which implements an extractor for PCAP-NG file format [*]_, and the .. autoproperty:: ts_timezone .. autoproperty:: linktype .. autoproperty:: block - .. autoproperty:: packet .. automethod:: register .. automethod:: register_block .. automethod:: register_option .. automethod:: register_record .. automethod:: register_secrets - - .. automethod:: unpack + .. automethod:: index .. automethod:: pack - + .. automethod:: unpack .. automethod:: read .. automethod:: make - .. automethod:: index - - .. automethod:: _decode_next_layer - .. automethod:: _dispatch_linktype + .. automethod:: __post_init__ + .. automethod:: __length_hint__ + .. automethod:: __index__ - .. automethod:: _get_payload .. automethod:: _make_data - + .. automethod:: _read_block_short + .. automethod:: _make_block_short + .. automethod:: _read_block_short_header .. automethod:: _read_block_unknown - .. automethod:: _read_block_shb .. automethod:: _read_block_idb - .. automethod:: _read_block_epb + .. automethod:: _read_block_packet .. automethod:: _read_block_spb .. automethod:: _read_block_nrb .. automethod:: _read_block_isb + .. automethod:: _read_block_epb .. automethod:: _read_block_systemd .. automethod:: _read_block_dsb .. automethod:: _read_block_cb - .. automethod:: _read_block_packet - - .. automethod:: _read_pcapng_options + .. automethod:: _read_block_shb .. automethod:: _read_option_unknown .. automethod:: _read_option_endofopt .. automethod:: _read_option_comment @@ -128,32 +145,26 @@ which implements an extractor for PCAP-NG file format [*]_, and the .. automethod:: _read_option_isb_usrdeliv .. automethod:: _read_option_pack_flags .. automethod:: _read_option_pack_hash - - .. automethod:: _read_nrb_records .. automethod:: _read_record_unknown .. automethod:: _read_record_end .. automethod:: _read_record_ipv4 .. automethod:: _read_record_ipv6 - .. automethod:: _read_secrets_unknown .. automethod:: _read_secrets_tls .. automethod:: _read_secrets_wireguard - .. automethod:: _read_secrets_zigbee_nwk .. automethod:: _read_secrets_zigbee_aps - + .. automethod:: _read_secrets_zigbee_nwk .. automethod:: _make_block_unknown - .. automethod:: _make_block_shb .. automethod:: _make_block_idb - .. automethod:: _make_block_epb + .. automethod:: _make_block_packet .. automethod:: _make_block_spb .. automethod:: _make_block_nrb .. automethod:: _make_block_isb + .. automethod:: _make_block_epb .. automethod:: _make_block_systemd .. automethod:: _make_block_dsb .. automethod:: _make_block_cb - .. automethod:: _make_block_packet - - .. automethod:: _make_pcapng_options + .. automethod:: _make_block_shb .. automethod:: _make_option_unknown .. automethod:: _make_option_endofopt .. automethod:: _make_option_comment @@ -192,45 +203,25 @@ which implements an extractor for PCAP-NG file format [*]_, and the .. automethod:: _make_option_isb_usrdeliv .. automethod:: _make_option_pack_flags .. automethod:: _make_option_pack_hash - - .. automethod:: _make_nrb_records .. automethod:: _make_record_unknown .. automethod:: _make_record_end .. automethod:: _make_record_ipv4 .. automethod:: _make_record_ipv6 - .. automethod:: _make_secrets_unknown .. automethod:: _make_secrets_tls .. automethod:: _make_secrets_wireguard - .. automethod:: _make_secrets_zigbee_nwk .. automethod:: _make_secrets_zigbee_aps - - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: - .. autoattribute:: __block__ - :no-value: - .. autoattribute:: __option__ - :no-value: - .. autoattribute:: __record__ - :no-value: - .. autoattribute:: __secrets__ - :no-value: - - .. automethod:: __post_init__ - .. automethod:: __index__ - - .. autoattribute:: PACKET_TYPES - .. autoattribute:: SHORT_HEADER_TYPES + .. automethod:: _make_secrets_zigbee_nwk + .. automethod:: _check_block_floor + .. automethod:: _get_payload .. automethod:: _get_timezone - .. automethod:: _read_block_short - .. automethod:: _read_block_short_header - .. automethod:: _make_block_short .. automethod:: _make_mac_addr - - .. automethod:: _check_block_floor - .. automethod:: __length_hint__ + .. automethod:: _decode_next_layer + .. automethod:: _dispatch_linktype + .. automethod:: _read_pcapng_options + .. automethod:: _read_nrb_records + .. automethod:: _make_pcapng_options + .. automethod:: _make_nrb_records Auxiliary Data -------------- diff --git a/docs/source/pcapkit/protocols/misc/raw.rst b/docs/source/pcapkit/protocols/misc/raw.rst index e3d4a4790a..2ab20b8a2b 100644 --- a/docs/source/pcapkit/protocols/misc/raw.rst +++ b/docs/source/pcapkit/protocols/misc/raw.rst @@ -20,11 +20,11 @@ an extractor for unknown protocols, and constructs a .. automethod:: read .. automethod:: make - .. automethod:: _make_data - .. automethod:: __post_init__ .. automethod:: __index__ + .. automethod:: _make_data + Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/protocol.rst b/docs/source/pcapkit/protocols/protocol.rst index aaf0239a34..4b370af712 100644 --- a/docs/source/pcapkit/protocols/protocol.rst +++ b/docs/source/pcapkit/protocols/protocol.rst @@ -201,13 +201,14 @@ Internal Definitions :no-members: :show-inheritance: - .. automethod:: __init_subclass__ - .. autoattribute:: __registry__ :no-value: + .. automethod:: register_protocol .. automethod:: get_protocol + .. automethod:: __init_subclass__ + :class:`Protocol` inherits the other members of this class, and they are documented under it above; each group below links to its entries. diff --git a/docs/source/pcapkit/protocols/transport/dccp.rst b/docs/source/pcapkit/protocols/transport/dccp.rst index ccb0b333ef..3be477fd4c 100644 --- a/docs/source/pcapkit/protocols/transport/dccp.rst +++ b/docs/source/pcapkit/protocols/transport/dccp.rst @@ -35,20 +35,29 @@ header 12 octets long. The fields of the packet type, then the options, follow. :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + + .. autoattribute:: __option__ + :no-value: + .. autoproperty:: name .. autoproperty:: length .. autoproperty:: src .. autoproperty:: dst - .. automethod:: register_option - .. automethod:: read .. automethod:: make + .. automethod:: register_option - .. automethod:: _make_data - .. automethod:: _verify_pseudo_checksum + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_dccp_options + .. automethod:: _make_dccp_options .. automethod:: _read_opt_unassigned .. automethod:: _read_opt_padding .. automethod:: _read_opt_mandatory @@ -63,8 +72,6 @@ header 12 octets long. The fields of the packet type, then the options, follow. .. automethod:: _read_opt_timestamp_echo .. automethod:: _read_opt_elapsed_time .. automethod:: _read_opt_data_checksum - - .. automethod:: _make_dccp_options .. automethod:: _make_opt_unassigned .. automethod:: _make_opt_padding .. automethod:: _make_opt_mandatory @@ -79,15 +86,7 @@ header 12 octets long. The fields of the packet type, then the options, follow. .. automethod:: _make_opt_timestamp_echo .. automethod:: _make_opt_elapsed_time .. automethod:: _make_opt_data_checksum - - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: - .. autoattribute:: __option__ - :no-value: - - .. automethod:: __index__ + .. automethod:: _verify_pseudo_checksum .. autodata:: pcapkit.protocols.transport.dccp.protocol.DCCP_MAX_HEADER_LEN diff --git a/docs/source/pcapkit/protocols/transport/sctp.rst b/docs/source/pcapkit/protocols/transport/sctp.rst index bda513e483..b8d93ef8de 100644 --- a/docs/source/pcapkit/protocols/transport/sctp.rst +++ b/docs/source/pcapkit/protocols/transport/sctp.rst @@ -30,6 +30,21 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + + .. autoattribute:: __chunk__ + :no-value: + + .. autoattribute:: __parameter__ + :no-value: + + .. autoattribute:: __cause__ + :no-value: + .. autoproperty:: name .. autoproperty:: length .. autoproperty:: src @@ -39,31 +54,20 @@ Octets Bits Name Description .. automethod:: read .. automethod:: make - .. automethod:: register .. automethod:: register_chunk .. automethod:: register_parameter .. automethod:: register_cause - .. automethod:: crc32c .. automethod:: calculate_checksum .. automethod:: validate_checksum - .. automethod:: _make_data - .. automethod:: _get_payload - .. automethod:: _decode_next_layer + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _read_sctp_chunks .. automethod:: _make_sctp_chunks - .. automethod:: _make_sctp_chunk - .. automethod:: _read_sctp_parameters - .. automethod:: _make_sctp_parameters - .. automethod:: _make_sctp_parameter - .. automethod:: _read_sctp_causes - .. automethod:: _make_sctp_causes - .. automethod:: _make_sctp_cause - .. automethod:: _make_param_types - + .. automethod:: _decode_next_layer .. automethod:: _read_chunk_donone .. automethod:: _read_chunk_data .. automethod:: _read_chunk_init @@ -78,22 +82,6 @@ Octets Bits Name Description .. automethod:: _read_chunk_cookie_echo .. automethod:: _read_chunk_cookie_ack .. automethod:: _read_chunk_shutdown_complete - - .. automethod:: _make_chunk_donone - .. automethod:: _make_chunk_data - .. automethod:: _make_chunk_init - .. automethod:: _make_chunk_init_ack - .. automethod:: _make_chunk_sack - .. automethod:: _make_chunk_heartbeat - .. automethod:: _make_chunk_heartbeat_ack - .. automethod:: _make_chunk_abort - .. automethod:: _make_chunk_shutdown - .. automethod:: _make_chunk_shutdown_ack - .. automethod:: _make_chunk_error - .. automethod:: _make_chunk_cookie_echo - .. automethod:: _make_chunk_cookie_ack - .. automethod:: _make_chunk_shutdown_complete - .. automethod:: _read_param_donone .. automethod:: _read_param_hbinfo .. automethod:: _read_param_ipv4 @@ -103,17 +91,6 @@ Octets Bits Name Description .. automethod:: _read_param_preservative .. automethod:: _read_param_hostname .. automethod:: _read_param_addrtypes - - .. automethod:: _make_param_donone - .. automethod:: _make_param_hbinfo - .. automethod:: _make_param_ipv4 - .. automethod:: _make_param_ipv6 - .. automethod:: _make_param_cookie - .. automethod:: _make_param_unrecognized - .. automethod:: _make_param_preservative - .. automethod:: _make_param_hostname - .. automethod:: _make_param_addrtypes - .. automethod:: _read_cause_donone .. automethod:: _read_cause_invalid_stream .. automethod:: _read_cause_missing_param @@ -128,7 +105,29 @@ Octets Bits Name Description .. automethod:: _read_cause_restart_addr .. automethod:: _read_cause_user_abort .. automethod:: _read_cause_protocol_violation - + .. automethod:: _make_chunk_donone + .. automethod:: _make_chunk_data + .. automethod:: _make_chunk_init + .. automethod:: _make_chunk_init_ack + .. automethod:: _make_chunk_sack + .. automethod:: _make_chunk_heartbeat + .. automethod:: _make_chunk_heartbeat_ack + .. automethod:: _make_chunk_abort + .. automethod:: _make_chunk_shutdown + .. automethod:: _make_chunk_shutdown_ack + .. automethod:: _make_chunk_error + .. automethod:: _make_chunk_cookie_echo + .. automethod:: _make_chunk_cookie_ack + .. automethod:: _make_chunk_shutdown_complete + .. automethod:: _make_param_donone + .. automethod:: _make_param_hbinfo + .. automethod:: _make_param_ipv4 + .. automethod:: _make_param_ipv6 + .. automethod:: _make_param_cookie + .. automethod:: _make_param_unrecognized + .. automethod:: _make_param_preservative + .. automethod:: _make_param_hostname + .. automethod:: _make_param_addrtypes .. automethod:: _make_cause_donone .. automethod:: _make_cause_invalid_stream .. automethod:: _make_cause_missing_param @@ -143,19 +142,15 @@ Octets Bits Name Description .. automethod:: _make_cause_restart_addr .. automethod:: _make_cause_user_abort .. automethod:: _make_cause_protocol_violation - - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: - .. autoattribute:: __chunk__ - :no-value: - .. autoattribute:: __parameter__ - :no-value: - .. autoattribute:: __cause__ - :no-value: - - .. automethod:: __index__ + .. automethod:: _get_payload + .. automethod:: _make_sctp_chunk + .. automethod:: _read_sctp_parameters + .. automethod:: _make_sctp_parameters + .. automethod:: _make_sctp_parameter + .. automethod:: _read_sctp_causes + .. automethod:: _make_sctp_causes + .. automethod:: _make_sctp_cause + .. automethod:: _make_param_types Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/transport/tcp.rst b/docs/source/pcapkit/protocols/transport/tcp.rst index 9c78f0f0da..8e41535260 100644 --- a/docs/source/pcapkit/protocols/transport/tcp.rst +++ b/docs/source/pcapkit/protocols/transport/tcp.rst @@ -38,22 +38,34 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + + .. autoattribute:: __option__ + :no-value: + + .. autoattribute:: __mp_option__ + :no-value: + .. autoproperty:: name .. autoproperty:: length .. autoproperty:: src .. autoproperty:: dst .. autoproperty:: connection + .. automethod:: read + .. automethod:: make .. automethod:: register_option .. automethod:: register_mp_option - .. automethod:: read - .. automethod:: make + .. automethod:: __index__ .. automethod:: _make_data - .. automethod:: _verify_pseudo_checksum - .. automethod:: _read_tcp_options + .. automethod:: _make_tcp_options .. automethod:: _read_mode_donone .. automethod:: _read_mode_eool .. automethod:: _read_mode_nop @@ -76,8 +88,6 @@ Octets Bits Name Description .. automethod:: _read_mode_timeout .. automethod:: _read_mode_ao .. automethod:: _read_mode_mp - .. automethod:: _read_mode_fastopen - .. automethod:: _read_mptcp_unknown .. automethod:: _read_mptcp_capable .. automethod:: _read_mptcp_join @@ -87,12 +97,7 @@ Octets Bits Name Description .. automethod:: _read_mptcp_prio .. automethod:: _read_mptcp_fail .. automethod:: _read_mptcp_fastclose - - .. automethod:: _read_join_syn - .. automethod:: _read_join_synack - .. automethod:: _read_join_ack - - .. automethod:: _make_tcp_options + .. automethod:: _read_mode_fastopen .. automethod:: _make_mode_donone .. automethod:: _make_mode_eool .. automethod:: _make_mode_nop @@ -115,8 +120,6 @@ Octets Bits Name Description .. automethod:: _make_mode_timeout .. automethod:: _make_mode_ao .. automethod:: _make_mode_mp - .. automethod:: _make_mode_fastopen - .. automethod:: _make_mptcp_unknown .. automethod:: _make_mptcp_capable .. automethod:: _make_mptcp_join @@ -126,22 +129,15 @@ Octets Bits Name Description .. automethod:: _make_mptcp_prio .. automethod:: _make_mptcp_fail .. automethod:: _make_mptcp_fastclose - + .. automethod:: _make_mode_fastopen + .. automethod:: _verify_pseudo_checksum + .. automethod:: _read_join_syn + .. automethod:: _read_join_synack + .. automethod:: _read_join_ack .. automethod:: _make_join_syn .. automethod:: _make_join_synack .. automethod:: _make_join_ack - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: - .. autoattribute:: __option__ - :no-value: - .. autoattribute:: __mp_option__ - :no-value: - - .. automethod:: __index__ - Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/transport/transport.rst b/docs/source/pcapkit/protocols/transport/transport.rst index 074c6b1e28..dd8c5575b7 100644 --- a/docs/source/pcapkit/protocols/transport/transport.rst +++ b/docs/source/pcapkit/protocols/transport/transport.rst @@ -14,6 +14,16 @@ which is a base class for transport layer protocols, e.g. :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + + .. important:: + + The attribute **must** be defined and maintained in subclass. + + .. autoattribute:: __layer__ + :no-value: + .. autoproperty:: layer .. automethod:: register @@ -24,12 +34,3 @@ which is a base class for transport layer protocols, e.g. .. automethod:: _make_port .. automethod:: _decode_next_layer - - .. autoattribute:: __layer__ - :no-value: - .. autoattribute:: __proto__ - :no-value: - - .. important:: - - The attribute **must** be defined and maintained in subclass. diff --git a/docs/source/pcapkit/protocols/transport/udp.rst b/docs/source/pcapkit/protocols/transport/udp.rst index dc66e0ee9d..05120d3654 100644 --- a/docs/source/pcapkit/protocols/transport/udp.rst +++ b/docs/source/pcapkit/protocols/transport/udp.rst @@ -22,6 +22,12 @@ Octets Bits Name Description :no-members: :show-inheritance: + .. autoattribute:: __proto__ + :no-value: + + .. autoattribute:: __proto_key__ + :no-value: + .. autoproperty:: name .. autoproperty:: length .. autoproperty:: src @@ -30,16 +36,11 @@ Octets Bits Name Description .. automethod:: read .. automethod:: make + .. automethod:: __index__ + .. automethod:: _make_data .. automethod:: _verify_pseudo_checksum - .. autoattribute:: __proto__ - :no-value: - .. autoattribute:: __proto_key__ - :no-value: - - .. automethod:: __index__ - Header Schemas -------------- diff --git a/pcapkit/foundation/registry/protocols/protocol.py b/pcapkit/foundation/registry/protocols/protocol.py index 41b10cbebf..73ac80ab6b 100644 --- a/pcapkit/foundation/registry/protocols/protocol.py +++ b/pcapkit/foundation/registry/protocols/protocol.py @@ -234,11 +234,12 @@ class is handed back, e.g. a custom ``IPv4`` released from under the def _release_name(target: 'tuple[type, Any]') -> 'None': """Hand back the lease ``target``'s registration held. - Once no code holds the lease, it leaves its name's stack. On top, it puts - the name back to the entry it displaced, or removes a new name -- unless the - name was written by other means since, in which case that write stands. - Buried, it hands what it displaced to the lease above, if that lease - displaced it. + Once no code holds the lease, it leaves its name's stack. A lease on top + undoes its write, unless the name was written by other means since. A new + name is removed. A displaced class is registered back with + :func:`register_protocol`, the path a code's restore takes, so it warns as + the code's restore does (:issue:`2773`). A buried lease hands what it + displaced to the lease above, if that lease displaced it. Args: target: The ``(destination, code)`` being registered over. @@ -266,8 +267,10 @@ def _release_name(target: 'tuple[type, Any]') -> 'None': elif _denotes(protocol_registry.get(lease.name), lease.protocol): if lease.displaced is MISSING: protocol_registry.pop(lease.name, None) + elif isinstance(lease.displaced, ModuleDescriptor): + register_protocol(lease.displaced.klass) else: - protocol_registry[lease.name] = lease.displaced + register_protocol(lease.displaced) logger.debug('released protocol: %s', lease.protocol.__name__) if not stack: _NAME_LEASES.pop(lease.name, None) diff --git a/pcapkit/protocols/misc/pcapng/schema.py b/pcapkit/protocols/misc/pcapng/schema.py index 224d833a0c..90ac31e59c 100644 --- a/pcapkit/protocols/misc/pcapng/schema.py +++ b/pcapkit/protocols/misc/pcapng/schema.py @@ -1090,12 +1090,12 @@ class Option(EnumSchema[Enum_OptionType]): #: Mapping of PCAP-NG option type numbers to schemas. __enum__: 'DefaultDict[str, DefaultDict[Enum_OptionType, Type[Option]]]' = collections.defaultdict( lambda: Option.registry['opt'], { - 'opt': collections.defaultdict(lambda: UnknownOption), - 'if': collections.defaultdict(lambda: UnknownOption), + 'dsb': collections.defaultdict(lambda: UnknownOption), 'epb': collections.defaultdict(lambda: UnknownOption), - 'ns': collections.defaultdict(lambda: UnknownOption), + 'if': collections.defaultdict(lambda: UnknownOption), 'isb': collections.defaultdict(lambda: UnknownOption), - 'dsb': collections.defaultdict(lambda: UnknownOption), + 'ns': collections.defaultdict(lambda: UnknownOption), + 'opt': collections.defaultdict(lambda: UnknownOption), 'pack': collections.defaultdict(lambda: UnknownOption), }, ) @@ -1128,7 +1128,8 @@ def register(code: 'Enum_OptionType', cls: 'Type[Option]', ns: 'Optional[str]' = Note: ``ns='opt'`` fans one registration out across every namespace, so the collision is reported once for the registration and names the - namespaces it displaced something in, rather than once per namespace. + namespaces it displaced something in, sorted, rather than once per + namespace. A namespace created by this call starts as a copy of ``opt``'s defaults, so nothing in it is a prior registration and it is exempt: @@ -1142,6 +1143,11 @@ def register(code: 'Enum_OptionType', cls: 'Type[Option]', ns: 'Optional[str]' = ``Option.registry[key][code]`` to see whether it is there would *insert* :class:`UnknownOption` for a code nobody registered. + .. versionchanged:: 1.6.0 + + The warning names the namespaces sorted, rather than in the order of + :attr:`__enum__` (:issue:`2772`). + """ if ns is None: ns = code.name.split('_')[0] @@ -1153,9 +1159,9 @@ def register(code: 'Enum_OptionType', cls: 'Type[Option]', ns: 'Optional[str]' = targets = list(Option.registry) if ns == 'opt' else [ns] if not fresh: - clash = [key for key in targets - if (incumbent := Option.registry[key].get(code)) is not None - and incumbent is not cls] + clash = sorted(key for key in targets + if (incumbent := Option.registry[key].get(code)) is not None + and incumbent is not cls) if clash: warn(f'PCAP-NG: [Option {code}] option already registered in ' f'namespace(s) {", ".join(repr(key) for key in clash)}, ' diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index 31b2852353..6feda9a885 100644 --- a/pcapkit/protocols/protocol.py +++ b/pcapkit/protocols/protocol.py @@ -574,34 +574,36 @@ class ProtocolBase(Generic[_PT, _ST], metaclass=ProtocolMeta): #: #: .. versionadded:: 1.6.0 __registry__: 'dict[str, ModuleDescriptor[ProtocolBase] | Type[ProtocolBase]]' = { - 'AH': ModuleDescriptor('pcapkit.protocols.internet.ah.protocol', 'AH'), + # Link Layer 'ARP': ModuleDescriptor('pcapkit.protocols.link.arp.protocol', 'ARP'), 'C_TAG': ModuleDescriptor('pcapkit.protocols.link.c_tag.protocol', 'C_Tag'), - 'DCCP': ModuleDescriptor('pcapkit.protocols.transport.dccp.protocol', 'DCCP'), - 'DRARP': ModuleDescriptor('pcapkit.protocols.application.drarp.protocol', 'DRARP'), - 'DTLS': ModuleDescriptor('pcapkit.protocols.application.dtls.protocol', 'DTLS'), - 'ESP': ModuleDescriptor('pcapkit.protocols.internet.esp.protocol', 'ESP'), 'ETHERNET': ModuleDescriptor('pcapkit.protocols.link.ethernet.protocol', 'Ethernet'), 'FRAD': ModuleDescriptor('pcapkit.protocols.link.frad.protocol', 'FRAD'), - 'FRAME': ModuleDescriptor('pcapkit.protocols.misc.pcap.frame.protocol', 'Frame'), - 'FTP': ModuleDescriptor('pcapkit.protocols.application.ftp.protocol', 'FTP'), - 'FTP_DATA': ModuleDescriptor('pcapkit.protocols.application.ftp_data.protocol', 'FTP_DATA'), 'GRE': ModuleDescriptor('pcapkit.protocols.link.gre.protocol', 'GRE'), - 'HEADER': ModuleDescriptor('pcapkit.protocols.misc.pcap.header.protocol', 'Header'), + 'IEEE802_11': ModuleDescriptor('pcapkit.protocols.link.ieee802_11.protocol', 'IEEE802_11'), + 'INARP': ModuleDescriptor('pcapkit.protocols.link.inarp.protocol', 'InARP'), + 'L2TP': ModuleDescriptor('pcapkit.protocols.link.l2tp.protocol', 'L2TP'), + 'L2TPV2': ModuleDescriptor('pcapkit.protocols.link.l2tpv2.protocol', 'L2TPv2'), + 'LINUXSLL': ModuleDescriptor('pcapkit.protocols.link.linux_sll.protocol', 'LinuxSLL'), + 'LINUXSLL1': ModuleDescriptor('pcapkit.protocols.link.linux_sll1.protocol', 'LinuxSLL1'), + 'LINUXSLL2': ModuleDescriptor('pcapkit.protocols.link.linux_sll2.protocol', 'LinuxSLL2'), + 'LOOPBACK': ModuleDescriptor('pcapkit.protocols.link.loopback.protocol', 'Loopback'), + 'NETLINK': ModuleDescriptor('pcapkit.protocols.link.netlink.protocol', 'Netlink'), + 'RADIOTAP': ModuleDescriptor('pcapkit.protocols.link.radiotap.protocol', 'Radiotap'), + 'S_TAG': ModuleDescriptor('pcapkit.protocols.link.s_tag.protocol', 'S_Tag'), + 'VLAN': ModuleDescriptor('pcapkit.protocols.link.vlan.protocol', 'VLAN'), + # Internet Layer + 'AH': ModuleDescriptor('pcapkit.protocols.internet.ah.protocol', 'AH'), + 'ESP': ModuleDescriptor('pcapkit.protocols.internet.esp.protocol', 'ESP'), 'HIP': ModuleDescriptor('pcapkit.protocols.internet.hip.protocol', 'HIP'), 'HOPOPT': ModuleDescriptor('pcapkit.protocols.internet.hopopt.protocol', 'HOPOPT'), - 'HTTP': ModuleDescriptor('pcapkit.protocols.application.http.protocol', 'HTTP'), - 'HTTPV1': ModuleDescriptor('pcapkit.protocols.application.httpv1.protocol', 'HTTPv1'), - 'HTTPV2': ModuleDescriptor('pcapkit.protocols.application.httpv2.protocol', 'HTTPv2'), 'ICMP': ModuleDescriptor('pcapkit.protocols.internet.icmp.protocol', 'ICMP'), 'ICMPV4': ModuleDescriptor('pcapkit.protocols.internet.icmpv4.protocol', 'ICMPv4'), 'ICMPV6': ModuleDescriptor('pcapkit.protocols.internet.icmpv6.protocol', 'ICMPv6'), - 'IEEE802_11': ModuleDescriptor('pcapkit.protocols.link.ieee802_11.protocol', 'IEEE802_11'), 'IGMP': ModuleDescriptor('pcapkit.protocols.internet.igmp.protocol', 'IGMP'), 'IGMPV1': ModuleDescriptor('pcapkit.protocols.internet.igmpv1.protocol', 'IGMPv1'), 'IGMPV2': ModuleDescriptor('pcapkit.protocols.internet.igmpv2.protocol', 'IGMPv2'), 'IGMPV3': ModuleDescriptor('pcapkit.protocols.internet.igmpv3.protocol', 'IGMPv3'), - 'INARP': ModuleDescriptor('pcapkit.protocols.link.inarp.protocol', 'InARP'), 'IP': ModuleDescriptor('pcapkit.protocols.internet.ip.protocol', 'IP'), 'IPSEC': ModuleDescriptor('pcapkit.protocols.internet.ipsec.protocol', 'IPsec'), 'IPV4': ModuleDescriptor('pcapkit.protocols.internet.ipv4.protocol', 'IPv4'), @@ -611,31 +613,34 @@ class ProtocolBase(Generic[_PT, _ST], metaclass=ProtocolMeta): 'IPV6_OPTS': ModuleDescriptor('pcapkit.protocols.internet.ipv6_opts.protocol', 'IPv6_Opts'), 'IPV6_ROUTE': ModuleDescriptor('pcapkit.protocols.internet.ipv6_route.protocol', 'IPv6_Route'), 'IPX': ModuleDescriptor('pcapkit.protocols.internet.ipx.protocol', 'IPX'), - 'IP_PSEUDO': ModuleDescriptor('pcapkit.protocols.misc.checksum.ip_pseudo.protocol', 'IP_Pseudo'), - 'L2TP': ModuleDescriptor('pcapkit.protocols.link.l2tp.protocol', 'L2TP'), - 'L2TPV2': ModuleDescriptor('pcapkit.protocols.link.l2tpv2.protocol', 'L2TPv2'), - 'LINUXSLL': ModuleDescriptor('pcapkit.protocols.link.linux_sll.protocol', 'LinuxSLL'), - 'LINUXSLL1': ModuleDescriptor('pcapkit.protocols.link.linux_sll1.protocol', 'LinuxSLL1'), - 'LINUXSLL2': ModuleDescriptor('pcapkit.protocols.link.linux_sll2.protocol', 'LinuxSLL2'), - 'LOOPBACK': ModuleDescriptor('pcapkit.protocols.link.loopback.protocol', 'Loopback'), 'MH': ModuleDescriptor('pcapkit.protocols.internet.mh.protocol', 'MH'), - 'NETLINK': ModuleDescriptor('pcapkit.protocols.link.netlink.protocol', 'Netlink'), + # Transport Layer + 'DCCP': ModuleDescriptor('pcapkit.protocols.transport.dccp.protocol', 'DCCP'), + 'SCTP': ModuleDescriptor('pcapkit.protocols.transport.sctp.protocol', 'SCTP'), + 'TCP': ModuleDescriptor('pcapkit.protocols.transport.tcp.protocol', 'TCP'), + 'UDP': ModuleDescriptor('pcapkit.protocols.transport.udp.protocol', 'UDP'), + # Application Layer + 'DRARP': ModuleDescriptor('pcapkit.protocols.application.drarp.protocol', 'DRARP'), + 'DTLS': ModuleDescriptor('pcapkit.protocols.application.dtls.protocol', 'DTLS'), + 'FTP': ModuleDescriptor('pcapkit.protocols.application.ftp.protocol', 'FTP'), + 'FTP_DATA': ModuleDescriptor('pcapkit.protocols.application.ftp_data.protocol', 'FTP_DATA'), + 'HTTP': ModuleDescriptor('pcapkit.protocols.application.http.protocol', 'HTTP'), + 'HTTPV1': ModuleDescriptor('pcapkit.protocols.application.httpv1.protocol', 'HTTPv1'), + 'HTTPV2': ModuleDescriptor('pcapkit.protocols.application.httpv2.protocol', 'HTTPv2'), 'NGAP': ModuleDescriptor('pcapkit.protocols.application.ngap.protocol', 'NGAP'), - 'NOPAYLOAD': ModuleDescriptor('pcapkit.protocols.misc.no_payload.protocol', 'NoPayload'), 'OSPF': ModuleDescriptor('pcapkit.protocols.application.ospf.protocol', 'OSPF'), - 'PCAPNG': ModuleDescriptor('pcapkit.protocols.misc.pcapng.protocol', 'PCAPNG'), - 'RADIOTAP': ModuleDescriptor('pcapkit.protocols.link.radiotap.protocol', 'Radiotap'), 'RARP': ModuleDescriptor('pcapkit.protocols.application.rarp.protocol', 'RARP'), - 'RAW': ModuleDescriptor('pcapkit.protocols.misc.raw.protocol', 'Raw'), - 'SCTP': ModuleDescriptor('pcapkit.protocols.transport.sctp.protocol', 'SCTP'), 'SSL': ModuleDescriptor('pcapkit.protocols.application.ssl.protocol', 'SSL'), 'SSLV2': ModuleDescriptor('pcapkit.protocols.application.sslv2.protocol', 'SSLv2'), 'SSLV3': ModuleDescriptor('pcapkit.protocols.application.sslv3.protocol', 'SSLv3'), - 'S_TAG': ModuleDescriptor('pcapkit.protocols.link.s_tag.protocol', 'S_Tag'), - 'TCP': ModuleDescriptor('pcapkit.protocols.transport.tcp.protocol', 'TCP'), 'TLS': ModuleDescriptor('pcapkit.protocols.application.tls.protocol', 'TLS'), - 'UDP': ModuleDescriptor('pcapkit.protocols.transport.udp.protocol', 'UDP'), - 'VLAN': ModuleDescriptor('pcapkit.protocols.link.vlan.protocol', 'VLAN'), + # Miscellaneous + 'FRAME': ModuleDescriptor('pcapkit.protocols.misc.pcap.frame.protocol', 'Frame'), + 'HEADER': ModuleDescriptor('pcapkit.protocols.misc.pcap.header.protocol', 'Header'), + 'IP_PSEUDO': ModuleDescriptor('pcapkit.protocols.misc.checksum.ip_pseudo.protocol', 'IP_Pseudo'), + 'NOPAYLOAD': ModuleDescriptor('pcapkit.protocols.misc.no_payload.protocol', 'NoPayload'), + 'PCAPNG': ModuleDescriptor('pcapkit.protocols.misc.pcapng.protocol', 'PCAPNG'), + 'RAW': ModuleDescriptor('pcapkit.protocols.misc.raw.protocol', 'Raw'), } #: Read-only copy of :attr:`__registry__` as shipped, which diff --git a/pcapkit/protocols/transport/sctp/protocol.py b/pcapkit/protocols/transport/sctp/protocol.py index 2a50c48041..6e0379342c 100644 --- a/pcapkit/protocols/transport/sctp/protocol.py +++ b/pcapkit/protocols/transport/sctp/protocol.py @@ -1682,1670 +1682,1670 @@ def _read_chunk_shutdown_complete(self, schema: 'Schema_ShutdownCompleteChunk', ), ) - def _make_chunk_donone(self, code: 'Enum_Chunk', chunk: 'Optional[Data_UnknownChunk]' = None, *, - flags: 'bytes' = b'\x00', - value: 'bytes' = b'', - **kwargs: 'Any') -> 'Schema_UnknownChunk': - """Make SCTP chunk of an unsupported type. + def _read_param_donone(self, schema: 'Schema_UnknownParameter', *, parameters: 'Parameters') -> 'Data_UnknownParameter': # pylint: disable=unused-argument + """Read SCTP chunk parameter of an unsupported type. - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - value: chunk value in :obj:`bytes` - **kwargs: arbitrary keyword arguments + This is the fall-through for every chunk parameter type :mod:`pcapkit` + does not implement. The parameter's value is recorded verbatim rather + than raising, so a chunk carrying an unknown parameter still parses. - Returns: - Constructed chunk schema. + Arguments: + schema: parsed parameter schema + parameters: extracted SCTP chunk parameters - Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte. + Returns: + Parsed parameter data. """ - if chunk is not None: - flags = chunk.flags - value = chunk.value + return Data_UnknownParameter( + type=schema.type, + length=schema.length, + value=schema.value, + ) - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + def _read_param_hbinfo(self, schema: 'Schema_HeartbeatInfoParameter', *, parameters: 'Parameters') -> 'Data_HeartbeatInfoParameter': # pylint: disable=unused-argument + """Read SCTP heartbeat info parameter. - return Schema_UnknownChunk( - type=code, - flags=flags, - length=self._make_uint(len(value) + 4, bits=16, name=f'[Chunk {code}] length'), - value=value, - ) + Structure of SCTP heartbeat info parameter [:rfc:`9260#section-3.3.5`]: - def _make_chunk_data(self, code: 'Enum_Chunk', chunk: 'Optional[Data_DATAChunk]' = None, *, - reserved: 'int' = 0, - I: 'bool' = False, # noqa: E741 - U: 'bool' = False, - B: 'bool' = True, - E: 'bool' = True, - tsn: 'int' = 0, - stream_id: 'int' = 0, - stream_seq: 'int' = 0, - ppid: 'Enum_PayloadProtocolIdentifier | int' = 0, - data: 'bytes' = b'', - **kwargs: 'Any') -> 'Schema_DATAChunk': - """Make SCTP DATA chunk. + .. code-block:: text - Args: - code: chunk type - chunk: chunk data - reserved: reserved flag bits - I: immediate bit - U: unordered bit - B: beginning fragment bit - E: ending fragment bit - tsn: transmission sequence number - stream_id: stream identifier - stream_seq: stream sequence number - ppid: payload protocol identifier, as a member or an :obj:`int` - code - data: user data - **kwargs: arbitrary keyword arguments + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Heartbeat Info Type = 1 | HB Info Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / Sender-Specific Heartbeat Info / + \\ \\ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed parameter schema + parameters: extracted SCTP chunk parameters Returns: - Constructed chunk schema. + Parsed parameter data. Raises: - ProtocolError: If ``data`` is empty, since :rfc:`9260#section-3.3.1` - requires at least one byte of user data, a field does not fit - its width, or ``ppid`` is a name, a :obj:`bool` or a code the - field cannot carry. + ProtocolError: If ``length`` is **NOT** at least ``4``. """ - if chunk is not None: - reserved = chunk.flags.reserved - I = chunk.flags.I # noqa: E741 - U = chunk.flags.U - B = chunk.flags.B - E = chunk.flags.E - tsn = chunk.tsn - stream_id = chunk.stream_id - stream_seq = chunk.stream_seq - ppid = chunk.ppid - data = chunk.data - - if not data: - raise ProtocolError(f'{self.alias}: [Chunk {code}] invalid data: ' - f'must be at least one octet, not {data!r}') + if schema.length < 4: + raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') - return Schema_DATAChunk( - type=code, - flags={ - 'reserved': self._make_uint(reserved, bits=4, name=f'[Chunk {code}] reserved'), - 'I': int(I), - 'U': int(U), - 'B': int(B), - 'E': int(E), - }, - length=self._make_uint(len(data) + 16, bits=16, name=f'[Chunk {code}] length'), - tsn=self._make_uint(tsn, bits=32, name=f'[Chunk {code}] tsn'), - stream_id=self._make_uint(stream_id, bits=16, name=f'[Chunk {code}] stream_id'), - stream_seq=self._make_uint(stream_seq, bits=16, name=f'[Chunk {code}] stream_seq'), - ppid=self._make_index(ppid, Enum_PayloadProtocolIdentifier, bits=32, - name=f'[Chunk {code}] ppid'), - data=data, + return Data_HeartbeatInfoParameter( + type=schema.type, + length=schema.length, + info=schema.info, ) - def _make_chunk_init(self, code: 'Enum_Chunk', chunk: 'Optional[Data_INITChunk]' = None, *, - flags: 'bytes' = b'\x00', - init_tag: 'int' = 0, - a_rwnd: 'int' = 1500, # minimum permitted by [RFC 9260] - outbound_streams: 'int' = 1, - inbound_streams: 'int' = 1, - init_tsn: 'int' = 0, - parameters: 'Optional[list[Schema_Parameter | tuple[Enum_Parameter | int, dict[str, Any]] | bytes] | Parameters]' = None, # pylint: disable=line-too-long - **kwargs: 'Any') -> 'Schema_INITChunk': - """Make SCTP INIT chunk. + def _read_param_ipv4(self, schema: 'Schema_IPv4AddressParameter', *, parameters: 'Parameters') -> 'Data_IPv4AddressParameter': # pylint: disable=unused-argument + """Read SCTP IPv4 address parameter. - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - init_tag: initiate tag - a_rwnd: advertised receiver window credit - outbound_streams: number of outbound streams - inbound_streams: number of inbound streams - init_tsn: initial transmission sequence number - parameters: optional and variable-length parameters - **kwargs: arbitrary keyword arguments + Structure of SCTP IPv4 address parameter [:rfc:`9260#section-3.3.2.1.1`]: + + .. code-block:: text + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type = 5 | Length = 8 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | IPv4 Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed parameter schema + parameters: extracted SCTP chunk parameters Returns: - Constructed chunk schema. + Parsed parameter data. Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte, or a field - does not fit its width. - - Note: - The chunk flags are reserved by :rfc:`9260#section-3.3.2`, so they - default to zero; a parsed chunk's flags are kept as received. + ProtocolError: If ``length`` is **NOT** ``8``. """ - if chunk is not None: - flags = chunk.flags - init_tag = chunk.init_tag - a_rwnd = chunk.a_rwnd - outbound_streams = chunk.outbound_streams - inbound_streams = chunk.inbound_streams - init_tsn = chunk.init_tsn - parameters = chunk.parameters + if schema.length != 8: + raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + return Data_IPv4AddressParameter( + type=schema.type, + length=schema.length, + address=schema.address, + ) - if parameters is not None: - parameters_value = self._make_sctp_parameters(parameters) - else: - parameters_value = [] - length = self._make_nested_length(20, parameters_value, - None if chunk is None else chunk.length) + def _read_param_ipv6(self, schema: 'Schema_IPv6AddressParameter', *, parameters: 'Parameters') -> 'Data_IPv6AddressParameter': # pylint: disable=unused-argument + """Read SCTP IPv6 address parameter. - return Schema_INITChunk( - type=code, - flags=flags, - length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), - init_tag=self._make_uint(init_tag, bits=32, name=f'[Chunk {code}] init_tag'), - a_rwnd=self._make_uint(a_rwnd, bits=32, name=f'[Chunk {code}] a_rwnd'), - outbound_streams=self._make_uint(outbound_streams, bits=16, name=f'[Chunk {code}] outbound_streams'), - inbound_streams=self._make_uint(inbound_streams, bits=16, name=f'[Chunk {code}] inbound_streams'), - init_tsn=self._make_uint(init_tsn, bits=32, name=f'[Chunk {code}] init_tsn'), - parameters=parameters_value, - ) + Structure of SCTP IPv6 address parameter [:rfc:`9260#section-3.3.2.1.2`]: - def _make_chunk_init_ack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_INITACKChunk]' = None, *, - flags: 'bytes' = b'\x00', - init_tag: 'int' = 0, - a_rwnd: 'int' = 1500, # minimum permitted by [RFC 9260] - outbound_streams: 'int' = 1, - inbound_streams: 'int' = 1, - init_tsn: 'int' = 0, - parameters: 'Optional[list[Schema_Parameter | tuple[Enum_Parameter | int, dict[str, Any]] | bytes] | Parameters]' = None, # pylint: disable=line-too-long - **kwargs: 'Any') -> 'Schema_INITACKChunk': - """Make SCTP INIT ACK chunk. + .. code-block:: text - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - init_tag: initiate tag - a_rwnd: advertised receiver window credit - outbound_streams: number of outbound streams - inbound_streams: number of inbound streams - init_tsn: initial transmission sequence number - parameters: optional and variable-length parameters - **kwargs: arbitrary keyword arguments + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type = 6 | Length = 20 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | IPv6 Address | + | | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed parameter schema + parameters: extracted SCTP chunk parameters Returns: - Constructed chunk schema. + Parsed parameter data. Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte, or a field - does not fit its width. + ProtocolError: If ``length`` is **NOT** ``20``. """ - if chunk is not None: - flags = chunk.flags - init_tag = chunk.init_tag - a_rwnd = chunk.a_rwnd - outbound_streams = chunk.outbound_streams - inbound_streams = chunk.inbound_streams - init_tsn = chunk.init_tsn - parameters = chunk.parameters + if schema.length != 20: + raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + return Data_IPv6AddressParameter( + type=schema.type, + length=schema.length, + address=schema.address, + ) - if parameters is not None: - parameters_value = self._make_sctp_parameters(parameters) - else: - parameters_value = [] - length = self._make_nested_length(20, parameters_value, - None if chunk is None else chunk.length) + def _read_param_cookie(self, schema: 'Schema_StateCookieParameter', *, parameters: 'Parameters') -> 'Data_StateCookieParameter': # pylint: disable=unused-argument + """Read SCTP state cookie parameter. - return Schema_INITACKChunk( - type=code, - flags=flags, - length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), - init_tag=self._make_uint(init_tag, bits=32, name=f'[Chunk {code}] init_tag'), - a_rwnd=self._make_uint(a_rwnd, bits=32, name=f'[Chunk {code}] a_rwnd'), - outbound_streams=self._make_uint(outbound_streams, bits=16, name=f'[Chunk {code}] outbound_streams'), - inbound_streams=self._make_uint(inbound_streams, bits=16, name=f'[Chunk {code}] inbound_streams'), - init_tsn=self._make_uint(init_tsn, bits=32, name=f'[Chunk {code}] init_tsn'), - parameters=parameters_value, - ) + Structure of SCTP state cookie parameter [:rfc:`9260#section-3.3.3.1.1`]: - def _make_chunk_sack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_SACKChunk]' = None, *, - flags: 'bytes' = b'\x00', - cum_tsn_ack: 'int' = 0, - a_rwnd: 'int' = 1500, # the INIT minimum [RFC 9260]; a SACK may advertise less - gap_blocks: 'Optional[list[Schema_GapAckBlock | Data_GapAckBlock | tuple[int, int]]]' = None, # pylint: disable=line-too-long - dup_tsn: 'Optional[list[int]]' = None, - **kwargs: 'Any') -> 'Schema_SACKChunk': - """Make SCTP SACK chunk. + .. code-block:: text - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - cum_tsn_ack: cumulative TSN ack - a_rwnd: advertised receiver window credit - gap_blocks: gap ack blocks, each as a schema, a data model or a - ``(start, end)`` pair - dup_tsn: duplicate TSNs - **kwargs: arbitrary keyword arguments + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type = 7 | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / Cookie / + \\ \\ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed parameter schema + parameters: extracted SCTP chunk parameters Returns: - Constructed chunk schema. + Parsed parameter data. Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte, or a - field, a gap ack block's offset or a duplicate TSN does not - fit its width. - - Note: - The counts of gap ack blocks and duplicate TSNs are derived from the - supplied lists rather than taken as arguments, so that they cannot - disagree with the lists they count. + ProtocolError: If ``length`` is **NOT** at least ``4``. """ - if chunk is not None: - flags = chunk.flags - cum_tsn_ack = chunk.cum_tsn_ack - a_rwnd = chunk.a_rwnd - gap_blocks = list(chunk.gap_blocks) - dup_tsn = list(chunk.dup_tsn) + if schema.length < 4: + raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + return Data_StateCookieParameter( + type=schema.type, + length=schema.length, + cookie=schema.cookie, + ) - blocks = [] # type: list[Schema_GapAckBlock] - for block in gap_blocks or []: - if isinstance(block, Schema_GapAckBlock): - blocks.append(block) - else: - start, end = ((block.start, block.end) if isinstance(block, Data_GapAckBlock) - else cast('tuple[int, int]', block)) - blocks.append(Schema_GapAckBlock( - start=self._make_uint(start, bits=16, name=f'[Chunk {code}] gap block start'), - end=self._make_uint(end, bits=16, name=f'[Chunk {code}] gap block end'), - )) - tsn_list = [self._make_uint(tsn, bits=32, name=f'[Chunk {code}] dup_tsn') for tsn in dup_tsn or []] + def _read_param_unrecognized(self, schema: 'Schema_UnrecognizedParameter', *, parameters: 'Parameters') -> 'Data_UnrecognizedParameter': # pylint: disable=unused-argument + """Read SCTP unrecognized parameter parameter. - return Schema_SACKChunk( - type=code, - flags=flags, - length=self._make_uint(16 + len(blocks) * 4 + len(tsn_list) * 4, bits=16, - name=f'[Chunk {code}] length'), - cum_tsn_ack=self._make_uint(cum_tsn_ack, bits=32, name=f'[Chunk {code}] cum_tsn_ack'), - a_rwnd=self._make_uint(a_rwnd, bits=32, name=f'[Chunk {code}] a_rwnd'), - num_gap_blocks=len(blocks), - num_dup_tsn=len(tsn_list), - gap_blocks=blocks, - dup_tsn=tsn_list, - ) + Structure of SCTP unrecognized parameter [:rfc:`9260#section-3.3.3.1.2`]: - def _make_chunk_heartbeat(self, code: 'Enum_Chunk', chunk: 'Optional[Data_HeartbeatChunk]' = None, *, - flags: 'bytes' = b'\x00', - parameters: 'Optional[list[Schema_Parameter | tuple[Enum_Parameter | int, dict[str, Any]] | bytes] | Parameters]' = None, # pylint: disable=line-too-long - **kwargs: 'Any') -> 'Schema_HeartbeatChunk': - """Make SCTP HEARTBEAT chunk. + .. code-block:: text - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - parameters: heartbeat information parameters - **kwargs: arbitrary keyword arguments + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type = 8 | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / Unrecognized Parameter / + \\ \\ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed parameter schema + parameters: extracted SCTP chunk parameters Returns: - Constructed chunk schema. + Parsed parameter data. Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte. + ProtocolError: If ``length`` is **NOT** at least ``4``. + + Note: + The offending parameter is recorded as raw :obj:`bytes`, complete + with its own type and length, rather than being parsed recursively: + by definition the sender did not recognise it, so neither + interpretation nor validation of its contents would be meaningful. """ - if chunk is not None: - flags = chunk.flags - parameters = chunk.parameters + if schema.length < 4: + raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + return Data_UnrecognizedParameter( + type=schema.type, + length=schema.length, + value=schema.value, + ) - if parameters is not None: - parameters_value = self._make_sctp_parameters(parameters) - else: - parameters_value = [] - length = self._make_nested_length(4, parameters_value, - None if chunk is None else chunk.length) + def _read_param_preservative(self, schema: 'Schema_CookiePreservativeParameter', *, parameters: 'Parameters') -> 'Data_CookiePreservativeParameter': # pylint: disable=unused-argument + """Read SCTP cookie preservative parameter. - return Schema_HeartbeatChunk( - type=code, - flags=flags, - length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), - parameters=parameters_value, - ) + Structure of SCTP cookie preservative parameter [:rfc:`9260#section-3.3.2.1.3`]: - def _make_chunk_heartbeat_ack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_HeartbeatACKChunk]' = None, *, - flags: 'bytes' = b'\x00', - parameters: 'Optional[list[Schema_Parameter | tuple[Enum_Parameter | int, dict[str, Any]] | bytes] | Parameters]' = None, # pylint: disable=line-too-long - **kwargs: 'Any') -> 'Schema_HeartbeatACKChunk': - """Make SCTP HEARTBEAT ACK chunk. + .. code-block:: text + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type = 9 | Length = 8 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Suggested Cookie Life-Span Increment (msec.) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - parameters: heartbeat information parameters - **kwargs: arbitrary keyword arguments + Arguments: + schema: parsed parameter schema + parameters: extracted SCTP chunk parameters Returns: - Constructed chunk schema. + Parsed parameter data. Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte. + ProtocolError: If ``length`` is **NOT** ``8``. """ - if chunk is not None: - flags = chunk.flags - parameters = chunk.parameters + if schema.length != 8: + raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + return Data_CookiePreservativeParameter( + type=schema.type, + length=schema.length, + increment=schema.increment, + ) - if parameters is not None: - parameters_value = self._make_sctp_parameters(parameters) - else: - parameters_value = [] - length = self._make_nested_length(4, parameters_value, - None if chunk is None else chunk.length) + def _read_param_hostname(self, schema: 'Schema_HostNameAddressParameter', *, parameters: 'Parameters') -> 'Data_HostNameAddressParameter': # pylint: disable=unused-argument + """Read SCTP host name address parameter. - return Schema_HeartbeatACKChunk( - type=code, - flags=flags, - length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), - parameters=parameters_value, - ) + Structure of SCTP host name address parameter [:rfc:`9260#section-3.3.2.1.4`]: - def _make_chunk_abort(self, code: 'Enum_Chunk', chunk: 'Optional[Data_AbortChunk]' = None, *, - reserved: 'int' = 0, - T: 'bool' = False, - error: 'Optional[list[Schema_ErrorCause | tuple[Enum_CauseCode | int, dict[str, Any]] | bytes] | Causes]' = None, # pylint: disable=line-too-long - **kwargs: 'Any') -> 'Schema_AbortChunk': - """Make SCTP ABORT chunk. + .. code-block:: text - Args: - code: chunk type - chunk: chunk data - reserved: reserved flag bits - T: whether the verification tag has been reflected - error: zero or more error causes - **kwargs: arbitrary keyword arguments + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type = 11 | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / Host Name / + \\ \\ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed parameter schema + parameters: extracted SCTP chunk parameters Returns: - Constructed chunk schema. + Parsed parameter data. Raises: - ProtocolError: If ``reserved`` does not fit 7 bits. + ProtocolError: If ``length`` is **NOT** at least ``4``. - """ - if chunk is not None: - reserved = chunk.flags.reserved - T = chunk.flags.T - error = chunk.error + Note: + The usage of this parameter is deprecated by + :rfc:`9260#section-3.3.2.1.4`; it is parsed so that a packet + carrying one can still be inspected. The host name is kept as raw + :obj:`bytes`, including any null terminator, since the encoding is + not specified on the wire. A name captured without a terminator is + kept as read and rebuilds as read (:issue:`1482`). - if error is not None: - error_value = self._make_sctp_causes(error) - else: - error_value = [] - length = self._make_nested_length(4, error_value, - None if chunk is None else chunk.length) + """ + if schema.length < 4: + raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') - return Schema_AbortChunk( - type=code, - flags={ - 'reserved': self._make_uint(reserved, bits=7, name=f'[Chunk {code}] reserved'), - 'T': int(T), - }, - length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), - error=error_value, + return Data_HostNameAddressParameter( + type=schema.type, + length=schema.length, + name=schema.name, ) - def _make_chunk_shutdown(self, code: 'Enum_Chunk', chunk: 'Optional[Data_ShutdownChunk]' = None, *, - flags: 'bytes' = b'\x00', - cum_tsn_ack: 'int' = 0, - **kwargs: 'Any') -> 'Schema_ShutdownChunk': - """Make SCTP SHUTDOWN chunk. + def _read_param_addrtypes(self, schema: 'Schema_SupportedAddressTypesParameter', *, parameters: 'Parameters') -> 'Data_SupportedAddressTypesParameter': # pylint: disable=unused-argument + """Read SCTP supported address types parameter. - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - cum_tsn_ack: cumulative TSN ack - **kwargs: arbitrary keyword arguments + Structure of SCTP supported address types parameter [:rfc:`9260#section-3.3.2.1.5`]: + + .. code-block:: text + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type = 12 | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Address Type #1 | Address Type #2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ...... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed parameter schema + parameters: extracted SCTP chunk parameters Returns: - Constructed chunk schema. + Parsed parameter data. Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte, or - ``cum_tsn_ack`` does not fit 32 bits. + ProtocolError: If ``length`` is **NOT** ``4`` plus a multiple of + ``2``. """ - if chunk is not None: - flags = chunk.flags - cum_tsn_ack = chunk.cum_tsn_ack - - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + if schema.length < 4 or (schema.length - 4) % 2: + raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') - return Schema_ShutdownChunk( - type=code, - flags=flags, - length=8, - cum_tsn_ack=self._make_uint(cum_tsn_ack, bits=32, name=f'[Chunk {code}] cum_tsn_ack'), + return Data_SupportedAddressTypesParameter( + type=schema.type, + length=schema.length, + types=tuple(schema.types), ) - def _make_chunk_shutdown_ack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_ShutdownACKChunk]' = None, *, - flags: 'bytes' = b'\x00', - **kwargs: 'Any') -> 'Schema_ShutdownACKChunk': - """Make SCTP SHUTDOWN ACK chunk. + def _read_cause_donone(self, schema: 'Schema_UnknownCause', *, causes: 'Causes') -> 'Data_UnknownCause': # pylint: disable=unused-argument + """Read SCTP error cause of an unsupported cause code. - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - **kwargs: arbitrary keyword arguments + This is the fall-through for every error cause code :mod:`pcapkit` does + not implement, e.g. those registered by SCTP extensions. The + cause-specific information is recorded verbatim rather than raising. - Returns: - Constructed chunk schema. + Arguments: + schema: parsed error cause schema + causes: extracted SCTP error causes - Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte. + Returns: + Parsed error cause data. """ - if chunk is not None: - flags = chunk.flags + return Data_UnknownCause( + code=schema.code, + length=schema.length, + value=schema.value, + ) - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + def _read_cause_invalid_stream(self, schema: 'Schema_InvalidStreamIdentifierCause', *, causes: 'Causes') -> 'Data_InvalidStreamIdentifierCause': # pylint: disable=unused-argument + """Read SCTP invalid stream identifier error cause. - return Schema_ShutdownACKChunk( - type=code, - flags=flags, - length=4, - ) + Structure of the cause [:rfc:`9260#section-3.3.10.1`]: - def _make_chunk_error(self, code: 'Enum_Chunk', chunk: 'Optional[Data_ErrorChunk]' = None, *, - flags: 'bytes' = b'\x00', - error: 'Optional[list[Schema_ErrorCause | tuple[Enum_CauseCode | int, dict[str, Any]] | bytes] | Causes]' = None, # pylint: disable=line-too-long - **kwargs: 'Any') -> 'Schema_ErrorChunk': - """Make SCTP ERROR chunk. + .. code-block:: text - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - error: one or more error causes - **kwargs: arbitrary keyword arguments + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Cause Code = 1 | Cause Length = 8 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream Identifier | (Reserved) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Constructed chunk schema. + Parsed error cause data. Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte. + ProtocolError: If ``length`` is **NOT** ``8``. """ - if chunk is not None: - flags = chunk.flags - error = chunk.error - - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') - - if error is not None: - error_value = self._make_sctp_causes(error) - else: - error_value = [] - length = self._make_nested_length(4, error_value, - None if chunk is None else chunk.length) + if schema.length != 8: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Schema_ErrorChunk( - type=code, - flags=flags, - length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), - error=error_value, + return Data_InvalidStreamIdentifierCause( + code=schema.code, + length=schema.length, + stream_id=schema.stream_id, + reserved=schema.reserved, ) - def _make_chunk_cookie_echo(self, code: 'Enum_Chunk', chunk: 'Optional[Data_CookieEchoChunk]' = None, *, - flags: 'bytes' = b'\x00', - cookie: 'bytes' = b'', - **kwargs: 'Any') -> 'Schema_CookieEchoChunk': - """Make SCTP COOKIE ECHO chunk. + def _read_cause_missing_param(self, schema: 'Schema_MissingMandatoryParameterCause', *, causes: 'Causes') -> 'Data_MissingMandatoryParameterCause': # pylint: disable=unused-argument + """Read SCTP missing mandatory parameter error cause. + + Structure of the cause [:rfc:`9260#section-3.3.10.2`]: - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - cookie: state cookie, as received in the INIT ACK chunk's state - cookie parameter - **kwargs: arbitrary keyword arguments + .. code-block:: text + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Cause Code = 2 | Cause Length = 8 + N * 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Number of missing params = N | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Missing Param Type #1 | Missing Param Type #2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Constructed chunk schema. + Parsed error cause data. Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte. + ProtocolError: If ``length`` does **NOT** match the declared number + of missing parameters. """ - if chunk is not None: - flags = chunk.flags - cookie = chunk.cookie - - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + if schema.length != 8 + schema.num * 2: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Schema_CookieEchoChunk( - type=code, - flags=flags, - length=self._make_uint(len(cookie) + 4, bits=16, name=f'[Chunk {code}] length'), - cookie=cookie, + return Data_MissingMandatoryParameterCause( + code=schema.code, + length=schema.length, + num=schema.num, + types=tuple(schema.types), ) - def _make_chunk_cookie_ack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_CookieACKChunk]' = None, *, - flags: 'bytes' = b'\x00', - **kwargs: 'Any') -> 'Schema_CookieACKChunk': - """Make SCTP COOKIE ACK chunk. + def _read_cause_stale_cookie(self, schema: 'Schema_StaleCookieCause', *, causes: 'Causes') -> 'Data_StaleCookieCause': # pylint: disable=unused-argument + """Read SCTP stale cookie error cause. - Args: - code: chunk type - chunk: chunk data - flags: raw chunk flags, as a single byte - **kwargs: arbitrary keyword arguments + Structure of the cause [:rfc:`9260#section-3.3.10.3`]: + + .. code-block:: text + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Cause Code = 3 | Cause Length = 8 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Measure of Staleness (usec.) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Constructed chunk schema. + Parsed error cause data. Raises: - ProtocolError: If ``flags`` is **NOT** exactly one byte. + ProtocolError: If ``length`` is **NOT** ``8``. """ - if chunk is not None: - flags = chunk.flags - - flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + if schema.length != 8: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Schema_CookieACKChunk( - type=code, - flags=flags, - length=4, + return Data_StaleCookieCause( + code=schema.code, + length=schema.length, + staleness=schema.staleness, ) - def _make_chunk_shutdown_complete(self, code: 'Enum_Chunk', chunk: 'Optional[Data_ShutdownCompleteChunk]' = None, *, - reserved: 'int' = 0, - T: 'bool' = False, - **kwargs: 'Any') -> 'Schema_ShutdownCompleteChunk': - """Make SCTP SHUTDOWN COMPLETE chunk. + def _read_cause_out_of_resource(self, schema: 'Schema_OutOfResourceCause', *, causes: 'Causes') -> 'Data_OutOfResourceCause': # pylint: disable=unused-argument + """Read SCTP out of resource error cause. - Args: - code: chunk type - chunk: chunk data - reserved: reserved flag bits - T: whether the verification tag has been reflected - **kwargs: arbitrary keyword arguments + Structure of the cause [:rfc:`9260#section-3.3.10.4`]: + + .. code-block:: text + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Cause Code = 4 | Cause Length = 4 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Arguments: + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Constructed chunk schema. + Parsed error cause data. Raises: - ProtocolError: If ``reserved`` does not fit 7 bits. + ProtocolError: If ``length`` is **NOT** ``4``. """ - if chunk is not None: - reserved = chunk.flags.reserved - T = chunk.flags.T + if schema.length != 4: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Schema_ShutdownCompleteChunk( - type=code, - flags={ - 'reserved': self._make_uint(reserved, bits=7, name=f'[Chunk {code}] reserved'), - 'T': int(T), - }, - length=4, + return Data_OutOfResourceCause( + code=schema.code, + length=schema.length, ) - def _read_param_donone(self, schema: 'Schema_UnknownParameter', *, parameters: 'Parameters') -> 'Data_UnknownParameter': # pylint: disable=unused-argument - """Read SCTP chunk parameter of an unsupported type. + def _read_cause_unresolvable_addr(self, schema: 'Schema_UnresolvableAddressCause', *, causes: 'Causes') -> 'Data_UnresolvableAddressCause': # pylint: disable=unused-argument + """Read SCTP unresolvable address error cause. - This is the fall-through for every chunk parameter type :mod:`pcapkit` - does not implement. The parameter's value is recorded verbatim rather - than raising, so a chunk carrying an unknown parameter still parses. + Structure of the cause [:rfc:`9260#section-3.3.10.5`]: + + .. code-block:: text + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Cause Code = 5 | Cause Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / Unresolvable Address / + \\ \\ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Arguments: - schema: parsed parameter schema - parameters: extracted SCTP chunk parameters + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Parsed parameter data. + Parsed error cause data. + + Raises: + ProtocolError: If ``length`` is **NOT** at least ``4``. """ - return Data_UnknownParameter( - type=schema.type, + if schema.length < 4: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') + + return Data_UnresolvableAddressCause( + code=schema.code, length=schema.length, value=schema.value, ) - def _read_param_hbinfo(self, schema: 'Schema_HeartbeatInfoParameter', *, parameters: 'Parameters') -> 'Data_HeartbeatInfoParameter': # pylint: disable=unused-argument - """Read SCTP heartbeat info parameter. + def _read_cause_unrecognized_chunk(self, schema: 'Schema_UnrecognizedChunkTypeCause', *, causes: 'Causes') -> 'Data_UnrecognizedChunkTypeCause': # pylint: disable=unused-argument + """Read SCTP unrecognized chunk type error cause. - Structure of SCTP heartbeat info parameter [:rfc:`9260#section-3.3.5`]: + Structure of the cause [:rfc:`9260#section-3.3.10.6`]: .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Heartbeat Info Type = 1 | HB Info Length | + | Cause Code = 6 | Cause Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / Sender-Specific Heartbeat Info / + / Unrecognized Chunk / \\ \\ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Arguments: - schema: parsed parameter schema - parameters: extracted SCTP chunk parameters + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Parsed parameter data. + Parsed error cause data. Raises: ProtocolError: If ``length`` is **NOT** at least ``4``. """ if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Data_HeartbeatInfoParameter( - type=schema.type, + return Data_UnrecognizedChunkTypeCause( + code=schema.code, length=schema.length, - info=schema.info, + value=schema.value, ) - def _read_param_ipv4(self, schema: 'Schema_IPv4AddressParameter', *, parameters: 'Parameters') -> 'Data_IPv4AddressParameter': # pylint: disable=unused-argument - """Read SCTP IPv4 address parameter. + def _read_cause_invalid_param(self, schema: 'Schema_InvalidMandatoryParameterCause', *, causes: 'Causes') -> 'Data_InvalidMandatoryParameterCause': # pylint: disable=unused-argument + """Read SCTP invalid mandatory parameter error cause. - Structure of SCTP IPv4 address parameter [:rfc:`9260#section-3.3.2.1.1`]: + Structure of the cause [:rfc:`9260#section-3.3.10.7`]: .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type = 5 | Length = 8 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | IPv4 Address | + | Cause Code = 7 | Cause Length = 4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Arguments: - schema: parsed parameter schema - parameters: extracted SCTP chunk parameters + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Parsed parameter data. + Parsed error cause data. Raises: - ProtocolError: If ``length`` is **NOT** ``8``. + ProtocolError: If ``length`` is **NOT** ``4``. """ - if schema.length != 8: - raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') + if schema.length != 4: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Data_IPv4AddressParameter( - type=schema.type, + return Data_InvalidMandatoryParameterCause( + code=schema.code, length=schema.length, - address=schema.address, ) - def _read_param_ipv6(self, schema: 'Schema_IPv6AddressParameter', *, parameters: 'Parameters') -> 'Data_IPv6AddressParameter': # pylint: disable=unused-argument - """Read SCTP IPv6 address parameter. + def _read_cause_unrecognized_params(self, schema: 'Schema_UnrecognizedParametersCause', *, causes: 'Causes') -> 'Data_UnrecognizedParametersCause': # pylint: disable=unused-argument + """Read SCTP unrecognized parameters error cause. - Structure of SCTP IPv6 address parameter [:rfc:`9260#section-3.3.2.1.2`]: + Structure of the cause [:rfc:`9260#section-3.3.10.8`]: .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type = 6 | Length = 20 | + | Cause Code = 8 | Cause Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | IPv6 Address | - | | - | | + / Unrecognized Parameters / + \\ \\ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Arguments: - schema: parsed parameter schema - parameters: extracted SCTP chunk parameters + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Parsed parameter data. + Parsed error cause data. Raises: - ProtocolError: If ``length`` is **NOT** ``20``. + ProtocolError: If ``length`` is **NOT** at least ``4``. """ - if schema.length != 20: - raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') + if schema.length < 4: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Data_IPv6AddressParameter( - type=schema.type, + return Data_UnrecognizedParametersCause( + code=schema.code, length=schema.length, - address=schema.address, + value=schema.value, ) - def _read_param_cookie(self, schema: 'Schema_StateCookieParameter', *, parameters: 'Parameters') -> 'Data_StateCookieParameter': # pylint: disable=unused-argument - """Read SCTP state cookie parameter. + def _read_cause_no_user_data(self, schema: 'Schema_NoUserDataCause', *, causes: 'Causes') -> 'Data_NoUserDataCause': # pylint: disable=unused-argument + """Read SCTP no user data error cause. - Structure of SCTP state cookie parameter [:rfc:`9260#section-3.3.3.1.1`]: + Structure of the cause [:rfc:`9260#section-3.3.10.9`]: .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type = 7 | Length | + | Cause Code = 9 | Cause Length = 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / Cookie / - \\ \\ + | TSN | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Arguments: - schema: parsed parameter schema - parameters: extracted SCTP chunk parameters + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Parsed parameter data. + Parsed error cause data. Raises: - ProtocolError: If ``length`` is **NOT** at least ``4``. + ProtocolError: If ``length`` is **NOT** ``8``. """ - if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') + if schema.length != 8: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Data_StateCookieParameter( - type=schema.type, + return Data_NoUserDataCause( + code=schema.code, length=schema.length, - cookie=schema.cookie, + tsn=schema.tsn, ) - def _read_param_unrecognized(self, schema: 'Schema_UnrecognizedParameter', *, parameters: 'Parameters') -> 'Data_UnrecognizedParameter': # pylint: disable=unused-argument - """Read SCTP unrecognized parameter parameter. + def _read_cause_cookie_shutdown(self, schema: 'Schema_CookieReceivedWhileShuttingDownCause', *, causes: 'Causes') -> 'Data_CookieReceivedWhileShuttingDownCause': # pylint: disable=unused-argument + """Read SCTP cookie received while shutting down error cause. - Structure of SCTP unrecognized parameter [:rfc:`9260#section-3.3.3.1.2`]: + Structure of the cause [:rfc:`9260#section-3.3.10.10`]: .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type = 8 | Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / Unrecognized Parameter / - \\ \\ + | Cause Code = 10 | Cause Length = 4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Arguments: - schema: parsed parameter schema - parameters: extracted SCTP chunk parameters + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Parsed parameter data. + Parsed error cause data. Raises: - ProtocolError: If ``length`` is **NOT** at least ``4``. - - Note: - The offending parameter is recorded as raw :obj:`bytes`, complete - with its own type and length, rather than being parsed recursively: - by definition the sender did not recognise it, so neither - interpretation nor validation of its contents would be meaningful. + ProtocolError: If ``length`` is **NOT** ``4``. """ - if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') + if schema.length != 4: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Data_UnrecognizedParameter( - type=schema.type, + return Data_CookieReceivedWhileShuttingDownCause( + code=schema.code, length=schema.length, - value=schema.value, ) - def _read_param_preservative(self, schema: 'Schema_CookiePreservativeParameter', *, parameters: 'Parameters') -> 'Data_CookiePreservativeParameter': # pylint: disable=unused-argument - """Read SCTP cookie preservative parameter. + def _read_cause_restart_addr(self, schema: 'Schema_RestartOfAnAssociationWithNewAddressesCause', *, causes: 'Causes') -> 'Data_RestartOfAnAssociationWithNewAddressesCause': # pylint: disable=unused-argument + """Read SCTP restart of an association with new addresses error cause. - Structure of SCTP cookie preservative parameter [:rfc:`9260#section-3.3.2.1.3`]: + Structure of the cause [:rfc:`9260#section-3.3.10.11`]: .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type = 9 | Length = 8 | + | Cause Code = 11 | Cause Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Suggested Cookie Life-Span Increment (msec.) | + / New Address TLVs / + \\ \\ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Arguments: - schema: parsed parameter schema - parameters: extracted SCTP chunk parameters + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Parsed parameter data. + Parsed error cause data. Raises: - ProtocolError: If ``length`` is **NOT** ``8``. + ProtocolError: If ``length`` is **NOT** at least ``4``. """ - if schema.length != 8: - raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') + if schema.length < 4: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Data_CookiePreservativeParameter( - type=schema.type, + return Data_RestartOfAnAssociationWithNewAddressesCause( + code=schema.code, length=schema.length, - increment=schema.increment, + value=schema.value, ) - def _read_param_hostname(self, schema: 'Schema_HostNameAddressParameter', *, parameters: 'Parameters') -> 'Data_HostNameAddressParameter': # pylint: disable=unused-argument - """Read SCTP host name address parameter. + def _read_cause_user_abort(self, schema: 'Schema_UserInitiatedAbortCause', *, causes: 'Causes') -> 'Data_UserInitiatedAbortCause': # pylint: disable=unused-argument + """Read SCTP user-initiated abort error cause. - Structure of SCTP host name address parameter [:rfc:`9260#section-3.3.2.1.4`]: + Structure of the cause [:rfc:`9260#section-3.3.10.12`]: .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type = 11 | Length | + | Cause Code = 12 | Cause Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / Host Name / + / Upper Layer Abort Reason / \\ \\ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Arguments: - schema: parsed parameter schema - parameters: extracted SCTP chunk parameters + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Parsed parameter data. + Parsed error cause data. Raises: ProtocolError: If ``length`` is **NOT** at least ``4``. - Note: - The usage of this parameter is deprecated by - :rfc:`9260#section-3.3.2.1.4`; it is parsed so that a packet - carrying one can still be inspected. The host name is kept as raw - :obj:`bytes`, including any null terminator, since the encoding is - not specified on the wire. A name captured without a terminator is - kept as read and rebuilds as read (:issue:`1482`). - """ if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Data_HostNameAddressParameter( - type=schema.type, + return Data_UserInitiatedAbortCause( + code=schema.code, length=schema.length, - name=schema.name, + info=schema.info, ) - def _read_param_addrtypes(self, schema: 'Schema_SupportedAddressTypesParameter', *, parameters: 'Parameters') -> 'Data_SupportedAddressTypesParameter': # pylint: disable=unused-argument - """Read SCTP supported address types parameter. + def _read_cause_protocol_violation(self, schema: 'Schema_ProtocolViolationCause', *, causes: 'Causes') -> 'Data_ProtocolViolationCause': # pylint: disable=unused-argument + """Read SCTP protocol violation error cause. - Structure of SCTP supported address types parameter [:rfc:`9260#section-3.3.2.1.5`]: + Structure of the cause [:rfc:`9260#section-3.3.10.13`]: .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type = 12 | Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Address Type #1 | Address Type #2 | + | Cause Code = 13 | Cause Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | ...... | + / Additional Information / + \\ \\ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Arguments: - schema: parsed parameter schema - parameters: extracted SCTP chunk parameters + schema: parsed error cause schema + causes: extracted SCTP error causes Returns: - Parsed parameter data. + Parsed error cause data. Raises: - ProtocolError: If ``length`` is **NOT** ``4`` plus a multiple of - ``2``. + ProtocolError: If ``length`` is **NOT** at least ``4``. """ - if schema.length < 4 or (schema.length - 4) % 2: - raise ProtocolError(f'{self.alias}: [Param {schema.type}] invalid format') + if schema.length < 4: + raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - return Data_SupportedAddressTypesParameter( - type=schema.type, + return Data_ProtocolViolationCause( + code=schema.code, length=schema.length, - types=tuple(schema.types), + info=schema.info, ) - def _make_param_donone(self, code: 'Enum_Parameter', param: 'Optional[Data_UnknownParameter]' = None, *, + def _make_chunk_donone(self, code: 'Enum_Chunk', chunk: 'Optional[Data_UnknownChunk]' = None, *, + flags: 'bytes' = b'\x00', value: 'bytes' = b'', - **kwargs: 'Any') -> 'Schema_UnknownParameter': - """Make SCTP chunk parameter of an unsupported type. + **kwargs: 'Any') -> 'Schema_UnknownChunk': + """Make SCTP chunk of an unsupported type. Args: - code: parameter type - param: parameter data - value: parameter value in :obj:`bytes` + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + value: chunk value in :obj:`bytes` **kwargs: arbitrary keyword arguments Returns: - Constructed parameter schema. + Constructed chunk schema. + + Raises: + ProtocolError: If ``flags`` is **NOT** exactly one byte. """ - if param is not None: - value = param.value + if chunk is not None: + flags = chunk.flags + value = chunk.value - return Schema_UnknownParameter( + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + + return Schema_UnknownChunk( type=code, - length=self._make_uint(len(value) + 4, bits=16, name=f'[Param {code}] length'), + flags=flags, + length=self._make_uint(len(value) + 4, bits=16, name=f'[Chunk {code}] length'), value=value, ) - def _make_param_hbinfo(self, code: 'Enum_Parameter', param: 'Optional[Data_HeartbeatInfoParameter]' = None, *, - info: 'bytes' = b'', - **kwargs: 'Any') -> 'Schema_HeartbeatInfoParameter': - """Make SCTP heartbeat info parameter. + def _make_chunk_data(self, code: 'Enum_Chunk', chunk: 'Optional[Data_DATAChunk]' = None, *, + reserved: 'int' = 0, + I: 'bool' = False, # noqa: E741 + U: 'bool' = False, + B: 'bool' = True, + E: 'bool' = True, + tsn: 'int' = 0, + stream_id: 'int' = 0, + stream_seq: 'int' = 0, + ppid: 'Enum_PayloadProtocolIdentifier | int' = 0, + data: 'bytes' = b'', + **kwargs: 'Any') -> 'Schema_DATAChunk': + """Make SCTP DATA chunk. Args: - code: parameter type - param: parameter data - info: sender-specific heartbeat info + code: chunk type + chunk: chunk data + reserved: reserved flag bits + I: immediate bit + U: unordered bit + B: beginning fragment bit + E: ending fragment bit + tsn: transmission sequence number + stream_id: stream identifier + stream_seq: stream sequence number + ppid: payload protocol identifier, as a member or an :obj:`int` + code + data: user data **kwargs: arbitrary keyword arguments Returns: - Constructed parameter schema. - - """ - if param is not None: - info = param.info - - return Schema_HeartbeatInfoParameter( - type=code, - length=self._make_uint(len(info) + 4, bits=16, name=f'[Param {code}] length'), - info=info, - ) - - def _make_param_ipv4(self, code: 'Enum_Parameter', param: 'Optional[Data_IPv4AddressParameter]' = None, *, - address: 'IPv4Address | int | str | bytes' = '0.0.0.0', # nosec: B104 - **kwargs: 'Any') -> 'Schema_IPv4AddressParameter': - """Make SCTP IPv4 address parameter. + Constructed chunk schema. - Args: - code: parameter type - param: parameter data - address: IPv4 address of the sending endpoint - **kwargs: arbitrary keyword arguments + Raises: + ProtocolError: If ``data`` is empty, since :rfc:`9260#section-3.3.1` + requires at least one byte of user data, a field does not fit + its width, or ``ppid`` is a name, a :obj:`bool` or a code the + field cannot carry. - Returns: - Constructed parameter schema. + """ + if chunk is not None: + reserved = chunk.flags.reserved + I = chunk.flags.I # noqa: E741 + U = chunk.flags.U + B = chunk.flags.B + E = chunk.flags.E + tsn = chunk.tsn + stream_id = chunk.stream_id + stream_seq = chunk.stream_seq + ppid = chunk.ppid + data = chunk.data - """ - if param is not None: - address = param.address + if not data: + raise ProtocolError(f'{self.alias}: [Chunk {code}] invalid data: ' + f'must be at least one octet, not {data!r}') - return Schema_IPv4AddressParameter( + return Schema_DATAChunk( type=code, - length=8, - address=address, + flags={ + 'reserved': self._make_uint(reserved, bits=4, name=f'[Chunk {code}] reserved'), + 'I': int(I), + 'U': int(U), + 'B': int(B), + 'E': int(E), + }, + length=self._make_uint(len(data) + 16, bits=16, name=f'[Chunk {code}] length'), + tsn=self._make_uint(tsn, bits=32, name=f'[Chunk {code}] tsn'), + stream_id=self._make_uint(stream_id, bits=16, name=f'[Chunk {code}] stream_id'), + stream_seq=self._make_uint(stream_seq, bits=16, name=f'[Chunk {code}] stream_seq'), + ppid=self._make_index(ppid, Enum_PayloadProtocolIdentifier, bits=32, + name=f'[Chunk {code}] ppid'), + data=data, ) - def _make_param_ipv6(self, code: 'Enum_Parameter', param: 'Optional[Data_IPv6AddressParameter]' = None, *, - address: 'IPv6Address | int | str | bytes' = '::', - **kwargs: 'Any') -> 'Schema_IPv6AddressParameter': - """Make SCTP IPv6 address parameter. + def _make_chunk_init(self, code: 'Enum_Chunk', chunk: 'Optional[Data_INITChunk]' = None, *, + flags: 'bytes' = b'\x00', + init_tag: 'int' = 0, + a_rwnd: 'int' = 1500, # minimum permitted by [RFC 9260] + outbound_streams: 'int' = 1, + inbound_streams: 'int' = 1, + init_tsn: 'int' = 0, + parameters: 'Optional[list[Schema_Parameter | tuple[Enum_Parameter | int, dict[str, Any]] | bytes] | Parameters]' = None, # pylint: disable=line-too-long + **kwargs: 'Any') -> 'Schema_INITChunk': + """Make SCTP INIT chunk. Args: - code: parameter type - param: parameter data - address: IPv6 address of the sending endpoint + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + init_tag: initiate tag + a_rwnd: advertised receiver window credit + outbound_streams: number of outbound streams + inbound_streams: number of inbound streams + init_tsn: initial transmission sequence number + parameters: optional and variable-length parameters **kwargs: arbitrary keyword arguments Returns: - Constructed parameter schema. - - """ - if param is not None: - address = param.address + Constructed chunk schema. - return Schema_IPv6AddressParameter( - type=code, - length=20, - address=address, - ) + Raises: + ProtocolError: If ``flags`` is **NOT** exactly one byte, or a field + does not fit its width. - def _make_param_cookie(self, code: 'Enum_Parameter', param: 'Optional[Data_StateCookieParameter]' = None, *, - cookie: 'bytes' = b'', - **kwargs: 'Any') -> 'Schema_StateCookieParameter': - """Make SCTP state cookie parameter. + Note: + The chunk flags are reserved by :rfc:`9260#section-3.3.2`, so they + default to zero; a parsed chunk's flags are kept as received. - Args: - code: parameter type - param: parameter data - cookie: state cookie - **kwargs: arbitrary keyword arguments + """ + if chunk is not None: + flags = chunk.flags + init_tag = chunk.init_tag + a_rwnd = chunk.a_rwnd + outbound_streams = chunk.outbound_streams + inbound_streams = chunk.inbound_streams + init_tsn = chunk.init_tsn + parameters = chunk.parameters - Returns: - Constructed parameter schema. + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') - """ - if param is not None: - cookie = param.cookie + if parameters is not None: + parameters_value = self._make_sctp_parameters(parameters) + else: + parameters_value = [] + length = self._make_nested_length(20, parameters_value, + None if chunk is None else chunk.length) - return Schema_StateCookieParameter( + return Schema_INITChunk( type=code, - length=self._make_uint(len(cookie) + 4, bits=16, name=f'[Param {code}] length'), - cookie=cookie, + flags=flags, + length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), + init_tag=self._make_uint(init_tag, bits=32, name=f'[Chunk {code}] init_tag'), + a_rwnd=self._make_uint(a_rwnd, bits=32, name=f'[Chunk {code}] a_rwnd'), + outbound_streams=self._make_uint(outbound_streams, bits=16, name=f'[Chunk {code}] outbound_streams'), + inbound_streams=self._make_uint(inbound_streams, bits=16, name=f'[Chunk {code}] inbound_streams'), + init_tsn=self._make_uint(init_tsn, bits=32, name=f'[Chunk {code}] init_tsn'), + parameters=parameters_value, ) - def _make_param_unrecognized(self, code: 'Enum_Parameter', param: 'Optional[Data_UnrecognizedParameter]' = None, *, - value: 'bytes' = b'', - **kwargs: 'Any') -> 'Schema_UnrecognizedParameter': - """Make SCTP unrecognized parameter parameter. + def _make_chunk_init_ack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_INITACKChunk]' = None, *, + flags: 'bytes' = b'\x00', + init_tag: 'int' = 0, + a_rwnd: 'int' = 1500, # minimum permitted by [RFC 9260] + outbound_streams: 'int' = 1, + inbound_streams: 'int' = 1, + init_tsn: 'int' = 0, + parameters: 'Optional[list[Schema_Parameter | tuple[Enum_Parameter | int, dict[str, Any]] | bytes] | Parameters]' = None, # pylint: disable=line-too-long + **kwargs: 'Any') -> 'Schema_INITACKChunk': + """Make SCTP INIT ACK chunk. Args: - code: parameter type - param: parameter data - value: the unrecognized parameter, complete with its type and length + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + init_tag: initiate tag + a_rwnd: advertised receiver window credit + outbound_streams: number of outbound streams + inbound_streams: number of inbound streams + init_tsn: initial transmission sequence number + parameters: optional and variable-length parameters **kwargs: arbitrary keyword arguments Returns: - Constructed parameter schema. + Constructed chunk schema. + + Raises: + ProtocolError: If ``flags`` is **NOT** exactly one byte, or a field + does not fit its width. """ - if param is not None: - value = param.value + if chunk is not None: + flags = chunk.flags + init_tag = chunk.init_tag + a_rwnd = chunk.a_rwnd + outbound_streams = chunk.outbound_streams + inbound_streams = chunk.inbound_streams + init_tsn = chunk.init_tsn + parameters = chunk.parameters - return Schema_UnrecognizedParameter( + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + + if parameters is not None: + parameters_value = self._make_sctp_parameters(parameters) + else: + parameters_value = [] + length = self._make_nested_length(20, parameters_value, + None if chunk is None else chunk.length) + + return Schema_INITACKChunk( type=code, - length=self._make_uint(len(value) + 4, bits=16, name=f'[Param {code}] length'), - value=value, + flags=flags, + length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), + init_tag=self._make_uint(init_tag, bits=32, name=f'[Chunk {code}] init_tag'), + a_rwnd=self._make_uint(a_rwnd, bits=32, name=f'[Chunk {code}] a_rwnd'), + outbound_streams=self._make_uint(outbound_streams, bits=16, name=f'[Chunk {code}] outbound_streams'), + inbound_streams=self._make_uint(inbound_streams, bits=16, name=f'[Chunk {code}] inbound_streams'), + init_tsn=self._make_uint(init_tsn, bits=32, name=f'[Chunk {code}] init_tsn'), + parameters=parameters_value, ) - def _make_param_preservative(self, code: 'Enum_Parameter', param: 'Optional[Data_CookiePreservativeParameter]' = None, *, - increment: 'int' = 0, - **kwargs: 'Any') -> 'Schema_CookiePreservativeParameter': - """Make SCTP cookie preservative parameter. + def _make_chunk_sack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_SACKChunk]' = None, *, + flags: 'bytes' = b'\x00', + cum_tsn_ack: 'int' = 0, + a_rwnd: 'int' = 1500, # the INIT minimum [RFC 9260]; a SACK may advertise less + gap_blocks: 'Optional[list[Schema_GapAckBlock | Data_GapAckBlock | tuple[int, int]]]' = None, # pylint: disable=line-too-long + dup_tsn: 'Optional[list[int]]' = None, + **kwargs: 'Any') -> 'Schema_SACKChunk': + """Make SCTP SACK chunk. Args: - code: parameter type - param: parameter data - increment: suggested cookie life-span increment, in milliseconds + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + cum_tsn_ack: cumulative TSN ack + a_rwnd: advertised receiver window credit + gap_blocks: gap ack blocks, each as a schema, a data model or a + ``(start, end)`` pair + dup_tsn: duplicate TSNs **kwargs: arbitrary keyword arguments Returns: - Constructed parameter schema. + Constructed chunk schema. Raises: - ProtocolError: If ``increment`` does not fit 32 bits. + ProtocolError: If ``flags`` is **NOT** exactly one byte, or a + field, a gap ack block's offset or a duplicate TSN does not + fit its width. + + Note: + The counts of gap ack blocks and duplicate TSNs are derived from the + supplied lists rather than taken as arguments, so that they cannot + disagree with the lists they count. """ - if param is not None: - increment = param.increment + if chunk is not None: + flags = chunk.flags + cum_tsn_ack = chunk.cum_tsn_ack + a_rwnd = chunk.a_rwnd + gap_blocks = list(chunk.gap_blocks) + dup_tsn = list(chunk.dup_tsn) - return Schema_CookiePreservativeParameter( + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + + blocks = [] # type: list[Schema_GapAckBlock] + for block in gap_blocks or []: + if isinstance(block, Schema_GapAckBlock): + blocks.append(block) + else: + start, end = ((block.start, block.end) if isinstance(block, Data_GapAckBlock) + else cast('tuple[int, int]', block)) + blocks.append(Schema_GapAckBlock( + start=self._make_uint(start, bits=16, name=f'[Chunk {code}] gap block start'), + end=self._make_uint(end, bits=16, name=f'[Chunk {code}] gap block end'), + )) + tsn_list = [self._make_uint(tsn, bits=32, name=f'[Chunk {code}] dup_tsn') for tsn in dup_tsn or []] + + return Schema_SACKChunk( type=code, - length=8, - increment=self._make_uint(increment, bits=32, name=f'[Param {code}] increment'), + flags=flags, + length=self._make_uint(16 + len(blocks) * 4 + len(tsn_list) * 4, bits=16, + name=f'[Chunk {code}] length'), + cum_tsn_ack=self._make_uint(cum_tsn_ack, bits=32, name=f'[Chunk {code}] cum_tsn_ack'), + a_rwnd=self._make_uint(a_rwnd, bits=32, name=f'[Chunk {code}] a_rwnd'), + num_gap_blocks=len(blocks), + num_dup_tsn=len(tsn_list), + gap_blocks=blocks, + dup_tsn=tsn_list, ) - def _make_param_hostname(self, code: 'Enum_Parameter', param: 'Optional[Data_HostNameAddressParameter]' = None, *, - name: 'bytes' = b'\x00', - **kwargs: 'Any') -> 'Schema_HostNameAddressParameter': - """Make SCTP host name address parameter. - - Args: - code: parameter type - param: parameter data - name: host name, including at least one null terminator + def _make_chunk_heartbeat(self, code: 'Enum_Chunk', chunk: 'Optional[Data_HeartbeatChunk]' = None, *, + flags: 'bytes' = b'\x00', + parameters: 'Optional[list[Schema_Parameter | tuple[Enum_Parameter | int, dict[str, Any]] | bytes] | Parameters]' = None, # pylint: disable=line-too-long + **kwargs: 'Any') -> 'Schema_HeartbeatChunk': + """Make SCTP HEARTBEAT chunk. + + Args: + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + parameters: heartbeat information parameters **kwargs: arbitrary keyword arguments Returns: - Constructed parameter schema. + Constructed chunk schema. Raises: - ProtocolError: If a user-supplied ``name`` is not null-terminated, - as required by :rfc:`9260#section-3.3.2.1.4`. - - Note: - A parsed ``param`` carries its name as captured, so a name the - reader accepted without a terminator rebuilds as read - (:issue:`1482`). The terminator check applies to ``name`` only. + ProtocolError: If ``flags`` is **NOT** exactly one byte. """ - if param is not None: - name = param.name - elif not name.endswith(b'\x00'): - raise ProtocolError(f'{self.alias}: [Param {code}] invalid name: ' - f'must be NUL-terminated, not {name!r}') + if chunk is not None: + flags = chunk.flags + parameters = chunk.parameters - return Schema_HostNameAddressParameter( + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') + + if parameters is not None: + parameters_value = self._make_sctp_parameters(parameters) + else: + parameters_value = [] + length = self._make_nested_length(4, parameters_value, + None if chunk is None else chunk.length) + + return Schema_HeartbeatChunk( type=code, - length=self._make_uint(len(name) + 4, bits=16, name=f'[Param {code}] length'), - name=name, + flags=flags, + length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), + parameters=parameters_value, ) - def _make_param_addrtypes(self, code: 'Enum_Parameter', param: 'Optional[Data_SupportedAddressTypesParameter]' = None, *, - types: 'Optional[list[Enum_Parameter | int]]' = None, - **kwargs: 'Any') -> 'Schema_SupportedAddressTypesParameter': - """Make SCTP supported address types parameter. + def _make_chunk_heartbeat_ack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_HeartbeatACKChunk]' = None, *, + flags: 'bytes' = b'\x00', + parameters: 'Optional[list[Schema_Parameter | tuple[Enum_Parameter | int, dict[str, Any]] | bytes] | Parameters]' = None, # pylint: disable=line-too-long + **kwargs: 'Any') -> 'Schema_HeartbeatACKChunk': + """Make SCTP HEARTBEAT ACK chunk. Args: - code: parameter type - param: parameter data - types: supported address types, given as address parameter types, - each a member or an :obj:`int` code + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + parameters: heartbeat information parameters **kwargs: arbitrary keyword arguments Returns: - Constructed parameter schema. + Constructed chunk schema. Raises: - ProtocolError: If an item of ``types`` is not a parameter type, - as :meth:`_make_param_types` checks. + ProtocolError: If ``flags`` is **NOT** exactly one byte. """ - if param is not None: - types = list(param.types) + if chunk is not None: + flags = chunk.flags + parameters = chunk.parameters - types_value = self._make_param_types(types) + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') - return Schema_SupportedAddressTypesParameter( + if parameters is not None: + parameters_value = self._make_sctp_parameters(parameters) + else: + parameters_value = [] + length = self._make_nested_length(4, parameters_value, + None if chunk is None else chunk.length) + + return Schema_HeartbeatACKChunk( type=code, - length=self._make_uint(len(types_value) * 2 + 4, bits=16, name=f'[Param {code}] length'), - types=types_value, + flags=flags, + length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), + parameters=parameters_value, ) - def _read_cause_donone(self, schema: 'Schema_UnknownCause', *, causes: 'Causes') -> 'Data_UnknownCause': # pylint: disable=unused-argument - """Read SCTP error cause of an unsupported cause code. - - This is the fall-through for every error cause code :mod:`pcapkit` does - not implement, e.g. those registered by SCTP extensions. The - cause-specific information is recorded verbatim rather than raising. + def _make_chunk_abort(self, code: 'Enum_Chunk', chunk: 'Optional[Data_AbortChunk]' = None, *, + reserved: 'int' = 0, + T: 'bool' = False, + error: 'Optional[list[Schema_ErrorCause | tuple[Enum_CauseCode | int, dict[str, Any]] | bytes] | Causes]' = None, # pylint: disable=line-too-long + **kwargs: 'Any') -> 'Schema_AbortChunk': + """Make SCTP ABORT chunk. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: chunk type + chunk: chunk data + reserved: reserved flag bits + T: whether the verification tag has been reflected + error: zero or more error causes + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed chunk schema. - """ - return Data_UnknownCause( - code=schema.code, - length=schema.length, - value=schema.value, - ) + Raises: + ProtocolError: If ``reserved`` does not fit 7 bits. - def _read_cause_invalid_stream(self, schema: 'Schema_InvalidStreamIdentifierCause', *, causes: 'Causes') -> 'Data_InvalidStreamIdentifierCause': # pylint: disable=unused-argument - """Read SCTP invalid stream identifier error cause. + """ + if chunk is not None: + reserved = chunk.flags.reserved + T = chunk.flags.T + error = chunk.error - Structure of the cause [:rfc:`9260#section-3.3.10.1`]: + if error is not None: + error_value = self._make_sctp_causes(error) + else: + error_value = [] + length = self._make_nested_length(4, error_value, + None if chunk is None else chunk.length) - .. code-block:: text + return Schema_AbortChunk( + type=code, + flags={ + 'reserved': self._make_uint(reserved, bits=7, name=f'[Chunk {code}] reserved'), + 'T': int(T), + }, + length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), + error=error_value, + ) - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 1 | Cause Length = 8 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Stream Identifier | (Reserved) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_chunk_shutdown(self, code: 'Enum_Chunk', chunk: 'Optional[Data_ShutdownChunk]' = None, *, + flags: 'bytes' = b'\x00', + cum_tsn_ack: 'int' = 0, + **kwargs: 'Any') -> 'Schema_ShutdownChunk': + """Make SCTP SHUTDOWN chunk. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + cum_tsn_ack: cumulative TSN ack + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed chunk schema. Raises: - ProtocolError: If ``length`` is **NOT** ``8``. + ProtocolError: If ``flags`` is **NOT** exactly one byte, or + ``cum_tsn_ack`` does not fit 32 bits. """ - if schema.length != 8: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - - return Data_InvalidStreamIdentifierCause( - code=schema.code, - length=schema.length, - stream_id=schema.stream_id, - reserved=schema.reserved, - ) - - def _read_cause_missing_param(self, schema: 'Schema_MissingMandatoryParameterCause', *, causes: 'Causes') -> 'Data_MissingMandatoryParameterCause': # pylint: disable=unused-argument - """Read SCTP missing mandatory parameter error cause. + if chunk is not None: + flags = chunk.flags + cum_tsn_ack = chunk.cum_tsn_ack - Structure of the cause [:rfc:`9260#section-3.3.10.2`]: + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') - .. code-block:: text + return Schema_ShutdownChunk( + type=code, + flags=flags, + length=8, + cum_tsn_ack=self._make_uint(cum_tsn_ack, bits=32, name=f'[Chunk {code}] cum_tsn_ack'), + ) - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 2 | Cause Length = 8 + N * 2 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Number of missing params = N | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Missing Param Type #1 | Missing Param Type #2 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_chunk_shutdown_ack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_ShutdownACKChunk]' = None, *, + flags: 'bytes' = b'\x00', + **kwargs: 'Any') -> 'Schema_ShutdownACKChunk': + """Make SCTP SHUTDOWN ACK chunk. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed chunk schema. Raises: - ProtocolError: If ``length`` does **NOT** match the declared number - of missing parameters. + ProtocolError: If ``flags`` is **NOT** exactly one byte. """ - if schema.length != 8 + schema.num * 2: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - - return Data_MissingMandatoryParameterCause( - code=schema.code, - length=schema.length, - num=schema.num, - types=tuple(schema.types), - ) - - def _read_cause_stale_cookie(self, schema: 'Schema_StaleCookieCause', *, causes: 'Causes') -> 'Data_StaleCookieCause': # pylint: disable=unused-argument - """Read SCTP stale cookie error cause. + if chunk is not None: + flags = chunk.flags - Structure of the cause [:rfc:`9260#section-3.3.10.3`]: + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') - .. code-block:: text + return Schema_ShutdownACKChunk( + type=code, + flags=flags, + length=4, + ) - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 3 | Cause Length = 8 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Measure of Staleness (usec.) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_chunk_error(self, code: 'Enum_Chunk', chunk: 'Optional[Data_ErrorChunk]' = None, *, + flags: 'bytes' = b'\x00', + error: 'Optional[list[Schema_ErrorCause | tuple[Enum_CauseCode | int, dict[str, Any]] | bytes] | Causes]' = None, # pylint: disable=line-too-long + **kwargs: 'Any') -> 'Schema_ErrorChunk': + """Make SCTP ERROR chunk. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + error: one or more error causes + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed chunk schema. Raises: - ProtocolError: If ``length`` is **NOT** ``8``. + ProtocolError: If ``flags`` is **NOT** exactly one byte. """ - if schema.length != 8: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - - return Data_StaleCookieCause( - code=schema.code, - length=schema.length, - staleness=schema.staleness, - ) + if chunk is not None: + flags = chunk.flags + error = chunk.error - def _read_cause_out_of_resource(self, schema: 'Schema_OutOfResourceCause', *, causes: 'Causes') -> 'Data_OutOfResourceCause': # pylint: disable=unused-argument - """Read SCTP out of resource error cause. + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') - Structure of the cause [:rfc:`9260#section-3.3.10.4`]: + if error is not None: + error_value = self._make_sctp_causes(error) + else: + error_value = [] + length = self._make_nested_length(4, error_value, + None if chunk is None else chunk.length) - .. code-block:: text + return Schema_ErrorChunk( + type=code, + flags=flags, + length=self._make_uint(length, bits=16, name=f'[Chunk {code}] length'), + error=error_value, + ) - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 4 | Cause Length = 4 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_chunk_cookie_echo(self, code: 'Enum_Chunk', chunk: 'Optional[Data_CookieEchoChunk]' = None, *, + flags: 'bytes' = b'\x00', + cookie: 'bytes' = b'', + **kwargs: 'Any') -> 'Schema_CookieEchoChunk': + """Make SCTP COOKIE ECHO chunk. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + cookie: state cookie, as received in the INIT ACK chunk's state + cookie parameter + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed chunk schema. Raises: - ProtocolError: If ``length`` is **NOT** ``4``. + ProtocolError: If ``flags`` is **NOT** exactly one byte. """ - if schema.length != 4: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - - return Data_OutOfResourceCause( - code=schema.code, - length=schema.length, - ) - - def _read_cause_unresolvable_addr(self, schema: 'Schema_UnresolvableAddressCause', *, causes: 'Causes') -> 'Data_UnresolvableAddressCause': # pylint: disable=unused-argument - """Read SCTP unresolvable address error cause. + if chunk is not None: + flags = chunk.flags + cookie = chunk.cookie - Structure of the cause [:rfc:`9260#section-3.3.10.5`]: + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') - .. code-block:: text + return Schema_CookieEchoChunk( + type=code, + flags=flags, + length=self._make_uint(len(cookie) + 4, bits=16, name=f'[Chunk {code}] length'), + cookie=cookie, + ) - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 5 | Cause Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / Unresolvable Address / - \\ \\ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_chunk_cookie_ack(self, code: 'Enum_Chunk', chunk: 'Optional[Data_CookieACKChunk]' = None, *, + flags: 'bytes' = b'\x00', + **kwargs: 'Any') -> 'Schema_CookieACKChunk': + """Make SCTP COOKIE ACK chunk. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: chunk type + chunk: chunk data + flags: raw chunk flags, as a single byte + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed chunk schema. Raises: - ProtocolError: If ``length`` is **NOT** at least ``4``. + ProtocolError: If ``flags`` is **NOT** exactly one byte. """ - if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') - - return Data_UnresolvableAddressCause( - code=schema.code, - length=schema.length, - value=schema.value, - ) - - def _read_cause_unrecognized_chunk(self, schema: 'Schema_UnrecognizedChunkTypeCause', *, causes: 'Causes') -> 'Data_UnrecognizedChunkTypeCause': # pylint: disable=unused-argument - """Read SCTP unrecognized chunk type error cause. + if chunk is not None: + flags = chunk.flags - Structure of the cause [:rfc:`9260#section-3.3.10.6`]: + flags = self._make_octets(flags, size=1, name=f'[Chunk {code}] flags') - .. code-block:: text + return Schema_CookieACKChunk( + type=code, + flags=flags, + length=4, + ) - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 6 | Cause Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / Unrecognized Chunk / - \\ \\ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_chunk_shutdown_complete(self, code: 'Enum_Chunk', chunk: 'Optional[Data_ShutdownCompleteChunk]' = None, *, + reserved: 'int' = 0, + T: 'bool' = False, + **kwargs: 'Any') -> 'Schema_ShutdownCompleteChunk': + """Make SCTP SHUTDOWN COMPLETE chunk. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: chunk type + chunk: chunk data + reserved: reserved flag bits + T: whether the verification tag has been reflected + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed chunk schema. Raises: - ProtocolError: If ``length`` is **NOT** at least ``4``. + ProtocolError: If ``reserved`` does not fit 7 bits. """ - if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') + if chunk is not None: + reserved = chunk.flags.reserved + T = chunk.flags.T - return Data_UnrecognizedChunkTypeCause( - code=schema.code, - length=schema.length, - value=schema.value, + return Schema_ShutdownCompleteChunk( + type=code, + flags={ + 'reserved': self._make_uint(reserved, bits=7, name=f'[Chunk {code}] reserved'), + 'T': int(T), + }, + length=4, ) - def _read_cause_invalid_param(self, schema: 'Schema_InvalidMandatoryParameterCause', *, causes: 'Causes') -> 'Data_InvalidMandatoryParameterCause': # pylint: disable=unused-argument - """Read SCTP invalid mandatory parameter error cause. - - Structure of the cause [:rfc:`9260#section-3.3.10.7`]: - - .. code-block:: text - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 7 | Cause Length = 4 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_param_donone(self, code: 'Enum_Parameter', param: 'Optional[Data_UnknownParameter]' = None, *, + value: 'bytes' = b'', + **kwargs: 'Any') -> 'Schema_UnknownParameter': + """Make SCTP chunk parameter of an unsupported type. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: parameter type + param: parameter data + value: parameter value in :obj:`bytes` + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. - - Raises: - ProtocolError: If ``length`` is **NOT** ``4``. + Constructed parameter schema. """ - if schema.length != 4: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') + if param is not None: + value = param.value - return Data_InvalidMandatoryParameterCause( - code=schema.code, - length=schema.length, + return Schema_UnknownParameter( + type=code, + length=self._make_uint(len(value) + 4, bits=16, name=f'[Param {code}] length'), + value=value, ) - def _read_cause_unrecognized_params(self, schema: 'Schema_UnrecognizedParametersCause', *, causes: 'Causes') -> 'Data_UnrecognizedParametersCause': # pylint: disable=unused-argument - """Read SCTP unrecognized parameters error cause. + def _make_param_hbinfo(self, code: 'Enum_Parameter', param: 'Optional[Data_HeartbeatInfoParameter]' = None, *, + info: 'bytes' = b'', + **kwargs: 'Any') -> 'Schema_HeartbeatInfoParameter': + """Make SCTP heartbeat info parameter. - Structure of the cause [:rfc:`9260#section-3.3.10.8`]: + Args: + code: parameter type + param: parameter data + info: sender-specific heartbeat info + **kwargs: arbitrary keyword arguments - .. code-block:: text + Returns: + Constructed parameter schema. - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 8 | Cause Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / Unrecognized Parameters / - \\ \\ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + """ + if param is not None: + info = param.info - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + return Schema_HeartbeatInfoParameter( + type=code, + length=self._make_uint(len(info) + 4, bits=16, name=f'[Param {code}] length'), + info=info, + ) - Returns: - Parsed error cause data. + def _make_param_ipv4(self, code: 'Enum_Parameter', param: 'Optional[Data_IPv4AddressParameter]' = None, *, + address: 'IPv4Address | int | str | bytes' = '0.0.0.0', # nosec: B104 + **kwargs: 'Any') -> 'Schema_IPv4AddressParameter': + """Make SCTP IPv4 address parameter. - Raises: - ProtocolError: If ``length`` is **NOT** at least ``4``. + Args: + code: parameter type + param: parameter data + address: IPv4 address of the sending endpoint + **kwargs: arbitrary keyword arguments + + Returns: + Constructed parameter schema. """ - if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') + if param is not None: + address = param.address - return Data_UnrecognizedParametersCause( - code=schema.code, - length=schema.length, - value=schema.value, + return Schema_IPv4AddressParameter( + type=code, + length=8, + address=address, ) - def _read_cause_no_user_data(self, schema: 'Schema_NoUserDataCause', *, causes: 'Causes') -> 'Data_NoUserDataCause': # pylint: disable=unused-argument - """Read SCTP no user data error cause. - - Structure of the cause [:rfc:`9260#section-3.3.10.9`]: - - .. code-block:: text - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 9 | Cause Length = 8 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | TSN | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_param_ipv6(self, code: 'Enum_Parameter', param: 'Optional[Data_IPv6AddressParameter]' = None, *, + address: 'IPv6Address | int | str | bytes' = '::', + **kwargs: 'Any') -> 'Schema_IPv6AddressParameter': + """Make SCTP IPv6 address parameter. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: parameter type + param: parameter data + address: IPv6 address of the sending endpoint + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. - - Raises: - ProtocolError: If ``length`` is **NOT** ``8``. + Constructed parameter schema. """ - if schema.length != 8: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') + if param is not None: + address = param.address - return Data_NoUserDataCause( - code=schema.code, - length=schema.length, - tsn=schema.tsn, + return Schema_IPv6AddressParameter( + type=code, + length=20, + address=address, ) - def _read_cause_cookie_shutdown(self, schema: 'Schema_CookieReceivedWhileShuttingDownCause', *, causes: 'Causes') -> 'Data_CookieReceivedWhileShuttingDownCause': # pylint: disable=unused-argument - """Read SCTP cookie received while shutting down error cause. - - Structure of the cause [:rfc:`9260#section-3.3.10.10`]: - - .. code-block:: text - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 10 | Cause Length = 4 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_param_cookie(self, code: 'Enum_Parameter', param: 'Optional[Data_StateCookieParameter]' = None, *, + cookie: 'bytes' = b'', + **kwargs: 'Any') -> 'Schema_StateCookieParameter': + """Make SCTP state cookie parameter. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: parameter type + param: parameter data + cookie: state cookie + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. - - Raises: - ProtocolError: If ``length`` is **NOT** ``4``. + Constructed parameter schema. """ - if schema.length != 4: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') + if param is not None: + cookie = param.cookie - return Data_CookieReceivedWhileShuttingDownCause( - code=schema.code, - length=schema.length, + return Schema_StateCookieParameter( + type=code, + length=self._make_uint(len(cookie) + 4, bits=16, name=f'[Param {code}] length'), + cookie=cookie, ) - def _read_cause_restart_addr(self, schema: 'Schema_RestartOfAnAssociationWithNewAddressesCause', *, causes: 'Causes') -> 'Data_RestartOfAnAssociationWithNewAddressesCause': # pylint: disable=unused-argument - """Read SCTP restart of an association with new addresses error cause. + def _make_param_unrecognized(self, code: 'Enum_Parameter', param: 'Optional[Data_UnrecognizedParameter]' = None, *, + value: 'bytes' = b'', + **kwargs: 'Any') -> 'Schema_UnrecognizedParameter': + """Make SCTP unrecognized parameter parameter. - Structure of the cause [:rfc:`9260#section-3.3.10.11`]: + Args: + code: parameter type + param: parameter data + value: the unrecognized parameter, complete with its type and length + **kwargs: arbitrary keyword arguments - .. code-block:: text + Returns: + Constructed parameter schema. - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 11 | Cause Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / New Address TLVs / - \\ \\ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + """ + if param is not None: + value = param.value - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + return Schema_UnrecognizedParameter( + type=code, + length=self._make_uint(len(value) + 4, bits=16, name=f'[Param {code}] length'), + value=value, + ) + + def _make_param_preservative(self, code: 'Enum_Parameter', param: 'Optional[Data_CookiePreservativeParameter]' = None, *, + increment: 'int' = 0, + **kwargs: 'Any') -> 'Schema_CookiePreservativeParameter': + """Make SCTP cookie preservative parameter. + + Args: + code: parameter type + param: parameter data + increment: suggested cookie life-span increment, in milliseconds + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed parameter schema. Raises: - ProtocolError: If ``length`` is **NOT** at least ``4``. + ProtocolError: If ``increment`` does not fit 32 bits. """ - if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') + if param is not None: + increment = param.increment - return Data_RestartOfAnAssociationWithNewAddressesCause( - code=schema.code, - length=schema.length, - value=schema.value, + return Schema_CookiePreservativeParameter( + type=code, + length=8, + increment=self._make_uint(increment, bits=32, name=f'[Param {code}] increment'), ) - def _read_cause_user_abort(self, schema: 'Schema_UserInitiatedAbortCause', *, causes: 'Causes') -> 'Data_UserInitiatedAbortCause': # pylint: disable=unused-argument - """Read SCTP user-initiated abort error cause. - - Structure of the cause [:rfc:`9260#section-3.3.10.12`]: - - .. code-block:: text - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 12 | Cause Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / Upper Layer Abort Reason / - \\ \\ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_param_hostname(self, code: 'Enum_Parameter', param: 'Optional[Data_HostNameAddressParameter]' = None, *, + name: 'bytes' = b'\x00', + **kwargs: 'Any') -> 'Schema_HostNameAddressParameter': + """Make SCTP host name address parameter. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: parameter type + param: parameter data + name: host name, including at least one null terminator + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed parameter schema. Raises: - ProtocolError: If ``length`` is **NOT** at least ``4``. + ProtocolError: If a user-supplied ``name`` is not null-terminated, + as required by :rfc:`9260#section-3.3.2.1.4`. + + Note: + A parsed ``param`` carries its name as captured, so a name the + reader accepted without a terminator rebuilds as read + (:issue:`1482`). The terminator check applies to ``name`` only. """ - if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') + if param is not None: + name = param.name + elif not name.endswith(b'\x00'): + raise ProtocolError(f'{self.alias}: [Param {code}] invalid name: ' + f'must be NUL-terminated, not {name!r}') - return Data_UserInitiatedAbortCause( - code=schema.code, - length=schema.length, - info=schema.info, + return Schema_HostNameAddressParameter( + type=code, + length=self._make_uint(len(name) + 4, bits=16, name=f'[Param {code}] length'), + name=name, ) - def _read_cause_protocol_violation(self, schema: 'Schema_ProtocolViolationCause', *, causes: 'Causes') -> 'Data_ProtocolViolationCause': # pylint: disable=unused-argument - """Read SCTP protocol violation error cause. - - Structure of the cause [:rfc:`9260#section-3.3.10.13`]: - - .. code-block:: text - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cause Code = 13 | Cause Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - / Additional Information / - \\ \\ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + def _make_param_addrtypes(self, code: 'Enum_Parameter', param: 'Optional[Data_SupportedAddressTypesParameter]' = None, *, + types: 'Optional[list[Enum_Parameter | int]]' = None, + **kwargs: 'Any') -> 'Schema_SupportedAddressTypesParameter': + """Make SCTP supported address types parameter. - Arguments: - schema: parsed error cause schema - causes: extracted SCTP error causes + Args: + code: parameter type + param: parameter data + types: supported address types, given as address parameter types, + each a member or an :obj:`int` code + **kwargs: arbitrary keyword arguments Returns: - Parsed error cause data. + Constructed parameter schema. Raises: - ProtocolError: If ``length`` is **NOT** at least ``4``. + ProtocolError: If an item of ``types`` is not a parameter type, + as :meth:`_make_param_types` checks. """ - if schema.length < 4: - raise ProtocolError(f'{self.alias}: [Cause {schema.code}] invalid format') + if param is not None: + types = list(param.types) - return Data_ProtocolViolationCause( - code=schema.code, - length=schema.length, - info=schema.info, + types_value = self._make_param_types(types) + + return Schema_SupportedAddressTypesParameter( + type=code, + length=self._make_uint(len(types_value) * 2 + 4, bits=16, name=f'[Param {code}] length'), + types=types_value, ) def _make_cause_donone(self, code: 'Enum_CauseCode', cause: 'Optional[Data_UnknownCause]' = None, *, diff --git a/tests/foundation/registry/test_registry_name_restore_unit.py b/tests/foundation/registry/test_registry_name_restore_unit.py index 53c2419c93..d22f91f0cf 100644 --- a/tests/foundation/registry/test_registry_name_restore_unit.py +++ b/tests/foundation/registry/test_registry_name_restore_unit.py @@ -12,7 +12,8 @@ :mod:`tests.foundation.registry.test_registry_symmetry_unit` covers the plain round trip for every registrar. These pin the cases it does not reach: a displaced built-in name, a class still held by a second code, a name that was -there before the registration, which a restore must not take away, and a +there before the registration, which a restore must not take away, a restore +registering the displaced class through ``register_protocol`` (#2773), and a built-in retaking its name from a custom class shadowing it -- plus a seeded fuzz over all of those, checked after every call. @@ -124,6 +125,53 @@ def test_restoring_a_code_puts_back_the_name_it_displaced(self) -> None: self.assertIs(self._shown(self.names['IPV4']), IPv4) self.assert_names_as_found() + def test_a_restore_registers_the_displaced_class_by_name(self) -> None: + """The name goes back through ``register_protocol``, as a code's entry does (#2773).""" + from unittest import mock + + from pcapkit.foundation.registry import register_tcp + from pcapkit.protocols.internet.ipv4.protocol import IPv4 + from pcapkit.protocols.protocol import ProtocolBase + from pcapkit.protocols.transport.tcp.protocol import TCP + + shipped = TCP.__proto__[80] + self._call(register_tcp, 80, self._custom('IPv4')) + with mock.patch.object(ProtocolBase, 'register_protocol', + wraps=ProtocolBase.register_protocol) as spy: + self._call(register_tcp, 80, shipped) + self.assertIn(mock.call(IPv4), spy.call_args_list) + self.assert_names_as_found() + + def test_a_name_restore_warns_as_the_code_restore_does(self) -> None: + """Each displaced entry put back is reported, the name's as the code's is (#2773). + + Restoring port 80 over a custom ``IPv4`` puts back both the shipped port + entry and the built-in's name, and each displaces the custom class, so + each warns. Over a new name, only the port warns: removing the name + displaces nothing. + + """ + from pcapkit.foundation.registry import register_tcp + from pcapkit.protocols.internet.ipv4.protocol import IPv4 + from pcapkit.protocols.transport.tcp.protocol import TCP + from pcapkit.utilities.warnings import RegistryWarning + + shipped = TCP.__proto__[80] + for name, expected in (('IPv4', ['port 80', 'protocol IPV4']), ('Custom', ['port 80'])): + with self.subTest(name=name): + self._reset() + custom = self._custom(name) + self._call(register_tcp, 80, custom) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + register_tcp(80, shipped) + messages = [str(item.message) for item in caught if issubclass(item.category, RegistryWarning)] + self.assertEqual([message.split(' already registered')[0] for message in messages], expected) + self.assertTrue(all(f'overwriting {custom!r} with ' in message for message in messages), messages) + if name == 'IPv4': + self.assertTrue(messages[1].endswith(f'with {IPv4!r}'), messages[1]) + self.assert_names_as_found() + def test_a_name_stays_while_another_code_still_holds_it(self) -> None: """The name goes with the last code dispatching to the class, not the first.""" from pcapkit.foundation.registry import register_tcp, register_udp diff --git a/tests/project/test_protocol_member_order_2490_unit.py b/tests/project/test_protocol_member_order_2490_unit.py index 2bb58846e9..49cd139f9c 100644 --- a/tests/project/test_protocol_member_order_2490_unit.py +++ b/tests/project/test_protocol_member_order_2490_unit.py @@ -15,19 +15,23 @@ class under ``pcapkit/protocols``, less the ``NotImplemented`` placeholders, registry fallback, the default of a ``getattr()`` or ``.get()`` there, is a specific reader rather than a helper (#2490); * ``specific`` -- rule c: the readers and makers of each method registry follow the - helpers, its readers before its makers, each set opening with the registry's - default and then in the registry's order; + helpers, every registry's readers before any registry's makers (#2772), each set + opening with the registry's default and then in the registry's order; * ``unreached`` -- subgroup d: every utility that ``read()``, ``make()`` and the registries do not reach comes after all those they do; * ``registry`` -- a registry map's entries are in the order of their keys' values: - an enum member's value, or an :class:`int` key's own. + an enum member's value, an :class:`int` key's own, a PCAP-NG option key's + namespace and then its value, or a :obj:`str` key alphabetically within the group + a comment line opens (#2772, #2773); +* ``layer`` -- each entry of ``ProtocolBase.__registry__`` is under the comment of + its module's layer, the layers in the order of :data:`LAYERS` (#2773). A method registry is a class-level dunder map, other than ``__proto__``, whose :obj:`str` values name the ``_read_*`` and ``_make_*`` methods an f-string in the class builds. Ordered by value, a registry also orders rule c by enum value, as the -page asks. A key the audit cannot value, such as the call that builds each key of -``PCAPNG.__option__``, leaves its map out of ``registry``, and rule c then follows -the registry's own order, as the page asks where there is no enum. +page asks. ``registry`` checks ``__proto__``, every method registry and every map +keyed by :obj:`str` alone; a key the audit cannot value leaves its map out, and +rule c then follows the registry's own order. A class that breaks a rule is an entry in :data:`KNOWN_EXCEPTIONS`, with the rules it breaks and the issue or PR whose reorder removes it. The table is checked in @@ -52,7 +56,7 @@ class no longer breaks, so a reorder deletes its entry in the same change. if TYPE_CHECKING: import pathlib - from typing import Any, Callable, Iterator, Optional + from typing import Any, Callable, Iterator, Optional, Sequence #: Resolves a name in a scanned module to the object it imports, else :data:`None`. Lookup = Callable[[str], Optional[Any]] @@ -79,9 +83,23 @@ class no longer breaks, so a reorder deletes its entry in the same change. 'data': '_make_data() is the first utility', 'pair': 'each _read_xxx() comes before its _make_xxx()', 'helpers': 'the helpers read() and make() name, in first-use order, each reader then its maker', - 'specific': "after the helpers, each registry's readers then its makers, default first, in registry order", + 'specific': "after the helpers, every registry's readers then every registry's makers, default first, " + 'in registry order', 'unreached': 'the utilities read(), make() and the registries do not reach come last', - 'registry': "a registry map's entries in the order of their keys' enum or int values", + 'registry': "a registry map's entries in the order of their keys: enum or int values, namespace then " + 'value, or str keys alphabetically within each commented group', + 'layer': "each __registry__ entry under its layer's comment, the layers in order", +} + +#: The comment each entry of ``ProtocolBase.__registry__`` sits under, by the +#: subpackage of :mod:`pcapkit.protocols` its module is in, in the order of the +#: groups (#2773). +LAYERS = { + 'link': 'Link Layer', + 'internet': 'Internet Layer', + 'transport': 'Transport Layer', + 'application': 'Application Layer', + 'misc': 'Miscellaneous', } #: Classes that break the order, as ``qualified name: (rules, remover)``: the @@ -286,22 +304,92 @@ def specific(cls: 'ast.ClassDef', utilities: 'list[str]') -> 'list[tuple[str, li return sets -def _key_value(key: 'Optional[ast.expr]', lookup: 'Optional[Lookup]') -> 'Optional[int]': - """The value a registry key orders by: an :class:`int` literal, or an enum member's value. +def _member(node: 'ast.expr', lookup: 'Optional[Lookup]') -> 'Optional[enum.Enum]': + """The enum member ``node`` names as ``Enum.MEMBER``, else :data:`None`. + + Raises: + LookupError: If ``Enum`` is not imported from :mod:`pcapkit.const`. + TypeError: If ``Enum.MEMBER`` is not an enum member. - :data:`None` for a key of any other shape, which no rule on the page orders. + """ + if not (isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name)) or lookup is None: + return None + owner = lookup(node.value.id) + if owner is None: + raise LookupError(f'{node.value.id} is not imported from pcapkit.const') + member = getattr(owner, node.attr) + if not isinstance(member, enum.Enum): + raise TypeError(f'{node.value.id}.{node.attr} is not an enum member') + return member + + +def _key_value(key: 'Optional[ast.expr]', lookup: 'Optional[Lookup]') -> 'Optional[Any]': + """The value a registry key orders by. + + An :class:`int` literal is its own value, an enum member its member's value and + a :obj:`str` literal the string itself. A call on a single PCAP-NG option type, + as ``_option_key()`` builds each key of ``PCAPNG.__option__``, is valued by the + place of its namespace in :class:`~pcapkit.const.pcapng.option_type.OptionType` + and then by its value (#2772). :data:`None` for a key of any other shape, which + no rule on the page orders. """ - if isinstance(key, ast.Constant) and isinstance(key.value, int) and not isinstance(key.value, bool): + if isinstance(key, ast.Constant) and isinstance(key.value, (int, str)) and not isinstance(key.value, bool): return key.value - if isinstance(key, ast.Attribute) and isinstance(key.value, ast.Name) and lookup is not None: - owner = lookup(key.value.id) - if owner is None: - raise LookupError(f'{key.value.id} is not imported from pcapkit.const') - member = getattr(owner, key.attr) - if not isinstance(member, enum.Enum): - raise TypeError(f'{key.value.id}.{key.attr} is not an enum member') - return int(member) + if isinstance(key, ast.Call): + if len(key.args) != 1 or key.keywords: + return None + try: + member = _member(key.args[0], lookup) + except (LookupError, TypeError): + return None + if member is None or not hasattr(member, 'opt_name'): + return None + spaces = list(dict.fromkeys(item.opt_name.split('_', 1)[0] for item in type(member))) + return spaces.index(member.opt_name.split('_', 1)[0]), member.opt_value + member = _member(key, lookup) if key is not None else None + return None if member is None else int(member) + + +def _groups(node: 'ast.Dict', lines: 'Optional[Sequence[str]]') -> 'list[Optional[str]]': + """The text of the comment line each entry of ``node`` sits under, else :data:`None`.""" + comments = {} # type: dict[int, str] + if lines is not None: + for number in range(node.lineno, (node.end_lineno or node.lineno) + 1): + text = lines[number - 1].strip() + if text.startswith('#'): + comments[number] = text[1:].strip() + found = [] # type: list[Optional[str]] + for key in node.keys: + above = [number for number in comments if key is not None and number < key.lineno] + found.append(comments[max(above)] if above else None) + return found + + +def _layer(value: 'ast.expr') -> 'Optional[str]': + """The :data:`LAYERS` comment a ``ModuleDescriptor('pcapkit.protocols....', ...)`` entry is under.""" + if isinstance(value, ast.Call) and getattr(value.func, 'id', None) == 'ModuleDescriptor' and value.args \ + and isinstance(value.args[0], ast.Constant) and isinstance(value.args[0].value, str): + parts = value.args[0].value.split('.') + if parts[:2] == ['pcapkit', 'protocols'] and len(parts) > 2: + return LAYERS.get(parts[2]) + return None + + +def _layer_finding(name: 'str', node: 'ast.Dict', groups: 'list[Optional[str]]') -> 'Optional[str]': + """What breaks the ``layer`` rule in the ``__registry__`` map ``node``, else :data:`None`.""" + titles = list(LAYERS.values()) + rank = 0 + for key, value, group in zip(node.keys, node.values, groups): + expected = _layer(value) + text = _key_text(key) if key is not None else '**' + if expected is None: + return f'{name}: {text} names no module of a pcapkit.protocols layer' + if group != expected: + return f'{name}: {text} under {group!r}, not {expected!r}' + if titles.index(group) < rank: + return f'{name}: {group!r} after {titles[rank]!r}' + rank = titles.index(group) return None @@ -321,11 +409,14 @@ def lookup(name: 'str') -> 'Optional[Any]': return lookup -def audit(cls: 'ast.ClassDef', lookup: 'Optional[Lookup]' = None) -> 'dict[str, str]': +def audit(cls: 'ast.ClassDef', lookup: 'Optional[Lookup]' = None, + lines: 'Optional[Sequence[str]]' = None) -> 'dict[str, str]': """The rules ``cls`` breaks, as ``kind: detail``; empty when it keeps the order. ``lookup`` resolves the names a registry key takes its enum from; without it, - only :class:`int` keys are valued. + only :class:`int` and :obj:`str` keys are valued. ``lines`` is the source + ``cls`` was parsed from, whose comment lines group a :obj:`str`-keyed map; + without it, such a map is one group. """ order = members(cls) @@ -371,6 +462,13 @@ def audit(cls: 'ast.ClassDef', lookup: 'Optional[Lookup]' = None) -> 'dict[str, for (label, readers), (_, makers) in zip(sets[::2], sets[1::2]): if readers and makers and position[readers[-1]] > position[makers[0]]: findings.setdefault('specific', f'{label}: {makers[0]} before {readers[-1]}') + readers = [name for _, names in sets[::2] for name in names] + makers = [name for _, names in sets[1::2] for name in names] + if readers and makers: + reader = max(readers, key=position.__getitem__) + maker = min(makers, key=position.__getitem__) + if position[reader] > position[maker]: + findings.setdefault('specific', f'{maker} before {reader}: every reader comes before every maker') helper = [name for name in expected if name not in registry] first = min((position[name] for name in registry), default=None) if helper and first is not None and position[helper[-1]] > first: @@ -382,21 +480,33 @@ def audit(cls: 'ast.ClassDef', lookup: 'Optional[Lookup]' = None) -> 'dict[str, if early: findings['unreached'] = f'{early[0]} before {utilities[last]}' + checked = {'__proto__', *(label.split()[0] for label, _ in sets)} for name, node, _ in maps(cls): - if name != '__proto__' and name not in {label.split()[0] for label, _ in sets}: - continue keys = [key for key in node.keys if key is not None] + named = bool(keys) and all(isinstance(key, ast.Constant) and isinstance(key.value, str) for key in keys) + if (name not in checked and not named) or len(keys) != len(node.keys): + continue values = [_key_value(key, lookup) for key in keys] - if len(keys) == len(node.keys) and None not in values and values != sorted(values): - out = next(index for index in range(1, len(values)) if values[index] < values[index - 1]) + if None in values or len({type(value) for value in values}) > 1: + continue + groups = _groups(node, lines) if named else [None] * len(keys) + out = next((index for index in range(1, len(values)) + if groups[index] == groups[index - 1] and values[index] < values[index - 1]), None) + if out is not None: findings.setdefault('registry', f'{name}: {_key_text(keys[out])} after {_key_text(keys[out - 1])}') + if named and name == '__registry__': + detail = _layer_finding(name, node, groups) + if detail is not None: + findings.setdefault('layer', detail) return findings def _key_text(key: 'ast.expr') -> 'str': - """A valued registry key as written: ``Enum.MEMBER``, or the :class:`int` literal.""" + """A valued registry key as written: ``Enum.MEMBER``, ``call(Enum.MEMBER)``, or the literal.""" if isinstance(key, ast.Attribute) and isinstance(key.value, ast.Name): return f'{key.value.id}.{key.attr}' + if isinstance(key, ast.Call) and isinstance(key.func, ast.Name) and len(key.args) == 1: + return f'{key.func.id}({_key_text(key.args[0])})' return repr(getattr(key, 'value', key)) @@ -425,28 +535,44 @@ def visit(node: 'ast.AST', prefix: 'str') -> 'Iterator[tuple[str, ast.ClassDef]] def audit_tree(root: 'pathlib.Path' = ROOT) -> 'dict[str, dict[str, str]]': """Every class under ``root``'s protocol package, mapped to what :func:`audit` finds.""" - return {name: audit(node, module_lookup(path)) - for path in protocol_modules(root / 'pcapkit' / 'protocols') - for name, node in classes(path, root)} + found = {} # type: dict[str, dict[str, str]] + for path in protocol_modules(root / 'pcapkit' / 'protocols'): + lookup, lines = module_lookup(path), path.read_text(encoding='utf-8').splitlines() + found.update((name, audit(node, lookup, lines)) for name, node in classes(path, root)) + return found class TestAudit(unittest.TestCase): """The audit finds each rule broken in a planted class body, and nothing in a sound one.""" - #: A class body in the order: ``_make_data()``, a helper pair, a registry - #: fallback, a method registry's readers and makers, and a helper nothing reaches. + #: A class body in the order: a by-name registry grouped by layer, ``_make_data()``, + #: a helper pair, a registry fallback, the readers of two method registries and + #: then their makers, and a helper nothing reaches. SOUND = ''' class Sound: + __registry__ = { + # Link Layer + 'ARP': ModuleDescriptor('pcapkit.protocols.link.arp.protocol', 'ARP'), + 'ETHERNET': ModuleDescriptor('pcapkit.protocols.link.ethernet.protocol', 'Ethernet'), + # Transport Layer + 'TCP': ModuleDescriptor('pcapkit.protocols.transport.tcp.protocol', 'TCP'), + # Miscellaneous + 'RAW': ModuleDescriptor('pcapkit.protocols.misc.raw.protocol', 'Raw'), + } __option__ = collections.defaultdict(lambda: 'unknown', { E.ALPHA: 'alpha', E.BETA: 'beta', }) + __type__ = collections.defaultdict(lambda: 'none', { + 1: 'echo', + }) @property def name(self): ... def read(self): self._read_addr() getattr(self, 'x', self._read_unknown) getattr(self, f'_read_opt_{name}') + getattr(self, f'_read_type_{name}') def make(self): self._make_addr() def __index__(self): ... @@ -459,12 +585,24 @@ def _make_unknown(self): ... def _read_opt_unknown(self): ... def _read_opt_alpha(self): ... def _read_opt_beta(self): ... + def _read_type_none(self): ... + def _read_type_echo(self): ... def _make_opt_unknown(self): ... def _make_opt_alpha(self): ... def _make_opt_beta(self): ... + def _make_type_none(self): ... + def _make_type_echo(self): ... def _decode_text(self): ... ''' + #: The by-name registry entries of :attr:`SOUND`, by name. + ENTRY = { + 'ARP': " 'ARP': ModuleDescriptor('pcapkit.protocols.link.arp.protocol', 'ARP'),\n", + 'ETHERNET': " 'ETHERNET': ModuleDescriptor('pcapkit.protocols.link.ethernet.protocol', 'Ethernet'),\n", + 'TCP': " 'TCP': ModuleDescriptor('pcapkit.protocols.transport.tcp.protocol', 'TCP'),\n", + 'RAW': " 'RAW': ModuleDescriptor('pcapkit.protocols.misc.raw.protocol', 'Raw'),\n", + } + class Code(enum.IntEnum): """The enum :attr:`SOUND`'s registry is keyed by.""" @@ -474,13 +612,14 @@ class Code(enum.IntEnum): def check(self, source: 'str') -> 'dict[str, str]': node = ast.parse(source).body[0] assert isinstance(node, ast.ClassDef) - return audit(node, {'E': self.Code}.get) + return audit(node, {'E': self.Code}.get, source.splitlines()) def test_a_sound_class_has_no_finding(self) -> 'None': self.assertEqual(self.check(self.SOUND), {}) def test_each_rule_is_found(self) -> 'None': readers = ' def _read_opt_alpha(self): ...\n def _read_opt_beta(self): ...\n' + transport = ' # Transport Layer\n' + self.ENTRY['TCP'] planted = { 'group': self.SOUND.replace(' def __index__(self): ...\n', '') + ' def __index__(self): ...\n', 'data': self.SOUND.replace(' @classmethod\n def _make_data(cls, data): ...\n', '') @@ -492,17 +631,45 @@ def test_each_rule_is_found(self) -> 'None': ' def _read_addr(self): ...\n', ' def _decode_text(self): ...\n def _read_addr(self): ...\n'), 'registry': self.SOUND.replace(" E.ALPHA: 'alpha',\n E.BETA: 'beta',\n", " E.BETA: 'alpha',\n E.ALPHA: 'beta',\n"), + # TCP moves into the Link group, where it still sorts last. + 'layer': self.SOUND.replace(transport, self.ENTRY['TCP'] + ' # Transport Layer\n'), } self.assertEqual(sorted(planted), sorted(RULES)) for kind, source in planted.items(): with self.subTest(rule=kind): self.assertNotEqual(source, self.SOUND) self.assertIn(kind, self.check(source)) - # Each of the checks for rule c, subgroup d and the registry order fires alone. - for kind in ('specific', 'unreached', 'registry'): + # Each of the checks for rule c, subgroup d, the registry order and the layers fires alone. + for kind in ('specific', 'unreached', 'registry', 'layer'): with self.subTest(alone=kind): self.assertEqual(sorted(self.check(planted[kind])), [kind]) + def test_every_reader_comes_before_every_maker(self) -> 'None': + # Each registry keeps its readers before its makers, but __type__'s readers + # follow __option__'s makers (#2772). + types = ' def _read_type_none(self): ...\n def _read_type_echo(self): ...\n' + source = self.SOUND.replace(types, '').replace(' def _make_opt_beta(self): ...\n', + ' def _make_opt_beta(self): ...\n' + types) + self.assertNotEqual(source, self.SOUND) + found = self.check(source) + self.assertEqual(sorted(found), ['specific']) + self.assertIn('every reader comes before every maker', found['specific']) + + def test_a_name_key_is_ordered_within_its_group(self) -> 'None': + swapped = self.SOUND.replace(self.ENTRY['ARP'] + self.ENTRY['ETHERNET'], + self.ENTRY['ETHERNET'] + self.ENTRY['ARP']) + self.assertNotEqual(swapped, self.SOUND) + self.assertEqual(self.check(swapped), {'registry': "__registry__: 'ARP' after 'ETHERNET'"}) + # Across groups, a name may sort before the one above it: RAW follows TCP. + self.assertLess(self.SOUND.index(self.ENTRY['TCP']), self.SOUND.index(self.ENTRY['RAW'])) + + def test_the_layers_keep_their_order(self) -> 'None': + transport = ' # Transport Layer\n' + self.ENTRY['TCP'] + misc = ' # Miscellaneous\n' + self.ENTRY['RAW'] + swapped = self.SOUND.replace(transport + misc, misc + transport) + self.assertNotEqual(swapped, self.SOUND) + self.assertEqual(self.check(swapped), {'layer': "__registry__: 'Transport Layer' after 'Miscellaneous'"}) + def test_the_specific_sets(self) -> 'None': node = ast.parse(self.SOUND).body[0] assert isinstance(node, ast.ClassDef) @@ -510,6 +677,8 @@ def test_the_specific_sets(self) -> 'None': self.assertEqual(specific(node, utilities), [ ('__option__ _read_opt_*', ['_read_opt_unknown', '_read_opt_alpha', '_read_opt_beta']), ('__option__ _make_opt_*', ['_make_opt_unknown', '_make_opt_alpha', '_make_opt_beta']), + ('__type__ _read_type_*', ['_read_type_none', '_read_type_echo']), + ('__type__ _make_type_*', ['_make_type_none', '_make_type_echo']), ]) def test_a_computed_key_is_not_valued(self) -> 'None': @@ -517,6 +686,21 @@ def test_a_computed_key_is_not_valued(self) -> 'None': " key(E.BETA): 'alpha',\n key(E.ALPHA): 'beta',\n") self.assertEqual(self.check(source), {}) + def test_an_option_key_orders_by_namespace_then_value(self) -> 'None': + from pcapkit.const.pcapng.option_type import OptionType + + def value(name: 'str') -> 'Any': + return _key_value(ast.parse(f'key(O.{name})', mode='eval').body, {'O': OptionType}.get) + + # Namespaces in the order OptionType declares them, whatever the values. + names = ['opt_endofopt', 'opt_custom_19373', 'if_name', 'if_rxspeed', 'epb_flags', 'ns_dnsname', + 'isb_starttime', 'pack_flags'] + values = [value(name) for name in names] + self.assertNotIn(None, values) + self.assertEqual(values, sorted(values)) + self.assertLess(value('if_name'), value('if_description')) + self.assertLess(value('opt_custom_19373'), value('if_name')) + def test_a_fallback_is_not_a_helper(self) -> 'None': node = ast.parse(self.SOUND).body[0] assert isinstance(node, ast.ClassDef) @@ -562,11 +746,15 @@ def test_the_reference_is_in_order(self) -> 'None': self.assertEqual(utilities, ['_make_data', *pairs]) def test_the_reference_registries_are_checked(self) -> 'None': - # Every key of these maps is valued, so the registry rule orders them. + # Every key of these maps is valued, so the registry rule orders them: by + # enum or int value, PCAPNG's option keys by namespace then value, and the + # PCAP-NG option schemas' namespaces alphabetically (#2772). cases = { ('internet/internet.py', 'Internet'): {'__proto__'}, ('internet/ipv4/protocol.py', 'IPv4'): {'__option__'}, ('link/link.py', 'Link'): {'__proto__'}, + ('misc/pcapng/protocol.py', 'PCAPNG'): {'__option__'}, + ('misc/pcapng/schema.py', 'Option'): {'__enum__'}, ('transport/tcp/protocol.py', 'TCP'): {'__proto__', '__option__', '__mp_option__'}, ('transport/udp/protocol.py', 'UDP'): {'__proto__'}, } @@ -581,6 +769,53 @@ def test_the_reference_registries_are_checked(self) -> 'None': self.assertNotIn(None, found[label], label) self.assertEqual(found[label], sorted(found[label]), label) + @staticmethod + def audit_moved(rel: 'str', name: 'str', line: 'str', before: 'str') -> 'dict[str, str]': + """What :func:`audit` finds in class ``name`` of module ``rel`` once ``line`` moves above ``before``.""" + path = PROTOCOLS / rel + text = path.read_text(encoding='utf-8') + assert text.count(line) == 1 and text.count(before) == 1, (line, before) + text = text.replace(line, '').replace(before, line + before) + node = next(node for node in ast.parse(text).body if isinstance(node, ast.ClassDef) and node.name == name) + return audit(node, module_lookup(path), text.splitlines()) + + def test_the_by_name_registry_is_grouped_by_layer(self) -> 'None': + node = dict(classes(PROTOCOLS / 'protocol.py'))['pcapkit.protocols.protocol.ProtocolBase'] + entries = next(entries for label, entries, _ in maps(node) if label == '__registry__') + lines = (PROTOCOLS / 'protocol.py').read_text(encoding='utf-8').splitlines() + self.assertEqual(list(dict.fromkeys(_groups(entries, lines))), list(LAYERS.values())) + self.assertEqual(self.findings['pcapkit.protocols.protocol.ProtocolBase'], {}) + + def entry(key: 'str', module: 'str', name: 'str') -> 'str': + return f" '{key}': ModuleDescriptor('pcapkit.protocols.{module}.protocol', '{name}'),\n" + + arp, c_tag = entry('ARP', 'link.arp', 'ARP'), entry('C_TAG', 'link.c_tag', 'C_Tag') + ah = entry('AH', 'internet.ah', 'AH') + # A misordered entry inside a group fails. + self.assertEqual(self.audit_moved('protocol.py', 'ProtocolBase', c_tag, arp), + {'registry': "__registry__: 'ARP' after 'C_TAG'"}) + # So does an entry in another layer's group, though it sorts first there. + self.assertEqual(self.audit_moved('protocol.py', 'ProtocolBase', ah, arp), + {'layer': "__registry__: 'AH' under 'Link Layer', not 'Internet Layer'"}) + + def test_the_pcapng_option_maps_are_checked(self) -> 'None': + def option(name: 'str', meth: 'str') -> 'str': + return f" _option_key(Enum_OptionType.{name}): '{meth}',\n" + + # PCAPNG.__option__: a namespace in OptionType's order, then each value. + rel = 'misc/pcapng/protocol.py' + endofopt, if_name = option('opt_endofopt', 'endofopt'), option('if_name', 'if_name') + found = self.audit_moved(rel, 'PCAPNG', if_name, endofopt) + self.assertEqual(found.get('registry'), '__option__: _option_key(Enum_OptionType.opt_endofopt) ' + 'after _option_key(Enum_OptionType.if_name)') + found = self.audit_moved(rel, 'PCAPNG', option('if_description', 'if_description'), if_name) + self.assertEqual(found.get('registry'), '__option__: _option_key(Enum_OptionType.if_name) ' + 'after _option_key(Enum_OptionType.if_description)') + # Option.__enum__: its namespaces alphabetically. + space = " '{}': collections.defaultdict(lambda: UnknownOption),\n" + self.assertEqual(self.audit_moved('misc/pcapng/schema.py', 'Option', space.format('opt'), space.format('dsb')), + {'registry': "__enum__: 'dsb' after 'opt'"}) + def test_the_page_examples(self) -> 'None': # Rule c: _read_opt_unassigned() opens the readers of IPv4. path = PROTOCOLS / 'internet' / 'ipv4' / 'protocol.py' diff --git a/tests/protocols/misc/test_pcapng_unit.py b/tests/protocols/misc/test_pcapng_unit.py index b43b8ee009..9175ef6096 100644 --- a/tests/protocols/misc/test_pcapng_unit.py +++ b/tests/protocols/misc/test_pcapng_unit.py @@ -4988,6 +4988,37 @@ def test_an_opt_namespace_collision_names_every_namespace_it_displaced(self) -> self.assertIs(option.registry[namespace][OptionType.opt_comment], UnknownOption) + def test_a_collision_names_its_namespaces_sorted(self) -> None: + """The warning names the namespaces sorted, not in the registry's order (#2772). + + A namespace created after the shipped ones is last in the registry, and + sorts first in the warning, so the text holds whatever order the registry + keeps its namespaces in. + + """ + from pcapkit.const.pcapng.option_type import OptionType + from pcapkit.protocols.misc.pcapng.schema import UnknownOption + from pcapkit.utilities.warnings import RegistryWarning + + option = self._snapshot() + endofopt = dict(option.registry)['opt'].get(OptionType.opt_endofopt) + self.assertIsNotNone(endofopt) + with warnings.catch_warnings(): + warnings.simplefilter('error') + option.register(OptionType.opt_endofopt, endofopt, ns='brand_new') + self.assertEqual(list(option.registry)[-1], 'brand_new') + self.assertNotEqual(list(option.registry), sorted(option.registry)) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + option.register(OptionType.opt_comment, UnknownOption, ns='opt') + + registry_warnings = [str(entry.message) for entry in caught + if entry.category is RegistryWarning] + self.assertEqual(len(registry_warnings), 1) + named = ', '.join(repr(namespace) for namespace in sorted(option.registry)) + self.assertIn(f'namespace(s) {named}, overwriting', registry_warnings[0]) + def test_a_namespace_created_by_the_registration_is_not_a_collision(self) -> None: """A fresh namespace starts as a copy of ``opt``'s defaults.