Source code for pcapkit.protocols.internet.mh.protocol

# -*- coding: utf-8 -*-
# pylint: disable=fixme
"""MH - Mobility Header
==========================

.. module:: pcapkit.protocols.internet.mh.protocol

:mod:`pcapkit.protocols.internet.mh.protocol` contains
:class:`~pcapkit.protocols.internet.mh.protocol.MH`,
which implements the extractor for Mobility Header
(MH) [*]_, whose structure is described as below:

======= ========= ================== ===============================
Octets      Bits        Name                    Description
======= ========= ================== ===============================
  0           0   ``mh.next``                 Next Header
  1           8   ``mh.length``               Header Length
  2          16   ``mh.type``                 Mobility Header Type
  3          24   ``mh.reserved``             Reserved
  4          32   ``mh.chksum``               Checksum
  6          48   ``mh.data``                 Message Data
======= ========= ================== ===============================

.. [*] https://en.wikipedia.org/wiki/Mobile_IP#Changes_in_IPv6_for_Mobile_IPv6

.. versionremoved:: 1.5.0

   ``pcapkit.protocols.internet.mh.NTPTimestamp``, with no alias; use
   :class:`pcapkit.protocols.internet.mh.data.NTPTimestamp` instead
   (:issue:`1886`).

"""
import collections
import datetime
import ipaddress
import math
from typing import TYPE_CHECKING, cast, overload

from aenum import IntEnum

from pcapkit.const.mh.access_type import AccessType as Enum_AccessType
from pcapkit.const.mh.ack_status_code import ACKStatusCode as Enum_ACKStatusCode
from pcapkit.const.mh.ani_suboption import ANISuboption as Enum_ANISuboption
from pcapkit.const.mh.auth_subtype import AuthSubtype as Enum_AuthSubtype
from pcapkit.const.mh.binding_ack_flag import BindingACKFlag as Enum_BindingACKFlag
from pcapkit.const.mh.binding_error import BindingError as Enum_BindingError
from pcapkit.const.mh.binding_revocation import BindingRevocation as Enum_BindingRevocation
from pcapkit.const.mh.binding_update_flag import BindingUpdateFlag as Enum_BindingUpdateFlag
from pcapkit.const.mh.cga_extension import CGAExtension as Enum_CGAExtension
from pcapkit.const.mh.cga_type import CGAType as Enum_CGAType
from pcapkit.const.mh.dhcp_support_mode import DHCPSupportMode as Enum_DHCPSupportMode
from pcapkit.const.mh.dns_status_code import DNSStatusCode as Enum_DNSStatusCode
from pcapkit.const.mh.dsmip6_tls_packet import DSMIP6TLSPacket as Enum_DSMIP6TLSPacket
from pcapkit.const.mh.dsmipv6_home_address import DSMIPv6HomeAddress as Enum_DSMIPv6HomeAddress
from pcapkit.const.mh.enumerating_algorithm import EnumeratingAlgorithm as Enum_EnumeratingAlgorithm
from pcapkit.const.mh.fb_ack_status import FlowBindingACKStatus as Enum_FlowBindingACKStatus
from pcapkit.const.mh.fb_action import FlowBindingAction as Enum_FlowBindingAction
from pcapkit.const.mh.fb_indication_trigger import \
    FlowBindingIndicationTrigger as Enum_FlowBindingIndicationTrigger
from pcapkit.const.mh.fb_type import FlowBindingType as Enum_FlowBindingType
from pcapkit.const.mh.flow_id_status import FlowIDStatus as Enum_FlowIDStatus
from pcapkit.const.mh.flow_id_suboption import FlowIDSuboption as Enum_FlowIDSuboption
from pcapkit.const.mh.handoff_type import HandoffType as Enum_HandoffType
from pcapkit.const.mh.handover_ack_flag import HandoverACKFlag as Enum_HandoverACKFlag
from pcapkit.const.mh.handover_ack_status import HandoverACKStatus as Enum_HandoverACKStatus
from pcapkit.const.mh.handover_initiate_flag import \
    HandoverInitiateFlag as Enum_HandoverInitiateFlag
from pcapkit.const.mh.handover_initiate_status import \
    HandoverInitiateStatus as Enum_HandoverInitiateStatus
from pcapkit.const.mh.home_address_reply import HomeAddressReply as Enum_HomeAddressReply
from pcapkit.const.mh.lla_code import LLACode as Enum_LLACode
from pcapkit.const.mh.lma_mag_suboption import \
    LMAControlledMAGSuboption as Enum_LMAControlledMAGSuboption
from pcapkit.const.mh.mn_group_id import MNGroupID as Enum_MNGroupID
from pcapkit.const.mh.mn_id_subtype import MNIDSubtype as Enum_MNIDSubtype
from pcapkit.const.mh.operator_id import OperatorID as Enum_OperatorID
from pcapkit.const.mh.option import Option as Enum_Option
from pcapkit.const.mh.packet import Packet as Enum_Packet
from pcapkit.const.mh.qos_attribute import QoSAttribute as Enum_QoSAttribute
from pcapkit.const.mh.revocation_status_code import \
    RevocationStatusCode as Enum_RevocationStatusCode
from pcapkit.const.mh.revocation_trigger import RevocationTrigger as Enum_RevocationTrigger
from pcapkit.const.mh.status_code import StatusCode as Enum_StatusCode
from pcapkit.const.mh.traffic_selector import TrafficSelector as Enum_TrafficSelector
from pcapkit.const.mh.upa_status import \
    UpdateNotificationACKStatus as Enum_UpdateNotificationACKStatus
from pcapkit.const.mh.upn_reason import UpdateNotificationReason as Enum_UpdateNotificationReason
from pcapkit.const.reg.transtype import TransType as Enum_TransType
from pcapkit.corekit.enum import EnumLookup
from pcapkit.corekit.multidict import OrderedMultiDict
from pcapkit.corekit.schema.ipaddress import parse_ip_address
from pcapkit.corekit.schema.padding import pad_length
from pcapkit.protocols.internet.ipv6_ext.protocol import IPv6_Ext
from pcapkit.protocols.internet.mh.data import MH as Data_MH
from pcapkit.protocols.internet.mh.data import \
    AccessNetworkIdentifierOption as Data_AccessNetworkIdentifierOption
from pcapkit.protocols.internet.mh.data import \
    AccessTechnologyTypeOption as Data_AccessTechnologyTypeOption
from pcapkit.protocols.internet.mh.data import \
    ActiveMulticastSubscriptionIPv4Option as Data_ActiveMulticastSubscriptionIPv4Option
from pcapkit.protocols.internet.mh.data import \
    ActiveMulticastSubscriptionIPv6Option as Data_ActiveMulticastSubscriptionIPv6Option
from pcapkit.protocols.internet.mh.data import \
    AllocationRetentionPriorityAttribute as Data_AllocationRetentionPriorityAttribute
from pcapkit.protocols.internet.mh.data import \
    AlternateCareofAddressOption as Data_AlternateCareofAddressOption
from pcapkit.protocols.internet.mh.data import \
    AlternateIPv4CareofAddressOption as Data_AlternateIPv4CareofAddressOption
from pcapkit.protocols.internet.mh.data import AnchoredPrefixOption as Data_AnchoredPrefixOption
from pcapkit.protocols.internet.mh.data import \
    ANICivicLocationSuboption as Data_ANICivicLocationSuboption
from pcapkit.protocols.internet.mh.data import \
    ANIGeoLocationSuboption as Data_ANIGeoLocationSuboption
from pcapkit.protocols.internet.mh.data import \
    ANIMAGGroupIdentifierSuboption as Data_ANIMAGGroupIdentifierSuboption
from pcapkit.protocols.internet.mh.data import \
    ANINetworkIdentifierSuboption as Data_ANINetworkIdentifierSuboption
from pcapkit.protocols.internet.mh.data import \
    ANIOperatorIdentifierSuboption as Data_ANIOperatorIdentifierSuboption
from pcapkit.protocols.internet.mh.data import ANISuboption as Data_ANISuboption
from pcapkit.protocols.internet.mh.data import \
    ANIUpdateTimerSuboption as Data_ANIUpdateTimerSuboption
from pcapkit.protocols.internet.mh.data import AuthOption as Data_AuthOption
from pcapkit.protocols.internet.mh.data import \
    AuthorizationDataOption as Data_AuthorizationDataOption
from pcapkit.protocols.internet.mh.data import BADFOption as Data_BADFOption
from pcapkit.protocols.internet.mh.data import BIDReferenceSuboption as Data_BIDReferenceSuboption
from pcapkit.protocols.internet.mh.data import \
    BindingAcknowledgementMessage as Data_BindingAcknowledgementMessage
from pcapkit.protocols.internet.mh.data import BindingErrorMessage as Data_BindingErrorMessage
from pcapkit.protocols.internet.mh.data import \
    BindingIdentifierOption as Data_BindingIdentifierOption
from pcapkit.protocols.internet.mh.data import \
    BindingRefreshAdviceOption as Data_BindingRefreshAdviceOption
from pcapkit.protocols.internet.mh.data import \
    BindingRefreshRequestMessage as Data_BindingRefreshRequestMessage
from pcapkit.protocols.internet.mh.data import \
    BindingReregistrationControlSuboption as Data_BindingReregistrationControlSuboption
from pcapkit.protocols.internet.mh.data import \
    BindingRevocationMessage as Data_BindingRevocationMessage
from pcapkit.protocols.internet.mh.data import BindingUpdateMessage as Data_BindingUpdateMessage
from pcapkit.protocols.internet.mh.data import BitRateAttribute as Data_BitRateAttribute
from pcapkit.protocols.internet.mh.data import CareofTestInitMessage as Data_CareofTestInitMessage
from pcapkit.protocols.internet.mh.data import CareofTestInitOption as Data_CareofTestInitOption
from pcapkit.protocols.internet.mh.data import CareofTestMessage as Data_CareofTestMessage
from pcapkit.protocols.internet.mh.data import CareofTestOption as Data_CareofTestOption
from pcapkit.protocols.internet.mh.data import CGAExtension as Data_CGAExtension
from pcapkit.protocols.internet.mh.data import CGAParameter as Data_CGAParameter
from pcapkit.protocols.internet.mh.data import CGAParametersOption as Data_CGAParametersOption
from pcapkit.protocols.internet.mh.data import \
    CGAParametersRequestOption as Data_CGAParametersRequestOption
from pcapkit.protocols.internet.mh.data import ContextRequest as Data_ContextRequest
from pcapkit.protocols.internet.mh.data import ContextRequestOption as Data_ContextRequestOption
from pcapkit.protocols.internet.mh.data import DelegatedMNPOption as Data_DelegatedMNPOption
from pcapkit.protocols.internet.mh.data import \
    DLIFLinkLayerAddressOption as Data_DLIFLinkLayerAddressOption
from pcapkit.protocols.internet.mh.data import \
    DLIFLinkLocalAddressOption as Data_DLIFLinkLocalAddressOption
from pcapkit.protocols.internet.mh.data import DNSUpdateOption as Data_DNSUpdateOption
from pcapkit.protocols.internet.mh.data import \
    DynamicIPMulticastSelectorOption as Data_DynamicIPMulticastSelectorOption
from pcapkit.protocols.internet.mh.data import ExperimentalExtension as Data_ExperimentalExtension
from pcapkit.protocols.internet.mh.data import ExperimentalMessage as Data_ExperimentalMessage
from pcapkit.protocols.internet.mh.data import \
    ExperimentalMobilityOption as Data_ExperimentalMobilityOption
from pcapkit.protocols.internet.mh.data import \
    FastBindingAcknowledgmentMessage as Data_FastBindingAcknowledgmentMessage
from pcapkit.protocols.internet.mh.data import \
    FastBindingUpdateMessage as Data_FastBindingUpdateMessage
from pcapkit.protocols.internet.mh.data import \
    FastNeighborAdvertisementMessage as Data_FastNeighborAdvertisementMessage
from pcapkit.protocols.internet.mh.data import \
    FlowBindingActionSuboption as Data_FlowBindingActionSuboption
from pcapkit.protocols.internet.mh.data import FlowBindingMessage as Data_FlowBindingMessage
from pcapkit.protocols.internet.mh.data import \
    FlowIdentificationOption as Data_FlowIdentificationOption
from pcapkit.protocols.internet.mh.data import \
    FlowIdentificationSuboption as Data_FlowIdentificationSuboption
from pcapkit.protocols.internet.mh.data import FlowSummaryOption as Data_FlowSummaryOption
from pcapkit.protocols.internet.mh.data import GREKeyOption as Data_GREKeyOption
from pcapkit.protocols.internet.mh.data import HandoffIndicatorOption as Data_HandoffIndicatorOption
from pcapkit.protocols.internet.mh.data import \
    HandoverAcknowledgeMessage as Data_HandoverAcknowledgeMessage
from pcapkit.protocols.internet.mh.data import \
    HandoverInitiateMessage as Data_HandoverInitiateMessage
from pcapkit.protocols.internet.mh.data import \
    HeartbeatControlSuboption as Data_HeartbeatControlSuboption
from pcapkit.protocols.internet.mh.data import HeartbeatMessage as Data_HeartbeatMessage
from pcapkit.protocols.internet.mh.data import HomeAgentSwitchMessage as Data_HomeAgentSwitchMessage
from pcapkit.protocols.internet.mh.data import \
    HomeNetworkPrefixOption as Data_HomeNetworkPrefixOption
from pcapkit.protocols.internet.mh.data import HomeTestInitMessage as Data_HomeTestInitMessage
from pcapkit.protocols.internet.mh.data import HomeTestMessage as Data_HomeTestMessage
from pcapkit.protocols.internet.mh.data import \
    IPv4AddressAcknowledgementOption as Data_IPv4AddressAcknowledgementOption
from pcapkit.protocols.internet.mh.data import \
    IPv4CareofAddressOption as Data_IPv4CareofAddressOption
from pcapkit.protocols.internet.mh.data import \
    IPv4DefaultRouterAddressOption as Data_IPv4DefaultRouterAddressOption
from pcapkit.protocols.internet.mh.data import \
    IPv4DHCPSupportModeOption as Data_IPv4DHCPSupportModeOption
from pcapkit.protocols.internet.mh.data import IPv4HomeAddressOption as Data_IPv4HomeAddressOption
from pcapkit.protocols.internet.mh.data import \
    IPv4HomeAddressReplyOption as Data_IPv4HomeAddressReplyOption
from pcapkit.protocols.internet.mh.data import \
    IPv4HomeAddressRequestOption as Data_IPv4HomeAddressRequestOption
from pcapkit.protocols.internet.mh.data import \
    IPv4TrafficOffloadSelectorOption as Data_IPv4TrafficOffloadSelectorOption
from pcapkit.protocols.internet.mh.data import \
    IPv6AddressPrefixOption as Data_IPv6AddressPrefixOption
from pcapkit.protocols.internet.mh.data import LinkLayerAddressOption as Data_LinkLayerAddressOption
from pcapkit.protocols.internet.mh.data import LinkLocalAddressOption as Data_LinkLocalAddressOption
from pcapkit.protocols.internet.mh.data import LMAAddressOption as Data_LMAAddressOption
from pcapkit.protocols.internet.mh.data import \
    LMAControlledMAGParametersOption as Data_LMAControlledMAGParametersOption
from pcapkit.protocols.internet.mh.data import \
    LMAControlledMAGSuboption as Data_LMAControlledMAGSuboption
from pcapkit.protocols.internet.mh.data import \
    LMAUserPlaneAddressOption as Data_LMAUserPlaneAddressOption
from pcapkit.protocols.internet.mh.data import LoadInformationOption as Data_LoadInformationOption
from pcapkit.protocols.internet.mh.data import \
    LocalizedRoutingAcknowledgmentMessage as Data_LocalizedRoutingAcknowledgmentMessage
from pcapkit.protocols.internet.mh.data import \
    LocalizedRoutingInitiationMessage as Data_LocalizedRoutingInitiationMessage
from pcapkit.protocols.internet.mh.data import LocalPrefixOption as Data_LocalPrefixOption
from pcapkit.protocols.internet.mh.data import MAGIdentifierOption as Data_MAGIdentifierOption
from pcapkit.protocols.internet.mh.data import MAGIPv6AddressOption as Data_MAGIPv6AddressOption
from pcapkit.protocols.internet.mh.data import \
    MAGMultipathBindingOption as Data_MAGMultipathBindingOption
from pcapkit.protocols.internet.mh.data import MesgIDOption as Data_MesgIDOption
from pcapkit.protocols.internet.mh.data import \
    MNGroupIdentifierOption as Data_MNGroupIdentifierOption
from pcapkit.protocols.internet.mh.data import MNIDOption as Data_MNIDOption
from pcapkit.protocols.internet.mh.data import MNLLAIIDOption as Data_MNLLAIIDOption
from pcapkit.protocols.internet.mh.data import MNLLIdentifierOption as Data_MNLLIdentifierOption
from pcapkit.protocols.internet.mh.data import \
    MobileNetworkPrefixOption as Data_MobileNetworkPrefixOption
from pcapkit.protocols.internet.mh.data import \
    MulticastAcknowledgementOption as Data_MulticastAcknowledgementOption
from pcapkit.protocols.internet.mh.data import \
    MulticastMobilityOption as Data_MulticastMobilityOption
from pcapkit.protocols.internet.mh.data import MultiPrefixExtension as Data_MultiPrefixExtension
from pcapkit.protocols.internet.mh.data import NATDetectionOption as Data_NATDetectionOption
from pcapkit.protocols.internet.mh.data import NonceIndicesOption as Data_NonceIndicesOption
from pcapkit.protocols.internet.mh.data import NTPTimestamp as Data_NTPTimestamp
from pcapkit.protocols.internet.mh.data import \
    PadFlowIdentificationSuboption as Data_PadFlowIdentificationSuboption
from pcapkit.protocols.internet.mh.data import PadOption as Data_PadOption
from pcapkit.protocols.internet.mh.data import \
    PermanentHomeKeygenTokenOption as Data_PermanentHomeKeygenTokenOption
from pcapkit.protocols.internet.mh.data import \
    PerSessionBitRateAttribute as Data_PerSessionBitRateAttribute
from pcapkit.protocols.internet.mh.data import PMIPv6Timestamp as Data_PMIPv6Timestamp
from pcapkit.protocols.internet.mh.data import PreviousMAAROption as Data_PreviousMAAROption
from pcapkit.protocols.internet.mh.data import QoSAttribute as Data_QoSAttribute
from pcapkit.protocols.internet.mh.data import \
    QoSTrafficSelectorAttribute as Data_QoSTrafficSelectorAttribute
from pcapkit.protocols.internet.mh.data import \
    QoSVendorSpecificAttribute as Data_QoSVendorSpecificAttribute
from pcapkit.protocols.internet.mh.data import QualityOfServiceOption as Data_QualityOfServiceOption
from pcapkit.protocols.internet.mh.data import \
    RedirectCapabilityOption as Data_RedirectCapabilityOption
from pcapkit.protocols.internet.mh.data import RedirectOption as Data_RedirectOption
from pcapkit.protocols.internet.mh.data import RestartCounterOption as Data_RestartCounterOption
from pcapkit.protocols.internet.mh.data import ServiceSelectionOption as Data_ServiceSelectionOption
from pcapkit.protocols.internet.mh.data import ServingMAAROption as Data_ServingMAAROption
from pcapkit.protocols.internet.mh.data import SignatureOption as Data_SignatureOption
from pcapkit.protocols.internet.mh.data import \
    SubscriptionQueryMessage as Data_SubscriptionQueryMessage
from pcapkit.protocols.internet.mh.data import \
    SubscriptionResponseMessage as Data_SubscriptionResponseMessage
from pcapkit.protocols.internet.mh.data import \
    TargetCareofAddressSuboption as Data_TargetCareofAddressSuboption
from pcapkit.protocols.internet.mh.data import TimestampOption as Data_TimestampOption
from pcapkit.protocols.internet.mh.data import \
    TrafficSelectorSuboption as Data_TrafficSelectorSuboption
from pcapkit.protocols.internet.mh.data import TransientBindingOption as Data_TransientBindingOption
from pcapkit.protocols.internet.mh.data import UnassignedANISuboption as Data_UnassignedANISuboption
from pcapkit.protocols.internet.mh.data import \
    UnassignedFlowIdentificationSuboption as Data_UnassignedFlowIdentificationSuboption
from pcapkit.protocols.internet.mh.data import \
    UnassignedLMAControlledMAGSuboption as Data_UnassignedLMAControlledMAGSuboption
from pcapkit.protocols.internet.mh.data import UnassignedOption as Data_UnassignedOption
from pcapkit.protocols.internet.mh.data import UnassignedQoSAttribute as Data_UnassignedQoSAttribute
from pcapkit.protocols.internet.mh.data import UnknownExtension as Data_UnknownExtension
from pcapkit.protocols.internet.mh.data import UnknownMessage as Data_UnknownMessage
from pcapkit.protocols.internet.mh.data import \
    UpdateNotificationAcknowledgementMessage as Data_UpdateNotificationAcknowledgementMessage
from pcapkit.protocols.internet.mh.data import \
    UpdateNotificationMessage as Data_UpdateNotificationMessage
from pcapkit.protocols.internet.mh.data import VendorSpecificOption as Data_VendorSpecificOption
from pcapkit.protocols.internet.mh.schema import MH as Schema_MH
from pcapkit.protocols.internet.mh.schema import \
    AccessNetworkIdentifierOption as Schema_AccessNetworkIdentifierOption
from pcapkit.protocols.internet.mh.schema import \
    AccessTechnologyTypeOption as Schema_AccessTechnologyTypeOption
from pcapkit.protocols.internet.mh.schema import \
    ActiveMulticastSubscriptionIPv4Option as Schema_ActiveMulticastSubscriptionIPv4Option
from pcapkit.protocols.internet.mh.schema import \
    ActiveMulticastSubscriptionIPv6Option as Schema_ActiveMulticastSubscriptionIPv6Option
from pcapkit.protocols.internet.mh.schema import \
    AllocationRetentionPriorityAttribute as Schema_AllocationRetentionPriorityAttribute
from pcapkit.protocols.internet.mh.schema import \
    AlternateCareofAddressOption as Schema_AlternateCareofAddressOption
from pcapkit.protocols.internet.mh.schema import \
    AlternateIPv4CareofAddressOption as Schema_AlternateIPv4CareofAddressOption
from pcapkit.protocols.internet.mh.schema import AnchoredPrefixOption as Schema_AnchoredPrefixOption
from pcapkit.protocols.internet.mh.schema import \
    ANICivicLocationSuboption as Schema_ANICivicLocationSuboption
from pcapkit.protocols.internet.mh.schema import \
    ANIGeoLocationSuboption as Schema_ANIGeoLocationSuboption
from pcapkit.protocols.internet.mh.schema import \
    ANIMAGGroupIdentifierSuboption as Schema_ANIMAGGroupIdentifierSuboption
from pcapkit.protocols.internet.mh.schema import \
    ANINetworkIdentifierSuboption as Schema_ANINetworkIdentifierSuboption
from pcapkit.protocols.internet.mh.schema import \
    ANIOperatorIdentifierSuboption as Schema_ANIOperatorIdentifierSuboption
from pcapkit.protocols.internet.mh.schema import ANISuboption as Schema_ANISuboption
from pcapkit.protocols.internet.mh.schema import \
    ANIUpdateTimerSuboption as Schema_ANIUpdateTimerSuboption
from pcapkit.protocols.internet.mh.schema import AuthOption as Schema_AuthOption
from pcapkit.protocols.internet.mh.schema import \
    AuthorizationDataOption as Schema_AuthorizationDataOption
from pcapkit.protocols.internet.mh.schema import BADFOption as Schema_BADFOption
from pcapkit.protocols.internet.mh.schema import \
    BIDReferenceSuboption as Schema_BIDReferenceSuboption
from pcapkit.protocols.internet.mh.schema import \
    BindingAcknowledgementMessage as Schema_BindingAcknowledgementMessage
from pcapkit.protocols.internet.mh.schema import BindingErrorMessage as Schema_BindingErrorMessage
from pcapkit.protocols.internet.mh.schema import \
    BindingIdentifierOption as Schema_BindingIdentifierOption
from pcapkit.protocols.internet.mh.schema import \
    BindingRefreshAdviceOption as Schema_BindingRefreshAdviceOption
from pcapkit.protocols.internet.mh.schema import \
    BindingRefreshRequestMessage as Schema_BindingRefreshRequestMessage
from pcapkit.protocols.internet.mh.schema import \
    BindingReregistrationControlSuboption as Schema_BindingReregistrationControlSuboption
from pcapkit.protocols.internet.mh.schema import \
    BindingRevocationMessage as Schema_BindingRevocationMessage
from pcapkit.protocols.internet.mh.schema import BindingUpdateMessage as Schema_BindingUpdateMessage
from pcapkit.protocols.internet.mh.schema import BitRateAttribute as Schema_BitRateAttribute
from pcapkit.protocols.internet.mh.schema import \
    CareofTestInitMessage as Schema_CareofTestInitMessage
from pcapkit.protocols.internet.mh.schema import CareofTestInitOption as Schema_CareofTestInitOption
from pcapkit.protocols.internet.mh.schema import CareofTestMessage as Schema_CareofTestMessage
from pcapkit.protocols.internet.mh.schema import CareofTestOption as Schema_CareofTestOption
from pcapkit.protocols.internet.mh.schema import CGAExtension as Schema_CGAExtension
from pcapkit.protocols.internet.mh.schema import CGAParameter as Schema_CGAParameter
from pcapkit.protocols.internet.mh.schema import CGAParametersOption as Schema_CGAParametersOption
from pcapkit.protocols.internet.mh.schema import \
    CGAParametersRequestOption as Schema_CGAParametersRequestOption
from pcapkit.protocols.internet.mh.schema import ContextRequestOption as Schema_ContextRequestOption
from pcapkit.protocols.internet.mh.schema import DelegatedMNPOption as Schema_DelegatedMNPOption
from pcapkit.protocols.internet.mh.schema import \
    DLIFLinkLayerAddressOption as Schema_DLIFLinkLayerAddressOption
from pcapkit.protocols.internet.mh.schema import \
    DLIFLinkLocalAddressOption as Schema_DLIFLinkLocalAddressOption
from pcapkit.protocols.internet.mh.schema import DNSUpdateOption as Schema_DNSUpdateOption
from pcapkit.protocols.internet.mh.schema import \
    DynamicIPMulticastSelectorOption as Schema_DynamicIPMulticastSelectorOption
from pcapkit.protocols.internet.mh.schema import \
    ExperimentalExtension as Schema_ExperimentalExtension
from pcapkit.protocols.internet.mh.schema import ExperimentalMessage as Schema_ExperimentalMessage
from pcapkit.protocols.internet.mh.schema import \
    ExperimentalMobilityOption as Schema_ExperimentalMobilityOption
from pcapkit.protocols.internet.mh.schema import \
    FastBindingAcknowledgmentMessage as Schema_FastBindingAcknowledgmentMessage
from pcapkit.protocols.internet.mh.schema import \
    FastBindingUpdateMessage as Schema_FastBindingUpdateMessage
from pcapkit.protocols.internet.mh.schema import \
    FastNeighborAdvertisementMessage as Schema_FastNeighborAdvertisementMessage
from pcapkit.protocols.internet.mh.schema import \
    FlowBindingActionSuboption as Schema_FlowBindingActionSuboption
from pcapkit.protocols.internet.mh.schema import FlowBindingMessage as Schema_FlowBindingMessage
from pcapkit.protocols.internet.mh.schema import \
    FlowIdentificationOption as Schema_FlowIdentificationOption
from pcapkit.protocols.internet.mh.schema import \
    FlowIdentificationSuboption as Schema_FlowIdentificationSuboption
from pcapkit.protocols.internet.mh.schema import FlowSummaryOption as Schema_FlowSummaryOption
from pcapkit.protocols.internet.mh.schema import GREKeyOption as Schema_GREKeyOption
from pcapkit.protocols.internet.mh.schema import \
    HandoffIndicatorOption as Schema_HandoffIndicatorOption
from pcapkit.protocols.internet.mh.schema import \
    HandoverAcknowledgeMessage as Schema_HandoverAcknowledgeMessage
from pcapkit.protocols.internet.mh.schema import \
    HandoverInitiateMessage as Schema_HandoverInitiateMessage
from pcapkit.protocols.internet.mh.schema import \
    HeartbeatControlSuboption as Schema_HeartbeatControlSuboption
from pcapkit.protocols.internet.mh.schema import HeartbeatMessage as Schema_HeartbeatMessage
from pcapkit.protocols.internet.mh.schema import \
    HomeAgentSwitchMessage as Schema_HomeAgentSwitchMessage
from pcapkit.protocols.internet.mh.schema import \
    HomeNetworkPrefixOption as Schema_HomeNetworkPrefixOption
from pcapkit.protocols.internet.mh.schema import HomeTestInitMessage as Schema_HomeTestInitMessage
from pcapkit.protocols.internet.mh.schema import HomeTestMessage as Schema_HomeTestMessage
from pcapkit.protocols.internet.mh.schema import \
    IPv4AddressAcknowledgementOption as Schema_IPv4AddressAcknowledgementOption
from pcapkit.protocols.internet.mh.schema import \
    IPv4CareofAddressOption as Schema_IPv4CareofAddressOption
from pcapkit.protocols.internet.mh.schema import \
    IPv4DefaultRouterAddressOption as Schema_IPv4DefaultRouterAddressOption
from pcapkit.protocols.internet.mh.schema import \
    IPv4DHCPSupportModeOption as Schema_IPv4DHCPSupportModeOption
from pcapkit.protocols.internet.mh.schema import \
    IPv4HomeAddressOption as Schema_IPv4HomeAddressOption
from pcapkit.protocols.internet.mh.schema import \
    IPv4HomeAddressReplyOption as Schema_IPv4HomeAddressReplyOption
from pcapkit.protocols.internet.mh.schema import \
    IPv4HomeAddressRequestOption as Schema_IPv4HomeAddressRequestOption
from pcapkit.protocols.internet.mh.schema import \
    IPv4TrafficOffloadSelectorOption as Schema_IPv4TrafficOffloadSelectorOption
from pcapkit.protocols.internet.mh.schema import \
    IPv6AddressPrefixOption as Schema_IPv6AddressPrefixOption
from pcapkit.protocols.internet.mh.schema import \
    LinkLayerAddressOption as Schema_LinkLayerAddressOption
from pcapkit.protocols.internet.mh.schema import \
    LinkLocalAddressOption as Schema_LinkLocalAddressOption
from pcapkit.protocols.internet.mh.schema import LMAAddressOption as Schema_LMAAddressOption
from pcapkit.protocols.internet.mh.schema import \
    LMAControlledMAGParametersOption as Schema_LMAControlledMAGParametersOption
from pcapkit.protocols.internet.mh.schema import \
    LMAControlledMAGSuboption as Schema_LMAControlledMAGSuboption
from pcapkit.protocols.internet.mh.schema import \
    LMAUserPlaneAddressOption as Schema_LMAUserPlaneAddressOption
from pcapkit.protocols.internet.mh.schema import \
    LoadInformationOption as Schema_LoadInformationOption
from pcapkit.protocols.internet.mh.schema import \
    LocalizedRoutingAcknowledgmentMessage as Schema_LocalizedRoutingAcknowledgmentMessage
from pcapkit.protocols.internet.mh.schema import \
    LocalizedRoutingInitiationMessage as Schema_LocalizedRoutingInitiationMessage
from pcapkit.protocols.internet.mh.schema import LocalPrefixOption as Schema_LocalPrefixOption
from pcapkit.protocols.internet.mh.schema import MAGIdentifierOption as Schema_MAGIdentifierOption
from pcapkit.protocols.internet.mh.schema import MAGIPv6AddressOption as Schema_MAGIPv6AddressOption
from pcapkit.protocols.internet.mh.schema import \
    MAGMultipathBindingOption as Schema_MAGMultipathBindingOption
from pcapkit.protocols.internet.mh.schema import MesgIDOption as Schema_MesgIDOption
from pcapkit.protocols.internet.mh.schema import \
    MNGroupIdentifierOption as Schema_MNGroupIdentifierOption
from pcapkit.protocols.internet.mh.schema import MNIDOption as Schema_MNIDOption
from pcapkit.protocols.internet.mh.schema import MNLLAIIDOption as Schema_MNLLAIIDOption
from pcapkit.protocols.internet.mh.schema import MNLLIdentifierOption as Schema_MNLLIdentifierOption
from pcapkit.protocols.internet.mh.schema import \
    MobileNetworkPrefixOption as Schema_MobileNetworkPrefixOption
from pcapkit.protocols.internet.mh.schema import \
    MulticastAcknowledgementOption as Schema_MulticastAcknowledgementOption
from pcapkit.protocols.internet.mh.schema import \
    MulticastMobilityOption as Schema_MulticastMobilityOption
from pcapkit.protocols.internet.mh.schema import MultiPrefixExtension as Schema_MultiPrefixExtension
from pcapkit.protocols.internet.mh.schema import NATDetectionOption as Schema_NATDetectionOption
from pcapkit.protocols.internet.mh.schema import NonceIndicesOption as Schema_NonceIndicesOption
from pcapkit.protocols.internet.mh.schema import Packet as Schema_Packet
from pcapkit.protocols.internet.mh.schema import \
    PadFlowIdentificationSuboption as Schema_PadFlowIdentificationSuboption
from pcapkit.protocols.internet.mh.schema import PadOption as Schema_PadOption
from pcapkit.protocols.internet.mh.schema import \
    PermanentHomeKeygenTokenOption as Schema_PermanentHomeKeygenTokenOption
from pcapkit.protocols.internet.mh.schema import \
    PerSessionBitRateAttribute as Schema_PerSessionBitRateAttribute
from pcapkit.protocols.internet.mh.schema import PreviousMAAROption as Schema_PreviousMAAROption
from pcapkit.protocols.internet.mh.schema import QoSAttribute as Schema_QoSAttribute
from pcapkit.protocols.internet.mh.schema import \
    QoSTrafficSelectorAttribute as Schema_QoSTrafficSelectorAttribute
from pcapkit.protocols.internet.mh.schema import \
    QoSVendorSpecificAttribute as Schema_QoSVendorSpecificAttribute
from pcapkit.protocols.internet.mh.schema import \
    QualityOfServiceOption as Schema_QualityOfServiceOption
from pcapkit.protocols.internet.mh.schema import \
    RedirectCapabilityOption as Schema_RedirectCapabilityOption
from pcapkit.protocols.internet.mh.schema import RedirectOption as Schema_RedirectOption
from pcapkit.protocols.internet.mh.schema import RestartCounterOption as Schema_RestartCounterOption
from pcapkit.protocols.internet.mh.schema import \
    ServiceSelectionOption as Schema_ServiceSelectionOption
from pcapkit.protocols.internet.mh.schema import ServingMAAROption as Schema_ServingMAAROption
from pcapkit.protocols.internet.mh.schema import SignatureOption as Schema_SignatureOption
from pcapkit.protocols.internet.mh.schema import \
    SubscriptionQueryMessage as Schema_SubscriptionQueryMessage
from pcapkit.protocols.internet.mh.schema import \
    SubscriptionResponseMessage as Schema_SubscriptionResponseMessage
from pcapkit.protocols.internet.mh.schema import \
    TargetCareofAddressSuboption as Schema_TargetCareofAddressSuboption
from pcapkit.protocols.internet.mh.schema import TimestampOption as Schema_TimestampOption
from pcapkit.protocols.internet.mh.schema import \
    TrafficSelectorSuboption as Schema_TrafficSelectorSuboption
from pcapkit.protocols.internet.mh.schema import \
    TransientBindingOption as Schema_TransientBindingOption
from pcapkit.protocols.internet.mh.schema import \
    UnassignedANISuboption as Schema_UnassignedANISuboption
from pcapkit.protocols.internet.mh.schema import \
    UnassignedFlowIdentificationSuboption as Schema_UnassignedFlowIdentificationSuboption
from pcapkit.protocols.internet.mh.schema import \
    UnassignedLMAControlledMAGSuboption as Schema_UnassignedLMAControlledMAGSuboption
from pcapkit.protocols.internet.mh.schema import UnassignedOption as Schema_UnassignedOption
from pcapkit.protocols.internet.mh.schema import \
    UnassignedQoSAttribute as Schema_UnassignedQoSAttribute
from pcapkit.protocols.internet.mh.schema import UnknownExtension as Schema_UnknownExtension
from pcapkit.protocols.internet.mh.schema import UnknownMessage as Schema_UnknownMessage
from pcapkit.protocols.internet.mh.schema import \
    UpdateNotificationAcknowledgementMessage as Schema_UpdateNotificationAcknowledgementMessage
from pcapkit.protocols.internet.mh.schema import \
    UpdateNotificationMessage as Schema_UpdateNotificationMessage
from pcapkit.protocols.internet.mh.schema import VendorSpecificOption as Schema_VendorSpecificOption
from pcapkit.protocols.misc.no_payload.protocol import NoPayload
from pcapkit.protocols.schema import Schema
from pcapkit.utilities.datetime import fromtimestamp, totimestamp
from pcapkit.utilities.exceptions import (BaseError, CaughtByCaller, EnumValueError, ProtocolError,
                                          UnsupportedCall)
from pcapkit.utilities.warnings import ProtocolWarning, RegistryWarning, warn

if TYPE_CHECKING:
    from datetime import datetime as dt_type
    from datetime import timedelta
    from ipaddress import IPv4Address, IPv6Address, IPv6Network
    from typing import IO, Any, Callable, DefaultDict, NoReturn, Optional

    from mypy_extensions import DefaultArg, KwArg, NamedArg
    from typing_extensions import Literal

    from pcapkit.corekit.multidict import OrderedMultiDict
    from pcapkit.corekit.protochain import ProtoChain
    from pcapkit.protocols.internet.mh.data import Option as Data_Option
    from pcapkit.protocols.internet.mh.schema import Option as Schema_Option
    from pcapkit.protocols.internet.mh.schema import Packet as Schema_Packet
    from pcapkit.protocols.protocol import ProtocolBase
    from pcapkit.protocols.schema import Schema

    Option = OrderedMultiDict[Enum_Option, Data_Option]
    Extension = OrderedMultiDict[Enum_CGAExtension, Data_CGAExtension]

    FlowIDSuboption = OrderedMultiDict[Enum_FlowIDSuboption,
                                       Data_FlowIdentificationSuboption]
    ANISuboption = OrderedMultiDict[Enum_ANISuboption, Data_ANISuboption]
    QoSAttribute = OrderedMultiDict[Enum_QoSAttribute, Data_QoSAttribute]
    LMAControlledMAGSuboption = OrderedMultiDict[Enum_LMAControlledMAGSuboption,
                                                 Data_LMAControlledMAGSuboption]

    PacketParser = Callable[[Schema_Packet, NamedArg(Schema_MH, 'header')], Data_MH]
    PacketConstructor = Callable[[DefaultArg(Optional[Data_MH]),
                                 KwArg(Any)], Schema_Packet]

    OptionParser = Callable[[Schema_Option, NamedArg(Option, 'options')], Data_Option]
    OptionConstructor = Callable[[Enum_Option, DefaultArg(Optional[Data_Option]),
                                  KwArg(Any)], Schema_Option]

    ExtensionParser = Callable[[Schema_CGAExtension, NamedArg(Extension, 'extensions')], Data_CGAExtension]
    ExtensionConstructor = Callable[[Enum_CGAExtension, DefaultArg(Optional[Data_CGAExtension]),
                                     KwArg(Any)], Schema_CGAExtension]

__all__ = ['MH']


[docs] class FastBindingAcknowledgmentStatus(EnumLookup, IntEnum): """[FastBindingAcknowledgmentStatus] Fast Binding Acknowledgment Status Codes. Status values of the fast binding acknowledgment (FBack) message, see :rfc:`5568#section-6.2.3`. Values below ``128`` indicate that the fast binding update was accepted by the receiving node, values of ``128`` and above that it was rejected. Note: :rfc:`5568#section-6.2.3` defines these values inline and IANA keeps no registry of them, so the enumeration lives here rather than in :mod:`pcapkit.const.mh`. It is also **not** interchangeable with the registered :class:`~pcapkit.const.mh.status_code.StatusCode`, since some of its values collide semantically: ``1`` means *NCoA is invalid* here but *accepted but prefix discovery necessary* there, and ``131`` means *incorrect interface identifier length* here but *home registration not supported* there. The enumeration is **closed**: an in-range value :rfc:`5568` leaves unassigned raises :exc:`~pcapkit.utilities.exceptions.EnumValueError` from :meth:`_missing_` instead of minting an ``Unassigned_N`` member, because this RFC-inline value set stays immutable (decided on :issue:`877`). Within an IPv6 chain the Mobility Header and everything after it are then :class:`~pcapkit.protocols.misc.raw.protocol.Raw`, as :rfc:`8200#section-4` has a node discard such a packet (:issue:`2477`). The class defines no ``get`` of its own: ``get``/``get_all`` come from :class:`~pcapkit.corekit.enum.EnumLookup`, like every other :class:`int`-valued registry here, so a key that is neither an :class:`int` nor a :class:`str` raises :exc:`~pcapkit.utilities.exceptions.EnumValueError`. A ``get`` override was rejected (decided on :issue:`935`; the override was deleted, not widened to accept ``default``): this class mints no alias (``__members__`` and ``list(cls)`` hold the same members), so an override could only repeat the base's dual :class:`int`/name resolution. """ #: Fast Binding Update accepted [:rfc:`5568#section-6.2.3`] Fast_Binding_Update_accepted = 0 #: Fast Binding Update accepted but NCoA is invalid; use the NCoA supplied in #: the "alternate" care-of address option [:rfc:`5568#section-6.2.3`] Fast_Binding_Update_accepted_but_NCoA_is_invalid = 1 #: Reason unspecified [:rfc:`5568#section-6.2.3`] Reason_unspecified = 128 #: Administratively prohibited [:rfc:`5568#section-6.2.3`] Administratively_prohibited = 129 #: Insufficient resources [:rfc:`5568#section-6.2.3`] Insufficient_resources = 130 #: Incorrect interface identifier length [:rfc:`5568#section-6.2.3`] Incorrect_interface_identifier_length = 131
[docs] @classmethod def _missing_(cls, value: 'int') -> 'NoReturn': """Lookup function used when value is not found. Args: value: Value to get enum item. Raises: EnumValueError: Always. :rfc:`5568#section-6.2.3` names this value set inline with no IANA registry behind it, and it stays immutable (decided on :issue:`877`) rather than minting an ``Unassigned_N`` placeholder member. """ raise EnumValueError('%r is not a valid %s' % (value, cls.__name__))
[docs] class IPv6AddressPrefixCode(EnumLookup, IntEnum): """[IPv6AddressPrefixCode] Mobility Header IPv6 Address/Prefix Option Codes. Option codes of the mobility header IPv6 address/prefix option, which identify which address the option carries, see :rfc:`5568#section-6.4.2`. Note: :rfc:`5568#section-6.4.2` defines these values inline and IANA keeps no registry of them, so the enumeration lives here rather than in :mod:`pcapkit.const.mh`. The identical code space of the neighbor discovery IP address/prefix option of :rfc:`5568#section-6.4.1` is likewise unregistered. The enumeration is **closed**: an in-range value :rfc:`5568` leaves unassigned raises :exc:`~pcapkit.utilities.exceptions.EnumValueError` from :meth:`_missing_` instead of minting an ``Unassigned_N`` member, because this RFC-inline value set stays immutable (decided on :issue:`877`). Within an IPv6 chain the Mobility Header and everything after it are then :class:`~pcapkit.protocols.misc.raw.protocol.Raw`, as :rfc:`8200#section-4` has a node discard such a packet (:issue:`2477`). The class defines no ``get`` of its own: ``get``/``get_all`` come from :class:`~pcapkit.corekit.enum.EnumLookup`, like every other :class:`int`-valued registry here, so a key that is neither an :class:`int` nor a :class:`str` raises :exc:`~pcapkit.utilities.exceptions.EnumValueError`. A ``get`` override was rejected (decided on :issue:`935`; the override was deleted, not widened to accept ``default``): this class mints no alias (``__members__`` and ``list(cls)`` hold the same members), so an override could only repeat the base's dual :class:`int`/name resolution. """ #: Old Care-of Address [:rfc:`5568#section-6.4.2`] Old_Care_of_Address = 1 #: New Care-of Address [:rfc:`5568#section-6.4.2`] New_Care_of_Address = 2 #: NAR's IP address [:rfc:`5568#section-6.4.2`] NAR_IP_address = 3 #: NAR's Prefix, sent in PrRtAdv; the prefix length field contains the number #: of valid leading bits in the prefix [:rfc:`5568#section-6.4.2`] NAR_Prefix = 4
[docs] @classmethod def _missing_(cls, value: 'int') -> 'NoReturn': """Lookup function used when value is not found. Args: value: Value to get enum item. Raises: EnumValueError: Always. :rfc:`5568#section-6.4.2` names this value set inline with no IANA registry behind it, and it stays immutable (decided on :issue:`877`) rather than minting an ``Unassigned_N`` placeholder member. """ raise EnumValueError('%r is not a valid %s' % (value, cls.__name__))
[docs] class LocalizedRoutingStatus(EnumLookup, IntEnum): """[LocalizedRoutingStatus] Localized Routing Acknowledgment Status Codes. Status values of the localized routing acknowledgment (LRA) message, see :rfc:`6705#section-10.2`. Values below ``128`` indicate that the initiation was processed successfully, values of ``128`` and above that it was rejected. Note: :rfc:`6705#section-10.2` defines these values inline and IANA keeps no registry of them -- neither a dedicated one nor entries in the general *Status Codes* registry -- so the enumeration lives here rather than in :mod:`pcapkit.const.mh`. It is **not** interchangeable with the registered :class:`~pcapkit.const.mh.status_code.StatusCode`, whose ``128`` and ``129`` mean something else entirely. The enumeration is **closed**: an in-range value :rfc:`6705` leaves unassigned raises :exc:`~pcapkit.utilities.exceptions.EnumValueError` from :meth:`_missing_` instead of minting an ``Unassigned_N`` member, because this RFC-inline value set stays immutable (decided on :issue:`877`). Within an IPv6 chain the Mobility Header and everything after it are then :class:`~pcapkit.protocols.misc.raw.protocol.Raw`, as :rfc:`8200#section-4` has a node discard such a packet (:issue:`2477`). The class defines no ``get`` of its own: ``get``/``get_all`` come from :class:`~pcapkit.corekit.enum.EnumLookup`, and since it cannot mint, an unassigned value raises through ``get`` exactly as it does through the bare constructor. A ``get`` override was rejected (decided on :issue:`880`): it had no callers, and rebuilding it on the immutable contract would add nothing to the base. """ #: Success [:rfc:`6705#section-10.2`] Success = 0 #: Localized Routing Not Allowed [:rfc:`6705#section-10.2`] Localized_Routing_Not_Allowed = 128 #: MN Not Attached [:rfc:`6705#section-10.2`] MN_Not_Attached = 129
[docs] @classmethod def _missing_(cls, value: 'int') -> 'NoReturn': """Lookup function used when value is not found. Args: value: Value to get enum item. Raises: EnumValueError: Always. :rfc:`6705#section-10.2` names this value set inline with no IANA registry behind it, and it stays immutable (decided on :issue:`877`) rather than minting an ``Unassigned_N`` placeholder member. """ raise EnumValueError('%r is not a valid %s' % (value, cls.__name__))
[docs] class LMAAddressCode(EnumLookup, IntEnum): """[LMAAddressCode] Local Mobility Anchor Address Option Codes. Option codes of the local mobility anchor address option, which say which address family the option carries, see :rfc:`5949#section-6.2.2`. Note: :rfc:`5949#section-6.2.2` defines these values inline and IANA keeps no registry of them, so the enumeration lives here rather than in :mod:`pcapkit.const.mh`. The enumeration is **closed**: an in-range value :rfc:`5949` leaves unassigned raises :exc:`~pcapkit.utilities.exceptions.EnumValueError` from :meth:`_missing_` instead of minting an ``Unassigned_N`` member, because this RFC-inline value set stays immutable (decided on :issue:`877`). Within an IPv6 chain the Mobility Header and everything after it are then :class:`~pcapkit.protocols.misc.raw.protocol.Raw`, as :rfc:`8200#section-4` has a node discard such a packet (:issue:`2477`). The class defines no ``get`` of its own: ``get``/``get_all`` come from :class:`~pcapkit.corekit.enum.EnumLookup`, and since it cannot mint, an unassigned value raises through ``get`` exactly as it does through the bare constructor. A ``get`` override was rejected (decided on :issue:`880`): it had no callers, and rebuilding it on the immutable contract would add nothing to the base. """ #: Reserved [:rfc:`5949#section-6.2.2`] Reserved = 0 #: IPv6 local mobility anchor address [:rfc:`5949#section-6.2.2`] IPv6_LMAA = 1 #: IPv4 local mobility anchor address [:rfc:`5949#section-6.2.2`] IPv4_LMAA = 2
[docs] @classmethod def _missing_(cls, value: 'int') -> 'NoReturn': """Lookup function used when value is not found. Args: value: Value to get enum item. Raises: EnumValueError: Always. :rfc:`5949#section-6.2.2` names this value set inline with no IANA registry behind it, and it stays immutable (decided on :issue:`877`) rather than minting an ``Unassigned_N`` placeholder member. """ raise EnumValueError('%r is not a valid %s' % (value, cls.__name__))
[docs] class MH(IPv6_Ext[Data_MH, Schema_MH], schema=Schema_MH, data=Data_MH): """This class implements Mobility Header. This class supports parsing of the following MH message types, which are registered in the :attr:`self.__message__ <pcapkit.protocols.internet.mh.protocol.MH.__message__>` attribute: .. list-table:: :header-rows: 1 * - Message Type - Message Parser - Message Constructor * - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Refresh_Request` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_brr` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_brr` * - :attr:`~pcapkit.const.mh.packet.Packet.Home_Test_Init` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hoti` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hoti` * - :attr:`~pcapkit.const.mh.packet.Packet.Care_of_Test_Init` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_coti` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_coti` * - :attr:`~pcapkit.const.mh.packet.Packet.Home_Test` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hot` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hot` * - :attr:`~pcapkit.const.mh.packet.Packet.Care_of_Test` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_cot` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_cot` * - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Update` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_bu` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_bu` * - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Acknowledgement` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_ba` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_ba` * - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Error` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_be` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_be` * - :attr:`~pcapkit.const.mh.packet.Packet.Fast_Binding_Update` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_fbu` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_fbu` * - :attr:`~pcapkit.const.mh.packet.Packet.Fast_Binding_Acknowledgment` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_fback` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_fback` * - :attr:`~pcapkit.const.mh.packet.Packet.Fast_Neighbor_Advertisement` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_fna` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_fna` * - :attr:`~pcapkit.const.mh.packet.Packet.Experimental_Mobility_Header` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_emh` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_emh` * - :attr:`~pcapkit.const.mh.packet.Packet.Home_Agent_Switch_Message` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_has` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_has` * - :attr:`~pcapkit.const.mh.packet.Packet.Heartbeat_Message` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hb` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hb` * - :attr:`~pcapkit.const.mh.packet.Packet.Handover_Initiate_Message` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hi` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hi` * - :attr:`~pcapkit.const.mh.packet.Packet.Handover_Acknowledge_Message` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hack` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hack` * - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Revocation_Message` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_brm` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_brm` * - :attr:`~pcapkit.const.mh.packet.Packet.Localized_Routing_Initiation` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_lri` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_lri` * - :attr:`~pcapkit.const.mh.packet.Packet.Localized_Routing_Acknowledgment` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_lra` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_lra` * - :attr:`~pcapkit.const.mh.packet.Packet.Update_Notification` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_upn` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_upn` * - :attr:`~pcapkit.const.mh.packet.Packet.Update_Notification_Acknowledgement` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_upa` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_upa` * - :attr:`~pcapkit.const.mh.packet.Packet.Flow_Binding_Message` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_fbm` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_fbm` * - :attr:`~pcapkit.const.mh.packet.Packet.Subscription_Query` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_sq` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_sq` * - :attr:`~pcapkit.const.mh.packet.Packet.Subscription_Response` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_sr` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_sr` This class supports parsing the following MH options, which are registered in the :attr:`self.__option__ <pcapkit.protocols.internet.mh.protocol.MH.__option__>` attribute: .. list-table:: :header-rows: 1 * - Option Code - Option Parser - Option Constructor * - :attr:`~pcapkit.const.mh.option.Option.Pad1` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_pad` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_pad` * - :attr:`~pcapkit.const.mh.option.Option.PadN` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_pad` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_pad` * - :attr:`~pcapkit.const.mh.option.Option.Binding_Refresh_Advice` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_bra` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_bra` * - :attr:`~pcapkit.const.mh.option.Option.Alternate_Care_of_Address` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_aca` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_aca` * - :attr:`~pcapkit.const.mh.option.Option.Nonce_Indices` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ni` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ni` * - :attr:`~pcapkit.const.mh.option.Option.Authorization_Data` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_bad` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_bad` * - :attr:`~pcapkit.const.mh.option.Option.Mobile_Network_Prefix_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mnp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mnp` * - :attr:`~pcapkit.const.mh.option.Option.Mobility_Header_Link_Layer_Address_option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lla` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lla` * - :attr:`~pcapkit.const.mh.option.Option.MN_ID_OPTION_TYPE` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mn_id` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mn_id` * - :attr:`~pcapkit.const.mh.option.Option.AUTH_OPTION_TYPE` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_auth` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_auth` * - :attr:`~pcapkit.const.mh.option.Option.MESG_ID_OPTION_TYPE` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mesg_id` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mesg_id` * - :attr:`~pcapkit.const.mh.option.Option.CGA_Parameters_Request` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_cga_pr` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_cga_pr` * - :attr:`~pcapkit.const.mh.option.Option.CGA_Parameters` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_cga_param` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_cga_param` * - :attr:`~pcapkit.const.mh.option.Option.Signature` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_signature` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_signature` * - :attr:`~pcapkit.const.mh.option.Option.Permanent_Home_Keygen_Token` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_phkt` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_phkt` * - :attr:`~pcapkit.const.mh.option.Option.Care_of_Test_Init` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ct_init` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ct_init` * - :attr:`~pcapkit.const.mh.option.Option.Care_of_Test` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ct` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ct` * - :attr:`~pcapkit.const.mh.option.Option.DNS_UPDATE_TYPE` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_dns` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_dns` * - :attr:`~pcapkit.const.mh.option.Option.Experimental_Mobility_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_exp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_exp` * - :attr:`~pcapkit.const.mh.option.Option.Vendor_Specific_Mobility_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_vendor` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_vendor` * - :attr:`~pcapkit.const.mh.option.Option.Service_Selection_Mobility_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_service` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_service` * - :attr:`~pcapkit.const.mh.option.Option.Binding_Authorization_Data_for_FMIPv6` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_badf` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_badf` * - :attr:`~pcapkit.const.mh.option.Option.Home_Network_Prefix_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_hnp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_hnp` * - :attr:`~pcapkit.const.mh.option.Option.Handoff_Indicator_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_hi` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_hi` * - :attr:`~pcapkit.const.mh.option.Option.Access_Technology_Type_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_att` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_att` * - :attr:`~pcapkit.const.mh.option.Option.Mobile_Node_Link_layer_Identifier_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mn_lli` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mn_lli` * - :attr:`~pcapkit.const.mh.option.Option.Link_local_Address_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lla_addr` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lla_addr` * - :attr:`~pcapkit.const.mh.option.Option.Timestamp_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_timestamp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_timestamp` * - :attr:`~pcapkit.const.mh.option.Option.Restart_Counter` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_restart` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_restart` * - :attr:`~pcapkit.const.mh.option.Option.IPv4_Home_Address` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_hoa` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_hoa` * - :attr:`~pcapkit.const.mh.option.Option.IPv4_Address_Acknowledgement` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_ack` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_ack` * - :attr:`~pcapkit.const.mh.option.Option.NAT_Detection` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_nat` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_nat` * - :attr:`~pcapkit.const.mh.option.Option.IPv4_Care_of_Address` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_coa` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_coa` * - :attr:`~pcapkit.const.mh.option.Option.GRE_Key_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_gre` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_gre` * - :attr:`~pcapkit.const.mh.option.Option.Mobility_Header_IPv6_Address_Prefix` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv6_ap` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv6_ap` * - :attr:`~pcapkit.const.mh.option.Option.Binding_Identifier` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_bid` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_bid` * - :attr:`~pcapkit.const.mh.option.Option.IPv4_Home_Address_Request` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_hoa_req` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_hoa_req` * - :attr:`~pcapkit.const.mh.option.Option.IPv4_Home_Address_Reply` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_hoa_rep` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_hoa_rep` * - :attr:`~pcapkit.const.mh.option.Option.IPv4_Default_Router_Address` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_router` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_router` * - :attr:`~pcapkit.const.mh.option.Option.IPv4_DHCP_Support_Mode` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_dhcp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_dhcp` * - :attr:`~pcapkit.const.mh.option.Option.Context_Request_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_cr` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_cr` * - :attr:`~pcapkit.const.mh.option.Option.Local_Mobility_Anchor_Address_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lmaa` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lmaa` * - :attr:`~pcapkit.const.mh.option.Option.Mobile_Node_Link_local_Address_Interface_Identifier_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mn_lla_iid` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mn_lla_iid` * - :attr:`~pcapkit.const.mh.option.Option.Transient_Binding` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_transient` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_transient` * - :attr:`~pcapkit.const.mh.option.Option.Flow_Summary_Mobility_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_fs` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_fs` * - :attr:`~pcapkit.const.mh.option.Option.Flow_Identification_Mobility_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_fid` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_fid` * - :attr:`~pcapkit.const.mh.option.Option.Redirect_Capability_Mobility_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_rc` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_rc` * - :attr:`~pcapkit.const.mh.option.Option.Redirect_Mobility_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_redirect` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_redirect` * - :attr:`~pcapkit.const.mh.option.Option.Load_Information_Mobility_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_load` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_load` * - :attr:`~pcapkit.const.mh.option.Option.Alternate_IPv4_Care_of_Address` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_alt_ipv4_coa` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_alt_ipv4_coa` * - :attr:`~pcapkit.const.mh.option.Option.Mobile_Node_Group_Identifier` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mn_group` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mn_group` * - :attr:`~pcapkit.const.mh.option.Option.MAG_IPv6_Address` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mag_addr` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mag_addr` * - :attr:`~pcapkit.const.mh.option.Option.Access_Network_Identifier` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ani` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ani` * - :attr:`~pcapkit.const.mh.option.Option.IPv4_Traffic_Offload_Selector` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_offload` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_offload` * - :attr:`~pcapkit.const.mh.option.Option.Dynamic_IP_Multicast_Selector` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mcast_sel` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mcast_sel` * - :attr:`~pcapkit.const.mh.option.Option.Delegated_Mobile_Network_Prefix` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_dmnp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_dmnp` * - :attr:`~pcapkit.const.mh.option.Option.Active_Multicast_Subscription_IPv4` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ams4` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ams4` * - :attr:`~pcapkit.const.mh.option.Option.Active_Multicast_Subscription_IPv6` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ams6` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ams6` * - :attr:`~pcapkit.const.mh.option.Option.Quality_of_Service` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_qos` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_qos` * - :attr:`~pcapkit.const.mh.option.Option.LMA_User_Plane_Address` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lma_up` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lma_up` * - :attr:`~pcapkit.const.mh.option.Option.Multicast_Mobility_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mcast` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mcast` * - :attr:`~pcapkit.const.mh.option.Option.Multicast_Acknowledgement_Option` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mcast_ack` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mcast_ack` * - :attr:`~pcapkit.const.mh.option.Option.LMA_Controlled_MAG_Parameters` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lcmp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lcmp` * - :attr:`~pcapkit.const.mh.option.Option.MAG_Multipath_Binding` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mag_mp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mag_mp` * - :attr:`~pcapkit.const.mh.option.Option.MAG_Identifier` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mag_id` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mag_id` * - :attr:`~pcapkit.const.mh.option.Option.Anchored_Prefix` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_anchored` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_anchored` * - :attr:`~pcapkit.const.mh.option.Option.Local_Prefix` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_local_prefix` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_local_prefix` * - :attr:`~pcapkit.const.mh.option.Option.Previous_MAAR` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_prev_maar` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_prev_maar` * - :attr:`~pcapkit.const.mh.option.Option.Serving_MAAR` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_serv_maar` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_serv_maar` * - :attr:`~pcapkit.const.mh.option.Option.DLIF_Link_Local_Address` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_dlif_lla` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_dlif_lla` * - :attr:`~pcapkit.const.mh.option.Option.DLIF_Link_Layer_Address` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_dlif_lladdr` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_dlif_lladdr` This class supports parsing of the following MH CGA extensions, which are registered in the :attr:`self.__extension__ <pcapkit.protocols.internet.mh.protocol.MH.__extension__>` attribute: .. list-table:: :header-rows: 1 * - CGA Extension Code - CGA Extension Parser - CGA Extension Constructor * - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Multi_Prefix` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_ext_multiprefix` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_ext_multiprefix` * - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Exp_FFFD` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_ext_exp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_ext_exp` * - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Exp_FFFE` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_ext_exp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_ext_exp` * - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Exp_FFFF` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_ext_exp` - :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_ext_exp` .. versionchanged:: 1.5.0 * The FBU and FBack Lifetime is read and built as plain seconds, per :rfc:`5568` (:issue:`493`). * A rebuild keeps a PadN with no data, the reserved fields, the reserved bits of the flag fields and the octets each padding field captured (:issue:`1223`, :issue:`1324`, :issue:`1329`, :issue:`1418`, :issue:`1437`). .. versionchanged:: 1.6.0 * :meth:`make` and the message, option and sub-option makers, given an integer argument its field cannot carry -- negative, too wide, or a :obj:`bool` for a field wider than one bit -- raise :exc:`~pcapkit.utilities.exceptions.ProtocolError`, rather than writing a different value or raising another exception; so does the access network identifier sub-option maker for a raw geo-location degree that does not fit 24 bits as two's complement, where it masked it. The option makers check their registry codes and their one-octet ``Option Length`` too (:issue:`2614`). * An option, sub-option, QoS attribute or CGA extension given to a maker as empty raw octets, which hold no type, raises :exc:`~pcapkit.utilities.exceptions.ProtocolError`, rather than being dropped beside another item or, for most, raising an :exc:`AttributeError` alone (:issue:`2723`). * A CGA parameter builds from a data model holding raw extensions, where it raised an :exc:`AttributeError`, and from a schema without ``public_key_test``, where it raised a :exc:`KeyError` (:issue:`2725`). * Each enumeration field of :meth:`make` and of the message, option and sub-option makers, and the type of each option, sub-option, QoS attribute and CGA extension given to them, takes a member or an :class:`int` code, resolved with the enumeration's ``get``; a name, a :obj:`bool` for a field wider than one bit, or a code the field cannot carry raises :exc:`~pcapkit.utilities.exceptions.ProtocolError`, rather than resolving against an enumeration inferred from the caller's frame or writing a different value. The Binding Error maker resolves ``status`` with :class:`~pcapkit.const.mh.binding_error.BindingError`, the registry its schema holds, rather than with :class:`~pcapkit.const.mh.status_code.StatusCode` (:issue:`2594`). The same makers raise it for a fixed-size octet argument of the wrong width -- the checksum, a cookie or keygen token, or reserved octets -- rather than padding or cutting it to the field. The checksum defaults to two zero octets (:issue:`2676`). .. versionremoved:: 1.6.0 * ``_decode_signed``, the two's-complement decoder of the geo-location degrees; :meth:`~pcapkit.protocols.protocol.Protocol._read_sint` reads them instead (:issue:`2614`). * The ``X_default``, ``X_namespace`` and ``X_reversed`` arguments that went with each enumeration field ``X`` of :meth:`make` and of the message and option makers; resolve a name with :meth:`~pcapkit.protocols.protocol.Protocol.resolve_index` first (:issue:`2594`). """ ########################################################################## # Defaults. ########################################################################## #: Message type to method mapping. A :obj:`str` value names the methods #: ``_read_msg_${name}`` and ``_make_msg_${name}``, falling back to #: ``_read_msg_unknown`` / ``_make_msg_unknown`` when they do not exist; any #: other value is a ``(parser, constructor)`` pair of callables. __message__ = collections.defaultdict( lambda: 'unknown', { Enum_Packet.Binding_Refresh_Request: 'brr', Enum_Packet.Home_Test_Init: 'hoti', Enum_Packet.Care_of_Test_Init: 'coti', Enum_Packet.Home_Test: 'hot', Enum_Packet.Care_of_Test: 'cot', Enum_Packet.Binding_Update: 'bu', Enum_Packet.Binding_Acknowledgement: 'ba', Enum_Packet.Binding_Error: 'be', Enum_Packet.Fast_Binding_Update: 'fbu', Enum_Packet.Fast_Binding_Acknowledgment: 'fback', Enum_Packet.Fast_Neighbor_Advertisement: 'fna', Enum_Packet.Experimental_Mobility_Header: 'emh', Enum_Packet.Home_Agent_Switch_Message: 'has', Enum_Packet.Heartbeat_Message: 'hb', Enum_Packet.Handover_Initiate_Message: 'hi', Enum_Packet.Handover_Acknowledge_Message: 'hack', Enum_Packet.Binding_Revocation_Message: 'brm', Enum_Packet.Localized_Routing_Initiation: 'lri', Enum_Packet.Localized_Routing_Acknowledgment: 'lra', Enum_Packet.Update_Notification: 'upn', Enum_Packet.Update_Notification_Acknowledgement: 'upa', Enum_Packet.Flow_Binding_Message: 'fbm', Enum_Packet.Subscription_Query: 'sq', Enum_Packet.Subscription_Response: 'sr', }, ) # type: DefaultDict[Enum_Packet | int, str | tuple[PacketParser, PacketConstructor]] #: Option type to method mapping. A :obj:`str` value names the methods #: ``_read_opt_${name}`` and ``_make_opt_${name}``, falling back to #: ``_read_opt_none`` / ``_make_opt_none`` when they do not exist; any other #: value is a ``(parser, constructor)`` pair of callables. __option__ = collections.defaultdict( lambda: 'none', { Enum_Option.Pad1: 'pad', Enum_Option.PadN: 'pad', Enum_Option.Binding_Refresh_Advice: 'bra', Enum_Option.Alternate_Care_of_Address: 'aca', Enum_Option.Nonce_Indices: 'ni', Enum_Option.Authorization_Data: 'bad', Enum_Option.Mobile_Network_Prefix_Option: 'mnp', Enum_Option.Mobility_Header_Link_Layer_Address_option: 'lla', Enum_Option.MN_ID_OPTION_TYPE: 'mn_id', Enum_Option.AUTH_OPTION_TYPE: 'auth', Enum_Option.MESG_ID_OPTION_TYPE: 'mesg_id', Enum_Option.CGA_Parameters_Request: 'cga_pr', Enum_Option.CGA_Parameters: 'cga_param', Enum_Option.Signature: 'signature', Enum_Option.Permanent_Home_Keygen_Token: 'phkt', Enum_Option.Care_of_Test_Init: 'ct_init', Enum_Option.Care_of_Test: 'ct', Enum_Option.DNS_UPDATE_TYPE: 'dns', Enum_Option.Experimental_Mobility_Option: 'exp', Enum_Option.Vendor_Specific_Mobility_Option: 'vendor', Enum_Option.Service_Selection_Mobility_Option: 'service', Enum_Option.Binding_Authorization_Data_for_FMIPv6: 'badf', Enum_Option.Home_Network_Prefix_Option: 'hnp', Enum_Option.Handoff_Indicator_Option: 'hi', Enum_Option.Access_Technology_Type_Option: 'att', Enum_Option.Mobile_Node_Link_layer_Identifier_Option: 'mn_lli', Enum_Option.Link_local_Address_Option: 'lla_addr', Enum_Option.Timestamp_Option: 'timestamp', Enum_Option.Restart_Counter: 'restart', Enum_Option.IPv4_Home_Address: 'ipv4_hoa', Enum_Option.IPv4_Address_Acknowledgement: 'ipv4_ack', Enum_Option.NAT_Detection: 'nat', Enum_Option.IPv4_Care_of_Address: 'ipv4_coa', Enum_Option.GRE_Key_Option: 'gre', Enum_Option.Mobility_Header_IPv6_Address_Prefix: 'ipv6_ap', Enum_Option.Binding_Identifier: 'bid', Enum_Option.IPv4_Home_Address_Request: 'ipv4_hoa_req', Enum_Option.IPv4_Home_Address_Reply: 'ipv4_hoa_rep', Enum_Option.IPv4_Default_Router_Address: 'ipv4_router', Enum_Option.IPv4_DHCP_Support_Mode: 'ipv4_dhcp', Enum_Option.Context_Request_Option: 'cr', Enum_Option.Local_Mobility_Anchor_Address_Option: 'lmaa', Enum_Option.Mobile_Node_Link_local_Address_Interface_Identifier_Option: 'mn_lla_iid', Enum_Option.Transient_Binding: 'transient', Enum_Option.Flow_Summary_Mobility_Option: 'fs', Enum_Option.Flow_Identification_Mobility_Option: 'fid', Enum_Option.Redirect_Capability_Mobility_Option: 'rc', Enum_Option.Redirect_Mobility_Option: 'redirect', Enum_Option.Load_Information_Mobility_Option: 'load', Enum_Option.Alternate_IPv4_Care_of_Address: 'alt_ipv4_coa', Enum_Option.Mobile_Node_Group_Identifier: 'mn_group', Enum_Option.MAG_IPv6_Address: 'mag_addr', Enum_Option.Access_Network_Identifier: 'ani', Enum_Option.IPv4_Traffic_Offload_Selector: 'offload', Enum_Option.Dynamic_IP_Multicast_Selector: 'mcast_sel', Enum_Option.Delegated_Mobile_Network_Prefix: 'dmnp', Enum_Option.Active_Multicast_Subscription_IPv4: 'ams4', Enum_Option.Active_Multicast_Subscription_IPv6: 'ams6', Enum_Option.Quality_of_Service: 'qos', Enum_Option.LMA_User_Plane_Address: 'lma_up', Enum_Option.Multicast_Mobility_Option: 'mcast', Enum_Option.Multicast_Acknowledgement_Option: 'mcast_ack', Enum_Option.LMA_Controlled_MAG_Parameters: 'lcmp', Enum_Option.MAG_Multipath_Binding: 'mag_mp', Enum_Option.MAG_Identifier: 'mag_id', Enum_Option.Anchored_Prefix: 'anchored', Enum_Option.Local_Prefix: 'local_prefix', Enum_Option.Previous_MAAR: 'prev_maar', Enum_Option.Serving_MAAR: 'serv_maar', Enum_Option.DLIF_Link_Local_Address: 'dlif_lla', Enum_Option.DLIF_Link_Layer_Address: 'dlif_lladdr', }, ) # type: DefaultDict[Enum_Option | int, str | tuple[OptionParser, OptionConstructor]] #: CGA extension type to method mapping. A :obj:`str` value names the #: methods ``_read_ext_${name}`` and ``_make_ext_${name}``, falling back to #: ``_read_ext_none`` / ``_make_ext_none`` when they do not exist; any other #: value is a ``(parser, constructor)`` pair of callables. __extension__ = collections.defaultdict( lambda: 'none', { Enum_CGAExtension.Multi_Prefix: 'multiprefix', Enum_CGAExtension.Exp_FFFD: 'exp', Enum_CGAExtension.Exp_FFFE: 'exp', Enum_CGAExtension.Exp_FFFF: 'exp', }, ) # type: DefaultDict[Enum_CGAExtension | int, str | tuple[ExtensionParser, ExtensionConstructor]] ########################################################################## # Properties. ########################################################################## @property def name(self) -> 'Literal["Mobility Header"]': """Name of current protocol.""" return 'Mobility Header' @property def alias(self) -> 'Literal["MH"]': """Acronym of corresponding protocol. Spelled out rather than left to :attr:`Protocol.alias <pcapkit.protocols.protocol.Protocol.alias>`'s class-name default, because :class:`~pcapkit.protocols.internet.ipv6_ext.protocol.IPv6_Ext` sits between this class and that default in the MRO and carries a concrete ``'IPv6-Ext'`` of its own. Inheriting it would rename this header in every :class:`~pcapkit.corekit.protochain.ProtoChain` string and in :meth:`IPv6._decode_next_layer <pcapkit.protocols.internet.ipv6.protocol.IPv6._decode_next_layer>`'s packet dict key. The value equals the class-name default. """ return 'MH' @property def length(self) -> 'int': """Header length of current protocol.""" return self._info.length @property def payload(self) -> 'ProtocolBase | NoReturn': """Payload of current instance. Raises: UnsupportedCall: if the protocol is used as an IPv6 extension header """ if self._extf: raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'payload'") return super().payload @property def protocol(self) -> 'Optional[str] | NoReturn': """Name of next layer protocol (if any). Raises: UnsupportedCall: if the protocol is used as an IPv6 extension header """ if self._extf: raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'protocol'") return super().protocol @property def protochain(self) -> 'ProtoChain | NoReturn': """Protocol chain of current instance. Raises: UnsupportedCall: if the protocol is used as an IPv6 extension header """ if self._extf: raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'protochain'") return super().protochain ########################################################################## # Methods. ##########################################################################
[docs] def read(self, length: 'Optional[int]' = None, *, version: 'Literal[4, 6]' = 4, # pylint: disable=arguments-differ,unused-argument extension: bool = False, **kwargs: 'Any') -> 'Data_MH': # pylint: disable=unused-argument """Read Mobility Header. Structure of MH header [:rfc:`6275`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Payload Proto | Header Len | MH Type | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | . . . Message Data . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: length: Length of packet data. version: IP protocol version. extension: If the protocol is used as an IPv6 extension header. **kwargs: Arbitrary keyword arguments. Returns: Parsed packet data. Raises: ProtocolError: If ``Header Len`` declares fewer octets than the fixed fields of the message type take, a message :rfc:`6275#section-9.2` has the receiver discard. .. versionchanged:: 1.6.0 * Such a message is refused, rather than read with the missing fields as zeros and rebuilt longer than captured (:issue:`2669`). * A header cut short is rebuilt with the ``Header Len`` it declared, rather than one worked out from the message (:issue:`2669`). """ if length is None: length = len(self) schema = self.__header__ # NOTE: A whole message whose fields run past its Header Len reads the rest as # zeros, and its rebuild writes them and a longer Header Len back (#2669). if '__short_read__' not in schema.__dict__ and '__short_read__' in schema.data.__dict__: raise ProtocolError(f'{self.alias}: [Type {schema.type}] invalid header length: {schema.length}') name = self._lookup_registry(self.__message__, schema.type) if isinstance(name, str): meth_name = f'_read_msg_{name}' meth = cast('PacketParser', getattr(self, meth_name, self._read_msg_unknown)) else: meth = name[0] mh = meth(schema.data, header=schema) if extension: # NOTE: As an IPv6 extension header the upper layer belongs to the enclosing # IPv6 header, so hold NoPayload here so that membership does not raise (#2487). self._next = NoPayload() # pylint: disable=attribute-defined-outside-init return mh return self._decode_next_layer(mh, schema.next, length - mh.length)
[docs] def make(self, next: 'Enum_TransType | int' = Enum_TransType.UDP, type: 'Enum_Packet | int' = Enum_Packet.Binding_Refresh_Request, reserved: 'int' = 0, chksum: 'bytes' = b'\x00\x00', data: 'bytes | Data_MH | Schema_Packet | dict[str, Any]' = b'\x00\x00', # minimum length payload: 'ProtocolBase | Schema | bytes' = b'', **kwargs: 'Any') -> 'Schema_MH': """Make (construct) packet data. Args: next: Next header type. type: Mobility Header type. reserved: Reserved octet after the MH type. chksum: Checksum, two octets. data: Message data. payload: Payload of next layer protocol. **kwargs: Arbitrary keyword arguments. Returns: Constructed packet data. Raises: ProtocolError: If ``data`` is none of the forms this maker reads -- raw :obj:`bytes`, a :obj:`dict` of message arguments, an :class:`~pcapkit.protocols.internet.mh.data.MH` data model or a :class:`~pcapkit.protocols.internet.mh.schema.Packet` schema; or if ``reserved``, or the header length of the message, does not fit in one octet; if ``chksum`` is not two octets; or if ``next`` or ``type`` is a name, a :obj:`bool` or a code its field cannot carry. .. versionchanged:: 1.5.0 Takes ``reserved`` ahead of ``chksum`` (:issue:`1329`), which shifts every later positional argument. """ next_val = self._make_index(next, Enum_TransType, bits=8, name='next header') type_val = self._make_index(type, Enum_Packet, bits=8, name='MH type') if isinstance(data, bytes): data_val = data # type: bytes | Schema_Packet elif isinstance(data, (dict, Data_MH)): name = self._lookup_registry(self.__message__, type_val) if isinstance(name, str): meth_name = f'_make_msg_{name}' meth = cast('PacketConstructor', getattr(self, meth_name, self._make_msg_unknown)) else: meth = name[1] if isinstance(data, dict): data_val = meth(**data) else: data_val = meth(data) elif isinstance(data, Schema_Packet): data_val = data else: raise ProtocolError(f'MH: [Type {type_val}] invalid data: must be bytes, a dict, ' f'an MH data model or a Packet schema, not {data!r}') # NOTE: Padded until ``len(data) + 6`` is a multiple of 8; rounding ``length`` up alone # would declare octets never emitted, and the re-parse would read past the buffer. data_val = self._pad_mh_message(data_val) # NOTE: A capture cut short rebuilds with the Header Len it declared, not one # worked out from the message, and its short read cuts the rest (#2669). header_len = (len(data_val) + 6) // 8 - 1 if isinstance(data, Data_MH) and data.get('__short_read__') is not None: header_len = data.length // 8 - 1 return Schema_MH( next=next_val, length=self._make_uint(header_len, bits=8, name='header length'), type=type_val, reserved=self._make_uint(reserved, bits=8, name='reserved octet'), chksum=self._make_octets(chksum, size=2, name='checksum'), data=data_val, payload=payload, )
[docs] @classmethod def register_message(cls, code: 'Enum_Packet', meth: 'str | tuple[PacketParser, PacketConstructor]') -> 'None': """Register a message parser. Args: code: MH message type code. meth: Name ``X`` of the ``_read_msg_X`` and ``_make_msg_X`` methods, or a ``(parser, constructor)`` pair of callables, see :attr:`__message__`. Warns: RegistryWarning: If a different entry is already registered for ``code``; it is overwritten. Handing back the entry already stored, or one equal to it, is a silent no-op. The entry ``code`` shipped with is accepted like any other, so an override can be undone (:issue:`1362`, :issue:`1363`). """ incumbent = cls.__message__.get(code) if incumbent is not None and incumbent == meth: return if incumbent is not None: warn(f'message type {code} already registered, overwriting', RegistryWarning) cls.__message__[code] = meth
[docs] @classmethod def register_option(cls, code: 'Enum_Option', meth: 'str | tuple[OptionParser, OptionConstructor]') -> 'None': """Register an option parser. Args: code: MH option code. meth: Name ``X`` of the ``_read_opt_X`` and ``_make_opt_X`` methods, or a ``(parser, constructor)`` pair of callables, see :attr:`__option__`. Warns: RegistryWarning: If a different entry is already registered for ``code``; it is overwritten. Handing back the entry already stored, or one equal to it, is a silent no-op. The entry ``code`` shipped with is accepted like any other, so an override can be undone (:issue:`1362`, :issue:`1363`). """ incumbent = cls.__option__.get(code) if incumbent is not None and incumbent == meth: return if incumbent is not None: warn(f'option {code} already registered, overwriting', RegistryWarning) cls.__option__[code] = meth
[docs] @classmethod def register_extension(cls, code: 'Enum_CGAExtension', meth: 'str | tuple[ExtensionParser, ExtensionConstructor]') -> 'None': """Register a CGA extension parser. Args: code: CGA extension code. meth: Name ``X`` of the ``_read_ext_X`` and ``_make_ext_X`` methods, or a ``(parser, constructor)`` pair of callables, see :attr:`__extension__`. Warns: RegistryWarning: If a different entry is already registered for ``code``; it is overwritten. Handing back the entry already stored, or one equal to it, is a silent no-op. The entry ``code`` shipped with is accepted like any other, so an override can be undone (:issue:`1362`, :issue:`1363`). """ incumbent = cls.__extension__.get(code) if incumbent is not None and incumbent == meth: return if incumbent is not None: warn(f'extension {code} already registered, overwriting', RegistryWarning) cls.__extension__[code] = meth
[docs] @classmethod def id(cls) -> 'tuple[Literal["MH"]]': # type: ignore[override] """Index ID of the protocol. Returns: Index ID of the protocol. """ return ('MH',)
########################################################################## # Data models. ########################################################################## @overload def __post_init__(self, file: 'IO[bytes] | bytes', length: 'Optional[int]' = ..., *, # pylint: disable=arguments-differ extension: 'bool' = ..., **kwargs: 'Any') -> 'None': ... @overload def __post_init__(self, **kwargs: 'Any') -> 'None': ... # pylint: disable=arguments-differ
[docs] def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Optional[int]' = None, *, # pylint: disable=arguments-differ extension: 'bool' = False, **kwargs: 'Any') -> 'None': """Post initialisation hook. Args: file: Source packet stream. length: Length of packet data. extension: If the protocol is used as an IPv6 extension header. **kwargs: Arbitrary keyword arguments. See Also: For construction arguments, please refer to :meth:`self.make <MH.make>`. """ #: bool: If the protocol is used as an IPv6 extension header. self._extf = extension # call super __post_init__ super().__post_init__(file, length, extension=extension, **kwargs) # type: ignore[arg-type]
def __length_hint__(self) -> 'Literal[8]': """Return an estimated length for the object. Eight octets: the six fixed ones and the message data that :rfc:`6275#section-6.1.1` counts in the first 8-octet unit. """ return 8
[docs] @classmethod def __index__(cls) -> 'Enum_TransType': # pylint: disable=invalid-index-returned """Numeral registry index of the protocol. Returns: Numeral registry index of the protocol in `IANA`_. .. _IANA: https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml """ return Enum_TransType.Mobility_Header # type: ignore[return-value]
########################################################################## # Utilities. ##########################################################################
[docs] @classmethod def _make_data(cls, data: 'Data_MH') -> 'dict[str, Any]': # type: ignore[override] """Create key-value pairs from ``data`` for protocol construction. Args: data: protocol data Returns: Key-value pairs for protocol construction. """ return { 'next': data.next, 'type': data.type, 'reserved': data.reserved, 'chksum': data.chksum, 'data': data, 'payload': cls._make_payload(data), }
def _pad_mh_message(self, data: 'Schema_Packet | bytes') -> 'Schema_Packet | bytes': """Pad an MH message so that the Mobility Header aligns to 8 octets. Args: data: Constructed message data. Returns: The message data, with padding options appended if any were needed. Note: The padding goes into the message's mobility options, which is where :rfc:`6275#section-6.1.1` puts it. A message whose body is opaque has nowhere to put it -- :class:`~pcapkit.protocols.internet.mh.schema.UnknownMessage` and :class:`~pcapkit.protocols.internet.mh.schema.ExperimentalMessage` carry raw bytes and no options, as does a ``data`` argument given directly as :obj:`bytes` -- so for those the padding is appended to the message body itself and a :class:`~pcapkit.utilities.warnings.ProtocolWarning` says so. Appending is necessary rather than optional: ``length`` is ``(len(data) + 6) // 8 - 1``, which floors, so leaving an opaque body short would emit 10, 12 or 14 octets while declaring 8, and a parser reads 8 and misinterprets the remainder. Since the caller asked for a packet to be built and the shortfall is recoverable, completing it beats refusing -- the warning is there because the emitted body is then not byte-for-byte what was handed in. """ pad_opts, pad_len = self._make_pad_options(len(data) + 6) if pad_len == 0: return data options = getattr(data, 'options', None) # NOTE: The ``isinstance`` test narrows ``data`` for the type checker; at runtime ``getattr`` # has already covered :obj:`bytes` by returning :obj:`None`. if isinstance(data, bytes) or options is None: warn(f'{self.alias}: message data of {len(data)} octets carries no ' f'mobility options to hold padding, so {pad_len} octet(s) were ' 'appended to the message body to align the header', ProtocolWarning) if isinstance(data, bytes): return data + b'\x00' * pad_len # An opaque schema body -- UnknownMessage, ExperimentalMessage -- keeps # its content in ``data`` rather than in options, so that is where the # octets go. Rebound rather than mutated in place so that ``len()`` and # ``pack()`` see the change, exactly as for the options branch below. body = getattr(data, 'data', None) if not isinstance(body, bytes): raise ProtocolError( f'{self.alias}: message data of {len(data)} octets needs ' f'{pad_len} octet(s) of padding, but the body is neither bytes ' 'nor a schema carrying bytes, so there is nowhere to put it') data.data = body + b'\x00' * pad_len return data # NOTE: Rebinding the attribute, not mutating the list, marks the schema updated, so ``len()`` # and ``pack()`` count the padding. data.options = list(options) + pad_opts return data
[docs] def _read_msg_unknown(self, schema: 'Schema_UnknownMessage', *, header: 'Schema_MH') -> 'Data_UnknownMessage': """Read unknown MH message type. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_UnknownMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, data=schema.data, ) return data
[docs] def _read_msg_brr(self, schema: 'Schema_BindingRefreshRequestMessage', *, header: 'Schema_MH') -> 'Data_BindingRefreshRequestMessage': """Read MH binding refresh request (BRR) message type. Structure of MH Binding Refresh Request Message [:rfc:`6275`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_BindingRefreshRequestMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, msg_reserved=schema.reserved, options=self._read_mh_options(schema.options) ) return data
[docs] def _read_msg_hoti(self, schema: 'Schema_HomeTestInitMessage', *, header: 'Schema_MH') -> 'Data_HomeTestInitMessage': """Read MH home test initiation (HoTI) message type. Structure of MH Home Test Initiation Message [:rfc:`6275`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Home Init Cookie + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_HomeTestInitMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, msg_reserved=schema.reserved, cookie=schema.cookie, options=self._read_mh_options(schema.options) ) return data
[docs] def _read_msg_coti(self, schema: 'Schema_CareofTestInitMessage', *, header: 'Schema_MH') -> 'Data_CareofTestInitMessage': """Read MH care-of test initiation (CoTI) message type. Structure of MH Care-of Test Initiation Message [:rfc:`6275`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Care-of Init Cookie + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_CareofTestInitMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, msg_reserved=schema.reserved, cookie=schema.cookie, options=self._read_mh_options(schema.options) ) return data
[docs] def _read_msg_hot(self, schema: 'Schema_HomeTestMessage', *, header: 'Schema_MH') -> 'Data_HomeTestMessage': """Read MH home test (HoT) message type. Structure of MH Home Test Message [:rfc:`6275`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Home Nonce Index | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Home Init Cookie + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Home Keygen Token + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_HomeTestMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, nonce_index=schema.nonce_index, cookie=schema.cookie, token=schema.token, options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_cot(self, schema: 'Schema_CareofTestMessage', *, header: 'Schema_MH') -> 'Data_CareofTestMessage': """Read MH care-of test (CoT) message type. Structure of MH Care-of Test Message [:rfc:`6275`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Care-of Nonce Index | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Care-of Init Cookie + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Care-of Keygen Token + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_CareofTestMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, nonce_index=schema.nonce_index, cookie=schema.cookie, token=schema.token, options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_bu(self, schema: 'Schema_BindingUpdateMessage', *, header: 'Schema_MH') -> 'Data_BindingUpdateMessage': """Read MH binding update (BU) message type. Structure of MH Binding Update Message [:rfc:`6275`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |A|H|L|K| Reserved | Lifetime | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Later RFCs assign more of the flag bits, ``M``, ``R``, ``P``, ``F``, ``T``, ``B``, ``S`` and ``D`` in that order (IANA *Binding Update Flags*); the low four bits remain reserved. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_BindingUpdateMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, ack=bool(schema.flags['A']), home=bool(schema.flags['H']), lla_compat=bool(schema.flags['L']), key_mngt=bool(schema.flags['K']), map_reg=bool(schema.flags['M']), mobile_router=bool(schema.flags['R']), proxy_reg=bool(schema.flags['P']), udp_encap=bool(schema.flags['F']), tlv_format=bool(schema.flags['T']), bulk_binding=bool(schema.flags['B']), multicast=bool(schema.flags['S']), dmm=bool(schema.flags['D']), flags_reserved=schema.flags['reserved'], lifetime=datetime.timedelta(seconds=schema.lifetime * 4), options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_ba(self, schema: 'Schema_BindingAcknowledgementMessage', *, header: 'Schema_MH') -> 'Data_BindingAcknowledgementMessage': """Read MH binding acknowledgement (BA) message type. Structure of MH Binding Acknowledgement Message [:rfc:`6275`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Status |K| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | Lifetime | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Later RFCs assign more of the flag bits, ``R``, ``P``, ``T``, ``B``, ``S`` and ``D`` in that order (IANA *Binding Acknowledgment Flags*); the lowest bit remains reserved. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_BindingAcknowledgementMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, status=schema.status, key_mngt=bool(schema.flags['K']), mobile_router=bool(schema.flags['R']), proxy_reg=bool(schema.flags['P']), tlv_format=bool(schema.flags['T']), bulk_binding=bool(schema.flags['B']), multicast=bool(schema.flags['S']), dmm=bool(schema.flags['D']), flags_reserved=schema.flags['reserved'], seq=schema.seq, lifetime=datetime.timedelta(seconds=schema.lifetime * 4), options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_be(self, schema: 'Schema_BindingErrorMessage', *, header: 'Schema_MH') -> 'Data_BindingErrorMessage': """Read MH binding error (BE) message type. Structure of MH Binding Error Message [:rfc:`6275`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Status | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + Home Address + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_BindingErrorMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, status=schema.status, msg_reserved=schema.reserved, home=schema.home, options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_fbu(self, schema: 'Schema_FastBindingUpdateMessage', *, header: 'Schema_MH') -> 'Data_FastBindingUpdateMessage': """Read MH fast binding update (FBU) message type. Structure of MH Fast Binding Update Message [:rfc:`5568#section-6.2.2`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |A|H|L|K| Reserved | Lifetime | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`5568#section-6.2.2` states that the FBU is *identical* to the Mobile IPv6 binding update (BU) message -- but that statement is about **message layout**, not field units. The same section defines the Lifetime as "the requested time in seconds", unlike the BU lifetime's units of 4 seconds [:rfc:`6275#section-6.1.7`], so it is not scaled on the way in. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_FastBindingUpdateMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, ack=bool(schema.flags['A']), home=bool(schema.flags['H']), lla_compat=bool(schema.flags['L']), key_mngt=bool(schema.flags['K']), flags_reserved=schema.flags['reserved'], lifetime=datetime.timedelta(seconds=schema.lifetime), options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_fback(self, schema: 'Schema_FastBindingAcknowledgmentMessage', *, header: 'Schema_MH') -> 'Data_FastBindingAcknowledgmentMessage': """Read MH fast binding acknowledgment (FBack) message type. Structure of MH Fast Binding Acknowledgment Message [:rfc:`5568#section-6.2.3`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Status |K| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | Lifetime | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`5568#section-6.2.3` defines the FBack status values inline instead of drawing them from the IANA *Status Codes* registry, whose values ``1`` and ``131`` mean something else entirely. The status is therefore reported as a :class:`~pcapkit.protocols.internet.mh.protocol.FastBindingAcknowledgmentStatus`, which is local to this module, rather than being mislabelled as a :class:`~pcapkit.const.mh.status_code.StatusCode`. The Lifetime is likewise defined by :rfc:`5568#section-6.2.3` as "the granted lifetime ... in seconds", unlike the BA lifetime's units of 4 seconds [:rfc:`6275#section-6.1.7`], so it is not scaled on the way in. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_FastBindingAcknowledgmentMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, status=FastBindingAcknowledgmentStatus(schema.status), key_mngt=bool(schema.flags['K']), flags_reserved=schema.flags['reserved'], seq=schema.seq, lifetime=datetime.timedelta(seconds=schema.lifetime), options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_fna(self, schema: 'Schema_FastNeighborAdvertisementMessage', *, header: 'Schema_MH') -> 'Data_FastNeighborAdvertisementMessage': """Read MH fast neighbor advertisement (FNA) message type. Structure of MH Fast Neighbor Advertisement Message [:rfc:`4068#section-6.3.3`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The FNA message is **deprecated** by :rfc:`5568#section-8`, which replaces it with the unsolicited neighbor advertisement (UNA) of :rfc:`4861`. Its wire format is therefore taken from :rfc:`4068`, the specification :rfc:`5568` obsoletes, since a NAR is still permitted to process the message as specified there. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_FastNeighborAdvertisementMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, msg_reserved=schema.reserved, options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_emh(self, schema: 'Schema_ExperimentalMessage', *, header: 'Schema_MH') -> 'Data_ExperimentalMessage': """Read MH experimental mobility header message type. Structure of MH Experimental Mobility Header Message [:rfc:`5096#section-3`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Payload Proto | Header Len | MH Type | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | . . . Message Data . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`5096#section-3` defines no fields beyond those of the mobility header itself, so the message data is carried opaquely -- its layout is whatever the experiment in question defines. Two bytes of padding are required when no data is present. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_ExperimentalMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, data=schema.data, ) return data
[docs] def _read_msg_has(self, schema: 'Schema_HomeAgentSwitchMessage', *, header: 'Schema_MH') -> 'Data_HomeAgentSwitchMessage': """Read MH home agent switch (HAS) message type. Structure of MH Home Agent Switch Message [:rfc:`5142#section-4`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |# of Addresses | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + . . . Home Agent Addresses . . . + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + . . . Mobility Options . . . + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The ``# of Addresses`` field is not carried into the data model, since it is exactly ``len(addresses)`` and a second copy of a length is a second thing to keep in step. An empty list is meaningful rather than degenerate: :rfc:`5142#section-4` uses a count of zero to tell the mobile node to run home agent discovery instead. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ if schema.count != len(schema.addresses): raise ProtocolError(f'{self.alias}: [Type {header.type}] invalid format') data = Data_HomeAgentSwitchMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, msg_reserved=schema.reserved, addresses=tuple(schema.addresses), options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_hb(self, schema: 'Schema_HeartbeatMessage', *, header: 'Schema_MH') -> 'Data_HeartbeatMessage': """Read MH heartbeat message type. Structure of MH Heartbeat Message [:rfc:`5847#section-3.3`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved |U|R| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The ``U`` and ``R`` flags are the two **least** significant bits of the first half-word, not the most significant ones the other mobility messages put their flags in [:rfc:`5847#section-3.3`]. The sequence number is 32 bits wide, also unlike the 16-bit ones elsewhere. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_HeartbeatMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, unsolicited=bool(schema.flags['U']), response=bool(schema.flags['R']), flags_reserved=schema.flags['reserved'], seq=schema.seq, options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_hi(self, schema: 'Schema_HandoverInitiateMessage', *, header: 'Schema_MH') -> 'Data_HandoverInitiateMessage': """Read MH handover initiate (HI) message type. Structure of MH Handover Initiate Message [:rfc:`5568#section-6.2.1.1`, :rfc:`5949#section-6.1.1`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |S|U|P|F| Resv | Code | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . | | . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`5568` defines only the ``S`` and ``U`` flags; the ``P`` and ``F`` flags, and code values ``2`` and ``3``, are added by :rfc:`5949#section-6.1.1`. All of these flags are registered in the IANA *Handover Initiate Flags* registry, hence all are parsed here. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_HandoverInitiateMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, assign=bool(schema.flags['S']), buffer=bool(schema.flags['U']), proxy=bool(schema.flags['P']), forward=bool(schema.flags['F']), flags_reserved=schema.flags['reserved'], code=schema.code, options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_hack(self, schema: 'Schema_HandoverAcknowledgeMessage', *, header: 'Schema_MH') -> 'Data_HandoverAcknowledgeMessage': """Read MH handover acknowledge (HAck) message type. Structure of MH Handover Acknowledge Message [:rfc:`5568#section-6.2.1.2`, :rfc:`5949#section-6.1.2`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |U|P|F| Reserved| Code | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . | | . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`5568` reserves the whole first octet of the message data; the ``U``, ``P`` and ``F`` flags carved out of it, and code values ``5``, ``6``, ``131`` and ``132``, are added by :rfc:`5949#section-6.1.2`. All of these flags are registered in the IANA *Handover Acknowledge Flags* registry, hence all are parsed here. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_HandoverAcknowledgeMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, buffer=bool(schema.flags['U']), proxy=bool(schema.flags['P']), forward=bool(schema.flags['F']), flags_reserved=schema.flags['reserved'], code=schema.code, options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_brm(self, schema: 'Schema_BindingRevocationMessage', *, header: 'Schema_MH') -> 'Data_BindingRevocationMessage': """Read MH binding revocation (BRM) message type. Structure of MH Binding Revocation Indication Message [:rfc:`5846#section-5.1`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | B.R. Type = 1 | R. Trigger | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # |P|V|G| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility options . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Structure of MH Binding Revocation Acknowledgement Message [:rfc:`5846#section-5.2`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | B.R. Type = 2 | Status | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # |P|V|G| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: One Mobility Header type carries both forms, told apart by the ``B.R. Type`` octet **inside** the message rather than by the header type. The two have identical geometry, and the only field whose meaning differs is the octet after it -- a revocation trigger in an indication and a status code in an acknowledgement, drawn from two different registries -- so a single data model carries both, with :attr:`~pcapkit.protocols.internet.mh.data.BindingRevocationMessage.br_type` saying which it is. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_BindingRevocationMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, br_type=schema.br_type, code=schema.code, seq=schema.seq, proxy=bool(schema.flags['P']), ipv4_hoa=bool(schema.flags['V']), global_revocation=bool(schema.flags['G']), flags_reserved=schema.flags['reserved'], options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_lri(self, schema: 'Schema_LocalizedRoutingInitiationMessage', *, header: 'Schema_MH') -> 'Data_LocalizedRoutingInitiationMessage': """Read MH localized routing initiation (LRI) message type. Structure of MH Localized Routing Initiation Message [:rfc:`6705#section-10.1`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved | Lifetime | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The lifetime is in **seconds** here, not in the units of 4 seconds that :rfc:`6275` uses for the binding messages [:rfc:`6705#section-10.1`], so it is not scaled on the way in. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_LocalizedRoutingInitiationMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, msg_reserved=schema.reserved, lifetime=datetime.timedelta(seconds=schema.lifetime), options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_lra(self, schema: 'Schema_LocalizedRoutingAcknowledgmentMessage', *, header: 'Schema_MH') -> 'Data_LocalizedRoutingAcknowledgmentMessage': """Read MH localized routing acknowledgment (LRA) message type. Structure of MH Localized Routing Acknowledgment Message [:rfc:`6705#section-10.2`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |U| Reserved | Status | Lifetime | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ The status codes are enumerated in :class:`~pcapkit.protocols.internet.mh.protocol.LocalizedRoutingStatus`, which is local to this module because :rfc:`6705` defines them inline and IANA registers them nowhere -- not even in the general *Status Codes* registry. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_LocalizedRoutingAcknowledgmentMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, unsolicited=bool(schema.flags['U']), flags_reserved=schema.flags['reserved'], status=LocalizedRoutingStatus(schema.status), lifetime=datetime.timedelta(seconds=schema.lifetime), options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_upn(self, schema: 'Schema_UpdateNotificationMessage', *, header: 'Schema_MH') -> 'Data_UpdateNotificationMessage': """Read MH update notification (UPN) message type. Structure of MH Update Notification Message [:rfc:`7077#section-4.1`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Notification Reason |A|D| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The notification reason is **16** bits wide, not the 8 that a status octet elsewhere in the Mobility Header would be. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_UpdateNotificationMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, reason=schema.reason, ack=bool(schema.flags['A']), retransmit=bool(schema.flags['D']), flags_reserved=schema.flags['reserved'], options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_upa(self, schema: 'Schema_UpdateNotificationAcknowledgementMessage', *, header: 'Schema_MH') -> 'Data_UpdateNotificationAcknowledgementMessage': """Read MH update notification acknowledgement (UPA) message type. Structure of MH Update Notification Acknowledgement Message [:rfc:`7077#section-4.2`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Status Code | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_UpdateNotificationAcknowledgementMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, status=schema.status, msg_reserved=schema.reserved, options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_fbm(self, schema: 'Schema_FlowBindingMessage', *, header: 'Schema_MH') -> 'Data_FlowBindingMessage': """Read MH flow binding (FB) message type. Structure of MH Flow Binding Indication Message [:rfc:`7109#section-6.1.1`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Flow Binding Type = 1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | Trigger |A| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility options . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Structure of MH Flow Binding Acknowledgement Message [:rfc:`7109#section-6.1.2`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Flow Binding Type = 2 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | Status | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility options . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: As with the binding revocation message, one Mobility Header type carries both forms and the ``Flow Binding Type`` field inside the message says which. The ``A`` flag exists only in the indication; the acknowledgement reserves the whole octet it is taken from, so a well-formed acknowledgement reads back as ``ack=False``. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_FlowBindingMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, fb_type=schema.fb_type, seq=schema.seq, code=schema.code, ack=bool(schema.flags['A']), flags_reserved=schema.flags['reserved'], options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_sq(self, schema: 'Schema_SubscriptionQueryMessage', *, header: 'Schema_MH') -> 'Data_SubscriptionQueryMessage': """Read MH subscription query (SQ) message type. Structure of MH Subscription Query Message [:rfc:`7161#section-4.3.1.2`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The sequence number is a single octet, counted modulo 256, unlike the 16-bit ones of most other mobility messages [:rfc:`7161#section-4.3.1.2`]. Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_SubscriptionQueryMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, msg_reserved=schema.reserved, options=self._read_mh_options(schema.options), ) return data
[docs] def _read_msg_sr(self, schema: 'Schema_SubscriptionResponseMessage', *, header: 'Schema_MH') -> 'Data_SubscriptionResponseMessage': """Read MH subscription response (SR) message type. Structure of MH Subscription Response Message [:rfc:`7161#section-4.3.2.2`]: .. code-block:: text +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence # |I| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | . . . Mobility Options . . . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed message type schema. header: Parsed MH header schema. Returns: Parsed message type data. """ data = Data_SubscriptionResponseMessage( next=header.next, length=self._mh_message_length(header.length), type=header.type, reserved=header.reserved, chksum=header.chksum, seq=schema.seq, info=bool(schema.flags['I']), flags_reserved=schema.flags['reserved'], options=self._read_mh_options(schema.options), ) return data
[docs] def _read_opt_none(self, schema: 'Schema_UnassignedOption', *, options: 'Option') -> 'Data_UnassignedOption': """Read MH unassigned option. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ data = Data_UnassignedOption( type=schema.type, length=self._mh_option_length(schema.length), data=schema.data, ) return data
[docs] def _read_opt_pad(self, schema: 'Schema_PadOption', *, options: 'Option') -> 'Data_PadOption': """Read MH padding option. Structure of MH padding option [:rfc:`6275`]: * ``Pad1`` option: .. code-block:: text 0 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ | Type = 0 | +-+-+-+-+-+-+-+-+ * ``PadN`` option: .. code-block:: text 0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - | Type = 1 | Option Length | Option Data +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. Note: A ``Pad1`` option occupies a single octet and carries no ``Option Length`` field, so its :attr:`~pcapkit.protocols.internet.mh.data.Option.length` is ``1`` rather than ``length + 2``. That one-octet wire shape is enforced by :class:`~pcapkit.protocols.internet.mh.schema.PadOption` itself, which sizes both the length octet and the padding data from the option type [:rfc:`6275#section-6.2.2`]; ``clen`` is therefore always ``0`` here for a parsed ``Pad1``, and the length check this method makes only guards a schema built by hand. A ``PadN`` whose ``Option Length`` is ``0`` is a valid two-octet padding option [:rfc:`6275#section-6.2.3`]. """ code, clen = schema.type, schema.length if code not in (Enum_Option.Pad1, Enum_Option.PadN): raise ProtocolError(f'{self.alias}: [OptNo {code}] invalid format') if code == Enum_Option.Pad1 and clen != 0: raise ProtocolError(f'{self.alias}: [OptNo {code}] invalid format') if code == Enum_Option.Pad1: size = 1 else: size = self._mh_option_length(clen) data = Data_PadOption( type=schema.type, length=size, data=schema.data, ) return data
[docs] def _read_opt_bra(self, schema: 'Schema_BindingRefreshAdviceOption', *, options: 'Option') -> 'Data_BindingRefreshAdviceOption': """Read MH binding refresh advice option. Structure of MH Binding Refresh Advice option [:rfc:`6275`]: .. 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 = 2 | Length = 2 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Refresh Interval | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The Refresh Interval is measured in units of four seconds [:rfc:`6275#section-6.2.4`], the same unit the BU/BA lifetime uses, so it is scaled here. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_BindingRefreshAdviceOption( type=schema.type, length=self._mh_option_length(schema.length), interval=datetime.timedelta(seconds=schema.interval * 4), ) return data
[docs] def _read_opt_aca(self, schema: 'Schema_AlternateCareofAddressOption', *, options: 'Option') -> 'Data_AlternateCareofAddressOption': """Read MH alternate care-of address option. Structure of MH Alternate Care-of Address option [:rfc:`6275`]: .. 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 = 3 | Length = 16 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + Alternate Care-of Address + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 16: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_AlternateCareofAddressOption( type=schema.type, length=self._mh_option_length(schema.length), address=schema.address, ) return data
[docs] def _read_opt_ni(self, schema: 'Schema_NonceIndicesOption', *, options: 'Option') -> 'Data_NonceIndicesOption': """Read MH nonce indices option. Structure of MH Nonce Indices option [:rfc:`6275`]: .. 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 = 4 | Length = 4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Home Nonce Index | Care-of Nonce Index | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 4: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_NonceIndicesOption( type=schema.type, length=self._mh_option_length(schema.length), home=schema.home, careof=schema.careof, ) return data
[docs] def _read_opt_bad(self, schema: 'Schema_AuthorizationDataOption', *, options: 'Option') -> 'Data_AuthorizationDataOption': """Read MH binding authorization data option. Structure of MH Binding Authorization Data option [:rfc:`6275`]: .. 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 | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | Authenticator | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length % 8 != 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_AuthorizationDataOption( type=schema.type, length=self._mh_option_length(schema.length), data=schema.data, ) return data
[docs] def _read_opt_mnp(self, schema: 'Schema_MobileNetworkPrefixOption', *, options: 'Option') -> 'Data_MobileNetworkPrefixOption': """Read MH mobile network prefix option. Structure of MH Mobile Network Prefix option [:rfc:`3963`]: .. 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 | Length | Reserved | Prefix Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + Mobile Network Prefix + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 18: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') # NOTE: Prefix and length stay apart, as captured: an IPv6Network holds neither bits past the # length nor a length past 128; RFC 3963 section 6.2 answers a bad prefix with a status (#2702). data = Data_MobileNetworkPrefixOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, prefix_length=schema.prefix_length, prefix=schema.prefix, ) return data
[docs] def _read_opt_lla(self, schema: 'Schema_LinkLayerAddressOption', *, options: 'Option') -> 'Data_LinkLayerAddressOption': """Read MH link-layer address (MH-LLA) option. Structure of MH Link-Layer Address option [:rfc:`5568`]: .. 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 | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option-Code | LLA .... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.code != Enum_LLACode.MN: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_LinkLayerAddressOption( type=schema.type, length=self._mh_option_length(schema.length), code=schema.code, lla=schema.lla, ) return data
[docs] def _read_opt_mn_id(self, schema: 'Schema_MNIDOption', *, options: 'Option') -> 'Data_MNIDOption': """Read MH mobile node identifier option. Structure of MH Mobile Node Identifier option [:rfc:`4283`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Subtype | Identifier ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ data = Data_MNIDOption( type=schema.type, length=self._mh_option_length(schema.length), subtype=schema.subtype, identifier=schema.identifier, ) return data
[docs] def _read_opt_auth(self, schema: 'Schema_AuthOption', *, options: 'Option') -> 'Data_AuthOption': """Read MH mobility message authentication option. Structure of MH Mobility Message Authentication option [:rfc:`4285`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | Subtype | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Mobility SPI | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Authentication Data .... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if (schema.length + 1) % 4 != 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_AuthOption( type=schema.type, length=self._mh_option_length(schema.length), subtype=schema.subtype, spi=schema.spi, data=schema.data, ) return data
[docs] def _read_opt_mesg_id(self, schema: 'Schema_MesgIDOption', *, options: 'Option') -> 'Data_MesgIDOption': """Read MH mobility message replay protection option. Structure of MH Mobility Message Replay Protection option [:rfc:`4285`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp ... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if (schema.length) % 8 != 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_MesgIDOption( type=schema.type, length=self._mh_option_length(schema.length), timestamp=schema.timestamp, ntp_timestamp=Data_NTPTimestamp(seconds=schema.seconds, fraction=schema.fraction), ) return data
[docs] def _read_opt_cga_pr(self, schema: 'Schema_CGAParametersRequestOption', *, options: 'Option') -> 'Data_CGAParametersRequestOption': """Read MH CGA parameters request option. Structure of MH CGA Parameters Request option [:rfc:`4866`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_CGAParametersRequestOption( type=schema.type, length=self._mh_option_length(schema.length), ) return data
[docs] def _read_opt_cga_param(self, schema: 'Schema_CGAParametersOption', *, options: 'Option') -> 'Data_CGAParametersOption': """Read MH CGA parameters option. Structure of MH CGA Parameters option [:rfc:`4866`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | : : : CGA Parameters : : : | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ for param in schema.parameters: if param.collision_count not in (0, 1, 2): raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_CGAParametersOption( type=schema.type, length=self._mh_option_length(schema.length), parameters=tuple(Data_CGAParameter( modifier=param.modifier, prefix=param.prefix, collision_count=param.collision_count, public_key=param.public_key, extensions=self._read_cga_extensions(param.extensions), ) for param in schema.parameters), ) return data
[docs] def _read_opt_signature(self, schema: 'Schema_SignatureOption', *, options: 'Option') -> 'Data_SignatureOption': """Read MH signature option. Structure of MH Signature option [:rfc:`4866`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | : : : Signature : : : | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ data = Data_SignatureOption( type=schema.type, length=self._mh_option_length(schema.length), signature=schema.signature, ) return data
[docs] def _read_opt_phkt(self, schema: 'Schema_PermanentHomeKeygenTokenOption', *, options: 'Option') -> 'Data_PermanentHomeKeygenTokenOption': """Read MH permanent home keygen token option. Structure of MH Permanent Home Keygen Token option [:rfc:`4866`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | : : : Permanent Home Keygen Token : : : | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ data = Data_PermanentHomeKeygenTokenOption( type=schema.type, length=self._mh_option_length(schema.length), token=schema.token, ) return data
[docs] def _read_opt_ct_init(self, schema: 'Schema_CareofTestInitOption', *, options: 'Option') -> 'Data_CareofTestInitOption': """Read MH Care-of Test Init option. Structure of MH Care-of Test Init option [:rfc:`4866`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_CareofTestInitOption( type=schema.type, length=self._mh_option_length(schema.length), ) return data
[docs] def _read_opt_ct(self, schema: 'Schema_CareofTestOption', *, options: 'Option') -> 'Data_CareofTestOption': """Read MH Care-of Test option. Structure of MH Care-of Test option [:rfc:`4866`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Care-of Keygen Token + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 8: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_CareofTestOption( type=schema.type, length=self._mh_option_length(schema.length), token=schema.token, ) return data
[docs] def _read_opt_dns(self, schema: 'Schema_DNSUpdateOption', *, options: 'Option') -> 'Data_DNSUpdateOption': """Read MH DNS-UPDATE-TYPE option. Structure of MH DNS-UPDATE-TYPE option [:rfc:`5026#section-8.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Status |R| Reserved | MN identity (FQDN) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The status field draws from the *Status Codes (DNS Update Mobility Option)* registry that :rfc:`5026#section-10` creates, **not** from the general mobility status codes, which is why it is typed :class:`~pcapkit.const.mh.dns_status_code.DNSStatusCode`. The identity is kept as :obj:`bytes` rather than decoded to :class:`str`, because the RFC says only "FQDN format" without saying whether that means the presentation form or the length-prefixed label form of :rfc:`1035`. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_DNSUpdateOption( type=schema.type, length=self._mh_option_length(schema.length), status=schema.status, remove=bool(schema.flags['R']), reserved=schema.flags['reserved'], identity=schema.identity, ) return data
[docs] def _read_opt_exp(self, schema: 'Schema_ExperimentalMobilityOption', *, options: 'Option') -> 'Data_ExperimentalMobilityOption': """Read MH experimental mobility option. Structure of MH Experimental Mobility option [:rfc:`5096#section-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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Data ..... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ data = Data_ExperimentalMobilityOption( type=schema.type, length=self._mh_option_length(schema.length), data=schema.data, ) return data
[docs] def _read_opt_vendor(self, schema: 'Schema_VendorSpecificOption', *, options: 'Option') -> 'Data_VendorSpecificOption': """Read MH vendor specific mobility option. Structure of MH Vendor Specific mobility option [:rfc:`5094#section-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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Vendor ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sub-Type | Data....... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The vendor ID is an SMI Network Management Private Enterprise Number, whose space is unbounded, so it is not enumerated. The sub-type is administered by that vendor rather than by IANA, so it is not enumerated either. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 5: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_VendorSpecificOption( type=schema.type, length=self._mh_option_length(schema.length), vendor=schema.vendor, subtype=schema.subtype, data=schema.data, ) return data
[docs] def _read_opt_service(self, schema: 'Schema_ServiceSelectionOption', *, options: 'Option') -> 'Data_ServiceSelectionOption': """Read MH service selection mobility option. Structure of MH Service Selection mobility option [:rfc:`5149#section-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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type = 20 | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identifier... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: Unlike the DNS identity of :meth:`_read_opt_dns`, this identifier is decoded to :class:`str`: :rfc:`5149#section-3` states outright that it is UTF-8 and NFKC-normalised. A length of zero is invalid. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length == 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_ServiceSelectionOption( type=schema.type, length=self._mh_option_length(schema.length), identifier=schema.identifier, ) return data
[docs] def _read_opt_badf(self, schema: 'Schema_BADFOption', *, options: 'Option') -> 'Data_BADFOption': """Read MH binding authorization data for FMIPv6 (BADF) option. Structure of MH Binding Authorization Data for FMIPv6 option [:rfc:`5568#section-6.4.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 = 21 | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SPI | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | Authenticator | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`5568#section-6.4.5` defines the option length as *the length of the Authenticator in bytes*, i.e. it does **not** cover the 4-byte SPI, unlike every other mobility option, whose length covers all of its option data. The wording is inherited from the :rfc:`6275` binding authorization data option (type ``5``), which carries no SPI and for which the two readings coincide. Because :rfc:`5568` also requires this option to be the **last** mobility option present, the discrepancy never has to be resolved in order to find the following option, so the literal reading is used here and the reported :attr:`~pcapkit.protocols.internet.mh.data.Option.length` accounts for the extra 4 bytes. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length == 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_BADFOption( type=schema.type, length=schema.length + 6, # 2 bytes for type & length, 4 bytes for SPI spi=schema.spi, data=schema.data, ) return data
[docs] def _read_opt_hnp(self, schema: 'Schema_HomeNetworkPrefixOption', *, options: 'Option') -> 'Data_HomeNetworkPrefixOption': """Read MH home network prefix option. Structure of MH Home Network Prefix option [:rfc:`5213#section-8.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Reserved | Prefix Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + Home Network Prefix + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 18: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') # NOTE: A prefix length past 128 is kept, as for option 6: RFC 5213 section 5.3.2 answers an # unauthorisable prefix with a status rather than discarding the option (#2702). data = Data_HomeNetworkPrefixOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, prefix_length=schema.prefix_length, prefix=schema.prefix, ) return data
[docs] def _read_opt_hi(self, schema: 'Schema_HandoffIndicatorOption', *, options: 'Option') -> 'Data_HandoffIndicatorOption': """Read MH handoff indicator option. Structure of MH Handoff Indicator option [:rfc:`5213#section-8.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Reserved (R) | HI | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_HandoffIndicatorOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, hi=schema.hi, ) return data
[docs] def _read_opt_att(self, schema: 'Schema_AccessTechnologyTypeOption', *, options: 'Option') -> 'Data_AccessTechnologyTypeOption': """Read MH access technology type option. Structure of MH Access Technology Type option [:rfc:`5213#section-8.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 | Length | Reserved (R) | ATT | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`5213` itself defines only access types ``0`` through ``5``. Values ``6`` through ``13`` were registered directly with IANA against 3GPP and 3GPP2 specifications rather than through an updating RFC, and :class:`~pcapkit.const.mh.access_type.AccessType` carries all of them. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_AccessTechnologyTypeOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, att=schema.att, ) return data
[docs] def _read_opt_mn_lli(self, schema: 'Schema_MNLLIdentifierOption', *, options: 'Option') -> 'Data_MNLLIdentifierOption': """Read MH mobile node link-layer identifier option. Structure of MH Mobile Node Link-layer Identifier option [:rfc:`5213#section-8.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Link-layer Identifier + . ... . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The identifier's octet and bit ordering is that of :rfc:`4861#section-4.6`, but only the *address encoding* is borrowed from there -- the option keeps its own mobility-option framing, in which the length is in octets rather than in the 8-octet units neighbour discovery uses. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_MNLLIdentifierOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, lli=schema.lli, ) return data
[docs] def _read_opt_lla_addr(self, schema: 'Schema_LinkLocalAddressOption', *, options: 'Option') -> 'Data_LinkLocalAddressOption': """Read MH link-local address option. Structure of MH Link-local Address option [:rfc:`5213#section-8.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 | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + Link-local Address + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`6543` reserves ``0200:5EFF:FE00:5213`` as the interface identifier a mobile access gateway may use here when it does not vary the address per access link. That changes which values are expected, not the option's shape. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 16: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_LinkLocalAddressOption( type=schema.type, length=self._mh_option_length(schema.length), address=schema.address, ) return data
[docs] def _read_opt_timestamp(self, schema: 'Schema_TimestampOption', *, options: 'Option') -> 'Data_TimestampOption': """Read MH timestamp option. Structure of MH Timestamp option [:rfc:`5213#section-8.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 | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Timestamp + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: This is **not** the :rfc:`1305` NTP timestamp that :meth:`_read_opt_mesg_id` reads, and the two must not be conflated. :rfc:`5213#section-8.8` counts seconds from the UNIX epoch, not NTP's 1900 one, and splits the 64 bits 48/16 rather than 32/32. Reading it as an NTP timestamp would be wrong in both the epoch and the field widths, which is why it gets its own :class:`~pcapkit.protocols.internet.mh.data.PMIPv6Timestamp`. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 8: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') seconds = schema.timestamp['seconds'] fraction = schema.timestamp['fraction'] try: # NOTE: Not the stdlib's, which stops at 3000 on Windows (#2099); the whole seconds # go in as an int, so the fraction cannot carry the last second of 9999 past it. timestamp = (fromtimestamp(seconds, datetime.timezone.utc) + datetime.timedelta(seconds=fraction / 65536)) # type: Optional[dt_type] except ValueError: # past the year 9999, which the 48-bit count reaches and datetime # cannot hold, so pmip_timestamp alone keeps it (#2702) timestamp = None data = Data_TimestampOption( type=schema.type, length=self._mh_option_length(schema.length), timestamp=timestamp, pmip_timestamp=Data_PMIPv6Timestamp(seconds=seconds, fraction=fraction), ) return data
[docs] def _read_opt_restart(self, schema: 'Schema_RestartCounterOption', *, options: 'Option') -> 'Data_RestartCounterOption': """Read MH restart counter option. Structure of MH Restart Counter option [:rfc:`5847#section-3.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Restart Counter | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`5847#section-3.4` says this option is valid only in a heartbeat *response*. That is a constraint on the sender rather than on the wire format, so it is not enforced here -- a capture carries whatever it carries. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 4: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_RestartCounterOption( type=schema.type, length=self._mh_option_length(schema.length), counter=schema.counter, ) return data
[docs] def _read_opt_ipv4_hoa(self, schema: 'Schema_IPv4HomeAddressOption', *, options: 'Option') -> 'Data_IPv4HomeAddressOption': """Read MH IPv4 home address option. Structure of MH IPv4 Home Address option [:rfc:`5555#section-3.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 | Length |Prefix-len |P| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IPv4 home address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: A prefix length of ``32`` means a single address rather than a prefix, and ``0`` is invalid [:rfc:`5555#section-3.1.1`]. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') prefix_length = schema.flags['prefix_length'] if prefix_length == 0 or prefix_length > 32: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_IPv4HomeAddressOption( type=schema.type, length=self._mh_option_length(schema.length), prefix_length=prefix_length, address=schema.address, request_prefix=bool(schema.flags['P']), reserved=schema.flags['reserved'], ) return data
[docs] def _read_opt_ipv4_ack(self, schema: 'Schema_IPv4AddressAcknowledgementOption', *, options: 'Option') -> 'Data_IPv4AddressAcknowledgementOption': """Read MH IPv4 address acknowledgement option. Structure of MH IPv4 Address Acknowledgement option [:rfc:`5555#section-3.2.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 | Length | Status |Pref-len |Res| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IPv4 home address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The status field draws from the *DSMIPv6 IPv4 Home Address Option Status Codes* registry of :rfc:`5555#section-8`, which despite its name governs **this** option rather than the IPv4 home address option. It is not the registry the similarly shaped IPv4 Home Address Reply option of :rfc:`5844` uses: that one has no value ``133``. Nor is it the pseudo-home-address registry of :rfc:`5726` that :class:`~pcapkit.const.mh.ack_status_code.ACKStatusCode` carries, whose name is the most obvious trap of these. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_IPv4AddressAcknowledgementOption( type=schema.type, length=self._mh_option_length(schema.length), status=schema.status, prefix_length=schema.flags['prefix_length'], reserved=schema.flags['reserved'], address=schema.address, ) return data
[docs] def _read_opt_nat(self, schema: 'Schema_NATDetectionOption', *, options: 'Option') -> 'Data_NATDetectionOption': """Read MH NAT detection option. Structure of MH NAT Detection option [:rfc:`5555#section-3.2.2`]: .. 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 | Length |F| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Refresh time | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: A refresh time of ``0`` means the field is to be ignored, and all ones that no keep-alives are needed [:rfc:`5555#section-3.2.2`]. Both are representable as a :class:`~datetime.timedelta`, so the raw value is recoverable from it without being stored twice. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_NATDetectionOption( type=schema.type, length=self._mh_option_length(schema.length), force=bool(schema.flags['F']), reserved=schema.flags['reserved'], refresh=datetime.timedelta(seconds=schema.refresh), ) return data
[docs] def _read_opt_ipv4_coa(self, schema: 'Schema_IPv4CareofAddressOption', *, options: 'Option') -> 'Data_IPv4CareofAddressOption': """Read MH IPv4 care-of address option. Structure of MH IPv4 Care-of Address option [:rfc:`5555#section-3.1.2`]: .. 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 | Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IPv4 Care-of address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: Unlike the IPv4 home address option, the whole half-word before the address is reserved -- there is no prefix length and no ``P`` flag carved out of it. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_IPv4CareofAddressOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, address=schema.address, ) return data
[docs] def _read_opt_gre(self, schema: 'Schema_GREKeyOption', *, options: 'Option') -> 'Data_GREKeyOption': """Read MH GRE key option. Structure of MH GRE Key option [:rfc:`5845#section-6.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 | Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | GRE Key Identifier | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The key identifier is present only when the option length is ``6``; a length of ``2`` says the option carries none [:rfc:`5845#section-6.1`]. No flag announces that, so the length is the only thing to branch on, and any other length is malformed. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length not in (2, 6): raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_GREKeyOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, key=schema.key if schema.length == 6 else None, ) return data
[docs] def _read_opt_ipv6_ap(self, schema: 'Schema_IPv6AddressPrefixOption', *, options: 'Option') -> 'Data_IPv6AddressPrefixOption': """Read MH mobility header IPv6 address/prefix option. Structure of MH Mobility Header IPv6 Address/Prefix option [:rfc:`5568#section-6.4.2`]: .. 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 | Length | Option-Code | Prefix Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + IPv6 Address/Prefix + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ The option code identifies which address the option carries, see :class:`~pcapkit.protocols.internet.mh.protocol.IPv6AddressPrefixCode`. It, too, is defined inline by :rfc:`5568#section-6.4.2` with no IANA registry behind it, hence the enumeration is local to this module. Note: :rfc:`5568` prints the type as ``17``, which is the *neighbor discovery* option type of its sibling in :rfc:`5568#section-6.4.1`. Errata ID 1816 (verified) corrects it to the IANA-assigned mobility option type ``34``, which is what this handler is registered against. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 18: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') if schema.prefix_length > 128: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_IPv6AddressPrefixOption( type=schema.type, length=self._mh_option_length(schema.length), code=IPv6AddressPrefixCode(schema.code), prefix_length=schema.prefix_length, address=schema.address, ) return data
[docs] def _read_opt_bid(self, schema: 'Schema_BindingIdentifierOption', *, options: 'Option') -> 'Data_BindingIdentifierOption': """Read MH binding identifier (BID) option. Structure of MH Binding Identifier option [:rfc:`5648#section-4.3`, as updated by :rfc:`6089#section-4.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 = 35 | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Binding ID (BID) | Status |H| BID-PRI | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------------------------+ + + : IPv4 or IPv6 Care-of Address (CoA) : + + +---------------------------------------------------------------+ Note: The care-of address is absent for a length of ``4``, an IPv4 address for ``8`` and an IPv6 address for ``20``; no other length is valid and the option carries no address-family flag, so the length is the only thing to branch on [:rfc:`5648#section-4.3`]. :rfc:`6089#section-4.1` renames the seven bits below the ``H`` flag from ``Reserved`` to ``BID-PRI``, a binding priority in which ``0`` marks a sender predating :rfc:`6089`. The newer reading is used, since a zero reads identically either way. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length not in (4, 8, 20): raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_BindingIdentifierOption( type=schema.type, length=self._mh_option_length(schema.length), bid=schema.bid, status=schema.status, simultaneous=bool(schema.flags['H']), bid_pri=schema.flags['BID_PRI'], address=cast('Optional[IPv4Address | IPv6Address]', schema.address), ) return data
[docs] def _read_opt_ipv4_hoa_req(self, schema: 'Schema_IPv4HomeAddressRequestOption', *, options: 'Option') -> 'Data_IPv4HomeAddressRequestOption': """Read MH IPv4 home address request option. Structure of MH IPv4 Home Address Request option [:rfc:`5844#section-3.3.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 | Length |Prefix-len | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IPv4 home address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: Unlike the IPv4 home address option of :rfc:`5555#section-3.1.1`, which this otherwise resembles, there is no ``P`` flag -- the reserved field is 10 bits rather than 9. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_IPv4HomeAddressRequestOption( type=schema.type, length=self._mh_option_length(schema.length), prefix_length=schema.flags['prefix_length'], reserved=schema.flags['reserved'], address=schema.address, ) return data
[docs] def _read_opt_ipv4_hoa_rep(self, schema: 'Schema_IPv4HomeAddressReplyOption', *, options: 'Option') -> 'Data_IPv4HomeAddressReplyOption': """Read MH IPv4 home address reply option. Structure of MH IPv4 Home Address Reply option [:rfc:`5844#section-3.3.2`]: .. 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 | Length | Status |Pref-len |Res| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IPv4 home address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The status draws from the *IPv4 Home Address Reply Status Codes* registry of :rfc:`5844#section-6`, which is **not** the DSMIPv6 one the identically shaped IPv4 address acknowledgement option of :rfc:`5555` uses -- that registry additionally defines ``133``. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_IPv4HomeAddressReplyOption( type=schema.type, length=self._mh_option_length(schema.length), status=schema.status, prefix_length=schema.flags['prefix_length'], reserved=schema.flags['reserved'], address=schema.address, ) return data
[docs] def _read_opt_ipv4_router(self, schema: 'Schema_IPv4DefaultRouterAddressOption', *, options: 'Option') -> 'Data_IPv4DefaultRouterAddressOption': """Read MH IPv4 default-router address option. Structure of MH IPv4 Default-Router Address option [:rfc:`5844#section-3.3.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Reserved (R) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | IPv4 Default-Router Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_IPv4DefaultRouterAddressOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, address=schema.address, ) return data
[docs] def _read_opt_ipv4_dhcp(self, schema: 'Schema_IPv4DHCPSupportModeOption', *, options: 'Option') -> 'Data_IPv4DHCPSupportModeOption': """Read MH IPv4 DHCP support mode option. Structure of MH IPv4 DHCP Support Mode option [:rfc:`5844#section-3.3.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Reserved (R) |S| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The ``S`` bit is the **last** bit of the option, not the first -- the reserved field precedes it rather than following it, which is the other way round from most flags in this module. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_IPv4DHCPSupportModeOption( type=schema.type, length=self._mh_option_length(schema.length), mode=Enum_DHCPSupportMode(schema.flags['S']), reserved=schema.flags['reserved'], ) return data
[docs] def _read_opt_cr(self, schema: 'Schema_ContextRequestOption', *, options: 'Option') -> 'Data_ContextRequestOption': """Read MH context request option. Structure of MH Context Request option [:rfc:`5949#section-6.2.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 +---------------+---------------+---------------+---------------+ | Option-Type | Option-Length | Reserved | +---------------+---------------+-------------------------------+ | Req-type-1 | Req-length-1 | Req-type-2 | Req-length-2 | +---------------------------------------------------------------+ | Req-type-3 | Req-length-3 | Req-option-3 | +---------------------------------------------------------------+ | ... | Note: The requested types are drawn from the mobility option registry itself, so a request for a vendor specific option (type ``19``) is followed by five octets naming the vendor and sub-type, whereas a request for a home network prefix (type ``22``) carries a request length of zero. There is neither a count field nor a terminator: the list runs to the end of the option. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') requests = [] # type: list[Data_ContextRequest] buffer = schema.requests index = 0 while index < len(buffer): if index + 2 > len(buffer): raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') req_type, req_len = buffer[index], buffer[index + 1] value = buffer[index + 2:index + 2 + req_len] if len(value) != req_len: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') requests.append(Data_ContextRequest( type=Enum_Option.get(req_type), length=req_len, value=value, )) index += 2 + req_len data = Data_ContextRequestOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, requests=tuple(requests), ) return data
[docs] def _read_opt_lmaa(self, schema: 'Schema_LMAAddressOption', *, options: 'Option') -> 'Data_LMAAddressOption': """Read MH local mobility anchor address option. Structure of MH Local Mobility Anchor Address option [:rfc:`5949#section-6.2.2`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option-Type | Option-Length | Option-Code | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Local Mobility Anchor Address ... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ The option codes are enumerated in :class:`~pcapkit.protocols.internet.mh.protocol.LMAAddressCode`, which is local to this module because :rfc:`5949` defines them inline with no IANA registry behind them. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length not in (6, 18): raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_LMAAddressOption( type=schema.type, length=self._mh_option_length(schema.length), code=LMAAddressCode(schema.code), reserved=schema.reserved, address=schema.address, ) return data
[docs] def _read_opt_mn_lla_iid(self, schema: 'Schema_MNLLAIIDOption', *, options: 'Option') -> 'Data_MNLLAIIDOption': """Read MH mobile node link-local address interface identifier option. Structure of MH Mobile Node Link-local Address Interface Identifier option [:rfc:`5949#section-6.2.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option-Type | Option-Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Interface Identifier + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 10: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_MNLLAIIDOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, iid=schema.iid, ) return data
[docs] def _read_opt_transient(self, schema: 'Schema_TransientBindingOption', *, options: 'Option') -> 'Data_TransientBindingOption': """Read MH transient binding option. Structure of MH Transient Binding option [:rfc:`6058#section-5.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 | Length | Reserved |L| Lifetime | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The lifetime is in units of **100 milliseconds**, which is unlike every other lifetime in this module [:rfc:`6058#section-5.1`]. The ``L`` flag is the last bit of the third octet, with the seven reserved bits above rather than below it. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_TransientBindingOption( type=schema.type, length=self._mh_option_length(schema.length), late=bool(schema.flags['L']), reserved=schema.flags['reserved'], lifetime=datetime.timedelta(milliseconds=schema.lifetime * 100), ) return data
[docs] def _read_opt_fs(self, schema: 'Schema_FlowSummaryOption', *, options: 'Option') -> 'Data_FlowSummaryOption': """Read MH flow summary mobility option. Structure of MH Flow Summary mobility option [:rfc:`6089#section-4.2.2`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Len | FID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | FID ........ +-+-+-+-+-+-+-+-+-+-+- Note: :rfc:`6089#section-8` prints the type codes of this option and the flow identification option the wrong way round, contradicting both its own figures and the IANA registry. The figures and the registry agree that the flow summary option is type ``44``, which is what is registered here. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length % 2 != 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_FlowSummaryOption( type=schema.type, length=self._mh_option_length(schema.length), fid=tuple(schema.fid), ) return data
[docs] def _read_opt_fid(self, schema: 'Schema_FlowIdentificationOption', *, options: 'Option') -> 'Data_FlowIdentificationOption': """Read MH flow identification mobility option. Structure of MH Flow Identification mobility option [:rfc:`6089#section-4.2`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Len | FID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | FID-PRI | Reserved | Status | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sub-options (optional) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The status draws from the *Flow Identification Mobility Option Status Codes* registry, which is neither the general mobility status codes nor the flow binding acknowledgement ones -- similarly named registries that must not be confused. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_FlowIdentificationOption( type=schema.type, length=self._mh_option_length(schema.length), fid=schema.fid, fid_pri=schema.fid_pri, reserved=schema.reserved, status=schema.status, suboptions=self._read_fid_suboptions(schema.suboptions), ) return data
[docs] def _read_opt_rc(self, schema: 'Schema_RedirectCapabilityOption', *, options: 'Option') -> 'Data_RedirectCapabilityOption': """Read MH redirect-capability mobility option. Structure of MH Redirect-Capability mobility option [:rfc:`6463#section-4.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_RedirectCapabilityOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, ) return data
[docs] def _read_opt_redirect(self, schema: 'Schema_RedirectOption', *, options: 'Option') -> 'Data_RedirectOption': """Read MH redirect mobility option. Structure of MH Redirect mobility option [:rfc:`6463#section-4.2`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length |K|N| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | Optional IPv6 r2LMA Address | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Optional IPv4 r2LMA Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`6463#section-4.2` forbids ``K`` and ``N`` from being both set and both clear, so exactly one address is present and the option length is 18 or 6 accordingly. The two encodings of the same fact are checked against each other here, since a disagreement means the option cannot be read either way. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ ipv6_flag = bool(schema.flags['K']) ipv4_flag = bool(schema.flags['N']) if ipv6_flag == ipv4_flag: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') if schema.length != (18 if ipv6_flag else 6): raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_RedirectOption( type=schema.type, length=self._mh_option_length(schema.length), ipv6=schema.ipv6, ipv4=schema.ipv4, reserved=schema.flags['reserved'], ) return data
[docs] def _read_opt_load(self, schema: 'Schema_LoadInformationOption', *, options: 'Option') -> 'Data_LoadInformationOption': """Read MH load information mobility option. Structure of MH Load Information mobility option [:rfc:`6463#section-4.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | Priority | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sessions in Use | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Maximum Sessions | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Used Capacity | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Maximum Capacity | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The two capacity fields are in **kilobytes** per second, unlike the quality-of-service bit rates, which are in bits per second [:rfc:`6463#section-4.3`]. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 18: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_LoadInformationOption( type=schema.type, length=self._mh_option_length(schema.length), priority=schema.priority, sessions_in_use=schema.sessions_in_use, max_sessions=schema.max_sessions, used_capacity=schema.used_capacity, max_capacity=schema.max_capacity, ) return data
[docs] def _read_opt_alt_ipv4_coa(self, schema: 'Schema_AlternateIPv4CareofAddressOption', *, options: 'Option') -> 'Data_AlternateIPv4CareofAddressOption': """Read MH alternate IPv4 care-of address option. Structure of MH Alternate IPv4 Care-of Address option [:rfc:`6463#section-4.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 | Option Type | Option Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Alternate IPv4 Care-of Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 4: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_AlternateIPv4CareofAddressOption( type=schema.type, length=self._mh_option_length(schema.length), address=schema.address, ) return data
[docs] def _read_opt_mn_group(self, schema: 'Schema_MNGroupIdentifierOption', *, options: 'Option') -> 'Data_MNGroupIdentifierOption': """Read MH mobile node group identifier option. Structure of MH Mobile Node Group Identifier option [:rfc:`6602#section-4.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Sub-type | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Mobile Node Group Identifier | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_MNGroupIdentifierOption( type=schema.type, length=self._mh_option_length(schema.length), subtype=schema.subtype, reserved=schema.reserved, group_id=schema.group_id, ) return data
[docs] def _read_opt_mag_addr(self, schema: 'Schema_MAGIPv6AddressOption', *, options: 'Option') -> 'Data_MAGIPv6AddressOption': """Read MH MAG IPv6 address option. Structure of MH MAG IPv6 Address option [:rfc:`6705#section-11.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 | Length | Reserved | Address Length| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + MAG IPv6 Address + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The option is modelled on the home network prefix option, which is why it carries an address *length* field at all; :rfc:`6705#section-11.1` requires it to be ``128``, since a full address is always carried. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 18: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_MAGIPv6AddressOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, address_length=schema.address_length, address=schema.address, ) return data
[docs] def _read_opt_ani(self, schema: 'Schema_AccessNetworkIdentifierOption', *, options: 'Option') -> 'Data_AccessNetworkIdentifierOption': """Read MH access network identifier option. Structure of MH Access Network Identifier option [:rfc:`6757#section-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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ... ANI Sub-option(s) ... ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: Unlike most options carrying sub-options, this one has **no** reserved octet between its own length and the first sub-option [:rfc:`6757#section-3`], and it must carry at least one. IANA names the sub-option registry "Access Network *Information*" while the RFC's prose says "Identifier"; they are the same registry. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length == 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_AccessNetworkIdentifierOption( type=schema.type, length=self._mh_option_length(schema.length), suboptions=self._read_ani_suboptions(schema.suboptions), ) return data
[docs] def _read_opt_offload(self, schema: 'Schema_IPv4TrafficOffloadSelectorOption', *, options: 'Option') -> 'Data_IPv4TrafficOffloadSelectorOption': """Read MH IPv4 traffic offload selector option. Structure of MH IPv4 Traffic Offload Selector option [:rfc:`6909#section-3.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 | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |M| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Traffic Selector Sub-option ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The traffic selector it carries is the *flow identification* sub-option of :rfc:`6089#section-4.2.1.4`, so it is read through the same sub-option registry rather than through one of its own. It is optional in a proxy binding update and mandatory in a proxy binding acknowledgement, hence a collection rather than a single value. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 4: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_IPv4TrafficOffloadSelectorOption( type=schema.type, length=self._mh_option_length(schema.length), mode=bool(schema.flags['M']), reserved=schema.flags['reserved'], selector=self._read_fid_suboptions(schema.selector), ) return data
[docs] def _read_opt_mcast_sel(self, schema: 'Schema_DynamicIPMulticastSelectorOption', *, options: 'Option') -> 'Data_DynamicIPMulticastSelectorOption': """Read MH dynamic IP multicast selector option. Structure of MH Dynamic IP Multicast Selector option [:rfc:`7028#section-5.1.2`]: .. 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 | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Protocol |M| Reserved |Nr of Mcast Address Records (N)| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Multicast Address Record [1] + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Multicast Address Record [N] + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The address records are :rfc:`3810#section-5.2` MLD or :rfc:`3376#section-4.2` IGMP structures. They belong to those protocols rather than to the mobility header, and each is self-describing through its own auxiliary-data length and source count, so they are recorded opaquely here rather than half-decoded. :attr:`~pcapkit.protocols.internet.mh.data.DynamicIPMulticastSelectorOption.protocol` says which of the two they are: ``143`` for MLDv2 and ``131`` for MLDv1, or ``0x12``, ``0x16`` and ``0x22`` for IGMPv1 to IGMPv3 [:rfc:`7028#section-8`]. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 4: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_DynamicIPMulticastSelectorOption( type=schema.type, length=self._mh_option_length(schema.length), protocol=schema.protocol, mode=bool(schema.flags['M']), reserved=schema.flags['reserved'], records=schema.records, data=schema.data, ) return data
[docs] def _read_opt_dmnp(self, schema: 'Schema_DelegatedMNPOption', *, options: 'Option') -> 'Data_DelegatedMNPOption': """Read MH delegated mobile network prefix option. Structure of MH Delegated Mobile Network Prefix option [:rfc:`7148#section-4.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 | Length |V| Reserved | Prefix Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | . . + IPv4 or IPv6 Delegated Mobile Network Prefix + | (DMNP) | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: Unlike the binding identifier and target care-of address, this option *does* carry an address-family flag, so the ``V`` bit rather than the option length is what selects the prefix width [:rfc:`7148#section-4.1`]. The two are checked against each other, since a disagreement leaves the option unreadable. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ ipv4 = bool(schema.flags['V']) if schema.length != (6 if ipv4 else 18): raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_DelegatedMNPOption( type=schema.type, length=self._mh_option_length(schema.length), ipv4=ipv4, reserved=schema.flags['reserved'], prefix_length=schema.prefix_length, prefix=schema.prefix, ) return data
[docs] def _read_opt_ams4(self, schema: 'Schema_ActiveMulticastSubscriptionIPv4Option', *, options: 'Option') -> 'Data_ActiveMulticastSubscriptionIPv4Option': """Read MH active multicast subscription IPv4 option. Structure of MH Active Multicast Subscription IPv4 option [:rfc:`7161#section-6.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 | Length | IGMP Type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Multicast Membership Context + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The membership context is an IGMP group address (:rfc:`1112`, :rfc:`2236`) or an IGMPv3 group record (:rfc:`3376#section-4.2`), which belong to IGMP rather than to the mobility header, so they are recorded opaquely. :attr:`~pcapkit.protocols.internet.mh.data.ActiveMulticastSubscriptionIPv4Option.igmp_type` says which: ``0x12`` IGMPv1, ``0x16`` IGMPv2, ``0x22`` IGMPv3. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 1: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_ActiveMulticastSubscriptionIPv4Option( type=schema.type, length=self._mh_option_length(schema.length), igmp_type=schema.igmp_type, context=schema.context, ) return data
[docs] def _read_opt_ams6(self, schema: 'Schema_ActiveMulticastSubscriptionIPv6Option', *, options: 'Option') -> 'Data_ActiveMulticastSubscriptionIPv6Option': """Read MH active multicast subscription IPv6 option. Structure of MH Active Multicast Subscription IPv6 option [:rfc:`7161#section-4.1.2`]: .. 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 | Length | MLD Type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Multicast Membership Context + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: For MLDv2 the context is an :rfc:`3810#section-5.2` multicast address record; for MLDv1 it is a reserved word followed by a multicast address [:rfc:`7161#section-4.1.3`]. Both belong to MLD, so the context is recorded opaquely and :attr:`~pcapkit.protocols.internet.mh.data.ActiveMulticastSubscriptionIPv6Option.mld_type` says which form it is in. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 1: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_ActiveMulticastSubscriptionIPv6Option( type=schema.type, length=self._mh_option_length(schema.length), mld_type=schema.mld_type, context=schema.context, ) return data
[docs] def _read_opt_qos(self, schema: 'Schema_QualityOfServiceOption', *, options: 'Option') -> 'Data_QualityOfServiceOption': """Read MH quality-of-service option. Structure of MH Quality-of-Service option [:rfc:`7222#section-4.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 | Length | SR-ID | TC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | OC | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ QoS Attribute(s) ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The traffic class octet is a 6-bit differentiated services code point with two reserved bits below it, which the receiver ignores [:rfc:`7222#section-4.1`]; both are carried into the data model, so that the octet rebuilds as captured. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. .. versionchanged:: 1.6.0 The two reserved bits of the traffic class are kept, as ``tc_reserved``, rather than dropped (:issue:`2684`). """ if schema.length < 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_QualityOfServiceOption( type=schema.type, length=self._mh_option_length(schema.length), sr_id=schema.sr_id, dscp=schema.tc >> 2, tc_reserved=schema.tc & 0b11, oc=schema.oc, reserved=schema.reserved, attributes=self._read_qos_attributes(schema.attributes), ) return data
[docs] def _read_opt_lma_up(self, schema: 'Schema_LMAUserPlaneAddressOption', *, options: 'Option') -> 'Data_LMAUserPlaneAddressOption': """Read MH LMA user-plane address option. Structure of MH LMA User-Plane Address option [:rfc:`7389#section-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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | . . + LMA User-Plane Address + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The address may legitimately be **absent**: a mobile access gateway sends the option with no address, or with an all-zero one, purely to say which transport it wants [:rfc:`7389#section-4`]. Only a local mobility anchor's reply is required to carry a real address. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length not in (2, 6, 18): raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_LMAUserPlaneAddressOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, address=cast('Optional[IPv4Address | IPv6Address]', schema.address if schema.length != 2 else None), ) return data
[docs] def _read_opt_mcast(self, schema: 'Schema_MulticastMobilityOption', *, options: 'Option') -> 'Data_MulticastMobilityOption': """Read MH multicast mobility option. Structure of MH Multicast Mobility option [:rfc:`7411#section-5.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Option-Code | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | MLD or IGMP Report Payload | ~ ~ | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: This option's length field is **not** the usual octet count. :rfc:`7411#section-5.3` measures it in 32-bit *words* and excludes the option code and reserved octets as well as the type and length ones, so the option occupies ``4 + length * 4`` octets rather than ``length + 2``. The option-collection machinery advances by the octets the schema actually consumed rather than by the declared length, so the unusual unit does not misframe the options that follow -- but it does mean :attr:`~pcapkit.protocols.internet.mh.data.Option.length` here is the true octet count, computed rather than copied. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ data = Data_MulticastMobilityOption( type=schema.type, length=schema.length * 4 + 4, code=schema.code, reserved=schema.reserved, data=schema.data, ) return data
[docs] def _read_opt_mcast_ack(self, schema: 'Schema_MulticastAcknowledgementOption', *, options: 'Option') -> 'Data_MulticastAcknowledgementOption': """Read MH multicast acknowledgement option. Structure of MH Multicast Acknowledgement option [:rfc:`7411#section-5.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Option-Code | Status | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | MLD or IGMP Unsupported Report Payload | ~ ~ | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: As with :meth:`_read_opt_mcast`, the length counts 32-bit words and excludes the option code and status octets [:rfc:`7411#section-5.4`]. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ data = Data_MulticastAcknowledgementOption( type=schema.type, length=schema.length * 4 + 4, code=schema.code, status=schema.status, data=schema.data, ) return data
[docs] def _read_opt_lcmp(self, schema: 'Schema_LMAControlledMAGParametersOption', *, options: 'Option') -> 'Data_LMAControlledMAGParametersOption': """Read MH LMA-controlled MAG parameters option. Structure of MH LMA-Controlled MAG Parameters option [:rfc:`8127#section-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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | LCMP Sub-Option(s) ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: This option is registered to :rfc:`8127`, **not** to :rfc:`7864` -- the latter defines the flow-mobility sub-options and the ``FLOW-MOBILITY`` update notification reason instead. The IANA registry is the authority, and it cites :rfc:`8127`. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length == 0: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_LMAControlledMAGParametersOption( type=schema.type, length=self._mh_option_length(schema.length), suboptions=self._read_lcmp_suboptions(schema.suboptions), ) return data
[docs] def _read_opt_mag_mp(self, schema: 'Schema_MAGMultipathBindingOption', *, options: 'Option') -> 'Data_MAGMultipathBindingOption': """Read MH MAG multipath binding option. Structure of MH MAG Multipath Binding option [:rfc:`8278#section-4.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 | Length | If-ATT | If-Label | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Binding ID |B|O| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`8278#section-4.1` makes the ``B`` and ``O`` flags mutually exclusive -- neither may be set while the other is -- and reserves binding identifiers ``0`` and ``255``. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 6: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') bulk = bool(schema.flags['B']) overwrite = bool(schema.flags['O']) if bulk and overwrite: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_MAGMultipathBindingOption( type=schema.type, length=self._mh_option_length(schema.length), att=schema.att, label=schema.label, bid=schema.bid, bulk=bulk, overwrite=overwrite, reserved=schema.flags['reserved'], ) return data
[docs] def _read_opt_mag_id(self, schema: 'Schema_MAGIdentifierOption', *, options: 'Option') -> 'Data_MAGIdentifierOption': """Read MH MAG identifier option. Structure of MH MAG Identifier option [:rfc:`8278#section-4.2`]: .. 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 | Length | Subtype | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identifier ... ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The sub-type is borrowed from the *mobile node* identifier subtype registry [:rfc:`4283`, :rfc:`8371`], so the identifier's encoding is whatever that subtype prescribes. :rfc:`8278` does not restate those encodings, so the identifier is kept as :obj:`bytes`. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_MAGIdentifierOption( type=schema.type, length=self._mh_option_length(schema.length), subtype=schema.subtype, reserved=schema.reserved, identifier=schema.identifier, ) return data
[docs] def _read_opt_anchored(self, schema: 'Schema_AnchoredPrefixOption', *, options: 'Option') -> 'Data_AnchoredPrefixOption': """Read MH anchored prefix option. Structure of MH Anchored Prefix option [:rfc:`8885#section-4.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Reserved | Prefix Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + Anchored Prefix + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 18: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') if schema.prefix_length > 128: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_AnchoredPrefixOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, prefix_length=schema.prefix_length, prefix=schema.prefix, ) return data
[docs] def _read_opt_local_prefix(self, schema: 'Schema_LocalPrefixOption', *, options: 'Option') -> 'Data_LocalPrefixOption': """Read MH local prefix option. Structure of MH Local Prefix option [:rfc:`8885#section-4.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | Reserved | Prefix Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + Local Prefix + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 18: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') if schema.prefix_length > 128: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_LocalPrefixOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, prefix_length=schema.prefix_length, prefix=schema.prefix, ) return data
[docs] def _read_opt_prev_maar(self, schema: 'Schema_PreviousMAAROption', *, options: 'Option') -> 'Data_PreviousMAAROption': """Read MH previous MAAR option. Structure of MH Previous MAAR option [:rfc:`8885#section-4.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 | Length | Reserved | Prefix Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + Previous MAAR + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + Home Network Prefix + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The prefix length applies to the *home network prefix* only; the previous MAAR's own field is a full address [:rfc:`8885#section-4.5`]. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 34: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') if schema.prefix_length > 128: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_PreviousMAAROption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, prefix_length=schema.prefix_length, maar=schema.maar, prefix=schema.prefix, ) return data
[docs] def _read_opt_serv_maar(self, schema: 'Schema_ServingMAAROption', *, options: 'Option') -> 'Data_ServingMAAROption': """Read MH serving MAAR option. Structure of MH Serving MAAR option [:rfc:`8885#section-4.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + S-MAAR's Address + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: Unlike the anchored and local prefix options, this one has neither a reserved octet nor a prefix length -- it carries a full address and nothing else. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 16: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_ServingMAAROption( type=schema.type, length=self._mh_option_length(schema.length), address=schema.address, ) return data
[docs] def _read_opt_dlif_lla(self, schema: 'Schema_DLIFLinkLocalAddressOption', *, options: 'Option') -> 'Data_DLIFLinkLocalAddressOption': """Read MH DLIF link-local address option. Structure of MH DLIF Link-Local Address option [:rfc:`8885#section-4.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 | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | | + DLIF Link-Local Address + | | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length != 16: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_DLIFLinkLocalAddressOption( type=schema.type, length=self._mh_option_length(schema.length), address=schema.address, ) return data
[docs] def _read_opt_dlif_lladdr(self, schema: 'Schema_DLIFLinkLayerAddressOption', *, options: 'Option') -> 'Data_DLIFLinkLayerAddressOption': """Read MH DLIF link-layer address option. Structure of MH DLIF Link-Layer Address option [:rfc:`8885#section-4.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 | Length | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + DLIF Link-Layer Address + . ... . | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: The address is encoded as in :rfc:`4861#section-4.6.2`, whose width is link-type dependent, so no fixed length can be checked here. :rfc:`8885#section-4.8` says outright that the option cannot be used on links where a link-layer address is unavailable. Args: schema: Parsed option schema. options: Parsed MH options. Returns: Constructed option data. """ if schema.length < 2: raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format') data = Data_DLIFLinkLayerAddressOption( type=schema.type, length=self._mh_option_length(schema.length), reserved=schema.reserved, lla=schema.lla, ) return data
[docs] def _read_ext_none(self, schema: 'Schema_UnknownExtension', *, extensions: 'Extension') -> 'Data_UnknownExtension': """Read unknown CGA extension. Args: schema: Parsed extension schema. extensions: Parsed MH CGA extensions. Returns: Constructed extension data. """ data = Data_UnknownExtension( type=schema.type, length=self._mh_extension_length(schema.length), data=schema.data, ) return data
[docs] def _read_ext_multiprefix(self, schema: 'Schema_MultiPrefixExtension', *, extensions: 'Extension') -> 'Data_MultiPrefixExtension': """Read multi-prefix CGA extension. Structure of Multi-Prefix CGA extension [:rfc:`5535`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Extension Type | Extension Data Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Prefix[1] + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Prefix[2] + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . . . . . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Prefix[n] + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: schema: Parsed extension schema. extensions: Parsed MH CGA extensions. Returns: Constructed extension data. """ data = Data_MultiPrefixExtension( type=schema.type, length=self._mh_extension_length(schema.length), flag=bool(schema.flags['P']), reserved=schema.flags['reserved'], prefixes=tuple(schema.prefixes), ) return data
[docs] def _read_ext_exp(self, schema: 'Schema_ExperimentalExtension', *, extensions: 'Extension') -> 'Data_ExperimentalExtension': """Read experimental CGA extension. Structure of an experimental CGA extension [:rfc:`4581#section-2`, :rfc:`4581#section-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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Extension Type | Extension Data Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | ~ Extension Data ~ | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: :rfc:`4581#section-3` assigns extension types ``0xFFFD``, ``0xFFFE`` and ``0xFFFF`` for experimental use as :rfc:`3692` recommends, and defines **no** structure for their extension data -- neither it nor :rfc:`5535` gives those three a layout, and the IANA registry records only their names. So the data is opaque *by specification* here rather than merely left undecoded, and no better parse is available to be written later. One handler serves all three, since they share a shape and differ only in their type code. Args: schema: Parsed extension schema. extensions: Parsed MH CGA extensions. Returns: Constructed extension data. """ data = Data_ExperimentalExtension( type=schema.type, length=self._mh_extension_length(schema.length), data=schema.data, ) return data
[docs] def _make_msg_unknown(self, message: 'Optional[Data_UnknownMessage]' = None, *, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_UnknownMessage': """Make MH unknown message type. Args: message: Message data model. data: Raw message data. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. """ if message is not None: data = message.data return Schema_UnknownMessage( data=data, )
[docs] def _make_msg_brr(self, message: 'Optional[Data_BindingRefreshRequestMessage]' = None, *, msg_reserved: 'int' = 0, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long **kwargs: 'Any') -> 'Schema_BindingRefreshRequestMessage': """Make MH binding refresh request (BRR) message type. Args: message: Message data model. msg_reserved: Reserved field before the mobility options. options: Mobility options. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``msg_reserved`` does not fit its field. """ if message is not None: msg_reserved = message.msg_reserved options = message.options else: options = options or [] return Schema_BindingRefreshRequestMessage( reserved=self._make_uint(msg_reserved, bits=16, name='reserved bits'), options=self._make_mh_options(options), )
[docs] def _make_msg_hoti(self, message: 'Optional[Data_HomeTestInitMessage]' = None, *, cookie: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00', options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long msg_reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_HomeTestInitMessage': """Make MH home test init (HoTI) message type. Args: message: Message data model. cookie: Home test cookie. options: Mobility options. msg_reserved: The two reserved octets of the message body as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``cookie`` is not eight octets, or ``msg_reserved`` is not two. """ if message is not None: msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00'), size=2, model=message, field='data') cookie = message.cookie options = message.options else: options = options or [] return Schema_HomeTestInitMessage( reserved=self._make_octets(msg_reserved, size=2, name='reserved octets'), cookie=self._make_octets(cookie, size=8, name='cookie'), options=self._make_mh_options(options), )
[docs] def _make_msg_coti(self, message: 'Optional[Data_CareofTestInitMessage]' = None, *, cookie: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00', options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long msg_reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_CareofTestInitMessage': """Make MH care-of test init (CoTI) message type. Args: message: Message data model. cookie: Care-of test cookie. options: Mobility options. msg_reserved: The two reserved octets of the message body as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``cookie`` is not eight octets, or ``msg_reserved`` is not two. """ if message is not None: msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00'), size=2, model=message, field='data') cookie = message.cookie options = message.options else: options = options or [] return Schema_CareofTestInitMessage( reserved=self._make_octets(msg_reserved, size=2, name='reserved octets'), cookie=self._make_octets(cookie, size=8, name='cookie'), options=self._make_mh_options(options), )
[docs] def _make_msg_hot(self, message: 'Optional[Data_HomeTestMessage]' = None, *, nonce_index: 'int' = 0, cookie: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00', token: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00', options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long **kwargs: 'Any') -> 'Schema_HomeTestMessage': """Make MH home test (HoT) message type. Args: message: Message data model. nonce_index: Home nonce index. cookie: Home test cookie. token: Home test token. options: Mobility options. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``nonce_index`` does not fit its field. Also if ``cookie`` or ``token`` is not eight octets. """ if message is not None: nonce_index = message.nonce_index cookie = message.cookie token = message.token options = message.options else: options = options or [] return Schema_HomeTestMessage( nonce_index=self._make_uint(nonce_index, bits=16, name='nonce index'), cookie=self._make_octets(cookie, size=8, name='cookie'), token=self._make_octets(token, size=8, name='token'), options=self._make_mh_options(options), )
[docs] def _make_msg_cot(self, message: 'Optional[Data_CareofTestMessage]' = None, *, nonce_index: 'int' = 0, cookie: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00', token: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00', options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long **kwargs: 'Any') -> 'Schema_CareofTestMessage': """Make MH care-of test (CoT) message type. Args: message: Message data model. nonce_index: Care-of nonce index. cookie: Care-of test cookie. token: Care-of test token. options: Mobility options. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``nonce_index`` does not fit its field. Also if ``cookie`` or ``token`` is not eight octets. """ if message is not None: nonce_index = message.nonce_index cookie = message.cookie token = message.token options = message.options else: options = options or [] return Schema_CareofTestMessage( nonce_index=self._make_uint(nonce_index, bits=16, name='nonce index'), cookie=self._make_octets(cookie, size=8, name='cookie'), token=self._make_octets(token, size=8, name='token'), options=self._make_mh_options(options), )
[docs] def _make_msg_bu(self, message: 'Optional[Data_BindingUpdateMessage]' = None, *, seq: 'int' = 0, ack: 'bool' = False, home: 'bool' = False, lla_compat: 'bool' = False, key_mngt: 'bool' = False, map_reg: 'bool' = False, mobile_router: 'bool' = False, proxy_reg: 'bool' = False, udp_encap: 'bool' = False, tlv_format: 'bool' = False, bulk_binding: 'bool' = False, multicast: 'bool' = False, dmm: 'bool' = False, flags_reserved: 'int' = 0, lifetime: 'int | timedelta' = 4, # reasonable default value options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long **kwargs: 'Any') -> 'Schema_BindingUpdateMessage': """Make MH binding update (BU) message type. Args: message: Message data model. seq: Sequence number. ack: Acknowledgement flag. home: Home registration flag. lla_compat: LLA compatibility flag. key_mngt: Key management mobility option flag. map_reg: MAP registration flag (``M``). mobile_router: Mobile router flag (``R``). proxy_reg: Proxy registration flag (``P``). udp_encap: Forcing UDP encapsulation flag (``F``). tlv_format: TLV-header format flag (``T``). bulk_binding: Bulk binding update flag (``B``). multicast: Multicast signaling flag (``S``). dmm: DMM flag (``D``). flags_reserved: Reserved bits, i.e. the low four bits of the flags word. lifetime: Lifetime in seconds or timedelta. options: Mobility options. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit its field. """ if message is not None: seq = message.seq ack = message.ack home = message.home lla_compat = message.lla_compat key_mngt = message.key_mngt map_reg = message.map_reg mobile_router = message.mobile_router proxy_reg = message.proxy_reg udp_encap = message.udp_encap tlv_format = message.tlv_format bulk_binding = message.bulk_binding multicast = message.multicast dmm = message.dmm flags_reserved = message.flags_reserved lifetime_val = math.ceil(message.lifetime.total_seconds()) options = message.options else: lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds()) options = options or [] # NOTE: The field counts units of 4 seconds, so the seconds are checked # before they are rounded up to units, and the units after. lifetime_val = self._make_uint(lifetime_val, bits=18, name='lifetime') return Schema_BindingUpdateMessage( seq=self._make_uint(seq, bits=16, name='sequence number'), flags={ 'A': ack, 'H': home, 'L': lla_compat, 'K': key_mngt, 'M': map_reg, 'R': mobile_router, 'P': proxy_reg, 'F': udp_encap, 'T': tlv_format, 'B': bulk_binding, 'S': multicast, 'D': dmm, 'reserved': self._make_uint(flags_reserved, bits=4, name='reserved bits'), }, lifetime=self._make_uint(math.ceil(lifetime_val / 4), bits=16, name='lifetime in units of 4 seconds'), options=self._make_mh_options(options), )
[docs] def _make_msg_ba(self, message: 'Optional[Data_BindingAcknowledgementMessage]' = None, *, status: 'Enum_StatusCode | int' = Enum_StatusCode.Binding_Update_accepted_Proxy_Binding_Update_accepted, # pylint: disable=line-too-long key_mngt: 'bool' = False, mobile_router: 'bool' = False, proxy_reg: 'bool' = False, tlv_format: 'bool' = False, bulk_binding: 'bool' = False, multicast: 'bool' = False, dmm: 'bool' = False, flags_reserved: 'int' = 0, seq: 'int' = 0, lifetime: 'int | timedelta' = 4, # reasonable default value options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long **kwargs: 'Any') -> 'Schema_BindingAcknowledgementMessage': """Make MH binding acknowledge (BA) message type. Args: message: Message data model. status: Status code. key_mngt: Key management mobility option flag. mobile_router: Mobile router flag (``R``). proxy_reg: Proxy registration flag (``P``). tlv_format: TLV-header format flag (``T``). bulk_binding: Bulk binding update flag (``B``). multicast: Multicast signaling flag (``S``). dmm: DMM flag (``D``). flags_reserved: Reserved bit, i.e. the lowest bit of the flags octet. seq: Sequence number. lifetime: Lifetime in seconds or timedelta. options: Mobility options. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit its field, or ``status`` is a name, a :obj:`bool` or a code its field cannot carry. """ if message is not None: status_val = message.status key_mngt = message.key_mngt mobile_router = message.mobile_router proxy_reg = message.proxy_reg tlv_format = message.tlv_format bulk_binding = message.bulk_binding multicast = message.multicast dmm = message.dmm flags_reserved = message.flags_reserved seq = message.seq lifetime_val = math.ceil(message.lifetime.total_seconds()) options = message.options else: status_val = self._make_index(status, Enum_StatusCode, bits=8, name='status') lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds()) options = options or [] # NOTE: The field counts units of 4 seconds, so the seconds are checked # before they are rounded up to units, and the units after. lifetime_val = self._make_uint(lifetime_val, bits=18, name='lifetime') return Schema_BindingAcknowledgementMessage( status=status_val, flags={ 'K': key_mngt, 'R': mobile_router, 'P': proxy_reg, 'T': tlv_format, 'B': bulk_binding, 'S': multicast, 'D': dmm, 'reserved': self._make_uint(flags_reserved, bits=1, name='reserved bits'), }, seq=self._make_uint(seq, bits=16, name='sequence number'), lifetime=self._make_uint(math.ceil(lifetime_val / 4), bits=16, name='lifetime in units of 4 seconds'), options=self._make_mh_options(options), )
[docs] def _make_msg_be(self, message: 'Optional[Data_BindingErrorMessage]' = None, *, status: 'Enum_BindingError | int' = 0, home: 'IPv6Address | int | str | bytes' = '::', options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long msg_reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_BindingErrorMessage': """Make MH binding error (BE) message type. Args: message: Message data model. status: Status [:rfc:`6275#section-6.1.9`]; ``0`` by default, a code no status is assigned. home: Home address. options: Mobility options. msg_reserved: The reserved octet of the message body as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``status`` is a name, a :obj:`bool` or a code its field cannot carry, or ``msg_reserved`` is not one octet. """ if message is not None: msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00'), size=1, model=message, field='data', offset=1) status_val = message.status home = message.home options = message.options else: status_val = self._make_index(status, Enum_BindingError, bits=8, name='status') options = options or [] return Schema_BindingErrorMessage( status=status_val, reserved=self._make_octets(msg_reserved, size=1, name='reserved octets'), home=home, options=self._make_mh_options(options), )
[docs] def _make_msg_fbu(self, message: 'Optional[Data_FastBindingUpdateMessage]' = None, *, seq: 'int' = 0, ack: 'bool' = True, # MUST be set, see RFC 5568, section 6.2.2 home: 'bool' = True, # MUST be set, see RFC 5568, section 6.2.2 lla_compat: 'bool' = False, key_mngt: 'bool' = False, lifetime: 'int | timedelta' = 4, # reasonable default value options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_FastBindingUpdateMessage': """Make MH fast binding update (FBU) message type. Args: message: Message data model. seq: Sequence number. ack: Acknowledgement flag. home: Home registration flag. lla_compat: LLA compatibility flag. key_mngt: Key management mobility option flag. lifetime: Lifetime, in seconds or as a :class:`~datetime.timedelta`. Unlike the BU/BA lifetime, this one counts seconds rather than units of 4 seconds [:rfc:`5568#section-6.2.2`], so the value is not scaled. options: Mobility options. flags_reserved: Reserved bits, i.e. the 12 bits after the ``K`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit its field. """ if message is not None: seq = message.seq ack = message.ack home = message.home lla_compat = message.lla_compat key_mngt = message.key_mngt flags_reserved = message.flags_reserved lifetime_val = math.ceil(message.lifetime.total_seconds()) options = message.options else: lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds()) options = options or [] return Schema_FastBindingUpdateMessage( seq=self._make_uint(seq, bits=16, name='sequence number'), flags={ 'A': ack, 'H': home, 'L': lla_compat, 'K': key_mngt, 'reserved': self._make_uint(flags_reserved, bits=12, name='reserved bits'), }, lifetime=self._make_uint(lifetime_val, bits=16, name='lifetime'), options=self._make_mh_options(options), )
[docs] def _make_msg_fback(self, message: 'Optional[Data_FastBindingAcknowledgmentMessage]' = None, *, status: 'FastBindingAcknowledgmentStatus | int' = FastBindingAcknowledgmentStatus.Fast_Binding_Update_accepted, # pylint: disable=line-too-long key_mngt: 'bool' = False, seq: 'int' = 0, lifetime: 'int | timedelta' = 4, # reasonable default value options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_FastBindingAcknowledgmentMessage': """Make MH fast binding acknowledgment (FBack) message type. Args: message: Message data model. status: Status code, see :class:`~pcapkit.protocols.internet.mh.protocol.FastBindingAcknowledgmentStatus`. key_mngt: Key management mobility option flag. seq: Sequence number. lifetime: Lifetime, in seconds or as a :class:`~datetime.timedelta`. Unlike the BU/BA lifetime, this one counts seconds rather than units of 4 seconds [:rfc:`5568#section-6.2.3`], so the value is not scaled. options: Mobility options. flags_reserved: Reserved bits, i.e. the seven bits after the ``K`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit its field, or ``status`` is a name, a :obj:`bool` or a code its field cannot carry. """ if message is not None: status_val = message.status key_mngt = message.key_mngt flags_reserved = message.flags_reserved seq = message.seq lifetime_val = math.ceil(message.lifetime.total_seconds()) options = message.options else: status_val = self._make_index(status, FastBindingAcknowledgmentStatus, bits=8, name='status') lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds()) options = options or [] return Schema_FastBindingAcknowledgmentMessage( status=status_val, flags={ 'K': key_mngt, 'reserved': self._make_uint(flags_reserved, bits=7, name='reserved bits'), }, seq=self._make_uint(seq, bits=16, name='sequence number'), lifetime=self._make_uint(lifetime_val, bits=16, name='lifetime'), options=self._make_mh_options(options), )
[docs] def _make_msg_fna(self, message: 'Optional[Data_FastNeighborAdvertisementMessage]' = None, *, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long msg_reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_FastNeighborAdvertisementMessage': """Make MH fast neighbor advertisement (FNA) message type. Args: message: Message data model. options: Mobility options. msg_reserved: The two reserved octets of the message body as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``msg_reserved`` is not two octets. """ if message is not None: msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00'), size=2, model=message, field='data') options = message.options else: options = options or [] return Schema_FastNeighborAdvertisementMessage( reserved=self._make_octets(msg_reserved, size=2, name='reserved octets'), options=self._make_mh_options(options), )
[docs] def _make_msg_emh(self, message: 'Optional[Data_ExperimentalMessage]' = None, *, data: 'bytes' = b'\x00\x00', # 2 bytes of padding, see RFC 5096, section 3 **kwargs: 'Any') -> 'Schema_ExperimentalMessage': """Make MH experimental mobility header message type. Args: message: Message data model. data: Experimental message data. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. """ if message is not None: data = message.data return Schema_ExperimentalMessage( data=data, )
[docs] def _make_msg_has(self, message: 'Optional[Data_HomeAgentSwitchMessage]' = None, *, addresses: 'Optional[list[IPv6Address | bytes | str | int]]' = None, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long msg_reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_HomeAgentSwitchMessage': """Make MH home agent switch (HAS) message type. Args: message: Message data model. addresses: Alternate home agent addresses. An empty list is valid and asks the mobile node to run home agent discovery. options: Mobility options. msg_reserved: The reserved octet of the message body as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``msg_reserved`` is not one octet. """ if message is not None: msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00'), size=1, model=message, field='data', offset=1) addresses = cast('list[IPv6Address | bytes | str | int]', list(message.addresses)) options = message.options else: addresses = addresses or [] options = options or [] return Schema_HomeAgentSwitchMessage( count=len(addresses), reserved=self._make_octets(msg_reserved, size=1, name='reserved octets'), addresses=addresses, options=self._make_mh_options(options), )
[docs] def _make_msg_hb(self, message: 'Optional[Data_HeartbeatMessage]' = None, *, unsolicited: 'bool' = False, response: 'bool' = False, seq: 'int' = 0, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_HeartbeatMessage': """Make MH heartbeat message type. Args: message: Message data model. unsolicited: Unsolicited flag. response: Response flag; unset makes this a heartbeat request. seq: Sequence number. options: Mobility options. flags_reserved: Reserved bits, i.e. the 14 bits before the ``U`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field. """ if message is not None: unsolicited = message.unsolicited response = message.response flags_reserved = message.flags_reserved seq = message.seq options = message.options else: options = options or [] return Schema_HeartbeatMessage( flags={ 'U': unsolicited, 'R': response, 'reserved': self._make_uint(flags_reserved, bits=14, name='reserved bits'), }, seq=self._make_uint(seq, bits=32, name='sequence number'), options=self._make_mh_options(options), )
[docs] def _make_msg_hi(self, message: 'Optional[Data_HandoverInitiateMessage]' = None, *, seq: 'int' = 0, assign: 'bool' = False, buffer: 'bool' = False, proxy: 'bool' = False, forward: 'bool' = False, code: 'Enum_HandoverInitiateStatus | int' = Enum_HandoverInitiateStatus.FBU_with_the_PCoA_as_source_IP_address, # pylint: disable=line-too-long options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_HandoverInitiateMessage': """Make MH handover initiate (HI) message type. Args: message: Message data model. seq: Sequence number. assign: Assigned address configuration flag. buffer: Buffer flag. proxy: Proxy flag. forward: Forwarding flag. code: Code. options: Mobility options. flags_reserved: Reserved bits, i.e. the four bits after the ``F`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field, or ``code`` is a name, a :obj:`bool` or a code its field cannot carry. """ if message is not None: seq = message.seq assign = message.assign buffer = message.buffer proxy = message.proxy forward = message.forward flags_reserved = message.flags_reserved code_val = message.code options = message.options else: code_val = self._make_index(code, Enum_HandoverInitiateStatus, bits=8, name='code') options = options or [] return Schema_HandoverInitiateMessage( seq=self._make_uint(seq, bits=16, name='sequence number'), flags={ 'S': assign, 'U': buffer, 'P': proxy, 'F': forward, 'reserved': self._make_uint(flags_reserved, bits=4, name='reserved bits'), }, code=code_val, options=self._make_mh_options(options), )
[docs] def _make_msg_hack(self, message: 'Optional[Data_HandoverAcknowledgeMessage]' = None, *, seq: 'int' = 0, buffer: 'bool' = False, proxy: 'bool' = False, forward: 'bool' = False, code: 'Enum_HandoverACKStatus | int' = Enum_HandoverACKStatus.Handover_Accepted_with_NCoA_valid, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_HandoverAcknowledgeMessage': """Make MH handover acknowledge (HAck) message type. Args: message: Message data model. seq: Sequence number. buffer: Buffer flag. proxy: Proxy flag. forward: Forwarding flag. code: Code. options: Mobility options. flags_reserved: Reserved bits, i.e. the five bits after the ``F`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field, or ``code`` is a name, a :obj:`bool` or a code its field cannot carry. """ if message is not None: seq = message.seq buffer = message.buffer proxy = message.proxy forward = message.forward flags_reserved = message.flags_reserved code_val = message.code options = message.options else: code_val = self._make_index(code, Enum_HandoverACKStatus, bits=8, name='code') options = options or [] return Schema_HandoverAcknowledgeMessage( seq=self._make_uint(seq, bits=16, name='sequence number'), flags={ 'U': buffer, 'P': proxy, 'F': forward, 'reserved': self._make_uint(flags_reserved, bits=5, name='reserved bits'), }, code=code_val, options=self._make_mh_options(options), )
[docs] def _make_msg_brm(self, message: 'Optional[Data_BindingRevocationMessage]' = None, *, br_type: 'Enum_BindingRevocation | int' = Enum_BindingRevocation.Binding_Revocation_Indication, code: 'Enum_RevocationTrigger | Enum_RevocationStatusCode | int' = Enum_RevocationTrigger.Unspecified, # pylint: disable=line-too-long seq: 'int' = 0, proxy: 'bool' = False, ipv4_hoa: 'bool' = False, global_revocation: 'bool' = False, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_BindingRevocationMessage': """Make MH binding revocation (BRM) message type. Args: message: Message data model. br_type: Binding revocation type, saying which form of the message this is. code: Revocation trigger, for an indication, or acknowledgement status, for an acknowledgement; resolved in the registry of the form ``br_type`` names. seq: Sequence number. proxy: Proxy binding flag. ipv4_hoa: IPv4 home address binding only flag. global_revocation: Global revocation flag. options: Mobility options. flags_reserved: Reserved bits, i.e. the 13 bits after the ``G`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field, or ``br_type`` or ``code`` is a name, a :obj:`bool` or a code its field cannot carry. """ if message is not None: br_type_val = message.br_type # type: Enum_BindingRevocation code_val = message.code # type: Enum_RevocationTrigger | Enum_RevocationStatusCode seq = message.seq proxy = message.proxy ipv4_hoa = message.ipv4_hoa global_revocation = message.global_revocation flags_reserved = message.flags_reserved options = message.options else: br_type_val = self._make_index(br_type, Enum_BindingRevocation, bits=8, name='binding revocation type') # NOTE: The registry ``br_code_selector`` reads the octet from. if br_type_val == Enum_BindingRevocation.Binding_Revocation_Acknowledgement: code_val = self._make_index(code, Enum_RevocationStatusCode, bits=8, name='status') else: code_val = self._make_index(code, Enum_RevocationTrigger, bits=8, name='revocation trigger') options = options or [] return Schema_BindingRevocationMessage( br_type=br_type_val, code=code_val, seq=self._make_uint(seq, bits=16, name='sequence number'), flags={ 'P': proxy, 'V': ipv4_hoa, 'G': global_revocation, 'reserved': self._make_uint(flags_reserved, bits=13, name='reserved bits'), }, options=self._make_mh_options(options), )
[docs] def _make_msg_lri(self, message: 'Optional[Data_LocalizedRoutingInitiationMessage]' = None, *, seq: 'int' = 0, lifetime: 'int | timedelta' = 0, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long msg_reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_LocalizedRoutingInitiationMessage': """Make MH localized routing initiation (LRI) message type. Args: message: Message data model. seq: Sequence number. lifetime: Lifetime, in seconds or as a :class:`~datetime.timedelta`. Unlike the binding messages, this one counts seconds rather than units of 4 seconds, so the value is not scaled. options: Mobility options. msg_reserved: The two reserved octets of the message body as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` or the lifetime does not fit its field. Also if ``msg_reserved`` is not two octets. """ if message is not None: msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00'), size=2, model=message, field='data', offset=2) seq = message.seq lifetime_val = math.ceil(message.lifetime.total_seconds()) options = message.options else: lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds()) options = options or [] return Schema_LocalizedRoutingInitiationMessage( seq=self._make_uint(seq, bits=16, name='sequence number'), reserved=self._make_octets(msg_reserved, size=2, name='reserved octets'), lifetime=self._make_uint(lifetime_val, bits=16, name='lifetime'), options=self._make_mh_options(options), )
[docs] def _make_msg_lra(self, message: 'Optional[Data_LocalizedRoutingAcknowledgmentMessage]' = None, *, seq: 'int' = 0, unsolicited: 'bool' = False, status: 'LocalizedRoutingStatus | int' = LocalizedRoutingStatus.Success, lifetime: 'int | timedelta' = 0, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_LocalizedRoutingAcknowledgmentMessage': """Make MH localized routing acknowledgment (LRA) message type. Args: message: Message data model. seq: Sequence number. unsolicited: Unsolicited flag. status: Status, see :class:`~pcapkit.protocols.internet.mh.protocol.LocalizedRoutingStatus`. lifetime: Lifetime, in seconds or as a :class:`~datetime.timedelta`. options: Mobility options. flags_reserved: Reserved bits, i.e. the seven bits after the ``U`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit its field, or ``status`` is a name, a :obj:`bool` or a code its field cannot carry. """ if message is not None: seq = message.seq unsolicited = message.unsolicited flags_reserved = message.flags_reserved status_val = message.status # type: int lifetime_val = math.ceil(message.lifetime.total_seconds()) options = message.options else: status_val = self._make_index(status, LocalizedRoutingStatus, bits=8, name='status') lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds()) options = options or [] return Schema_LocalizedRoutingAcknowledgmentMessage( seq=self._make_uint(seq, bits=16, name='sequence number'), flags={ 'U': unsolicited, 'reserved': self._make_uint(flags_reserved, bits=7, name='reserved bits'), }, status=status_val, lifetime=self._make_uint(lifetime_val, bits=16, name='lifetime'), options=self._make_mh_options(options), )
[docs] def _make_msg_upn(self, message: 'Optional[Data_UpdateNotificationMessage]' = None, *, seq: 'int' = 0, reason: 'Enum_UpdateNotificationReason | int' = Enum_UpdateNotificationReason.FORCE_REREGISTRATION, # pylint: disable=line-too-long ack: 'bool' = False, retransmit: 'bool' = False, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_UpdateNotificationMessage': """Make MH update notification (UPN) message type. Args: message: Message data model. seq: Sequence number. reason: Notification reason. ack: Acknowledgement requested flag. retransmit: Retransmission flag. options: Mobility options. flags_reserved: Reserved bits, i.e. the 14 bits after the ``D`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field, or ``reason`` is a name, a :obj:`bool` or a code its field cannot carry. """ if message is not None: seq = message.seq reason_val = message.reason # type: Enum_UpdateNotificationReason ack = message.ack retransmit = message.retransmit flags_reserved = message.flags_reserved options = message.options else: reason_val = self._make_index(reason, Enum_UpdateNotificationReason, bits=16, name='notification reason') options = options or [] return Schema_UpdateNotificationMessage( seq=self._make_uint(seq, bits=16, name='sequence number'), reason=reason_val, flags={ 'A': ack, 'D': retransmit, 'reserved': self._make_uint(flags_reserved, bits=14, name='reserved bits'), }, options=self._make_mh_options(options), )
[docs] def _make_msg_upa(self, message: 'Optional[Data_UpdateNotificationAcknowledgementMessage]' = None, *, seq: 'int' = 0, status: 'Enum_UpdateNotificationACKStatus | int' = Enum_UpdateNotificationACKStatus.SUCCESS, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long msg_reserved: 'bytes' = b'\x00\x00\x00', **kwargs: 'Any') -> 'Schema_UpdateNotificationAcknowledgementMessage': """Make MH update notification acknowledgement (UPA) message type. Args: message: Message data model. seq: Sequence number. status: Status. options: Mobility options. msg_reserved: The three reserved octets of the message body as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` does not fit its field, or ``status`` is a name, a :obj:`bool` or a code its field cannot carry. Also if ``msg_reserved`` is not three octets. """ if message is not None: msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00\x00'), size=3, model=message, field='data', offset=3) seq = message.seq status_val = message.status # type: Enum_UpdateNotificationACKStatus options = message.options else: status_val = self._make_index(status, Enum_UpdateNotificationACKStatus, bits=8, name='status') options = options or [] return Schema_UpdateNotificationAcknowledgementMessage( seq=self._make_uint(seq, bits=16, name='sequence number'), status=status_val, reserved=self._make_octets(msg_reserved, size=3, name='reserved octets'), options=self._make_mh_options(options), )
[docs] def _make_msg_fbm(self, message: 'Optional[Data_FlowBindingMessage]' = None, *, fb_type: 'Enum_FlowBindingType | int' = Enum_FlowBindingType.Indication, seq: 'int' = 0, code: 'Enum_FlowBindingIndicationTrigger | Enum_FlowBindingACKStatus | int' = Enum_FlowBindingIndicationTrigger.Unspecified, # pylint: disable=line-too-long ack: 'bool' = False, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_FlowBindingMessage': """Make MH flow binding (FB) message type. Args: message: Message data model. fb_type: Flow binding type, saying which form of the message this is. seq: Sequence number. code: Indication trigger, for an indication, or acknowledgement status, for an acknowledgement; resolved in the registry of the form ``fb_type`` names. ack: Acknowledgement requested flag. Meaningful only in an indication. options: Mobility options. flags_reserved: Reserved bits, i.e. the seven bits after the ``A`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field, or ``fb_type`` or ``code`` is a name, a :obj:`bool` or a code its field cannot carry. """ if message is not None: fb_type_val = message.fb_type # type: Enum_FlowBindingType seq = message.seq code_val = message.code # type: Enum_FlowBindingIndicationTrigger | Enum_FlowBindingACKStatus ack = message.ack flags_reserved = message.flags_reserved options = message.options else: fb_type_val = self._make_index(fb_type, Enum_FlowBindingType, bits=16, name='flow binding type') # NOTE: The registry ``fb_code_selector`` reads the octet from. if fb_type_val == Enum_FlowBindingType.Acknowledgement: code_val = self._make_index(code, Enum_FlowBindingACKStatus, bits=8, name='status') else: code_val = self._make_index(code, Enum_FlowBindingIndicationTrigger, bits=8, name='trigger') options = options or [] return Schema_FlowBindingMessage( fb_type=fb_type_val, seq=self._make_uint(seq, bits=16, name='sequence number'), code=code_val, flags={ 'A': ack, 'reserved': self._make_uint(flags_reserved, bits=7, name='reserved bits'), }, options=self._make_mh_options(options), )
[docs] def _make_msg_sq(self, message: 'Optional[Data_SubscriptionQueryMessage]' = None, *, seq: 'int' = 0, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long msg_reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_SubscriptionQueryMessage': """Make MH subscription query (SQ) message type. Args: message: Message data model. seq: Sequence number, counted modulo 256. options: Mobility options. msg_reserved: The reserved octet of the message body as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` does not fit its field. Also if ``msg_reserved`` is not one octet. """ if message is not None: msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00'), size=1, model=message, field='data', offset=1) seq = message.seq options = message.options else: options = options or [] return Schema_SubscriptionQueryMessage( seq=self._make_uint(seq, bits=8, name='sequence number'), reserved=self._make_octets(msg_reserved, size=1, name='reserved octets'), options=self._make_mh_options(options), )
[docs] def _make_msg_sr(self, message: 'Optional[Data_SubscriptionResponseMessage]' = None, *, seq: 'int' = 0, info: 'bool' = False, options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long flags_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_SubscriptionResponseMessage': """Make MH subscription response (SR) message type. Args: message: Message data model. seq: Sequence number, echoed from the query. info: Multicast information flag. options: Mobility options. flags_reserved: Reserved bits, i.e. the seven bits after the ``I`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed message type. Raises: ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field. """ if message is not None: seq = message.seq info = message.info flags_reserved = message.flags_reserved options = message.options else: options = options or [] return Schema_SubscriptionResponseMessage( seq=self._make_uint(seq, bits=8, name='sequence number'), flags={ 'I': info, 'reserved': self._make_uint(flags_reserved, bits=7, name='reserved bits'), }, options=self._make_mh_options(options), )
[docs] def _make_opt_none(self, type: 'Enum_Option', option: 'Optional[Data_UnassignedOption]' = None, *, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_UnassignedOption': """Make MH unassigned option. Args: type: Option type. option: Option data model. data: Option data. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. """ if option is not None: data = option.data return Schema_UnassignedOption( type=type, length=self._make_uint(len(data), bits=8, name='Option Length'), data=data, )
[docs] def _make_opt_pad(self, type: 'Enum_Option', option: 'Optional[Data_PadOption]' = None, *, length: 'int' = 0, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_PadOption': """Make MH pad option. Args: type: Option type. option: Option data model. length: Value of the ``Option Length`` field, i.e. the number of padding octets *after* the two octets of the option header. data: The ``PadN`` option data, truncated or zero-filled to ``length`` octets. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``length`` does not fit the one-octet ``Option Length``. Warns: ProtocolWarning: If ``length`` is non-zero for a ``Pad1``, which has no ``Option Length`` field to carry it; the option is made as a ``PadN`` instead. Note: :attr:`Data_PadOption.length <pcapkit.protocols.internet.mh.data.Option.length>` counts the *whole* option, whereas :attr:`Schema_PadOption.length <pcapkit.protocols.internet.mh.schema.Option.length>` is the ``Option Length`` field -- two octets fewer, and absent altogether for a ``Pad1``. Copying one into the other unconverted would make a re-made parsed ``PadN`` come back two octets too long. A ``PadN`` with ``length`` ``0`` is a valid two-octet option [:rfc:`6275#section-6.2.3`] and is kept as one. """ if option is not None: length = 0 if option.type == Enum_Option.Pad1 else option.length - 2 data = getattr(option, 'data', b'') if type == Enum_Option.Pad1 and length != 0: warn(f'{self.alias}: [OptNo {type}] invalid length: must be 0 for a Pad1 option, ' f'not {length!r}; making a PadN', ProtocolWarning) type = Enum_Option.PadN # type: ignore[assignment] return Schema_PadOption( type=type, length=self._make_uint(length, bits=8, name='Option Length'), data=data, )
[docs] def _make_opt_bra(self, type: 'Enum_Option', option: 'Optional[Data_BindingRefreshAdviceOption]' = None, *, interval: 'int | timedelta' = 0, **kwargs: 'Any') -> 'Schema_BindingRefreshAdviceOption': """Make MH binding refresh advice option. Args: type: Option type. option: Option data model. interval: Refresh interval before re-registration, in units of 4 seconds or as a :class:`~datetime.timedelta` [:rfc:`6275#section-6.2.4`]. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the interval does not fit its field. """ if option is not None: interval_val = math.ceil(option.interval.total_seconds() / 4) else: interval_val = self._seconds(interval, 4) return Schema_BindingRefreshAdviceOption( type=type, length=2, interval=self._make_uint(interval_val, bits=16, name='refresh interval'), )
[docs] def _make_opt_aca(self, type: 'Enum_Option', option: 'Optional[Data_AlternateCareofAddressOption]' = None, *, address: 'bytes | str | int | IPv6Address' = '::', **kwargs: 'Any') -> 'Schema_AlternateCareofAddressOption': """Make MH alternate care-of address option. Args: type: Option type. option: Option data model. address: Alternate care-of address. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. """ if option is not None: address = option.address return Schema_AlternateCareofAddressOption( type=type, length=16, address=address, )
[docs] def _make_opt_ni(self, type: 'Enum_Option', option: 'Optional[Data_NonceIndicesOption]' = None, *, home: 'int' = 0, careof: 'int' = 0, **kwargs: 'Any') -> 'Schema_NonceIndicesOption': """Make MH nonce indices option. Args: type: Option type. option: Option data model. home: Home nonce index. careof: Care-of nonce index. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``home`` or ``careof`` does not fit its field. """ if option is not None: home = option.home careof = option.careof return Schema_NonceIndicesOption( type=type, length=4, home=self._make_uint(home, bits=16, name='home nonce index'), careof=self._make_uint(careof, bits=16, name='care-of nonce index'), )
[docs] def _make_opt_bad(self, type: 'Enum_Option', option: 'Optional[Data_AuthorizationDataOption]' = None, *, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_AuthorizationDataOption': """Make MH binding authorization data option. Args: type: Option type. option: Option data model. data: Authenticator. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``data`` is not a multiple of eight octets, the length :meth:`_read_opt_bad` accepts for an :rfc:`6275` authenticator. Also if the option is too long for its one-octet ``Option Length``. """ if option is not None: data = option.data if len(data) % 8 != 0: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid data: ' f'must be a multiple of eight octets, not {data!r}') return Schema_AuthorizationDataOption( type=type, length=self._make_uint(len(data), bits=8, name='Option Length'), data=data, )
[docs] def _make_opt_mnp(self, type: 'Enum_Option', option: 'Optional[Data_MobileNetworkPrefixOption]' = None, *, prefix: 'bytes | str | int | IPv6Address | IPv6Network' = '::/0', prefix_length: 'Optional[int]' = None, reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_MobileNetworkPrefixOption': """Make MH mobile network prefix option. Args: type: Option type. option: Option data model. prefix: Mobile network prefix, an IPv6 address or network, whose bits past ``prefix_length`` are written as given. prefix_length: Prefix length; defaults to that of a network or of text ending in ``/<length>``, and to 128 for an address. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix`` is not an IPv6 address or network, or ``prefix_length`` is negative, a :obj:`bool`, or does not fit in one octet. Also if ``reserved`` is not one octet. Note: The prefix length is checked against its 8-bit field alone, as :rfc:`3963#section-6.2` has a home agent answer a prefix it cannot use with a status rather than discard the option, so whatever the reader keeps is written back. .. versionchanged:: 1.6.0 New argument ``prefix_length``. ``prefix`` keeps the bits set past its length, rather than raising :exc:`ValueError`, and a prefix length past 128 is written (:issue:`2702`). """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') prefix_length = option.prefix_length prefix_addr = option.prefix else: try: # NOTE: An interface keeps bits past its length, unlike a network, and takes an address, # network or text alike; a bool is refused first, as it would pass for ``::1``. prefix_val = None if isinstance(prefix, bool) else ipaddress.IPv6Interface(prefix) except ValueError: prefix_val = None if prefix_val is None: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid mobile network prefix: {prefix!r}') if prefix_length is None: prefix_length = prefix_val.network.prefixlen prefix_addr = prefix_val.ip prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length') return Schema_MobileNetworkPrefixOption( type=type, length=18, reserved=self._make_octets(reserved, size=1, name='reserved octets'), prefix_length=prefix_length, prefix=prefix_addr, )
[docs] def _make_opt_lla(self, type: 'Enum_Option', option: 'Optional[Data_LinkLayerAddressOption]' = None, *, address: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_LinkLayerAddressOption': """Make MH link-layer address option. Args: type: Option type. option: Option data model. address: Link-layer address. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. """ if option is not None: address = option.lla return Schema_LinkLayerAddressOption( type=type, length=self._make_uint(len(address) + 1, bits=8, name='Option Length'), code=Enum_LLACode.MN, # type: ignore[arg-type] lla=address, )
[docs] def _make_opt_mn_id(self, type: 'Enum_Option', option: 'Optional[Data_MNIDOption]' = None, *, subtype: 'Enum_MNIDSubtype | int' = Enum_MNIDSubtype.IPv6_Address, identifier: 'bytes | str | IPv6Address | int' = '::', **kwargs: 'Any') -> 'Schema_MNIDOption': """Make MH mobile node identifier option. Args: type: Option type. option: Option data model. subtype: MN-ID subtype. identifier: Identifier. An :obj:`int` is accepted for every subtype except ``NAI``, with the sole exception of a :obj:`bool`, which is rejected for every subtype -- see ``Raises`` below. For ``IPv6_Address`` it is converted and validated the same way as any other value :class:`ipaddress.IPv6Address` accepts. For every other subtype -- each a numeric identifier (an IMSI, a P-TMSI, an EUI-48/64 address, a GUTI, a DUID) -- it is converted to its own minimal big-endian octets, at least one. ``NAI`` is text (RFC 4283's ``user@realm`` form) rather than a numeric identifier, so there is no non-arbitrary int-to-text mapping the way there is int-to-address or int-to-octets, and an :obj:`int` is rejected there. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``identifier`` is a :obj:`bool`, for any subtype -- :obj:`bool` is an :obj:`int` subclass, so ``True`` would otherwise be converted, to ``::1`` for ``IPv6_Address`` and to a one-octet identifier for the numeric subtypes, neither of which a caller passing a flag can plausibly have meant; pass ``int(...)`` to get the numeric value. If ``identifier`` is a negative :obj:`int` (no subtype has a wire form for one), an :obj:`int` of any value with the ``NAI`` subtype, an :obj:`int` of ``2**128`` or above with the ``IPv6_Address`` subtype (whose wire form is a fixed 16 octets, unlike the other subtypes, which have no ceiling and simply pack into more), or ``identifier`` is of a type its subtype's field cannot hold at all: anything but :obj:`str` for ``NAI``, anything but :obj:`bytes`/:obj:`bytearray`/:obj:`int` for the remaining subtypes -- an :obj:`int` is converted rather than rejected there -- or anything :class:`ipaddress.IPv6Address` itself does not accept for ``IPv6_Address``. Also if ``subtype`` is a name, a :obj:`bool` or a code its field cannot carry, or the option is too long for its one-octet ``Option Length``. .. versionchanged:: 1.6.0 An ``NAI`` identifier's length counts the octets it packs as, rather than its characters (:issue:`2684`). """ if option is not None: subtype_val = option.subtype identifier = option.identifier self._make_uint(subtype_val, bits=8, name='MN-ID subtype') else: subtype_val = self._make_index(subtype, Enum_MNIDSubtype, bits=8, name='MN-ID subtype') if isinstance(identifier, int) and identifier < 0: # NOTE: Checked before the subtype dispatch, since no subtype can pack a # negative identifier and each would leak a different bare stdlib error. raise ProtocolError( f'{self.alias}: [OptNo {type}] MN-ID subtype {subtype_val!r} ' f'identifier must be a non-negative int, not {identifier!r}') if isinstance(identifier, bool): # NOTE: Checked before the dispatch, since a ``bool`` would otherwise pack as # ``::1`` or as one octet; a caller wanting the integer passes ``int(flag)``. raise ProtocolError( f'{self.alias}: [OptNo {type}] MN-ID identifier must not be a ' f'bool, not {identifier!r} -- pass int({identifier!r}) if the ' f'numeric value is what is wanted') # NOTE: ``subtype_val`` picks the wire format (see ``mn_id_selector``), not the type # of ``identifier``; an ``IPv6_Address`` always packs 16 octets, so it is normalised. if subtype_val == Enum_MNIDSubtype.IPv6_Address: if isinstance(identifier, int) and identifier >= 1 << 128: # NOTE: Only ``IPv6_Address`` caps at 128 bits, so this bound lives here, and is # checked explicitly so the ``try`` below handles only unconvertible types. raise ProtocolError( f'{self.alias}: [OptNo {type}] MN-ID subtype IPv6_Address ' f'identifier must be an int below 2**128, not {identifier!r}') if not isinstance(identifier, ipaddress.IPv6Address): # NOTE: Wraps only this call, turning ``AddressValueError`` for anything # ``IPv6Address`` cannot convert into a ``ProtocolError``. try: identifier = ipaddress.IPv6Address(identifier) except ValueError as error: raise ProtocolError( f'{self.alias}: [OptNo {type}] MN-ID subtype IPv6_Address ' f'identifier must be an ipaddress.IPv6Address, an int, or ' f'bytes/str it accepts, not {identifier!r}') from error id_len = 16 elif isinstance(identifier, int): if subtype_val == Enum_MNIDSubtype.NAI: # NOTE: A decimal-digit string is not an NAI (``user@realm``, RFC 4283), so an # int is refused rather than packed as text. raise ProtocolError( f'{self.alias}: [OptNo {type}] MN-ID subtype NAI identifier ' f'must be str, not int -- pass str({identifier!r}) if a ' f'decimal-digit NAI is really what is wanted') # NOTE: Every other subtype's field is a BytesField (see mn_id_selector), # so the identifier is written as its minimal big-endian octets, at least one. id_len = self._uint_size(identifier, minimum=1) identifier = identifier.to_bytes(id_len, 'big') elif subtype_val == Enum_MNIDSubtype.NAI: # NOTE: The ``StringField`` packs with ``.encode``, so a non-str would leak a bare # stdlib error; ``bytes`` is refused rather than decoded, as the wrong representation. if not isinstance(identifier, str): raise ProtocolError( f'{self.alias}: [OptNo {type}] MN-ID subtype NAI ' f'identifier must be str, not {identifier!r}') # NOTE: The field packs UTF-8 octets, or a DecodedString's ``raw`` # ones, so the length counts those rather than characters (#2684). id_len = len(getattr(identifier, 'raw', None) or identifier.encode()) else: # NOTE: The ``BytesField`` packs with ``struct``'s ``s`` format, which takes only # ``bytes`` and ``bytearray``; anything else would leak a bare stdlib error. if not isinstance(identifier, (bytes, bytearray)): raise ProtocolError( f'{self.alias}: [OptNo {type}] MN-ID subtype {subtype_val!r} ' f'identifier must be bytes, not {identifier!r}') id_len = len(identifier) return Schema_MNIDOption( type=type, length=self._make_uint(1 + id_len, bits=8, name='Option Length'), subtype=subtype_val, identifier=identifier, )
[docs] def _make_opt_auth(self, type: 'Enum_Option', option: 'Optional[Data_AuthOption]' = None, *, subtype: 'Enum_AuthSubtype | int' = Enum_AuthSubtype.MN_HA, spi: 'int' = 0, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_AuthOption': """Make MH authentication option. Args: type: Option type. option: Option data model. subtype: Authentication subtype. spi: Security parameter index. data: Authentication data. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``data`` is not two octets more than a multiple of four, which is the only length leaving an ``Option Length`` of ``5 + len(data)`` that :meth:`_read_opt_auth` accepts; or if ``spi`` does not fit in four octets. Also if ``subtype`` is a name, a :obj:`bool` or a code its field cannot carry, or the option is too long for its one-octet ``Option Length``. """ if option is not None: subtype_val = option.subtype spi = option.spi data = option.data else: subtype_val = self._make_index(subtype, Enum_AuthSubtype, bits=8, name='subtype') if (len(data) + 6) % 4 != 0: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid data: must be two octets ' f'more than a multiple of four, not {data!r}') return Schema_AuthOption( type=type, length=self._make_uint(5 + len(data), bits=8, name='Option Length'), subtype=subtype_val, spi=self._make_uint(spi, bits=32, name='SPI'), data=data, )
[docs] def _make_opt_mesg_id(self, type: 'Enum_Option', option: 'Optional[Data_MesgIDOption]' = None, *, timestamp: 'Optional[Data_NTPTimestamp]' = None, interval: 'Optional[dt_type]' = None, **kwargs: 'Any') -> 'Schema_MesgIDOption': """Make MH mobility message replay protection option. Args: type: Option type. option: Option data model. timestamp: NTP timestamp, see :rfc:`1305`. interval: Moment to encode when neither ``option`` nor ``timestamp`` is given (``option`` supplies its own ``ntp_timestamp``); defaults to the current time. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. """ if option is not None: timestamp = option.ntp_timestamp if timestamp is None: interval = interval or datetime.datetime.now(datetime.timezone.utc) int_ts = totimestamp(interval) ts_sec = math.floor(int_ts) ts_frc = min(round((int_ts - ts_sec) * 2**32), 0xFFFF_FFFF) # 1/2**32 seconds per unit timestamp = Data_NTPTimestamp(seconds=ts_sec + 2_208_988_800, # 70 years fraction=ts_frc) return Schema_MesgIDOption( type=type, length=8, seconds=timestamp.seconds, fraction=timestamp.fraction, )
[docs] def _make_opt_cga_pr(self, type: 'Enum_Option', option: 'Optional[Data_CGAParametersRequestOption]' = None, **kwargs: 'Any') -> 'Schema_CGAParametersRequestOption': """Make MH CGA parameters request option. Args: type: Option type. option: Option data model. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. """ return Schema_CGAParametersRequestOption( type=type, length=0, )
[docs] def _make_opt_cga_param(self, type: 'Enum_Option', option: 'Optional[Data_CGAParametersOption]' = None, *, parameters: 'Optional[list[Schema_CGAParameter | Data_CGAParameter | bytes]]' = None, **kwargs: 'Any') -> 'Schema_CGAParametersOption': """Make MH CGA parameters option. Args: type: Option type. option: Option data model. parameters: CGA parameters, each as octets, a schema or a data model. A :obj:`dict` is refused: a mapping stands for a maker's keyword arguments, and no maker takes a CGA parameter's fields, so build the data model from them. A schema without ``public_key_test`` is built from a copy that takes one from its key, and is left as given. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``, or an item of ``parameters`` is not octets, a schema or a data model, such as a :obj:`dict`. """ if option is not None: parameters = cast('list[Data_CGAParameter]', option.parameters) # type: ignore[assignment] if parameters is None: parameters = [] param = [] # type: list[Schema_CGAParameter | bytes] length = 0 for data in parameters: if isinstance(data, bytes): length += len(data) param.append(data) elif isinstance(data, Schema_CGAParameter): schema = data # NOTE: Test the schema, not ``hasattr``: an unset field is not # stored, so ``hasattr`` finds the class's field object (#2725). if 'public_key_test' not in data: # A copy takes the key test, leaving the caller's schema as given. schema = Schema_CGAParameter.from_dict( data.to_dict(), public_key_test={'len': max(len(data.public_key) - 2, 0)}) _, ext_len = self._make_cga_extensions(schema.extensions) length += 25 + len(schema.public_key) + ext_len param.append(schema) elif isinstance(data, Data_CGAParameter): ext, ext_len = self._make_cga_extensions(data.extensions) schema = Schema_CGAParameter( modifier=data.modifier, prefix=data.prefix, collision_count=data.collision_count, public_key=data.public_key, extensions=ext, ) schema.public_key_test = {'len': max(len(data.public_key) - 2, 0)} length += 25 + len(data.public_key) + ext_len param.append(schema) else: raise ProtocolError(f'{self.alias}: [OptNo {type}] unknown CGA parameter format: must be bytes, ' f'a CGA parameter schema or data model, not {data!r}') return Schema_CGAParametersOption( type=type, length=self._make_uint(length, bits=8, name='Option Length'), parameters=param, )
[docs] def _make_opt_signature(self, type: 'Enum_Option', option: 'Optional[Data_SignatureOption]' = None, *, signature: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_SignatureOption': """Make MH signature option. Args: type: Option type. option: Option data model. signature: Signature data. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. """ if option is not None: signature = option.signature return Schema_SignatureOption( type=type, length=self._make_uint(len(signature), bits=8, name='Option Length'), signature=signature, )
[docs] def _make_opt_phkt(self, type: 'Enum_Option', option: 'Optional[Data_PermanentHomeKeygenTokenOption]' = None, *, token: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_PermanentHomeKeygenTokenOption': """Make MH permanent home keygen token option. Args: type: Option type. option: Option data model. token: Token data. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. """ if option is not None: token = option.token return Schema_PermanentHomeKeygenTokenOption( type=type, length=self._make_uint(len(token), bits=8, name='Option Length'), token=token, )
[docs] def _make_opt_ct_init(self, type: 'Enum_Option', option: 'Optional[Data_CareofTestInitOption]' = None, **kwargs: 'Any') -> 'Schema_CareofTestInitOption': """Make MH Care-of Test Init option. Args: type: Option type. option: Option data model. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. """ return Schema_CareofTestInitOption( type=type, length=0, )
[docs] def _make_opt_ct(self, type: 'Enum_Option', option: 'Optional[Data_CareofTestOption]' = None, token: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00', **kwargs: 'Any') -> 'Schema_CareofTestOption': """Make MH Care-of Test option. Args: type: Option type. option: Option data model. token: Care-of keygen token. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``token`` is not eight octets. """ if option is not None: token = option.token return Schema_CareofTestOption( type=type, length=8, token=self._make_octets(token, size=8, name='token'), )
[docs] def _make_opt_dns(self, type: 'Enum_Option', option: 'Optional[Data_DNSUpdateOption]' = None, *, status: 'Enum_DNSStatusCode | int' = Enum_DNSStatusCode.DNS_update_performed, remove: 'bool' = False, identity: 'bytes' = b'', reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_DNSUpdateOption': """Make MH DNS-UPDATE-TYPE option. Args: type: Option type. option: Option data model. status: Status. remove: Remove flag. identity: Mobile node identity, in FQDN form. reserved: Reserved bits, i.e. the seven bits after the ``R`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``reserved`` does not fit its field. Also if ``status`` is a name, a :obj:`bool` or a code its field cannot carry, or the option is too long for its one-octet ``Option Length``. """ if option is not None: status_val = option.status # type: Enum_DNSStatusCode remove = option.remove reserved = option.reserved identity = option.identity self._make_uint(status_val, bits=8, name='status') else: status_val = self._make_index(status, Enum_DNSStatusCode, bits=8, name='status') return Schema_DNSUpdateOption( type=type, length=self._make_uint(2 + len(identity), bits=8, name='Option Length'), status=status_val, flags={'R': int(remove), 'reserved': self._make_uint(reserved, bits=7, name='reserved bits')}, identity=identity, )
[docs] def _make_opt_exp(self, type: 'Enum_Option', option: 'Optional[Data_ExperimentalMobilityOption]' = None, *, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_ExperimentalMobilityOption': """Make MH experimental mobility option. Args: type: Option type. option: Option data model. data: Experimental data. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. """ if option is not None: data = option.data return Schema_ExperimentalMobilityOption( type=type, length=self._make_uint(len(data), bits=8, name='Option Length'), data=data, )
[docs] def _make_opt_vendor(self, type: 'Enum_Option', option: 'Optional[Data_VendorSpecificOption]' = None, *, vendor: 'int' = 0, subtype: 'int' = 0, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_VendorSpecificOption': """Make MH vendor specific mobility option. Args: type: Option type. option: Option data model. vendor: Vendor ID, an SMI Network Management Private Enterprise Number. subtype: Vendor-administered sub-type. data: Vendor-specific data. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``vendor`` or ``subtype`` does not fit its field. Also if the option is too long for its one-octet ``Option Length``. """ if option is not None: vendor = option.vendor subtype = option.subtype data = option.data return Schema_VendorSpecificOption( type=type, length=self._make_uint(5 + len(data), bits=8, name='Option Length'), vendor=self._make_uint(vendor, bits=32, name='vendor ID'), subtype=self._make_uint(subtype, bits=8, name='vendor subtype'), data=data, )
[docs] def _make_opt_service(self, type: 'Enum_Option', option: 'Optional[Data_ServiceSelectionOption]' = None, *, identifier: 'str' = '', **kwargs: 'Any') -> 'Schema_ServiceSelectionOption': """Make MH service selection mobility option. Args: type: Option type. option: Option data model. identifier: Service identifier. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. .. versionchanged:: 1.6.0 The length counts the octets a :class:`~pcapkit.corekit.fields.strings.DecodedString` identifier packs back as, rather than its text re-encoded (:issue:`2684`). """ if option is not None: identifier = option.identifier # NOTE: An identifier that is not UTF-8 parses as a DecodedString, whose # ``raw`` octets are what packs back, so the length counts those (#2684). encoded = getattr(identifier, 'raw', None) or identifier.encode() if not encoded: raise ProtocolError(f'{self.alias}: [OptNo {type}] service identifier must not be empty') return Schema_ServiceSelectionOption( type=type, length=self._make_uint(len(encoded), bits=8, name='Option Length'), identifier=identifier, )
[docs] def _make_opt_badf(self, type: 'Enum_Option', option: 'Optional[Data_BADFOption]' = None, *, spi: 'int' = 0, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_BADFOption': """Make MH binding authorization data for FMIPv6 (BADF) option. Args: type: Option type. option: Option data model. spi: Security parameter index. data: Authenticator. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``data`` is empty, since the ``length`` field then reads as ``0``, which :meth:`_read_opt_badf` rejects; or if ``spi`` does not fit in four octets. Also if the option is too long for its one-octet ``Option Length``. Note: The ``length`` field counts the authenticator only, excluding the SPI, see :meth:`_read_opt_badf`. """ if option is not None: spi = option.spi data = option.data if not data: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid data: ' f'must be at least one octet, not {data!r}') return Schema_BADFOption( type=type, length=self._make_uint(len(data), bits=8, name='Option Length'), spi=self._make_uint(spi, bits=32, name='SPI'), data=data, )
[docs] def _make_opt_hnp(self, type: 'Enum_Option', option: 'Optional[Data_HomeNetworkPrefixOption]' = None, *, prefix_length: 'int' = 64, prefix: 'bytes | str | int | IPv6Address' = '::', reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_HomeNetworkPrefixOption': """Make MH home network prefix option. Args: type: Option type. option: Option data model. prefix_length: Prefix length. prefix: Home network prefix. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`, or does not fit in one octet. Also if ``reserved`` is not one octet. Note: The prefix length is checked against its 8-bit field alone, as :rfc:`5213#section-5.3.2` has a local mobility anchor answer a prefix it cannot authorise with a status rather than discard the option, so whatever the reader keeps is written back. .. versionchanged:: 1.6.0 A prefix length longer than the address is written, rather than refused, as the reader keeps one (:issue:`2702`). """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') prefix_length = option.prefix_length prefix = option.prefix prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length') return Schema_HomeNetworkPrefixOption( type=type, length=18, reserved=self._make_octets(reserved, size=1, name='reserved octets'), prefix_length=prefix_length, prefix=prefix, )
[docs] def _make_opt_hi(self, type: 'Enum_Option', option: 'Optional[Data_HandoffIndicatorOption]' = None, *, hi: 'Enum_HandoffType | int' = Enum_HandoffType.Attachment_over_a_new_interface, reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_HandoffIndicatorOption': """Make MH handoff indicator option. Args: type: Option type. option: Option data model. hi: Handoff indicator. reserved: Reserved octet before the handoff indicator. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``reserved`` does not fit its field. Also if ``hi`` is a name, a :obj:`bool` or a code its field cannot carry. """ if option is not None: hi_val = option.hi # type: Enum_HandoffType reserved = option.reserved self._make_uint(hi_val, bits=8, name='handoff type') else: hi_val = self._make_index(hi, Enum_HandoffType, bits=8, name='handoff type') return Schema_HandoffIndicatorOption( type=type, length=2, reserved=self._make_uint(reserved, bits=8, name='reserved octet'), hi=hi_val, )
[docs] def _make_opt_att(self, type: 'Enum_Option', option: 'Optional[Data_AccessTechnologyTypeOption]' = None, *, att: 'Enum_AccessType | int' = Enum_AccessType.Virtual, reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_AccessTechnologyTypeOption': """Make MH access technology type option. Args: type: Option type. option: Option data model. att: Access technology type. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``att`` is a name, a :obj:`bool` or a code its field cannot carry. Also if ``reserved`` is not one octet. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') att_val = option.att # type: Enum_AccessType self._make_uint(att_val, bits=8, name='access technology type') else: att_val = self._make_index(att, Enum_AccessType, bits=8, name='access technology type') return Schema_AccessTechnologyTypeOption( type=type, length=2, reserved=self._make_octets(reserved, size=1, name='reserved octets'), att=att_val, )
[docs] def _make_opt_mn_lli(self, type: 'Enum_Option', option: 'Optional[Data_MNLLIdentifierOption]' = None, *, lli: 'bytes' = b'', reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_MNLLIdentifierOption': """Make MH mobile node link-layer identifier option. Args: type: Option type. option: Option data model. lli: Link-layer identifier. reserved: The two reserved octets as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. Also if ``reserved`` is not two octets. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00') lli = option.lli return Schema_MNLLIdentifierOption( type=type, length=self._make_uint(2 + len(lli), bits=8, name='Option Length'), reserved=self._make_octets(reserved, size=2, name='reserved octets'), lli=lli, )
[docs] def _make_opt_lla_addr(self, type: 'Enum_Option', option: 'Optional[Data_LinkLocalAddressOption]' = None, *, address: 'bytes | str | int | IPv6Address' = '::', **kwargs: 'Any') -> 'Schema_LinkLocalAddressOption': """Make MH link-local address option. Args: type: Option type. option: Option data model. address: Link-local address. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. """ if option is not None: address = option.address return Schema_LinkLocalAddressOption( type=type, length=16, address=address, )
[docs] def _make_opt_timestamp(self, type: 'Enum_Option', option: 'Optional[Data_TimestampOption]' = None, *, seconds: 'int' = 0, fraction: 'int' = 0, **kwargs: 'Any') -> 'Schema_TimestampOption': """Make MH timestamp option. Args: type: Option type. option: Option data model. seconds: Seconds since 1 January 1970, in the leading 48 bits. fraction: Fraction of a second, in units of 1/65536. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``seconds`` or ``fraction`` does not fit its field. Note: The fixed-point pair is taken from :attr:`~pcapkit.protocols.internet.mh.data.TimestampOption.pmip_timestamp` rather than recomputed from :attr:`~pcapkit.protocols.internet.mh.data.TimestampOption.timestamp`, since a :class:`~datetime.datetime` holds microseconds and the wire format holds 1/65536ths -- converting between them does not round-trip. """ if option is not None: seconds = option.pmip_timestamp.seconds fraction = option.pmip_timestamp.fraction seconds = self._make_uint(seconds, bits=48, name='timestamp seconds') fraction = self._make_uint(fraction, bits=16, name='timestamp fraction') return Schema_TimestampOption( type=type, length=8, timestamp={ 'seconds': seconds, 'fraction': fraction, }, )
[docs] def _make_opt_restart(self, type: 'Enum_Option', option: 'Optional[Data_RestartCounterOption]' = None, *, counter: 'int' = 0, **kwargs: 'Any') -> 'Schema_RestartCounterOption': """Make MH restart counter option. Args: type: Option type. option: Option data model. counter: Restart counter. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``counter`` does not fit its field. """ if option is not None: counter = option.counter return Schema_RestartCounterOption( type=type, length=4, counter=self._make_uint(counter, bits=32, name='restart counter'), )
[docs] def _make_opt_ipv4_hoa(self, type: 'Enum_Option', option: 'Optional[Data_IPv4HomeAddressOption]' = None, *, prefix_length: 'int' = 32, address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind request_prefix: 'bool' = False, reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4HomeAddressOption': """Make MH IPv4 home address option. Args: type: Option type. option: Option data model. prefix_length: Prefix length; ``32`` for a single address. address: IPv4 home address. request_prefix: Mobile network prefix request flag. reserved: Reserved bits, i.e. the nine bits after the ``P`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix_length`` is zero, negative, a :obj:`bool`, or longer than the address, or ``reserved`` does not fit its field. """ if option is not None: prefix_length = option.prefix_length address = option.address request_prefix = option.request_prefix reserved = option.reserved prefix_length = self._make_uint(prefix_length, bits=6, name='prefix length') if prefix_length == 0 or prefix_length > 32: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}') return Schema_IPv4HomeAddressOption( type=type, length=6, flags={ 'prefix_length': prefix_length, 'P': int(request_prefix), 'reserved': self._make_uint(reserved, bits=9, name='reserved bits'), }, address=address, )
[docs] def _make_opt_ipv4_ack(self, type: 'Enum_Option', option: 'Optional[Data_IPv4AddressAcknowledgementOption]' = None, *, status: 'Enum_DSMIPv6HomeAddress | int' = Enum_DSMIPv6HomeAddress.Success, prefix_length: 'int' = 32, address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4AddressAcknowledgementOption': """Make MH IPv4 address acknowledgement option. Args: type: Option type. option: Option data model. status: Status. prefix_length: Allocated prefix length. address: Assigned IPv4 home address. reserved: Reserved bits, i.e. the two bits after the prefix length. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix_length`` or ``reserved`` is negative, a :obj:`bool`, or does not fit its field. Also if ``status`` is a name, a :obj:`bool` or a code its field cannot carry. Note: The prefix length is checked against its 6-bit field alone, as :rfc:`5555#section-3.2.1` does not have the receiver discard one longer than the address, so whatever the reader keeps is written back. .. versionchanged:: 1.6.0 A prefix length longer than the address is written, rather than refused, so that every one the reader keeps rebuilds (:issue:`2684`). """ if option is not None: status_val = option.status # type: Enum_DSMIPv6HomeAddress prefix_length = option.prefix_length reserved = option.reserved address = option.address self._make_uint(status_val, bits=8, name='status') else: status_val = self._make_index(status, Enum_DSMIPv6HomeAddress, bits=8, name='status') prefix_length = self._make_uint(prefix_length, bits=6, name='prefix length') return Schema_IPv4AddressAcknowledgementOption( type=type, length=6, status=status_val, flags={'prefix_length': prefix_length, 'reserved': self._make_uint(reserved, bits=2, name='reserved bits')}, address=address, )
[docs] def _make_opt_nat(self, type: 'Enum_Option', option: 'Optional[Data_NATDetectionOption]' = None, *, force: 'bool' = False, refresh: 'int | timedelta' = 0, reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_NATDetectionOption': """Make MH NAT detection option. Args: type: Option type. option: Option data model. force: Force UDP encapsulation flag. refresh: Suggested NAT binding refresh interval, in seconds or as a :class:`~datetime.timedelta`. reserved: Reserved bits, i.e. the 15 bits after the ``F`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``reserved`` or the refresh interval does not fit its field. """ if option is not None: force = option.force reserved = option.reserved refresh_val = math.ceil(option.refresh.total_seconds()) else: refresh_val = refresh if isinstance(refresh, int) else math.ceil(refresh.total_seconds()) return Schema_NATDetectionOption( type=type, length=6, flags={'F': int(force), 'reserved': self._make_uint(reserved, bits=15, name='reserved bits')}, refresh=self._make_uint(refresh_val, bits=32, name='refresh interval'), )
[docs] def _make_opt_ipv4_coa(self, type: 'Enum_Option', option: 'Optional[Data_IPv4CareofAddressOption]' = None, *, address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_IPv4CareofAddressOption': """Make MH IPv4 care-of address option. Args: type: Option type. option: Option data model. address: IPv4 care-of address. reserved: The two reserved octets as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``reserved`` is not two octets. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00') address = option.address return Schema_IPv4CareofAddressOption( type=type, length=6, reserved=self._make_octets(reserved, size=2, name='reserved octets'), address=address, )
[docs] def _make_opt_gre(self, type: 'Enum_Option', option: 'Optional[Data_GREKeyOption]' = None, *, key: 'Optional[int]' = None, reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_GREKeyOption': """Make MH GRE key option. Args: type: Option type. option: Option data model. key: GRE key identifier, or :obj:`None` to omit it -- which is what an option length of 2 means on the wire. reserved: The two reserved octets as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``key`` does not fit its field. Also if ``reserved`` is not two octets. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00') key = option.key return Schema_GREKeyOption( type=type, length=2 if key is None else 6, reserved=self._make_octets(reserved, size=2, name='reserved octets'), key=None if key is None else self._make_uint(key, bits=32, name='GRE key'), )
[docs] def _make_opt_ipv6_ap(self, type: 'Enum_Option', option: 'Optional[Data_IPv6AddressPrefixOption]' = None, *, code: 'IPv6AddressPrefixCode | int' = IPv6AddressPrefixCode.New_Care_of_Address, prefix_length: 'int' = 128, address: 'bytes | str | int | IPv6Address' = '::', **kwargs: 'Any') -> 'Schema_IPv6AddressPrefixOption': """Make MH mobility header IPv6 address/prefix option. Args: type: Option type. option: Option data model. code: Option code, see :class:`~pcapkit.protocols.internet.mh.protocol.IPv6AddressPrefixCode`. prefix_length: Prefix length. address: IPv6 address/prefix. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`, or longer than the address, or ``code`` is a name, a :obj:`bool` or a code its field cannot carry. """ if option is not None: code_val = option.code prefix_length = option.prefix_length address = option.address else: code_val = self._make_index(code, IPv6AddressPrefixCode, bits=8, name='code') prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length') if prefix_length > 128: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}') return Schema_IPv6AddressPrefixOption( type=type, length=18, code=code_val, prefix_length=prefix_length, address=address, )
[docs] def _make_opt_bid(self, type: 'Enum_Option', option: 'Optional[Data_BindingIdentifierOption]' = None, *, bid: 'int' = 1, status: 'Enum_StatusCode | int' = Enum_StatusCode.Binding_Update_accepted_Proxy_Binding_Update_accepted, # pylint: disable=line-too-long simultaneous: 'bool' = False, bid_pri: 'int' = 0, address: 'Optional[bytes | str | int | IPv4Address | IPv6Address]' = None, **kwargs: 'Any') -> 'Schema_BindingIdentifierOption': """Make MH binding identifier (BID) option. Args: type: Option type. option: Option data model. bid: Binding identifier. status: Status, overriding the message status for this binding alone. simultaneous: Simultaneous home and foreign binding flag. bid_pri: Binding priority. address: Care-of address, or :obj:`None` to omit it. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``bid`` or ``bid_pri`` does not fit its field. Also if ``status`` is a name, a :obj:`bool` or a code its field cannot carry. Note: The option length is derived from the address family, since that is the only thing that carries it on the wire: 4 with no address, 8 for an IPv4 one and 20 for an IPv6 one [:rfc:`5648#section-4.3`]. """ if option is not None: bid = option.bid status_val = option.status # type: Enum_StatusCode simultaneous = option.simultaneous bid_pri = option.bid_pri address = option.address self._make_uint(status_val, bits=8, name='status') else: status_val = self._make_index(status, Enum_StatusCode, bits=8, name='status') bid = self._make_uint(bid, bits=16, name='binding identifier') bid_pri = self._make_uint(bid_pri, bits=7, name='binding priority') if address is None: length = 4 else: # NOTE: Converted here, as the length follows the family, so ``parse_ip_address`` # refuses a ``bool`` that would otherwise pack as ``0.0.0.1``. addr = parse_ip_address( address, f'{self.alias}: [OptNo {type}] invalid care-of address') length = 8 if addr.version == 4 else 20 address = addr return Schema_BindingIdentifierOption( type=type, length=length, bid=bid, status=status_val, flags={ 'H': int(simultaneous), 'BID_PRI': bid_pri, }, address=address, )
[docs] def _make_opt_ipv4_hoa_req(self, type: 'Enum_Option', option: 'Optional[Data_IPv4HomeAddressRequestOption]' = None, *, prefix_length: 'int' = 32, address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4HomeAddressRequestOption': """Make MH IPv4 home address request option. Args: type: Option type. option: Option data model. prefix_length: Prefix length of the requested home network. address: Requested IPv4 home address. reserved: Reserved bits, i.e. the ten bits after the prefix length. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix_length`` or ``reserved`` is negative, a :obj:`bool`, or does not fit its field. Note: The prefix length is checked against its 6-bit field alone, as :rfc:`5844#section-3.3.1` does not have the receiver discard one longer than the address, so whatever the reader keeps is written back. .. versionchanged:: 1.6.0 A prefix length longer than the address is written, rather than refused, so that every one the reader keeps rebuilds (:issue:`2684`). """ if option is not None: prefix_length = option.prefix_length reserved = option.reserved address = option.address prefix_length = self._make_uint(prefix_length, bits=6, name='prefix length') return Schema_IPv4HomeAddressRequestOption( type=type, length=6, flags={'prefix_length': prefix_length, 'reserved': self._make_uint(reserved, bits=10, name='reserved bits')}, address=address, )
[docs] def _make_opt_ipv4_hoa_rep(self, type: 'Enum_Option', option: 'Optional[Data_IPv4HomeAddressReplyOption]' = None, *, status: 'Enum_HomeAddressReply | int' = Enum_HomeAddressReply.Success, prefix_length: 'int' = 32, address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4HomeAddressReplyOption': """Make MH IPv4 home address reply option. Args: type: Option type. option: Option data model. status: Status. prefix_length: Prefix length of the assigned home network. address: Assigned IPv4 home address. reserved: Reserved bits, i.e. the two bits after the prefix length. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix_length`` or ``reserved`` is negative, a :obj:`bool`, or does not fit its field. Also if ``status`` is a name, a :obj:`bool` or a code its field cannot carry. Note: The prefix length is checked against its 6-bit field alone, as :rfc:`5844#section-3.3.2` does not have the receiver discard one longer than the address, so whatever the reader keeps is written back. .. versionchanged:: 1.6.0 A prefix length longer than the address is written, rather than refused, so that every one the reader keeps rebuilds (:issue:`2684`). """ if option is not None: status_val = option.status # type: Enum_HomeAddressReply prefix_length = option.prefix_length reserved = option.reserved address = option.address self._make_uint(status_val, bits=8, name='status') else: status_val = self._make_index(status, Enum_HomeAddressReply, bits=8, name='status') prefix_length = self._make_uint(prefix_length, bits=6, name='prefix length') return Schema_IPv4HomeAddressReplyOption( type=type, length=6, status=status_val, flags={'prefix_length': prefix_length, 'reserved': self._make_uint(reserved, bits=2, name='reserved bits')}, address=address, )
[docs] def _make_opt_ipv4_router(self, type: 'Enum_Option', option: 'Optional[Data_IPv4DefaultRouterAddressOption]' = None, *, address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_IPv4DefaultRouterAddressOption': """Make MH IPv4 default-router address option. Args: type: Option type. option: Option data model. address: IPv4 default-router address. reserved: The two reserved octets as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``reserved`` is not two octets. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00') address = option.address return Schema_IPv4DefaultRouterAddressOption( type=type, length=6, reserved=self._make_octets(reserved, size=2, name='reserved octets'), address=address, )
[docs] def _make_opt_ipv4_dhcp(self, type: 'Enum_Option', option: 'Optional[Data_IPv4DHCPSupportModeOption]' = None, *, mode: 'Enum_DHCPSupportMode | int' = Enum_DHCPSupportMode.Unassigned_0x0, reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4DHCPSupportModeOption': """Make MH IPv4 DHCP support mode option. Args: type: Option type. option: Option data model. mode: DHCP support mode. reserved: Reserved bits, i.e. the 15 bits before the ``S`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the mode does not fit the one-bit ``S`` flag, or ``reserved`` does not fit its field; or if ``mode`` is a name. """ if option is not None: mode_val = self._make_uint(int(option.mode), bits=1, name='DHCP support mode') reserved = option.reserved else: mode_val = int(self._make_index(mode, Enum_DHCPSupportMode, bits=1, name='DHCP support mode')) return Schema_IPv4DHCPSupportModeOption( type=type, length=2, flags={'S': mode_val, 'reserved': self._make_uint(reserved, bits=15, name='reserved bits')}, )
[docs] def _make_opt_cr(self, type: 'Enum_Option', option: 'Optional[Data_ContextRequestOption]' = None, *, requests: 'Optional[list[Data_ContextRequest | tuple[Enum_Option | int, bytes]]]' = None, reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_ContextRequestOption': """Make MH context request option. Args: type: Option type. option: Option data model. requests: Requested contexts, each either a data model or a ``(mobility option type, extra data)`` pair. reserved: The two reserved octets as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``, or the mobility option type of a pair is a name, a :obj:`bool` or a code its field cannot carry. Also if ``reserved`` is not two octets. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00') requests = cast('list[Data_ContextRequest | tuple[Enum_Option | int, bytes]]', list(option.requests)) else: requests = requests or [] buffer = bytearray() for request in requests: if isinstance(request, Data_ContextRequest): req_type, value = int(request.type), request.value else: raw_type, value = request req_type = int(self._make_index(raw_type, Enum_Option, bits=8, name='request type')) if len(value) > 0xFF: raise ProtocolError(f'{self.alias}: [OptNo {type}] context request value of ' f'{len(value)} octets does not fit its length field') buffer.append(req_type) buffer.append(len(value)) buffer.extend(value) return Schema_ContextRequestOption( type=type, length=self._make_uint(2 + len(buffer), bits=8, name='Option Length'), reserved=self._make_octets(reserved, size=2, name='reserved octets'), requests=bytes(buffer), )
[docs] def _make_opt_lmaa(self, type: 'Enum_Option', option: 'Optional[Data_LMAAddressOption]' = None, *, code: 'Optional[LMAAddressCode | int]' = None, address: 'bytes | str | int | IPv4Address | IPv6Address' = '::', reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_LMAAddressOption': """Make MH local mobility anchor address option. Args: type: Option type. option: Option data model. code: Option code, see :class:`~pcapkit.protocols.internet.mh.protocol.LMAAddressCode`. If not given, the code of the address family of ``address``. address: Local mobility anchor address. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If an explicit ``code`` is not the code :rfc:`5949#section-6.2.2` gives the address family of ``address`` (:issue:`2280`), or is a name, a :obj:`bool` or a code its field cannot carry. Also if ``reserved`` is not one octet. Note: The option length is derived from the address family rather than from ``code``, so that the two cannot be emitted disagreeing. A data model passed as ``option`` keeps its code as captured, even where it disagrees with the family, so that the capture rebuilds byte for byte. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') code_val = self._make_uint(int(option.code), bits=8, name='code') # type: Optional[int] address = option.address elif code is None: code_val = None else: code_val = self._make_index(code, LMAAddressCode, bits=8, name='code') if isinstance(address, bytes) and len(address) not in (4, 16): raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid address: {address!r}') # NOTE: Normalised here, so the attribute matches a parse and the width follows the # address; ``parse_ip_address`` refuses a ``bool`` the schema's guard would never see. addr = parse_ip_address( address, f'{self.alias}: [OptNo {type}] invalid address') # NOTE: RFC 5949 ties each code to one address family, so an unset code is the family's and a # set one must agree (#2280). A data model as ``option`` skips this, so a captured mismatch rebuilds. family_code = LMAAddressCode.IPv4_LMAA if addr.version == 4 else LMAAddressCode.IPv6_LMAA if code_val is None: code_val = int(family_code) elif option is None and code_val != family_code: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid code: ' f'code does not match address family: {code_val}') return Schema_LMAAddressOption( type=type, length=6 if addr.version == 4 else 18, code=code_val, reserved=self._make_octets(reserved, size=1, name='reserved octets'), address=addr, )
[docs] def _make_opt_mn_lla_iid(self, type: 'Enum_Option', option: 'Optional[Data_MNLLAIIDOption]' = None, *, iid: 'bytes' = b'\x00' * 8, reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_MNLLAIIDOption': """Make MH mobile node link-local address interface identifier option. Args: type: Option type. option: Option data model. iid: Interface identifier; exactly 8 octets. reserved: The two reserved octets as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``iid`` is not eight octets, or ``reserved`` is not two. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00') iid = option.iid if len(iid) != 8: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid interface identifier: {iid!r}') return Schema_MNLLAIIDOption( type=type, length=10, reserved=self._make_octets(reserved, size=2, name='reserved octets'), iid=iid, )
[docs] def _make_opt_transient(self, type: 'Enum_Option', option: 'Optional[Data_TransientBindingOption]' = None, *, late: 'bool' = False, lifetime: 'int | timedelta' = 0, reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_TransientBindingOption': """Make MH transient binding option. Args: type: Option type. option: Option data model. late: Late path switch flag. lifetime: Maximum lifetime of the transient state, in units of 100 milliseconds or as a :class:`~datetime.timedelta`. reserved: Reserved bits, i.e. the seven bits before the ``L`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the lifetime does not fit in one octet, or ``reserved`` does not fit its field. """ if option is not None: late = option.late reserved = option.reserved lifetime_val = math.ceil(option.lifetime.total_seconds() * 10) else: lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil( lifetime.total_seconds() * 10) lifetime_val = self._make_uint(lifetime_val, bits=8, name='lifetime') return Schema_TransientBindingOption( type=type, length=2, flags={'L': int(late), 'reserved': self._make_uint(reserved, bits=7, name='reserved bits')}, lifetime=lifetime_val, )
[docs] def _make_opt_fs(self, type: 'Enum_Option', option: 'Optional[Data_FlowSummaryOption]' = None, *, fid: 'Optional[list[int]]' = None, **kwargs: 'Any') -> 'Schema_FlowSummaryOption': """Make MH flow summary mobility option. Args: type: Option type. option: Option data model. fid: Flow identifiers being refreshed. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If an entry of ``fid`` does not fit its field. Also if the option is too long for its one-octet ``Option Length``. """ if option is not None: fid = list(option.fid) else: fid = fid or [] for item in fid: self._make_uint(item, bits=16, name='flow identifier') return Schema_FlowSummaryOption( type=type, length=self._make_uint(len(fid) * 2, bits=8, name='Option Length'), fid=fid, )
[docs] def _make_opt_fid(self, type: 'Enum_Option', option: 'Optional[Data_FlowIdentificationOption]' = None, *, fid: 'int' = 1, fid_pri: 'int' = 1, status: 'Enum_FlowIDStatus | int' = Enum_FlowIDStatus.Flow_binding_successful, suboptions: 'Optional[FlowIDSuboption | list[Schema_FlowIdentificationSuboption | tuple[Enum_FlowIDSuboption | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_FlowIdentificationOption': """Make MH flow identification mobility option. Args: type: Option type. option: Option data model. fid: Flow identifier. fid_pri: Flow priority. status: Status. suboptions: Sub-options. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``fid`` or ``fid_pri`` does not fit its field. Also if ``status`` is a name, a :obj:`bool` or a code its field cannot carry, or the option is too long for its one-octet ``Option Length``. Also if ``reserved`` is not one octet. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') fid = option.fid fid_pri = option.fid_pri status_val = option.status # type: Enum_FlowIDStatus suboptions = option.suboptions self._make_uint(status_val, bits=8, name='status') else: status_val = self._make_index(status, Enum_FlowIDStatus, bits=8, name='status') suboptions = suboptions or [] entries = self._make_fid_suboptions(suboptions) length = 6 + sum(len(entry) if isinstance(entry, bytes) else len(entry.pack()) for entry in entries) return Schema_FlowIdentificationOption( type=type, length=self._make_uint(length, bits=8, name='Option Length'), fid=self._make_uint(fid, bits=16, name='flow identifier'), fid_pri=self._make_uint(fid_pri, bits=16, name='flow identifier priority'), reserved=self._make_octets(reserved, size=1, name='reserved octets'), status=status_val, suboptions=entries, )
[docs] def _make_opt_rc(self, type: 'Enum_Option', option: 'Optional[Data_RedirectCapabilityOption]' = None, *, reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_RedirectCapabilityOption': """Make MH redirect-capability mobility option. Args: type: Option type. option: Option data model. reserved: The two reserved octets as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``reserved`` is not two octets. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00') return Schema_RedirectCapabilityOption( type=type, length=2, reserved=self._make_octets(reserved, size=2, name='reserved octets'), )
[docs] def _make_opt_redirect(self, type: 'Enum_Option', option: 'Optional[Data_RedirectOption]' = None, *, ipv6: 'Optional[bytes | str | int | IPv6Address]' = None, ipv4: 'Optional[bytes | str | int | IPv4Address]' = None, reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_RedirectOption': """Make MH redirect mobility option. Args: type: Option type. option: Option data model. ipv6: IPv6 address of the redirected-to local mobility anchor. ipv4: IPv4 address of the redirected-to local mobility anchor. reserved: Reserved bits, i.e. the 14 bits after the ``N`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If neither or both addresses are given. :rfc:`6463#section-4.2` requires exactly one. Also if ``reserved`` does not fit its field. """ if option is not None: ipv6 = option.ipv6 ipv4 = option.ipv4 reserved = option.reserved if (ipv6 is None) == (ipv4 is None): raise ProtocolError(f'{self.alias}: [OptNo {type}] exactly one of the IPv6 and ' 'IPv4 addresses must be given') return Schema_RedirectOption( type=type, length=18 if ipv6 is not None else 6, flags={ 'K': int(ipv6 is not None), 'N': int(ipv4 is not None), 'reserved': self._make_uint(reserved, bits=14, name='reserved bits'), }, ipv6=ipv6, ipv4=ipv4, )
[docs] def _make_opt_load(self, type: 'Enum_Option', option: 'Optional[Data_LoadInformationOption]' = None, *, priority: 'int' = 0, sessions_in_use: 'int' = 0, max_sessions: 'int' = 0, used_capacity: 'int' = 0, max_capacity: 'int' = 0, **kwargs: 'Any') -> 'Schema_LoadInformationOption': """Make MH load information mobility option. Args: type: Option type. option: Option data model. priority: Priority; a lower value is a higher priority. sessions_in_use: Mobility sessions currently in use. max_sessions: Maximum number of mobility sessions accepted. used_capacity: Used capacity, in kilobytes per second. max_capacity: Maximum capacity, in kilobytes per second. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If a priority, session or capacity value does not fit its field. """ if option is not None: priority = option.priority sessions_in_use = option.sessions_in_use max_sessions = option.max_sessions used_capacity = option.used_capacity max_capacity = option.max_capacity return Schema_LoadInformationOption( type=type, length=18, priority=self._make_uint(priority, bits=16, name='priority'), sessions_in_use=self._make_uint(sessions_in_use, bits=32, name='sessions in use'), max_sessions=self._make_uint(max_sessions, bits=32, name='maximum sessions'), used_capacity=self._make_uint(used_capacity, bits=32, name='used capacity'), max_capacity=self._make_uint(max_capacity, bits=32, name='maximum capacity'), )
[docs] def _make_opt_alt_ipv4_coa(self, type: 'Enum_Option', option: 'Optional[Data_AlternateIPv4CareofAddressOption]' = None, *, address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind **kwargs: 'Any') -> 'Schema_AlternateIPv4CareofAddressOption': """Make MH alternate IPv4 care-of address option. Args: type: Option type. option: Option data model. address: Alternate IPv4 care-of address. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. """ if option is not None: address = option.address return Schema_AlternateIPv4CareofAddressOption( type=type, length=4, address=address, )
[docs] def _make_opt_mn_group(self, type: 'Enum_Option', option: 'Optional[Data_MNGroupIdentifierOption]' = None, *, subtype: 'Enum_MNGroupID | int' = Enum_MNGroupID.Bulk_Binding_Update_Group, group_id: 'int' = 1, reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_MNGroupIdentifierOption': """Make MH mobile node group identifier option. Args: type: Option type. option: Option data model. subtype: Sub-type. group_id: Mobile node group identifier. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``group_id`` does not fit its field. Also if ``subtype`` is a name, a :obj:`bool` or a code its field cannot carry. Also if ``reserved`` is not one octet. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') subtype_val = option.subtype # type: Enum_MNGroupID group_id = option.group_id self._make_uint(subtype_val, bits=8, name='group identifier subtype') else: subtype_val = self._make_index(subtype, Enum_MNGroupID, bits=8, name='group identifier subtype') return Schema_MNGroupIdentifierOption( type=type, length=6, subtype=subtype_val, reserved=self._make_octets(reserved, size=1, name='reserved octets'), group_id=self._make_uint(group_id, bits=32, name='group identifier'), )
[docs] def _make_opt_mag_addr(self, type: 'Enum_Option', option: 'Optional[Data_MAGIPv6AddressOption]' = None, *, address_length: 'int' = 128, address: 'bytes | str | int | IPv6Address' = '::', reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_MAGIPv6AddressOption': """Make MH MAG IPv6 address option. Args: type: Option type. option: Option data model. address_length: Address length, in bits; :rfc:`6705#section-11.1` requires 128. address: MAG IPv6 address. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``address_length`` does not fit its field. Also if ``reserved`` is not one octet. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') address_length = option.address_length address = option.address return Schema_MAGIPv6AddressOption( type=type, length=18, reserved=self._make_octets(reserved, size=1, name='reserved octets'), address_length=self._make_uint(address_length, bits=8, name='address length'), address=address, )
[docs] def _make_opt_ani(self, type: 'Enum_Option', option: 'Optional[Data_AccessNetworkIdentifierOption]' = None, *, suboptions: 'Optional[ANISuboption | list[Schema_ANISuboption | tuple[Enum_ANISuboption | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long **kwargs: 'Any') -> 'Schema_AccessNetworkIdentifierOption': """Make MH access network identifier option. Args: type: Option type. option: Option data model. suboptions: Sub-options; at least one is required. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. """ if option is not None: suboptions = option.suboptions else: suboptions = suboptions or [] entries = self._make_ani_suboptions(suboptions) if not entries: raise ProtocolError(f'{self.alias}: [OptNo {type}] at least one sub-option is required') length = sum(len(entry) if isinstance(entry, bytes) else len(entry.pack()) for entry in entries) return Schema_AccessNetworkIdentifierOption( type=type, length=self._make_uint(length, bits=8, name='Option Length'), suboptions=entries, )
[docs] def _make_opt_offload(self, type: 'Enum_Option', option: 'Optional[Data_IPv4TrafficOffloadSelectorOption]' = None, *, mode: 'bool' = False, selector: 'Optional[FlowIDSuboption | list[Schema_FlowIdentificationSuboption | tuple[Enum_FlowIDSuboption | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4TrafficOffloadSelectorOption': """Make MH IPv4 traffic offload selector option. Args: type: Option type. option: Option data model. mode: Offload mode flag. selector: Traffic selector sub-options. reserved: Reserved bits, i.e. the 31 bits after the ``M`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``reserved`` does not fit its field. Also if the option is too long for its one-octet ``Option Length``. """ if option is not None: mode = option.mode reserved = option.reserved selector = option.selector else: selector = selector or [] entries = self._make_fid_suboptions(selector) length = 4 + sum(len(entry) if isinstance(entry, bytes) else len(entry.pack()) for entry in entries) return Schema_IPv4TrafficOffloadSelectorOption( type=type, length=self._make_uint(length, bits=8, name='Option Length'), flags={'M': int(mode), 'reserved': self._make_uint(reserved, bits=31, name='reserved bits')}, selector=entries, )
[docs] def _make_opt_mcast_sel(self, type: 'Enum_Option', option: 'Optional[Data_DynamicIPMulticastSelectorOption]' = None, *, protocol: 'int' = 143, mode: 'bool' = False, records: 'int' = 0, data: 'bytes' = b'', reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_DynamicIPMulticastSelectorOption': """Make MH dynamic IP multicast selector option. Args: type: Option type. option: Option data model. protocol: MLD or IGMP protocol number identifying the record format. mode: Selector mode flag. records: Number of multicast address records in ``data``. data: Multicast address records, opaque to this module. reserved: Reserved bits, i.e. the seven bits after the ``M`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``protocol``, ``reserved`` or ``records`` does not fit its field. Also if the option is too long for its one-octet ``Option Length``. """ if option is not None: protocol = option.protocol mode = option.mode reserved = option.reserved records = option.records data = option.data return Schema_DynamicIPMulticastSelectorOption( type=type, length=self._make_uint(4 + len(data), bits=8, name='Option Length'), protocol=self._make_uint(protocol, bits=8, name='protocol'), flags={'M': int(mode), 'reserved': self._make_uint(reserved, bits=7, name='reserved bits')}, records=self._make_uint(records, bits=16, name='number of records'), data=data, )
[docs] def _make_opt_dmnp(self, type: 'Enum_Option', option: 'Optional[Data_DelegatedMNPOption]' = None, *, prefix_length: 'int' = 64, prefix: 'bytes | str | int | IPv4Address | IPv6Address' = '::', reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_DelegatedMNPOption': """Make MH delegated mobile network prefix option. Args: type: Option type. option: Option data model. prefix_length: Prefix length. prefix: Delegated mobile network prefix. reserved: Reserved bits, i.e. the seven bits after the ``V`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Note: The ``V`` flag is derived from the prefix's own family rather than taken as an argument, so that the flag and the emitted width cannot disagree. The prefix length is checked against its 8-bit field alone, as :rfc:`7148#section-4.1` does not have the receiver discard one longer than the address, so whatever the reader keeps is written back. Raises: ProtocolError: If ``prefix_length`` or ``reserved`` is negative, a :obj:`bool`, or does not fit its field. .. versionchanged:: 1.6.0 A prefix length longer than the address is written, rather than refused, so that every one the reader keeps rebuilds (:issue:`2684`). """ if option is not None: reserved = option.reserved prefix_length = option.prefix_length prefix = option.prefix # NOTE: The ``V`` flag and width follow the family here, so ``parse_ip_address`` refuses # a ``bool``; the default ``prefix_length`` only hides it behind the range check. addr = parse_ip_address( prefix, f'{self.alias}: [OptNo {type}] invalid mobile network prefix') ipv4 = addr.version == 4 prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length') return Schema_DelegatedMNPOption( type=type, length=6 if ipv4 else 18, flags={'V': int(ipv4), 'reserved': self._make_uint(reserved, bits=7, name='reserved bits')}, prefix_length=prefix_length, prefix=addr, )
[docs] def _make_opt_ams4(self, type: 'Enum_Option', option: 'Optional[Data_ActiveMulticastSubscriptionIPv4Option]' = None, *, igmp_type: 'int' = 0x22, context: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_ActiveMulticastSubscriptionIPv4Option': """Make MH active multicast subscription IPv4 option. Args: type: Option type. option: Option data model. igmp_type: IGMP message type identifying the context format. context: Multicast membership context, opaque to this module. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``igmp_type`` does not fit its field. Also if the option is too long for its one-octet ``Option Length``. """ if option is not None: igmp_type = option.igmp_type context = option.context return Schema_ActiveMulticastSubscriptionIPv4Option( type=type, length=self._make_uint(1 + len(context), bits=8, name='Option Length'), igmp_type=self._make_uint(igmp_type, bits=8, name='IGMP type'), context=context, )
[docs] def _make_opt_ams6(self, type: 'Enum_Option', option: 'Optional[Data_ActiveMulticastSubscriptionIPv6Option]' = None, *, mld_type: 'int' = 143, context: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_ActiveMulticastSubscriptionIPv6Option': """Make MH active multicast subscription IPv6 option. Args: type: Option type. option: Option data model. mld_type: MLD message type identifying the context format. context: Multicast membership context, opaque to this module. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``mld_type`` does not fit its field. Also if the option is too long for its one-octet ``Option Length``. """ if option is not None: mld_type = option.mld_type context = option.context return Schema_ActiveMulticastSubscriptionIPv6Option( type=type, length=self._make_uint(1 + len(context), bits=8, name='Option Length'), mld_type=self._make_uint(mld_type, bits=8, name='MLD type'), context=context, )
[docs] def _make_opt_qos(self, type: 'Enum_Option', option: 'Optional[Data_QualityOfServiceOption]' = None, *, sr_id: 'int' = 0, dscp: 'int' = 0, oc: 'int' = 0, attributes: 'Optional[QoSAttribute | list[Schema_QoSAttribute | tuple[Enum_QoSAttribute | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long reserved: 'bytes' = b'\x00\x00\x00', tc_reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_QualityOfServiceOption': """Make MH quality-of-service option. Args: type: Option type. option: Option data model. sr_id: Service request identifier. dscp: Differentiated services code point, 6 bits. oc: Operational code. attributes: Quality-of-service attributes. reserved: The three reserved octets as captured. tc_reserved: The 2 reserved bits of the traffic class, after ``dscp``. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``sr_id``, ``dscp``, ``tc_reserved`` or ``oc`` does not fit its field. Also if the option is too long for its one-octet ``Option Length``. Also if ``reserved`` is not three octets. .. versionchanged:: 1.6.0 Takes ``tc_reserved``, which a data model passed as ``option`` carries as well (:issue:`2684`). """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00\x00') sr_id = option.sr_id dscp = option.dscp tc_reserved = getattr(option, 'tc_reserved', 0) oc = option.oc attributes = option.attributes else: attributes = attributes or [] dscp = self._make_uint(dscp, bits=6, name='DSCP') tc_reserved = self._make_uint(tc_reserved, bits=2, name='traffic class reserved bits') entries = self._make_qos_attributes(attributes) length = 6 + sum(len(entry) if isinstance(entry, bytes) else len(entry.pack()) for entry in entries) return Schema_QualityOfServiceOption( type=type, length=self._make_uint(length, bits=8, name='Option Length'), sr_id=self._make_uint(sr_id, bits=8, name='service request identifier'), tc=dscp << 2 | tc_reserved, oc=self._make_uint(oc, bits=8, name='operational code'), reserved=self._make_octets(reserved, size=3, name='reserved octets'), attributes=entries, )
[docs] def _make_opt_lma_up(self, type: 'Enum_Option', option: 'Optional[Data_LMAUserPlaneAddressOption]' = None, *, address: 'Optional[bytes | str | int | IPv4Address | IPv6Address]' = None, reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_LMAUserPlaneAddressOption': """Make MH LMA user-plane address option. Args: type: Option type. option: Option data model. address: LMA user-plane address, or :obj:`None` to omit it -- which is how a mobile access gateway asks for a transport without naming an address. reserved: The two reserved octets as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``reserved`` is not two octets. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00') address = option.address reserved = self._make_octets(reserved, size=2, name='reserved octets') if address is None: return Schema_LMAUserPlaneAddressOption(type=type, length=2, reserved=reserved, address=b'') # NOTE: The length follows the family here, so ``parse_ip_address`` refuses a ``bool`` # the schema's guard would never see; ``None`` stays an absent address. addr = parse_ip_address( address, f'{self.alias}: [OptNo {type}] invalid LMA user-plane address') return Schema_LMAUserPlaneAddressOption( type=type, length=6 if addr.version == 4 else 18, reserved=reserved, address=addr, )
[docs] def _make_opt_mcast(self, type: 'Enum_Option', option: 'Optional[Data_MulticastMobilityOption]' = None, *, code: 'int' = 2, data: 'bytes' = b'', reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_MulticastMobilityOption': """Make MH multicast mobility option. Args: type: Option type. option: Option data model. code: Option code. data: MLD or IGMP report payload, opaque to this module. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the payload is not a whole number of 32-bit words. :rfc:`7411#section-5.3` measures this option's length in words, so a payload that is not a multiple of 4 octets cannot be described by it at all. Also if ``code`` does not fit in one octet, or the option is too long for its one-octet ``Option Length``. Also if ``reserved`` is not one octet. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') code = option.code data = option.data if len(data) % 4 != 0: raise ProtocolError(f'{self.alias}: [OptNo {type}] payload of {len(data)} octets is ' 'not a whole number of 32-bit words') return Schema_MulticastMobilityOption( type=type, length=self._make_uint(len(data) // 4, bits=8, name='Option Length'), code=self._make_uint(code, bits=8, name='code'), reserved=self._make_octets(reserved, size=1, name='reserved octets'), data=data, )
[docs] def _make_opt_mcast_ack(self, type: 'Enum_Option', option: 'Optional[Data_MulticastAcknowledgementOption]' = None, *, code: 'int' = 0, status: 'int' = 1, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_MulticastAcknowledgementOption': """Make MH multicast acknowledgement option. Args: type: Option type. option: Option data model. code: Option code; always ``0``. status: Status. data: MLD or IGMP unsupported report payload, opaque to this module. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the payload is not a whole number of 32-bit words, or ``code`` or ``status`` does not fit in one octet. Also if the option is too long for its one-octet ``Option Length``. """ if option is not None: code = option.code status = option.status data = option.data if len(data) % 4 != 0: raise ProtocolError(f'{self.alias}: [OptNo {type}] payload of {len(data)} octets is ' 'not a whole number of 32-bit words') return Schema_MulticastAcknowledgementOption( type=type, length=self._make_uint(len(data) // 4, bits=8, name='Option Length'), code=self._make_uint(code, bits=8, name='code'), status=self._make_uint(status, bits=8, name='status'), data=data, )
[docs] def _make_opt_lcmp(self, type: 'Enum_Option', option: 'Optional[Data_LMAControlledMAGParametersOption]' = None, *, suboptions: 'Optional[LMAControlledMAGSuboption | list[Schema_LMAControlledMAGSuboption | tuple[Enum_LMAControlledMAGSuboption | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long **kwargs: 'Any') -> 'Schema_LMAControlledMAGParametersOption': """Make MH LMA-controlled MAG parameters option. Args: type: Option type. option: Option data model. suboptions: Sub-options; at least one is required. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. """ if option is not None: suboptions = option.suboptions else: suboptions = suboptions or [] entries = self._make_lcmp_suboptions(suboptions) if not entries: raise ProtocolError(f'{self.alias}: [OptNo {type}] at least one sub-option is required') length = sum(len(entry) if isinstance(entry, bytes) else len(entry.pack()) for entry in entries) return Schema_LMAControlledMAGParametersOption( type=type, length=self._make_uint(length, bits=8, name='Option Length'), suboptions=entries, )
[docs] def _make_opt_mag_mp(self, type: 'Enum_Option', option: 'Optional[Data_MAGMultipathBindingOption]' = None, *, att: 'Enum_AccessType | int' = Enum_AccessType.Virtual, label: 'int' = 0, bid: 'int' = 1, bulk: 'bool' = False, overwrite: 'bool' = False, reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_MAGMultipathBindingOption': """Make MH MAG multipath binding option. Args: type: Option type. option: Option data model. att: Interface access-technology type. label: Interface label. bid: Binding identifier; ``0`` and ``255`` are reserved. bulk: Bulk re-registration flag. overwrite: Registration overwrite flag. reserved: Reserved bits, i.e. the 22 bits after the ``O`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If both flags are set, which :rfc:`8278#section-4.1` forbids, or if ``label``, ``bid`` or ``reserved`` does not fit its field. Also if ``att`` is a name, a :obj:`bool` or a code its field cannot carry. """ if option is not None: att_val = option.att # type: Enum_AccessType label = option.label bid = option.bid bulk = option.bulk overwrite = option.overwrite reserved = option.reserved self._make_uint(att_val, bits=8, name='access technology type') else: att_val = self._make_index(att, Enum_AccessType, bits=8, name='access technology type') if bulk and overwrite: raise ProtocolError(f'{self.alias}: [OptNo {type}] the bulk re-registration and ' 'registration overwrite flags are mutually exclusive') return Schema_MAGMultipathBindingOption( type=type, length=6, att=att_val, label=self._make_uint(label, bits=8, name='interface label'), bid=self._make_uint(bid, bits=8, name='binding identifier'), flags={ 'B': int(bulk), 'O': int(overwrite), 'reserved': self._make_uint(reserved, bits=22, name='reserved bits'), }, )
[docs] def _make_opt_mag_id(self, type: 'Enum_Option', option: 'Optional[Data_MAGIdentifierOption]' = None, *, subtype: 'Enum_MNIDSubtype | int' = Enum_MNIDSubtype.NAI, identifier: 'bytes' = b'', reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_MAGIdentifierOption': """Make MH MAG identifier option. Args: type: Option type. option: Option data model. subtype: Sub-type, from the mobile node identifier subtype registry. identifier: Identifier. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``subtype`` is a name, a :obj:`bool` or a code its field cannot carry, or the option is too long for its one-octet ``Option Length``. Also if ``reserved`` is not one octet. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') subtype_val = option.subtype # type: Enum_MNIDSubtype identifier = option.identifier self._make_uint(subtype_val, bits=8, name='MAG identifier subtype') else: subtype_val = self._make_index(subtype, Enum_MNIDSubtype, bits=8, name='MAG identifier subtype') return Schema_MAGIdentifierOption( type=type, length=self._make_uint(2 + len(identifier), bits=8, name='Option Length'), subtype=subtype_val, reserved=self._make_octets(reserved, size=1, name='reserved octets'), identifier=identifier, )
[docs] def _make_opt_anchored(self, type: 'Enum_Option', option: 'Optional[Data_AnchoredPrefixOption]' = None, *, prefix_length: 'int' = 64, prefix: 'bytes | str | int | IPv6Address' = '::', reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_AnchoredPrefixOption': """Make MH anchored prefix option. Args: type: Option type. option: Option data model. prefix_length: Prefix length. prefix: Anchored prefix. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`, or longer than the address. Also if ``reserved`` is not one octet. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') prefix_length = option.prefix_length prefix = option.prefix prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length') if prefix_length > 128: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}') return Schema_AnchoredPrefixOption( type=type, length=18, reserved=self._make_octets(reserved, size=1, name='reserved octets'), prefix_length=prefix_length, prefix=prefix, )
[docs] def _make_opt_local_prefix(self, type: 'Enum_Option', option: 'Optional[Data_LocalPrefixOption]' = None, *, prefix_length: 'int' = 64, prefix: 'bytes | str | int | IPv6Address' = '::', reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_LocalPrefixOption': """Make MH local prefix option. Args: type: Option type. option: Option data model. prefix_length: Prefix length. prefix: Local prefix. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`, or longer than the address. Also if ``reserved`` is not one octet. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') prefix_length = option.prefix_length prefix = option.prefix prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length') if prefix_length > 128: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}') return Schema_LocalPrefixOption( type=type, length=18, reserved=self._make_octets(reserved, size=1, name='reserved octets'), prefix_length=prefix_length, prefix=prefix, )
[docs] def _make_opt_prev_maar(self, type: 'Enum_Option', option: 'Optional[Data_PreviousMAAROption]' = None, *, prefix_length: 'int' = 64, maar: 'bytes | str | int | IPv6Address' = '::', prefix: 'bytes | str | int | IPv6Address' = '::', reserved: 'bytes' = b'\x00', **kwargs: 'Any') -> 'Schema_PreviousMAAROption': """Make MH previous MAAR option. Args: type: Option type. option: Option data model. prefix_length: Prefix length of ``prefix``. maar: Previous MAAR's global address. prefix: Mobile node's home network prefix. reserved: The reserved octet as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`, or longer than the address. Also if ``reserved`` is not one octet. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00') prefix_length = option.prefix_length maar = option.maar prefix = option.prefix prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length') if prefix_length > 128: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}') return Schema_PreviousMAAROption( type=type, length=34, reserved=self._make_octets(reserved, size=1, name='reserved octets'), prefix_length=prefix_length, maar=maar, prefix=prefix, )
[docs] def _make_opt_serv_maar(self, type: 'Enum_Option', option: 'Optional[Data_ServingMAAROption]' = None, *, address: 'bytes | str | int | IPv6Address' = '::', **kwargs: 'Any') -> 'Schema_ServingMAAROption': """Make MH serving MAAR option. Args: type: Option type. option: Option data model. address: Serving MAAR's global address. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. """ if option is not None: address = option.address return Schema_ServingMAAROption( type=type, length=16, address=address, )
[docs] def _make_opt_dlif_lla(self, type: 'Enum_Option', option: 'Optional[Data_DLIFLinkLocalAddressOption]' = None, *, address: 'bytes | str | int | IPv6Address' = '::', **kwargs: 'Any') -> 'Schema_DLIFLinkLocalAddressOption': """Make MH DLIF link-local address option. Args: type: Option type. option: Option data model. address: Distributed logical interface's link-local address. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. """ if option is not None: address = option.address return Schema_DLIFLinkLocalAddressOption( type=type, length=16, address=address, )
[docs] def _make_opt_dlif_lladdr(self, type: 'Enum_Option', option: 'Optional[Data_DLIFLinkLayerAddressOption]' = None, *, lla: 'bytes' = b'', reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_DLIFLinkLayerAddressOption': """Make MH DLIF link-layer address option. Args: type: Option type. option: Option data model. lla: Distributed logical interface's link-layer address. reserved: The two reserved octets as captured. **kwargs: Arbitrary keyword arguments. Returns: Constructed option schema. Raises: ProtocolError: If the option is too long for its one-octet ``Option Length``. Also if ``reserved`` is not two octets. """ if option is not None: reserved = getattr(option, 'reserved', b'\x00\x00') lla = option.lla return Schema_DLIFLinkLayerAddressOption( type=type, length=self._make_uint(2 + len(lla), bits=8, name='Option Length'), reserved=self._make_octets(reserved, size=2, name='reserved octets'), lla=lla, )
[docs] def _make_ext_none(self, type: 'Enum_CGAExtension', option: 'Optional[Data_UnknownExtension]' = None, *, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_UnknownExtension': """Make CGA extension. Args: type: Extension type. option: Extension data model. data: Extension data. **kwargs: Arbitrary keyword arguments. Returns: Constructed extension schema. """ if option is not None: data = option.data return Schema_UnknownExtension( type=type, length=len(data), data=data, )
[docs] def _make_ext_multiprefix(self, type: 'Enum_CGAExtension', option: 'Optional[Data_MultiPrefixExtension]' = None, *, flag: 'bool' = False, prefixes: 'Optional[list[int]]' = None, reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_MultiPrefixExtension': """Make CGA multi-prefix extension. Args: type: Extension type. option: Extension data model. flag: Public key flag. prefixes: Prefixes. reserved: Reserved bits, i.e. the 31 bits after the ``P`` flag. **kwargs: Arbitrary keyword arguments. Returns: Constructed extension schema. Raises: ProtocolError: If ``reserved`` or an entry of ``prefixes`` does not fit its field. """ if option is not None: flag = option.flag reserved = option.reserved # NOTE: ``list()``, not a cast: the data model holds a :obj:`tuple`, which # ``ListField`` refuses to pack. prefixes = list(option.prefixes) else: prefixes = prefixes or [] for item in prefixes: self._make_uint(item, bits=64, name='prefix') return Schema_MultiPrefixExtension( type=type, # NOTE: A 4-octet flags word, then 8 octets per prefix, as each is a # ``UInt64Field`` in ``MultiPrefixExtension.prefixes``. length=4 + len(prefixes) * 8, flags={ 'P': int(flag), 'reserved': self._make_uint(reserved, bits=31, name='reserved bits'), }, prefixes=prefixes, )
[docs] def _make_ext_exp(self, type: 'Enum_CGAExtension', option: 'Optional[Data_ExperimentalExtension]' = None, *, data: 'bytes' = b'', **kwargs: 'Any') -> 'Schema_ExperimentalExtension': """Make experimental CGA extension. Args: type: Extension type. option: Extension data model. data: Extension data, which :rfc:`4581#section-3` gives no structure. **kwargs: Arbitrary keyword arguments. Returns: Constructed extension schema. """ if option is not None: data = option.data return Schema_ExperimentalExtension( type=type, length=len(data), data=data, )
@staticmethod def _mh_message_length(header_len: 'int') -> 'int': """Compute the total on-the-wire MH header length for a given ``Header Len``. Per :rfc:`6275#section-6.1.1`, ``Header Len`` is *"the length of the Mobility Header, in units of 8 octets, excluding the first 8 octets"* -- i.e. the total header is ``8 + 8 * header_len`` octets, or equivalently ``(header_len + 1) * 8``. Every ``_read_msg_*`` below reports this value as the parsed message's own ``.length``, which :meth:`~pcapkit.protocols.internet.mh.protocol.MH.read` subtracts from the outer packet length to find the next layer's length. It is the inverse of :meth:`make`'s ``(len(data_val) + 6) // 8 - 1``. Do NOT drop the ``+ 1``: the units either side of it differ (octets vs. 8-octet units), and dropping the offset silently reinterprets the field. Args: header_len: raw ``Header Len`` field value, as read off the wire. Returns: Total length, in octets, of the on-the-wire MH header. """ return (header_len + 1) * 8 @staticmethod def _mh_option_length(schema_length: 'int') -> 'int': """Compute an MH option's whole-option length from its on-the-wire ``Option Length``. Per :rfc:`6275#section-6.2`, an MH option's ``Option Length`` field (what each ``Schema_*Option.length`` here holds) counts *"the length of the option, in octets, excluding the Option Type and Option Length fields"* -- so the whole option, which is what every ``_read_opt_*`` below reports back as the parsed option's own ``.length``, is two octets more. The exceptions are the options whose ``Option Length`` is not that octet count: a ``Pad1`` option, which has no ``Option Length`` field at all (see the ``Note:`` on :meth:`_read_opt_pad` below), the BADF option, whose length leaves out its SPI (see :meth:`_read_opt_badf`), and the multicast mobility and acknowledgement options, whose length counts 32-bit words (see :meth:`_read_opt_mcast`). Do NOT drop the ``+ 2``. A CGA extension's type and length fields are two octets each [:rfc:`4581#section-2`], so its readers use :meth:`_mh_extension_length` instead; this helper would report each one two octets short. Only the read side's stored lengths go through it; most ``_make_opt_*`` methods recompute ``Option Length`` from the value instead. Args: schema_length: raw ``Option Length`` field value, as read off the wire. Returns: Whole-option length, in octets, including the Option Type and Option Length fields. """ return schema_length + 2
[docs] def _read_mh_options(self, options_schema: 'list[Schema_Option]') -> 'Option': """Read MH options. Structure of MH option [:rfc:`6275`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Option Type | Option Length | Option Data... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: options_schema: Parsed MH options. Returns: Parsed MH options data. Raises: ProtocolError: If an option read by a ``_read_opt_*`` method has fields that do not take exactly the octets its ``Option Length`` declares [:rfc:`6275#section-6.2.1`]. A parser registered as a callable reports its own length, and is not checked. Or if the capture cut an option short and its parser refuses the zeros it lacks, or its maker does not write the octets captured back. .. versionchanged:: 1.6.0 * Such an option is refused, rather than read from octets outside it and rebuilt with the length its fields give (:issue:`2684`). * An option the capture cut short is read, its missing octets as zeros, if its maker writes the octets captured back (:issue:`2694`). """ options = OrderedMultiDict() # type: Option for schema in options_schema: type = schema.type name = self._lookup_registry(self.__option__, type) if isinstance(name, str): meth_name = f'_read_opt_{name}' meth = cast('OptionParser', getattr(self, meth_name, self._read_opt_none)) maker = cast('OptionConstructor', getattr(self, f'_make_opt_{name}', self._make_opt_none)) else: meth, maker = name # NOTE: A cut-short option reads its missing octets as zeros; it stands if its parser # takes them and its maker writes the captured ones back (#2694), else it is refused. if '__short_read__' in schema.__dict__: try: with CaughtByCaller(BaseError): data = meth(schema, options=options) made = maker(type, data).pack()[:len(schema)] except BaseError as error: raise ProtocolError(f'{self.alias}: [OptNo {type}] option cut short: {error}') from error if made != bytes(schema): raise ProtocolError(f'{self.alias}: [OptNo {type}] option cut short does not ' 'rebuild as captured') else: data = meth(schema, options=options) # NOTE: Fields are read at their own widths, so a length they do not fill exactly reads # octets not the option's, and rebuilds as another (#2684). if isinstance(name, str) and len(schema) != data.length: raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid option length: {schema.length}') # record option data options.add(type, data) return options
[docs] def _read_fid_suboptions( self, suboptions_schema: 'list[Schema_FlowIdentificationSuboption]', ) -> 'FlowIDSuboption': """Read MH flow identification sub-options. Structure of MH flow identification sub-option [:rfc:`6089#section-4.2.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sub-Opt Type |Sub-Opt Length | Sub-Option Data... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ These are sub-options of one mobility option rather than mobility options in their own right, so they are dispatched here rather than through :attr:`self.__option__ <MH.__option__>`. Note: Dispatch is on the sub-option **type code** rather than on ``isinstance`` of the schema class: the code is on the wire and the registry keys on it, so it is the cheaper discriminator. A class test would also be sound, since every :class:`~pcapkit.protocols.schema.Schema` subclass carries its own :class:`abc.ABCMeta` cache (:issue:`439`). The same reasoning applies to :meth:`_read_ani_suboptions`, :meth:`_read_qos_attributes`, :meth:`_read_lcmp_suboptions` and their constructor counterparts. Args: suboptions_schema: Parsed sub-option schemas. Returns: Parsed sub-option data. Raises: ProtocolError: If a sub-option's fields do not take exactly the octets its ``Sub-Opt Length`` declares [:rfc:`6089#section-4.2.1`], or a target care-of address is neither IPv4 nor IPv6 [:rfc:`7109#section-6.2.2`], as the binding identifier option's is refused. .. versionchanged:: 1.6.0 Such a sub-option is refused, as a mobility option is by :meth:`_read_mh_options`, rather than read from octets outside it or left unable to rebuild (:issue:`2684`). """ suboptions = OrderedMultiDict() # type: FlowIDSuboption for schema in suboptions_schema: code = schema.type if code in (Enum_FlowIDSuboption.Pad, Enum_FlowIDSuboption.PadN): pad = cast('Schema_PadFlowIdentificationSuboption', schema) size = 1 if code == Enum_FlowIDSuboption.Pad else self._mh_option_length(pad.length) data = Data_PadFlowIdentificationSuboption( type=code, length=size, data=pad.data, ) # type: Data_FlowIdentificationSuboption elif code == Enum_FlowIDSuboption.BID_Reference: bid_ref = cast('Schema_BIDReferenceSuboption', schema) data = Data_BIDReferenceSuboption( type=code, length=self._mh_option_length(bid_ref.length), bid=tuple(bid_ref.bid), ) elif code == Enum_FlowIDSuboption.Traffic_Selector: selector = cast('Schema_TrafficSelectorSuboption', schema) data = Data_TrafficSelectorSuboption( type=code, length=self._mh_option_length(selector.length), ts_format=selector.ts_format, reserved=selector.reserved, selector=selector.selector, ) elif code == Enum_FlowIDSuboption.Flow_Binding_Action: action = cast('Schema_FlowBindingActionSuboption', schema) data = Data_FlowBindingActionSuboption( type=code, length=self._mh_option_length(action.length), reserved=action.reserved, action=action.action, ) elif code == Enum_FlowIDSuboption.Target_Care_of_Address: target = cast('Schema_TargetCareofAddressSuboption', schema) if target.length not in (6, 18): raise ProtocolError(f'{self.alias}: [FID SubOpt {code}] invalid sub-option length: {target.length}') data = Data_TargetCareofAddressSuboption( type=code, length=self._mh_option_length(target.length), reserved=target.reserved, address=target.address, ) else: unknown = cast('Schema_UnassignedFlowIdentificationSuboption', schema) data = Data_UnassignedFlowIdentificationSuboption( type=code, length=self._mh_option_length(unknown.length), data=unknown.data, ) if len(schema) != data.length: raise ProtocolError(f'{self.alias}: [FID SubOpt {code}] invalid sub-option length: {schema.length}') suboptions.add(code, data) return suboptions
[docs] def _read_ani_suboptions( self, suboptions_schema: 'list[Schema_ANISuboption]', ) -> 'ANISuboption': """Read MH access network identifier sub-options. Structure of MH access network identifier sub-option [:rfc:`6757#section-3.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ANI Type | ANI Length | Option Data ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ The geo-location degrees of :rfc:`6757#section-3.1.2` are 24-bit **two's-complement** fixed-point values with nine integer bits. A :class:`~pcapkit.corekit.fields.strings.BitField` reads them unsigned, so the sign is applied here by :meth:`_read_sint`, and both the decoded degrees and the raw signed integers are recorded -- the latter so that the exact wire value survives a round trip through the data model. Note: Dispatch is on the sub-option type code rather than on ``isinstance`` of the schema class, for the reason given in :meth:`_read_fid_suboptions`. Args: suboptions_schema: Parsed sub-option schemas. Returns: Parsed sub-option data. Raises: ProtocolError: If a sub-option's fields do not take exactly the octets its ``ANI Length`` declares [:rfc:`6757#section-3.1`]. .. versionchanged:: 1.6.0 Such a sub-option is refused, as a mobility option is by :meth:`_read_mh_options` (:issue:`2684`). """ suboptions = OrderedMultiDict() # type: ANISuboption for schema in suboptions_schema: code = schema.type if code == Enum_ANISuboption.Network_Identifier: net = cast('Schema_ANINetworkIdentifierSuboption', schema) data = Data_ANINetworkIdentifierSuboption( type=code, length=self._mh_option_length(net.length), utf8=bool(net.flags['E']), reserved=net.flags['reserved'], net_name=net.net_name, ap_name=net.ap_name, ) # type: Data_ANISuboption elif code == Enum_ANISuboption.Geo_Location: geo = cast('Schema_ANIGeoLocationSuboption', schema) raw_lat = self._read_sint(geo.location['latitude'], 24) raw_lon = self._read_sint(geo.location['longitude'], 24) data = Data_ANIGeoLocationSuboption( type=code, length=self._mh_option_length(geo.length), latitude=raw_lat / 2 ** 15, longitude=raw_lon / 2 ** 15, raw_latitude=raw_lat, raw_longitude=raw_lon, ) elif code == Enum_ANISuboption.Operator_Identifier: operator = cast('Schema_ANIOperatorIdentifierSuboption', schema) data = Data_ANIOperatorIdentifierSuboption( type=code, length=self._mh_option_length(operator.length), op_id_type=operator.op_id_type, identifier=operator.identifier, ) elif code == Enum_ANISuboption.Civic_Location: civic = cast('Schema_ANICivicLocationSuboption', schema) data = Data_ANICivicLocationSuboption( type=code, length=self._mh_option_length(civic.length), format=civic.format, reserved=civic.reserved, location=civic.location, ) elif code == Enum_ANISuboption.MAG_Group_Identifier: group = cast('Schema_ANIMAGGroupIdentifierSuboption', schema) data = Data_ANIMAGGroupIdentifierSuboption( type=code, length=self._mh_option_length(group.length), group_id=group.group_id, ) elif code == Enum_ANISuboption.ANI_Update_Timer: timer = cast('Schema_ANIUpdateTimerSuboption', schema) data = Data_ANIUpdateTimerSuboption( type=code, length=self._mh_option_length(timer.length), timer=datetime.timedelta(seconds=timer.timer * 4), ) else: unknown = cast('Schema_UnassignedANISuboption', schema) data = Data_UnassignedANISuboption( type=code, length=self._mh_option_length(unknown.length), data=unknown.data, ) if len(schema) != data.length: raise ProtocolError(f'{self.alias}: [ANI SubOpt {code}] invalid sub-option length: {schema.length}') suboptions.add(code, data) return suboptions
[docs] def _read_qos_attributes( self, attributes_schema: 'list[Schema_QoSAttribute]', ) -> 'QoSAttribute': """Read MH quality-of-service attributes. Structure of MH quality-of-service attribute [:rfc:`7222#section-4.2`]: .. 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 | Length | Value ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: Every bit rate here is in **bits** per second [:rfc:`7222#section-4.2.1`], not the kilobytes per second of the load information option's capacity fields. Dispatch is on the attribute type code rather than on ``isinstance`` of the schema class, for the reason given in :meth:`_read_fid_suboptions`. Args: attributes_schema: Parsed attribute schemas. Returns: Parsed attribute data. Raises: ProtocolError: If an attribute's fields do not take exactly the octets its ``Length`` declares [:rfc:`7222#section-4.2`]. .. versionchanged:: 1.6.0 Such an attribute is refused, as a mobility option is by :meth:`_read_mh_options` (:issue:`2684`). """ attributes = OrderedMultiDict() # type: QoSAttribute for schema in attributes_schema: code = schema.type if code in (Enum_QoSAttribute.Per_Session_Agg_Max_DL_Bit_Rate, Enum_QoSAttribute.Per_Session_Agg_Max_UL_Bit_Rate): session = cast('Schema_PerSessionBitRateAttribute', schema) data = Data_PerSessionBitRateAttribute( type=code, length=self._mh_option_length(session.length), service=bool(session.flags['S']), exclude=bool(session.flags['E']), reserved=session.flags['reserved'], rate=session.rate, ) # type: Data_QoSAttribute elif code in (Enum_QoSAttribute.Per_MN_Agg_Max_DL_Bit_Rate, Enum_QoSAttribute.Per_MN_Agg_Max_UL_Bit_Rate, Enum_QoSAttribute.Aggregate_Max_DL_Bit_Rate, Enum_QoSAttribute.Aggregate_Max_UL_Bit_Rate, Enum_QoSAttribute.Guaranteed_DL_Bit_Rate, Enum_QoSAttribute.Guaranteed_UL_Bit_Rate): rate = cast('Schema_BitRateAttribute', schema) data = Data_BitRateAttribute( type=code, length=self._mh_option_length(rate.length), reserved=rate.reserved, rate=rate.rate, ) elif code == Enum_QoSAttribute.Allocation_Retention_Priority: arp = cast('Schema_AllocationRetentionPriorityAttribute', schema) data = Data_AllocationRetentionPriorityAttribute( type=code, length=self._mh_option_length(arp.length), reserved=arp.reserved, priority_level=arp.priority['PL'], preemption_capability=arp.priority['PC'], preemption_vulnerability=arp.priority['PV'], ) elif code == Enum_QoSAttribute.QoS_Traffic_Selector: selector = cast('Schema_QoSTrafficSelectorAttribute', schema) data = Data_QoSTrafficSelectorAttribute( type=code, length=self._mh_option_length(selector.length), reserved=selector.reserved, ts_format=selector.ts_format, selector=selector.selector, ) elif code == Enum_QoSAttribute.QoS_Vendor_Specific_Attribute: vendor = cast('Schema_QoSVendorSpecificAttribute', schema) data = Data_QoSVendorSpecificAttribute( type=code, length=self._mh_option_length(vendor.length), reserved=vendor.reserved, vendor=vendor.vendor, subtype=vendor.subtype, data=vendor.data, ) else: unknown = cast('Schema_UnassignedQoSAttribute', schema) data = Data_UnassignedQoSAttribute( type=code, length=self._mh_option_length(unknown.length), data=unknown.data, ) if len(schema) != data.length: raise ProtocolError(f'{self.alias}: [QoS Attr {code}] invalid attribute length: {schema.length}') attributes.add(code, data) return attributes
[docs] def _read_lcmp_suboptions( self, suboptions_schema: 'list[Schema_LMAControlledMAGSuboption]', ) -> 'LMAControlledMAGSuboption': """Read MH LMA-controlled MAG parameters sub-options. Structure of MH LMA-controlled MAG parameters sub-option [:rfc:`8127#section-3.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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | LCMP Type | LCMP Length | Sub-Option Data ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Note: Only the re-registration start time is in units of 4 seconds; every other interval in these two sub-options is in whole seconds [:rfc:`8127#section-3.1.1`, :rfc:`8127#section-3.1.2`]. Dispatch is on the sub-option type code rather than on ``isinstance`` of the schema class, for the reason given in :meth:`_read_fid_suboptions`. Args: suboptions_schema: Parsed sub-option schemas. Returns: Parsed sub-option data. Raises: ProtocolError: If a sub-option's fields do not take exactly the octets its ``LCMP Length`` declares [:rfc:`8127#section-3.1`]. .. versionchanged:: 1.6.0 Such a sub-option is refused, as a mobility option is by :meth:`_read_mh_options` (:issue:`2684`). """ suboptions = OrderedMultiDict() # type: LMAControlledMAGSuboption for schema in suboptions_schema: code = schema.type if code == Enum_LMAControlledMAGSuboption.Binding_Re_registration_Control: rereg = cast('Schema_BindingReregistrationControlSuboption', schema) data = Data_BindingReregistrationControlSuboption( type=code, length=self._mh_option_length(rereg.length), start_time=datetime.timedelta(seconds=rereg.start_time * 4), initial_retransmission=datetime.timedelta( seconds=rereg.initial_retransmission), max_retransmission=datetime.timedelta(seconds=rereg.max_retransmission), ) # type: Data_LMAControlledMAGSuboption elif code == Enum_LMAControlledMAGSuboption.Heartbeat_Control: heartbeat = cast('Schema_HeartbeatControlSuboption', schema) data = Data_HeartbeatControlSuboption( type=code, length=self._mh_option_length(heartbeat.length), interval=datetime.timedelta(seconds=heartbeat.interval), retransmission_delay=datetime.timedelta( seconds=heartbeat.retransmission_delay), max_retransmissions=heartbeat.max_retransmissions, ) else: unknown = cast('Schema_UnassignedLMAControlledMAGSuboption', schema) data = Data_UnassignedLMAControlledMAGSuboption( type=code, length=self._mh_option_length(unknown.length), data=unknown.data, ) if len(schema) != data.length: raise ProtocolError(f'{self.alias}: [LCMP SubOpt {code}] invalid sub-option length: {schema.length}') suboptions.add(code, data) return suboptions
@staticmethod def _mh_extension_length(schema_length: 'int') -> 'int': """Compute a CGA extension's whole-structure length from its on-the-wire ``Extension Data Length``. This is the CGA-extension counterpart of :meth:`_mh_option_length`, and it deliberately adds ``4`` rather than ``2``. A CGA extension is **not** an :rfc:`6275#section-6.2` mobility option: per :rfc:`4581#section-2`, which defines the TLV format and formally updates :rfc:`3972`, its ``Extension Type`` is a *"16-bit identifier of the type of the Extension Field"* and its ``Extension Data Length`` a *"16-bit unsigned integer. Length of the Extension Data field of this option, in octets"*. So the fixed header is two 2-octet fields, not two 1-octet ones, and the whole structure is ``4`` octets more than the stored length -- a fact :rfc:`5535#section-5` states from the other direction for the one non-experimental assigned type, whose ``Ext Len`` is the *"[l]ength of the Extension in octets, not including the first 4 octets"*. Passing these lengths through :meth:`_mh_option_length` would report every parsed CGA extension two octets short: an 8-octet extension with an ``Extension Data Length`` of ``4`` would come back as ``6``. The write side is unaffected, since :meth:`_make_cga_extensions` measures ``len(schema.pack())`` instead. Args: schema_length: raw ``Extension Data Length`` field value, as read off the wire. Returns: Whole-extension length, in octets, including the Extension Type and Extension Data Length fields. """ return schema_length + 4
[docs] def _read_cga_extensions(self, extensions_schema: 'list[Schema_CGAExtension]') -> 'Extension': """Read CGA extensions. Structure of CGA extensions [:rfc:`4581`]: .. 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Extension Type | Extension Data Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | ~ Extension Data ~ | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: extensions_schema: Parsed CGA extensions. Returns: Parsed CGA extensions data. Raises: ProtocolError: If an extension read by a ``_read_ext_*`` method has fields that do not take exactly the octets its ``Extension Data Length`` declares [:rfc:`4581#section-2`]; a parser registered as a callable is not checked. .. versionchanged:: 1.6.0 Such an extension is refused, as a mobility option is by :meth:`_read_mh_options` (:issue:`2684`). """ extensions = OrderedMultiDict() # type: Extension for schema in extensions_schema: type = schema.type name = self._lookup_registry(self.__extension__, type) if isinstance(name, str): meth_name = f'_read_ext_{name}' meth = cast('ExtensionParser', getattr(self, meth_name, self._read_ext_none)) else: meth = name[0] data = meth(schema, extensions=extensions) if isinstance(name, str) and len(schema) != data.length: raise ProtocolError(f'{self.alias}: [CGA Ext {type}] invalid extension length: {schema.length}') # record extension data extensions.add(type, data) return extensions
def _make_pad_options(self, offset: 'int') -> 'tuple[list[Schema_PadOption], int]': """Make the padding options needed to align the header to 8 octets. Args: offset: Number of octets emitted so far, **counting the six octets of the fixed header** (``Payload Proto``, ``Header Len``, ``MH Type``, ``Reserved`` and ``Checksum``) as well as the whole message data. Returns: Tuple of the padding option schemas and the number of octets they occupy. Note: The Mobility Header is a multiple of 8 octets and :attr:`~pcapkit.protocols.internet.mh.schema.MH.length` counts those units less one [:rfc:`6275#section-6.1.1`]. Its fixed part is **six** octets, not the two of an IPv6 options header, so it is ``len(data) + 6`` that has to be aligned -- which means the message data is well formed only when it is 2 octets past a multiple of 8, a different modulus from the one the extension headers use. A ``PadN`` option spends two octets on its own type and ``Option Length`` fields before any padding data, so occupying ``pad_len`` octets means an ``Option Length`` of ``pad_len - 2``. One octet is padded with a ``Pad1`` option instead, that being the only form which can occupy a single octet. Two octets are one ``PadN`` with an ``Option Length`` of ``0``, not two ``Pad1``: :rfc:`6275#section-6.2.2` says a ``PadN`` should be used rather than multiple ``Pad1`` options. """ pad_len = pad_length(offset, 8) if pad_len == 0: return [], 0 if pad_len == 1: return [self._make_opt_pad(Enum_Option.Pad1, length=0)], pad_len # type: ignore[arg-type] return [self._make_opt_pad(Enum_Option.PadN, length=pad_len - 2)], pad_len # type: ignore[arg-type]
[docs] def _make_mh_options(self, options: 'Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]') -> 'list[Schema_Option | bytes]': # pylint: disable=line-too-long """Make options for MH. Args: options: MH options. Returns: Mobility options list. Raises: ProtocolError: If the type of an option, given in a ``(type, kwargs)`` pair or as the key of parsed data, is a name, a :obj:`bool` or a code its field cannot carry, or if an option given as raw octets is empty (:issue:`2723`). """ if isinstance(options, list): options_list = [] # type: list[Schema_Option | bytes] for schema in options: if isinstance(schema, bytes): if not schema: raise ProtocolError(f'{self.alias}: invalid option: {schema!r} holds no type octet') code = Enum_Option.get(int.from_bytes(schema[0:1], 'big', signed=False)) data = schema # type: Schema_Option | bytes elif isinstance(schema, Schema): data = schema else: kind, args = cast('tuple[Enum_Option | int, dict[str, Any]]', schema) code = self._make_index(kind, Enum_Option, bits=8, name='option type') name = self._lookup_registry(self.__option__, code) if isinstance(name, str): meth_name = f'_make_opt_{name}' meth = cast('OptionConstructor', getattr(self, meth_name, self._make_opt_none)) else: meth = name[1] data = meth(code, **args) options_list.append(data) return options_list options_list = [] for key, option in options.items(multi=True): code = self._make_index(key, Enum_Option, bits=8, name='option type') name = self._lookup_registry(self.__option__, code) if isinstance(name, str): meth_name = f'_make_opt_{name}' meth = cast('OptionConstructor', getattr(self, meth_name, self._make_opt_none)) else: meth = name[1] data = meth(code, option) options_list.append(data) return options_list
[docs] def _make_fid_suboptions( self, suboptions: 'FlowIDSuboption | list[Schema_FlowIdentificationSuboption | tuple[Enum_FlowIDSuboption | int, dict[str, Any]] | bytes]', # pylint: disable=line-too-long ) -> 'list[Schema_FlowIdentificationSuboption | bytes]': """Make MH flow identification sub-options. Args: suboptions: Sub-options, as parsed data, schemas, ``(type, kwargs)`` pairs, or raw octets. Returns: Sub-option schema list. Raises: ProtocolError: If the type of a sub-option, given in a ``(type, kwargs)`` pair or as the key of parsed data, is a name, a :obj:`bool` or a code its field cannot carry, or if a sub-option given as raw octets is empty (:issue:`2723`). """ if isinstance(suboptions, list): entries = [] # type: list[Schema_FlowIdentificationSuboption | bytes] for item in suboptions: if isinstance(item, bytes) and not item: raise ProtocolError(f'{self.alias}: invalid sub-option: {item!r} holds no type octet') if isinstance(item, (bytes, Schema)): entries.append(cast('Schema_FlowIdentificationSuboption | bytes', item)) else: kind, args = cast('tuple[Enum_FlowIDSuboption | int, dict[str, Any]]', item) code = self._make_index(kind, Enum_FlowIDSuboption, bits=8, name='sub-option type') entries.append(self._make_fid_suboption(code, **args)) return entries return [self._make_fid_suboption( self._make_index(key, Enum_FlowIDSuboption, bits=8, name='sub-option type'), option) for key, option in suboptions.items(multi=True)]
[docs] def _make_fid_suboption(self, code: 'Enum_FlowIDSuboption', option: 'Optional[Data_FlowIdentificationSuboption]' = None, **kwargs: 'Any') -> 'Schema_FlowIdentificationSuboption': """Make one MH flow identification sub-option. Args: code: Sub-option type. option: Sub-option data model. **kwargs: Sub-option fields, when no data model is given. Returns: Constructed sub-option schema. Raises: ProtocolError: If an entry of a ``BID_Reference`` sub-option's ``bid`` does not fit its field, a ``ts_format`` or ``action`` is a name, a :obj:`bool` or a code its field cannot carry, or a sub-option's ``reserved`` is not as many octets as its field. Note: The data model parameter is named ``option`` rather than ``data`` to match :meth:`_make_opt_fid` and the rest of this module -- and because ``data`` would **shadow a field**. The traffic selector and unassigned sub-options both carry a field of their own called ``data``, so a caller's ``data=`` would bind to the model parameter instead of landing in ``**kwargs``, and the fallback that reads ``data`` from ``**kwargs`` would never see it: the payload would be silently dropped and the length written as though it were empty, with no exception. Dispatch is on ``code`` rather than on ``isinstance`` of the schema or data class, for the reason given in :meth:`_read_fid_suboptions`. """ if code in (Enum_FlowIDSuboption.Pad, Enum_FlowIDSuboption.PadN): if code == Enum_FlowIDSuboption.Pad: pad_len = 0 elif option is not None: pad_len = option.length - 2 else: pad_len = cast('int', kwargs.get('length', 0)) if option is not None: pad_data = getattr(option, 'data', b'') else: pad_data = cast('bytes', kwargs.get('data', b'')) return Schema_PadFlowIdentificationSuboption(type=code, length=pad_len, data=pad_data) if code == Enum_FlowIDSuboption.BID_Reference: if option is not None: bid = list(cast('Data_BIDReferenceSuboption', option).bid) else: bid = cast('list[int]', kwargs.get('bid') or []) for item in bid: self._make_uint(item, bits=16, name='binding identifier') return Schema_BIDReferenceSuboption(type=code, length=len(bid) * 2, bid=bid) if code == Enum_FlowIDSuboption.Traffic_Selector: if option is not None: selector_opt = cast('Data_TrafficSelectorSuboption', option) ts_format = selector_opt.ts_format # type: Enum_TrafficSelector | int selector = selector_opt.selector reserved = getattr(selector_opt, 'reserved', b'\x00') else: ts_format = self._make_index( kwargs.get('ts_format', Enum_TrafficSelector.IPv6_Binary_Traffic_Selector), Enum_TrafficSelector, bits=8, name='TS format') selector = cast('bytes', kwargs.get('selector', b'')) reserved = cast('bytes', kwargs.get('reserved', b'\x00')) return Schema_TrafficSelectorSuboption( type=code, length=2 + len(selector), ts_format=cast('Enum_TrafficSelector', ts_format), reserved=self._make_octets(reserved, size=1, name='reserved octets'), selector=selector) if code == Enum_FlowIDSuboption.Flow_Binding_Action: if option is not None: action = cast('Data_FlowBindingActionSuboption', option).action # type: Enum_FlowBindingAction | int reserved = getattr(option, 'reserved', b'\x00') else: action = self._make_index(kwargs.get('action', Enum_FlowBindingAction.Add), Enum_FlowBindingAction, bits=8, name='action') reserved = cast('bytes', kwargs.get('reserved', b'\x00')) return Schema_FlowBindingActionSuboption( type=code, length=2, reserved=self._make_octets(reserved, size=1, name='reserved octets'), action=cast('Enum_FlowBindingAction', action)) if code == Enum_FlowIDSuboption.Target_Care_of_Address: if option is not None: address = cast('Data_TargetCareofAddressSuboption', option).address # type: Any reserved = getattr(option, 'reserved', b'\x00\x00') else: address = kwargs.get('address', '::') reserved = cast('bytes', kwargs.get('reserved', b'\x00\x00')) # NOTE: The sub-option length below is derived ahead of the schema, so a bare ``ip_address`` # would launder a ``bool`` past its guard: ``address=True`` packs as ``0506000000000001``. addr = parse_ip_address( address, f'{self.alias}: [OptNo {code}] invalid target care-of address') return Schema_TargetCareofAddressSuboption( type=code, length=6 if addr.version == 4 else 18, reserved=self._make_octets(reserved, size=2, name='reserved octets'), address=addr) if option is not None: payload = cast('Data_UnassignedFlowIdentificationSuboption', option).data else: payload = cast('bytes', kwargs.get('data', b'')) return Schema_UnassignedFlowIdentificationSuboption( type=code, length=len(payload), data=payload)
[docs] def _make_ani_suboptions( self, suboptions: 'ANISuboption | list[Schema_ANISuboption | tuple[Enum_ANISuboption | int, dict[str, Any]] | bytes]', ) -> 'list[Schema_ANISuboption | bytes]': """Make MH access network identifier sub-options. Args: suboptions: Sub-options, as parsed data, schemas, ``(type, kwargs)`` pairs, or raw octets. Returns: Sub-option schema list. Raises: ProtocolError: If the type of a sub-option, given in a ``(type, kwargs)`` pair or as the key of parsed data, is a name, a :obj:`bool` or a code its field cannot carry, or if a sub-option given as raw octets is empty (:issue:`2723`). """ if isinstance(suboptions, list): entries = [] # type: list[Schema_ANISuboption | bytes] for item in suboptions: if isinstance(item, bytes) and not item: raise ProtocolError(f'{self.alias}: invalid sub-option: {item!r} holds no type octet') if isinstance(item, (bytes, Schema)): entries.append(cast('Schema_ANISuboption | bytes', item)) else: kind, args = cast('tuple[Enum_ANISuboption | int, dict[str, Any]]', item) code = self._make_index(kind, Enum_ANISuboption, bits=8, name='sub-option type') entries.append(self._make_ani_suboption(code, **args)) return entries return [self._make_ani_suboption( self._make_index(key, Enum_ANISuboption, bits=8, name='sub-option type'), option) for key, option in suboptions.items(multi=True)]
[docs] def _make_ani_suboption(self, code: 'Enum_ANISuboption', option: 'Optional[Data_ANISuboption]' = None, **kwargs: 'Any') -> 'Schema_ANISuboption': """Make one MH access network identifier sub-option. Args: code: Sub-option type. option: Sub-option data model. **kwargs: Sub-option fields, when no data model is given. Returns: Constructed sub-option schema. Raises: ProtocolError: If a raw geo-location degree does not fit in 24 bits as two's complement, or a sub-option's ``reserved``, ``format``, ``group_id`` or ``timer`` does not fit its field, or ``op_id_type`` is a name, a :obj:`bool` or a code its field cannot carry. Note: The geo-location degrees are re-encoded from the **raw** signed integers rather than from the decoded floats, since a float cannot always be converted back to the same 24-bit fixed-point value, and :meth:`_make_sint` gives back the bits :meth:`_read_sint` read. The data model parameter is named ``option`` rather than ``data`` so that it cannot shadow a sub-option field of that name -- see :meth:`_make_fid_suboption`. Dispatch is on ``code`` rather than on ``isinstance``, for the reason given in :meth:`_read_fid_suboptions`. """ if code == Enum_ANISuboption.Network_Identifier: if option is not None: net = cast('Data_ANINetworkIdentifierSuboption', option) utf8, net_name, ap_name = net.utf8, net.net_name, net.ap_name reserved = net.reserved else: utf8 = cast('bool', kwargs.get('utf8', False)) reserved = cast('int', kwargs.get('reserved', 0)) net_name = cast('bytes', kwargs.get('net_name', b'')) ap_name = cast('bytes', kwargs.get('ap_name', b'')) return Schema_ANINetworkIdentifierSuboption( type=code, length=3 + len(net_name) + len(ap_name), flags={'E': int(utf8), 'reserved': self._make_uint(reserved, bits=7, name='reserved bits')}, net_name_len=len(net_name), net_name=net_name, ap_name_len=len(ap_name), ap_name=ap_name) if code == Enum_ANISuboption.Geo_Location: if option is not None: geo = cast('Data_ANIGeoLocationSuboption', option) raw_lat, raw_lon = geo.raw_latitude, geo.raw_longitude else: raw_lat = cast('int', kwargs.get('raw_latitude', 0)) raw_lon = cast('int', kwargs.get('raw_longitude', 0)) return Schema_ANIGeoLocationSuboption( type=code, length=6, location={ 'latitude': self._make_sint(raw_lat, bits=24, name='ANI geo-location latitude'), 'longitude': self._make_sint(raw_lon, bits=24, name='ANI geo-location longitude'), }) if code == Enum_ANISuboption.Operator_Identifier: if option is not None: operator = cast('Data_ANIOperatorIdentifierSuboption', option) op_id_type = operator.op_id_type # type: Enum_OperatorID | int identifier = operator.identifier else: op_id_type = self._make_index( kwargs.get('op_id_type', Enum_OperatorID.Realm_of_the_Operator), Enum_OperatorID, bits=8, name='Op-ID type') identifier = cast('bytes', kwargs.get('identifier', b'')) return Schema_ANIOperatorIdentifierSuboption( type=code, length=1 + len(identifier), op_id_type=cast('Enum_OperatorID', op_id_type), identifier=identifier) if code == Enum_ANISuboption.Civic_Location: if option is not None: civic = cast('Data_ANICivicLocationSuboption', option) fmt, location = civic.format, civic.location octets = getattr(civic, 'reserved', b'\x00') else: fmt = cast('int', kwargs.get('format', 0)) location = cast('bytes', kwargs.get('location', b'')) octets = cast('bytes', kwargs.get('reserved', b'\x00')) return Schema_ANICivicLocationSuboption( type=code, length=2 + len(location), format=self._make_uint(fmt, bits=8, name='civic location format'), reserved=self._make_octets(octets, size=1, name='reserved octets'), location=location) if code == Enum_ANISuboption.MAG_Group_Identifier: if option is not None: group_id = cast('Data_ANIMAGGroupIdentifierSuboption', option).group_id else: group_id = cast('int', kwargs.get('group_id', 0)) return Schema_ANIMAGGroupIdentifierSuboption( type=code, length=2, group_id=self._make_uint(group_id, bits=16, name='MAG group identifier')) if code == Enum_ANISuboption.ANI_Update_Timer: if option is not None: timer = math.ceil(cast('Data_ANIUpdateTimerSuboption', option).timer.total_seconds() / 4) else: raw_timer = kwargs.get('timer', 0) timer = raw_timer if isinstance(raw_timer, int) else math.ceil( raw_timer.total_seconds() / 4) return Schema_ANIUpdateTimerSuboption( type=code, length=2, timer=self._make_uint(timer, bits=16, name='ANI update timer')) if option is not None: payload = cast('Data_UnassignedANISuboption', option).data else: payload = cast('bytes', kwargs.get('data', b'')) return Schema_UnassignedANISuboption(type=code, length=len(payload), data=payload)
[docs] def _make_qos_attributes( self, attributes: 'QoSAttribute | list[Schema_QoSAttribute | tuple[Enum_QoSAttribute | int, dict[str, Any]] | bytes]', ) -> 'list[Schema_QoSAttribute | bytes]': """Make MH quality-of-service attributes. Args: attributes: Attributes, as parsed data, schemas, ``(type, kwargs)`` pairs, or raw octets. Returns: Attribute schema list. Raises: ProtocolError: If the type of an attribute, given in a ``(type, kwargs)`` pair or as the key of parsed data, is a name, a :obj:`bool` or a code its field cannot carry, or if an attribute given as raw octets is empty (:issue:`2723`). """ if isinstance(attributes, list): entries = [] # type: list[Schema_QoSAttribute | bytes] for item in attributes: if isinstance(item, bytes) and not item: raise ProtocolError(f'{self.alias}: invalid attribute: {item!r} holds no type octet') if isinstance(item, (bytes, Schema)): entries.append(cast('Schema_QoSAttribute | bytes', item)) else: kind, args = cast('tuple[Enum_QoSAttribute | int, dict[str, Any]]', item) code = self._make_index(kind, Enum_QoSAttribute, bits=8, name='attribute type') entries.append(self._make_qos_attribute(code, **args)) return entries return [self._make_qos_attribute( self._make_index(key, Enum_QoSAttribute, bits=8, name='attribute type'), option) for key, option in attributes.items(multi=True)]
[docs] def _make_qos_attribute(self, code: 'Enum_QoSAttribute', option: 'Optional[Data_QoSAttribute]' = None, **kwargs: 'Any') -> 'Schema_QoSAttribute': """Make one MH quality-of-service attribute. Args: code: Attribute type. option: Attribute data model. **kwargs: Attribute fields, when no data model is given. Returns: Constructed attribute schema. Raises: ProtocolError: If a rate, priority, vendor or reserved value does not fit its field, or ``ts_format`` is a name, a :obj:`bool` or a code its field cannot carry. Note: The data model parameter is named ``option`` rather than ``data`` deliberately. The vendor-specific attribute of :rfc:`7222#section-4.2.11` has a field of its own called ``data``, so a parameter of that name would capture a caller's ``data=`` and leave the ``kwargs.get('data')`` fallback empty, silently dropping the vendor payload and writing the length as though it were empty. ``vendor`` and ``subtype`` do not collide, so the loss would look like a partial success rather than a bug. Dispatch is on ``code`` rather than on ``isinstance``, for the reason given in :meth:`_read_fid_suboptions`. """ if code in (Enum_QoSAttribute.Per_Session_Agg_Max_DL_Bit_Rate, Enum_QoSAttribute.Per_Session_Agg_Max_UL_Bit_Rate): if option is not None: session = cast('Data_PerSessionBitRateAttribute', option) service, exclude, rate = session.service, session.exclude, session.rate reserved = session.reserved else: service = cast('bool', kwargs.get('service', False)) exclude = cast('bool', kwargs.get('exclude', False)) reserved = cast('int', kwargs.get('reserved', 0)) rate = cast('int', kwargs.get('rate', 0)) return Schema_PerSessionBitRateAttribute( type=code, length=6, flags={'S': int(service), 'E': int(exclude), 'reserved': self._make_uint(reserved, bits=14, name='reserved bits')}, rate=self._make_uint(rate, bits=32, name='bit rate')) if code in (Enum_QoSAttribute.Per_MN_Agg_Max_DL_Bit_Rate, Enum_QoSAttribute.Per_MN_Agg_Max_UL_Bit_Rate, Enum_QoSAttribute.Aggregate_Max_DL_Bit_Rate, Enum_QoSAttribute.Aggregate_Max_UL_Bit_Rate, Enum_QoSAttribute.Guaranteed_DL_Bit_Rate, Enum_QoSAttribute.Guaranteed_UL_Bit_Rate): if option is not None: rate = cast('Data_BitRateAttribute', option).rate octets = getattr(option, 'reserved', b'\x00\x00') else: rate = cast('int', kwargs.get('rate', 0)) octets = cast('bytes', kwargs.get('reserved', b'\x00\x00')) return Schema_BitRateAttribute(type=code, length=6, reserved=self._make_octets(octets, size=2, name='reserved octets'), rate=self._make_uint(rate, bits=32, name='bit rate')) if code == Enum_QoSAttribute.Allocation_Retention_Priority: if option is not None: arp = cast('Data_AllocationRetentionPriorityAttribute', option) level = arp.priority_level capability = arp.preemption_capability vulnerability = arp.preemption_vulnerability octets = getattr(arp, 'reserved', b'\x00') else: level = cast('int', kwargs.get('priority_level', 1)) capability = cast('int', kwargs.get('preemption_capability', 0)) vulnerability = cast('int', kwargs.get('preemption_vulnerability', 0)) octets = cast('bytes', kwargs.get('reserved', b'\x00')) return Schema_AllocationRetentionPriorityAttribute( type=code, length=2, reserved=self._make_octets(octets, size=1, name='reserved octets'), priority={ 'PL': self._make_uint(level, bits=4, name='priority level'), 'PC': self._make_uint(capability, bits=2, name='preemption capability'), 'PV': self._make_uint(vulnerability, bits=2, name='preemption vulnerability'), }) if code == Enum_QoSAttribute.QoS_Traffic_Selector: if option is not None: ts_attr = cast('Data_QoSTrafficSelectorAttribute', option) ts_format = ts_attr.ts_format # type: Enum_TrafficSelector | int selector = ts_attr.selector octets = getattr(ts_attr, 'reserved', b'\x00') else: ts_format = self._make_index( kwargs.get('ts_format', Enum_TrafficSelector.IPv6_Binary_Traffic_Selector), Enum_TrafficSelector, bits=8, name='TS format') selector = cast('bytes', kwargs.get('selector', b'')) octets = cast('bytes', kwargs.get('reserved', b'\x00')) return Schema_QoSTrafficSelectorAttribute( type=code, length=2 + len(selector), reserved=self._make_octets(octets, size=1, name='reserved octets'), ts_format=cast('Enum_TrafficSelector', ts_format), selector=selector) if code == Enum_QoSAttribute.QoS_Vendor_Specific_Attribute: if option is not None: vendor_attr = cast('Data_QoSVendorSpecificAttribute', option) vendor, subtype, payload = (vendor_attr.vendor, vendor_attr.subtype, vendor_attr.data) octets = getattr(vendor_attr, 'reserved', b'\x00\x00') else: vendor = cast('int', kwargs.get('vendor', 0)) subtype = cast('int', kwargs.get('subtype', 0)) payload = cast('bytes', kwargs.get('data', b'')) octets = cast('bytes', kwargs.get('reserved', b'\x00\x00')) return Schema_QoSVendorSpecificAttribute( type=code, length=7 + len(payload), reserved=self._make_octets(octets, size=2, name='reserved octets'), vendor=self._make_uint(vendor, bits=32, name='vendor ID'), subtype=self._make_uint(subtype, bits=8, name='vendor subtype'), data=payload) if option is not None: payload = cast('Data_UnassignedQoSAttribute', option).data else: payload = cast('bytes', kwargs.get('data', b'')) return Schema_UnassignedQoSAttribute(type=code, length=len(payload), data=payload)
[docs] def _make_lcmp_suboptions( self, suboptions: 'LMAControlledMAGSuboption | list[Schema_LMAControlledMAGSuboption | tuple[Enum_LMAControlledMAGSuboption | int, dict[str, Any]] | bytes]', # pylint: disable=line-too-long ) -> 'list[Schema_LMAControlledMAGSuboption | bytes]': """Make MH LMA-controlled MAG parameters sub-options. Args: suboptions: Sub-options, as parsed data, schemas, ``(type, kwargs)`` pairs, or raw octets. Returns: Sub-option schema list. Raises: ProtocolError: If the type of a sub-option, given in a ``(type, kwargs)`` pair or as the key of parsed data, is a name, a :obj:`bool` or a code its field cannot carry, or if a sub-option given as raw octets is empty (:issue:`2723`). """ if isinstance(suboptions, list): entries = [] # type: list[Schema_LMAControlledMAGSuboption | bytes] for item in suboptions: if isinstance(item, bytes) and not item: raise ProtocolError(f'{self.alias}: invalid sub-option: {item!r} holds no type octet') if isinstance(item, (bytes, Schema)): entries.append(cast('Schema_LMAControlledMAGSuboption | bytes', item)) else: kind, args = cast( 'tuple[Enum_LMAControlledMAGSuboption | int, dict[str, Any]]', item) code = self._make_index(kind, Enum_LMAControlledMAGSuboption, bits=8, name='sub-option type') entries.append(self._make_lcmp_suboption(code, **args)) return entries return [self._make_lcmp_suboption( self._make_index(key, Enum_LMAControlledMAGSuboption, bits=8, name='sub-option type'), option) for key, option in suboptions.items(multi=True)]
[docs] @staticmethod def _seconds(value: 'int | timedelta', unit: 'int' = 1) -> 'int': """Return an interval in wire units. Args: value: Interval, already in wire units when an :obj:`int`, or as a :class:`~datetime.timedelta`. unit: Number of seconds one wire unit stands for. Returns: The interval in wire units. """ if isinstance(value, int): return value return math.ceil(value.total_seconds() / unit)
[docs] def _make_lcmp_suboption(self, code: 'Enum_LMAControlledMAGSuboption', option: 'Optional[Data_LMAControlledMAGSuboption]' = None, **kwargs: 'Any') -> 'Schema_LMAControlledMAGSuboption': """Make one MH LMA-controlled MAG parameters sub-option. Args: code: Sub-option type. option: Sub-option data model. **kwargs: Sub-option fields, when no data model is given. Returns: Constructed sub-option schema. Raises: ProtocolError: If a timer or count does not fit its field. Note: The data model parameter is named ``option`` rather than ``data`` so that it cannot shadow a sub-option field of that name -- see :meth:`_make_fid_suboption`. Dispatch is on ``code`` rather than on ``isinstance``, for the reason given in :meth:`_read_fid_suboptions`. """ if code == Enum_LMAControlledMAGSuboption.Binding_Re_registration_Control: if option is not None: rereg = cast('Data_BindingReregistrationControlSuboption', option) start = math.ceil(rereg.start_time.total_seconds() / 4) initial = math.ceil(rereg.initial_retransmission.total_seconds()) maximum = math.ceil(rereg.max_retransmission.total_seconds()) else: start = self._seconds(kwargs.get('start_time', 0), 4) initial = self._seconds(kwargs.get('initial_retransmission', 0)) maximum = self._seconds(kwargs.get('max_retransmission', 0)) return Schema_BindingReregistrationControlSuboption( type=code, length=6, start_time=self._make_uint(start, bits=16, name='re-registration start time'), initial_retransmission=self._make_uint(initial, bits=16, name='initial retransmission timer'), max_retransmission=self._make_uint(maximum, bits=16, name='maximum retransmission timer')) if code == Enum_LMAControlledMAGSuboption.Heartbeat_Control: if option is not None: heartbeat = cast('Data_HeartbeatControlSuboption', option) interval = math.ceil(heartbeat.interval.total_seconds()) delay = math.ceil(heartbeat.retransmission_delay.total_seconds()) count = heartbeat.max_retransmissions else: interval = self._seconds(kwargs.get('interval', 0)) delay = self._seconds(kwargs.get('retransmission_delay', 0)) count = cast('int', kwargs.get('max_retransmissions', 0)) return Schema_HeartbeatControlSuboption( type=code, length=6, interval=self._make_uint(interval, bits=16, name='heartbeat interval'), retransmission_delay=self._make_uint(delay, bits=16, name='retransmission delay'), max_retransmissions=self._make_uint(count, bits=16, name='maximum retransmissions')) if option is not None: payload = cast('Data_UnassignedLMAControlledMAGSuboption', option).data else: payload = cast('bytes', kwargs.get('data', b'')) return Schema_UnassignedLMAControlledMAGSuboption( type=code, length=len(payload), data=payload)
[docs] def _make_cga_extensions(self, extensions: 'Extension | list[Schema_CGAExtension | tuple[Enum_CGAExtension | int, dict[str, Any]] | bytes]') -> 'tuple[list[Schema_CGAExtension | bytes], int]': # pylint: disable=line-too-long """Make CGA extensions for MH. Args: extensions: CGA extensions. Returns: Tuple of extensions and total length of extensions. Raises: ProtocolError: If the type of an extension, given in a ``(type, kwargs)`` pair or as the key of parsed data, is a name, a :obj:`bool` or a code its field cannot carry, or if an extension given as raw octets is empty (:issue:`2723`). """ total_length = 0 if isinstance(extensions, list): extensions_list = [] # type: list[Schema_CGAExtension | bytes] for schema in extensions: if isinstance(schema, bytes): if not schema: raise ProtocolError(f'{self.alias}: invalid CGA extension: {schema!r} holds no type field') code = Enum_CGAExtension.get(int.from_bytes(schema[0:2], 'big', signed=False)) data = schema # type: Schema_CGAExtension | bytes data_len = len(data) elif isinstance(schema, Schema): data = schema data_len = len(schema.pack()) else: kind, args = cast('tuple[Enum_CGAExtension | int, dict[str, Any]]', schema) code = self._make_index(kind, Enum_CGAExtension, bits=16, name='extension type') name = self._lookup_registry(self.__extension__, code) if isinstance(name, str): meth_name = f'_make_ext_{name}' meth = cast('ExtensionConstructor', getattr(self, meth_name, self._make_ext_none)) else: meth = name[1] data = meth(code, **args) data_len = len(data.pack()) extensions_list.append(data) total_length += data_len return extensions_list, total_length extensions_list = [] for key, extension in extensions.items(multi=True): code = self._make_index(key, Enum_CGAExtension, bits=16, name='extension type') name = self._lookup_registry(self.__extension__, code) if isinstance(name, str): meth_name = f'_make_ext_{name}' meth = cast('ExtensionConstructor', getattr(self, meth_name, self._make_ext_none)) else: meth = name[1] data = meth(code, extension) data_len = len(data.pack()) extensions_list.append(data) total_length += data_len return extensions_list, total_length