# -*- coding: utf-8 -*-
# pylint: disable=fixme
"""mobility header
:mod:`pcapkit.protocols.internet.mh` contains
:class:`~pcapkit.protocols.internet.mh.MH` only,
which implements 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 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
"""
import collections
import datetime
import ipaddress
import math
from typing import TYPE_CHECKING, cast, overload
from aenum import IntEnum, extend_enum
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.fields.ipaddress import parse_ip_address
from pcapkit.corekit.multidict import OrderedMultiDict
from pcapkit.protocols.data.internet.mh import MH as Data_MH
from pcapkit.protocols.data.internet.mh import \
AccessNetworkIdentifierOption as Data_AccessNetworkIdentifierOption
from pcapkit.protocols.data.internet.mh import \
AccessTechnologyTypeOption as Data_AccessTechnologyTypeOption
from pcapkit.protocols.data.internet.mh import \
ActiveMulticastSubscriptionIPv4Option as Data_ActiveMulticastSubscriptionIPv4Option
from pcapkit.protocols.data.internet.mh import \
ActiveMulticastSubscriptionIPv6Option as Data_ActiveMulticastSubscriptionIPv6Option
from pcapkit.protocols.data.internet.mh import \
AllocationRetentionPriorityAttribute as Data_AllocationRetentionPriorityAttribute
from pcapkit.protocols.data.internet.mh import \
AlternateCareofAddressOption as Data_AlternateCareofAddressOption
from pcapkit.protocols.data.internet.mh import \
AlternateIPv4CareofAddressOption as Data_AlternateIPv4CareofAddressOption
from pcapkit.protocols.data.internet.mh import AnchoredPrefixOption as Data_AnchoredPrefixOption
from pcapkit.protocols.data.internet.mh import \
ANICivicLocationSuboption as Data_ANICivicLocationSuboption
from pcapkit.protocols.data.internet.mh import \
ANIGeoLocationSuboption as Data_ANIGeoLocationSuboption
from pcapkit.protocols.data.internet.mh import \
ANIMAGGroupIdentifierSuboption as Data_ANIMAGGroupIdentifierSuboption
from pcapkit.protocols.data.internet.mh import \
ANINetworkIdentifierSuboption as Data_ANINetworkIdentifierSuboption
from pcapkit.protocols.data.internet.mh import \
ANIOperatorIdentifierSuboption as Data_ANIOperatorIdentifierSuboption
from pcapkit.protocols.data.internet.mh import ANISuboption as Data_ANISuboption
from pcapkit.protocols.data.internet.mh import \
ANIUpdateTimerSuboption as Data_ANIUpdateTimerSuboption
from pcapkit.protocols.data.internet.mh import AuthOption as Data_AuthOption
from pcapkit.protocols.data.internet.mh import \
AuthorizationDataOption as Data_AuthorizationDataOption
from pcapkit.protocols.data.internet.mh import BADFOption as Data_BADFOption
from pcapkit.protocols.data.internet.mh import BIDReferenceSuboption as Data_BIDReferenceSuboption
from pcapkit.protocols.data.internet.mh import \
BindingAcknowledgementMessage as Data_BindingAcknowledgementMessage
from pcapkit.protocols.data.internet.mh import BindingErrorMessage as Data_BindingErrorMessage
from pcapkit.protocols.data.internet.mh import \
BindingIdentifierOption as Data_BindingIdentifierOption
from pcapkit.protocols.data.internet.mh import \
BindingRefreshAdviceOption as Data_BindingRefreshAdviceOption
from pcapkit.protocols.data.internet.mh import \
BindingRefreshRequestMessage as Data_BindingRefreshRequestMessage
from pcapkit.protocols.data.internet.mh import \
BindingReregistrationControlSuboption as Data_BindingReregistrationControlSuboption
from pcapkit.protocols.data.internet.mh import \
BindingRevocationMessage as Data_BindingRevocationMessage
from pcapkit.protocols.data.internet.mh import BindingUpdateMessage as Data_BindingUpdateMessage
from pcapkit.protocols.data.internet.mh import BitRateAttribute as Data_BitRateAttribute
from pcapkit.protocols.data.internet.mh import CareofTestInitMessage as Data_CareofTestInitMessage
from pcapkit.protocols.data.internet.mh import CareofTestInitOption as Data_CareofTestInitOption
from pcapkit.protocols.data.internet.mh import CareofTestMessage as Data_CareofTestMessage
from pcapkit.protocols.data.internet.mh import CareofTestOption as Data_CareofTestOption
from pcapkit.protocols.data.internet.mh import CGAExtension as Data_CGAExtension
from pcapkit.protocols.data.internet.mh import CGAParameter as Data_CGAParameter
from pcapkit.protocols.data.internet.mh import CGAParametersOption as Data_CGAParametersOption
from pcapkit.protocols.data.internet.mh import \
CGAParametersRequestOption as Data_CGAParametersRequestOption
from pcapkit.protocols.data.internet.mh import ContextRequest as Data_ContextRequest
from pcapkit.protocols.data.internet.mh import ContextRequestOption as Data_ContextRequestOption
from pcapkit.protocols.data.internet.mh import DelegatedMNPOption as Data_DelegatedMNPOption
from pcapkit.protocols.data.internet.mh import \
DLIFLinkLayerAddressOption as Data_DLIFLinkLayerAddressOption
from pcapkit.protocols.data.internet.mh import \
DLIFLinkLocalAddressOption as Data_DLIFLinkLocalAddressOption
from pcapkit.protocols.data.internet.mh import DNSUpdateOption as Data_DNSUpdateOption
from pcapkit.protocols.data.internet.mh import \
DynamicIPMulticastSelectorOption as Data_DynamicIPMulticastSelectorOption
from pcapkit.protocols.data.internet.mh import ExperimentalExtension as Data_ExperimentalExtension
from pcapkit.protocols.data.internet.mh import ExperimentalMessage as Data_ExperimentalMessage
from pcapkit.protocols.data.internet.mh import \
ExperimentalMobilityOption as Data_ExperimentalMobilityOption
from pcapkit.protocols.data.internet.mh import \
FastBindingAcknowledgmentMessage as Data_FastBindingAcknowledgmentMessage
from pcapkit.protocols.data.internet.mh import \
FastBindingUpdateMessage as Data_FastBindingUpdateMessage
from pcapkit.protocols.data.internet.mh import \
FastNeighborAdvertisementMessage as Data_FastNeighborAdvertisementMessage
from pcapkit.protocols.data.internet.mh import \
FlowBindingActionSuboption as Data_FlowBindingActionSuboption
from pcapkit.protocols.data.internet.mh import FlowBindingMessage as Data_FlowBindingMessage
from pcapkit.protocols.data.internet.mh import \
FlowIdentificationOption as Data_FlowIdentificationOption
from pcapkit.protocols.data.internet.mh import \
FlowIdentificationSuboption as Data_FlowIdentificationSuboption
from pcapkit.protocols.data.internet.mh import FlowSummaryOption as Data_FlowSummaryOption
from pcapkit.protocols.data.internet.mh import GREKeyOption as Data_GREKeyOption
from pcapkit.protocols.data.internet.mh import HandoffIndicatorOption as Data_HandoffIndicatorOption
from pcapkit.protocols.data.internet.mh import \
HandoverAcknowledgeMessage as Data_HandoverAcknowledgeMessage
from pcapkit.protocols.data.internet.mh import \
HandoverInitiateMessage as Data_HandoverInitiateMessage
from pcapkit.protocols.data.internet.mh import \
HeartbeatControlSuboption as Data_HeartbeatControlSuboption
from pcapkit.protocols.data.internet.mh import HeartbeatMessage as Data_HeartbeatMessage
from pcapkit.protocols.data.internet.mh import HomeAgentSwitchMessage as Data_HomeAgentSwitchMessage
from pcapkit.protocols.data.internet.mh import \
HomeNetworkPrefixOption as Data_HomeNetworkPrefixOption
from pcapkit.protocols.data.internet.mh import HomeTestInitMessage as Data_HomeTestInitMessage
from pcapkit.protocols.data.internet.mh import HomeTestMessage as Data_HomeTestMessage
from pcapkit.protocols.data.internet.mh import \
IPv4AddressAcknowledgementOption as Data_IPv4AddressAcknowledgementOption
from pcapkit.protocols.data.internet.mh import \
IPv4CareofAddressOption as Data_IPv4CareofAddressOption
from pcapkit.protocols.data.internet.mh import \
IPv4DefaultRouterAddressOption as Data_IPv4DefaultRouterAddressOption
from pcapkit.protocols.data.internet.mh import \
IPv4DHCPSupportModeOption as Data_IPv4DHCPSupportModeOption
from pcapkit.protocols.data.internet.mh import IPv4HomeAddressOption as Data_IPv4HomeAddressOption
from pcapkit.protocols.data.internet.mh import \
IPv4HomeAddressReplyOption as Data_IPv4HomeAddressReplyOption
from pcapkit.protocols.data.internet.mh import \
IPv4HomeAddressRequestOption as Data_IPv4HomeAddressRequestOption
from pcapkit.protocols.data.internet.mh import \
IPv4TrafficOffloadSelectorOption as Data_IPv4TrafficOffloadSelectorOption
from pcapkit.protocols.data.internet.mh import \
IPv6AddressPrefixOption as Data_IPv6AddressPrefixOption
from pcapkit.protocols.data.internet.mh import LinkLayerAddressOption as Data_LinkLayerAddressOption
from pcapkit.protocols.data.internet.mh import LinkLocalAddressOption as Data_LinkLocalAddressOption
from pcapkit.protocols.data.internet.mh import LMAAddressOption as Data_LMAAddressOption
from pcapkit.protocols.data.internet.mh import \
LMAControlledMAGParametersOption as Data_LMAControlledMAGParametersOption
from pcapkit.protocols.data.internet.mh import \
LMAControlledMAGSuboption as Data_LMAControlledMAGSuboption
from pcapkit.protocols.data.internet.mh import \
LMAUserPlaneAddressOption as Data_LMAUserPlaneAddressOption
from pcapkit.protocols.data.internet.mh import LoadInformationOption as Data_LoadInformationOption
from pcapkit.protocols.data.internet.mh import \
LocalizedRoutingAcknowledgmentMessage as Data_LocalizedRoutingAcknowledgmentMessage
from pcapkit.protocols.data.internet.mh import \
LocalizedRoutingInitiationMessage as Data_LocalizedRoutingInitiationMessage
from pcapkit.protocols.data.internet.mh import LocalPrefixOption as Data_LocalPrefixOption
from pcapkit.protocols.data.internet.mh import MAGIdentifierOption as Data_MAGIdentifierOption
from pcapkit.protocols.data.internet.mh import MAGIPv6AddressOption as Data_MAGIPv6AddressOption
from pcapkit.protocols.data.internet.mh import \
MAGMultipathBindingOption as Data_MAGMultipathBindingOption
from pcapkit.protocols.data.internet.mh import MesgIDOption as Data_MesgIDOption
from pcapkit.protocols.data.internet.mh import \
MNGroupIdentifierOption as Data_MNGroupIdentifierOption
from pcapkit.protocols.data.internet.mh import MNIDOption as Data_MNIDOption
from pcapkit.protocols.data.internet.mh import MNLLAIIDOption as Data_MNLLAIIDOption
from pcapkit.protocols.data.internet.mh import MNLLIdentifierOption as Data_MNLLIdentifierOption
from pcapkit.protocols.data.internet.mh import \
MobileNetworkPrefixOption as Data_MobileNetworkPrefixOption
from pcapkit.protocols.data.internet.mh import \
MulticastAcknowledgementOption as Data_MulticastAcknowledgementOption
from pcapkit.protocols.data.internet.mh import \
MulticastMobilityOption as Data_MulticastMobilityOption
from pcapkit.protocols.data.internet.mh import MultiPrefixExtension as Data_MultiPrefixExtension
from pcapkit.protocols.data.internet.mh import NATDetectionOption as Data_NATDetectionOption
from pcapkit.protocols.data.internet.mh import NonceIndicesOption as Data_NonceIndicesOption
from pcapkit.protocols.data.internet.mh import \
PadFlowIdentificationSuboption as Data_PadFlowIdentificationSuboption
from pcapkit.protocols.data.internet.mh import PadOption as Data_PadOption
from pcapkit.protocols.data.internet.mh import \
PermanentHomeKeygenTokenOption as Data_PermanentHomeKeygenTokenOption
from pcapkit.protocols.data.internet.mh import \
PerSessionBitRateAttribute as Data_PerSessionBitRateAttribute
from pcapkit.protocols.data.internet.mh import PreviousMAAROption as Data_PreviousMAAROption
from pcapkit.protocols.data.internet.mh import QoSAttribute as Data_QoSAttribute
from pcapkit.protocols.data.internet.mh import \
QoSTrafficSelectorAttribute as Data_QoSTrafficSelectorAttribute
from pcapkit.protocols.data.internet.mh import \
QoSVendorSpecificAttribute as Data_QoSVendorSpecificAttribute
from pcapkit.protocols.data.internet.mh import QualityOfServiceOption as Data_QualityOfServiceOption
from pcapkit.protocols.data.internet.mh import \
RedirectCapabilityOption as Data_RedirectCapabilityOption
from pcapkit.protocols.data.internet.mh import RedirectOption as Data_RedirectOption
from pcapkit.protocols.data.internet.mh import RestartCounterOption as Data_RestartCounterOption
from pcapkit.protocols.data.internet.mh import ServiceSelectionOption as Data_ServiceSelectionOption
from pcapkit.protocols.data.internet.mh import ServingMAAROption as Data_ServingMAAROption
from pcapkit.protocols.data.internet.mh import SignatureOption as Data_SignatureOption
from pcapkit.protocols.data.internet.mh import \
SubscriptionQueryMessage as Data_SubscriptionQueryMessage
from pcapkit.protocols.data.internet.mh import \
SubscriptionResponseMessage as Data_SubscriptionResponseMessage
from pcapkit.protocols.data.internet.mh import \
TargetCareofAddressSuboption as Data_TargetCareofAddressSuboption
from pcapkit.protocols.data.internet.mh import TimestampOption as Data_TimestampOption
from pcapkit.protocols.data.internet.mh import \
TrafficSelectorSuboption as Data_TrafficSelectorSuboption
from pcapkit.protocols.data.internet.mh import TransientBindingOption as Data_TransientBindingOption
from pcapkit.protocols.data.internet.mh import UnassignedANISuboption as Data_UnassignedANISuboption
from pcapkit.protocols.data.internet.mh import \
UnassignedFlowIdentificationSuboption as Data_UnassignedFlowIdentificationSuboption
from pcapkit.protocols.data.internet.mh import \
UnassignedLMAControlledMAGSuboption as Data_UnassignedLMAControlledMAGSuboption
from pcapkit.protocols.data.internet.mh import UnassignedOption as Data_UnassignedOption
from pcapkit.protocols.data.internet.mh import UnassignedQoSAttribute as Data_UnassignedQoSAttribute
from pcapkit.protocols.data.internet.mh import UnknownExtension as Data_UnknownExtension
from pcapkit.protocols.data.internet.mh import UnknownMessage as Data_UnknownMessage
from pcapkit.protocols.data.internet.mh import \
UpdateNotificationAcknowledgementMessage as Data_UpdateNotificationAcknowledgementMessage
from pcapkit.protocols.data.internet.mh import \
UpdateNotificationMessage as Data_UpdateNotificationMessage
from pcapkit.protocols.data.internet.mh import VendorSpecificOption as Data_VendorSpecificOption
from pcapkit.protocols.internet.internet import Internet
from pcapkit.protocols.schema.internet.mh import MH as Schema_MH
from pcapkit.protocols.schema.internet.mh import \
AccessNetworkIdentifierOption as Schema_AccessNetworkIdentifierOption
from pcapkit.protocols.schema.internet.mh import \
AccessTechnologyTypeOption as Schema_AccessTechnologyTypeOption
from pcapkit.protocols.schema.internet.mh import \
ActiveMulticastSubscriptionIPv4Option as Schema_ActiveMulticastSubscriptionIPv4Option
from pcapkit.protocols.schema.internet.mh import \
ActiveMulticastSubscriptionIPv6Option as Schema_ActiveMulticastSubscriptionIPv6Option
from pcapkit.protocols.schema.internet.mh import \
AllocationRetentionPriorityAttribute as Schema_AllocationRetentionPriorityAttribute
from pcapkit.protocols.schema.internet.mh import \
AlternateCareofAddressOption as Schema_AlternateCareofAddressOption
from pcapkit.protocols.schema.internet.mh import \
AlternateIPv4CareofAddressOption as Schema_AlternateIPv4CareofAddressOption
from pcapkit.protocols.schema.internet.mh import AnchoredPrefixOption as Schema_AnchoredPrefixOption
from pcapkit.protocols.schema.internet.mh import \
ANICivicLocationSuboption as Schema_ANICivicLocationSuboption
from pcapkit.protocols.schema.internet.mh import \
ANIGeoLocationSuboption as Schema_ANIGeoLocationSuboption
from pcapkit.protocols.schema.internet.mh import \
ANIMAGGroupIdentifierSuboption as Schema_ANIMAGGroupIdentifierSuboption
from pcapkit.protocols.schema.internet.mh import \
ANINetworkIdentifierSuboption as Schema_ANINetworkIdentifierSuboption
from pcapkit.protocols.schema.internet.mh import \
ANIOperatorIdentifierSuboption as Schema_ANIOperatorIdentifierSuboption
from pcapkit.protocols.schema.internet.mh import ANISuboption as Schema_ANISuboption
from pcapkit.protocols.schema.internet.mh import \
ANIUpdateTimerSuboption as Schema_ANIUpdateTimerSuboption
from pcapkit.protocols.schema.internet.mh import AuthOption as Schema_AuthOption
from pcapkit.protocols.schema.internet.mh import \
AuthorizationDataOption as Schema_AuthorizationDataOption
from pcapkit.protocols.schema.internet.mh import BADFOption as Schema_BADFOption
from pcapkit.protocols.schema.internet.mh import \
BIDReferenceSuboption as Schema_BIDReferenceSuboption
from pcapkit.protocols.schema.internet.mh import \
BindingAcknowledgementMessage as Schema_BindingAcknowledgementMessage
from pcapkit.protocols.schema.internet.mh import BindingErrorMessage as Schema_BindingErrorMessage
from pcapkit.protocols.schema.internet.mh import \
BindingIdentifierOption as Schema_BindingIdentifierOption
from pcapkit.protocols.schema.internet.mh import \
BindingRefreshAdviceOption as Schema_BindingRefreshAdviceOption
from pcapkit.protocols.schema.internet.mh import \
BindingRefreshRequestMessage as Schema_BindingRefreshRequestMessage
from pcapkit.protocols.schema.internet.mh import \
BindingReregistrationControlSuboption as Schema_BindingReregistrationControlSuboption
from pcapkit.protocols.schema.internet.mh import \
BindingRevocationMessage as Schema_BindingRevocationMessage
from pcapkit.protocols.schema.internet.mh import BindingUpdateMessage as Schema_BindingUpdateMessage
from pcapkit.protocols.schema.internet.mh import BitRateAttribute as Schema_BitRateAttribute
from pcapkit.protocols.schema.internet.mh import \
CareofTestInitMessage as Schema_CareofTestInitMessage
from pcapkit.protocols.schema.internet.mh import CareofTestInitOption as Schema_CareofTestInitOption
from pcapkit.protocols.schema.internet.mh import CareofTestMessage as Schema_CareofTestMessage
from pcapkit.protocols.schema.internet.mh import CareofTestOption as Schema_CareofTestOption
from pcapkit.protocols.schema.internet.mh import CGAExtension as Schema_CGAExtension
from pcapkit.protocols.schema.internet.mh import CGAParameter as Schema_CGAParameter
from pcapkit.protocols.schema.internet.mh import CGAParametersOption as Schema_CGAParametersOption
from pcapkit.protocols.schema.internet.mh import \
CGAParametersRequestOption as Schema_CGAParametersRequestOption
from pcapkit.protocols.schema.internet.mh import ContextRequestOption as Schema_ContextRequestOption
from pcapkit.protocols.schema.internet.mh import DelegatedMNPOption as Schema_DelegatedMNPOption
from pcapkit.protocols.schema.internet.mh import \
DLIFLinkLayerAddressOption as Schema_DLIFLinkLayerAddressOption
from pcapkit.protocols.schema.internet.mh import \
DLIFLinkLocalAddressOption as Schema_DLIFLinkLocalAddressOption
from pcapkit.protocols.schema.internet.mh import DNSUpdateOption as Schema_DNSUpdateOption
from pcapkit.protocols.schema.internet.mh import \
DynamicIPMulticastSelectorOption as Schema_DynamicIPMulticastSelectorOption
from pcapkit.protocols.schema.internet.mh import \
ExperimentalExtension as Schema_ExperimentalExtension
from pcapkit.protocols.schema.internet.mh import ExperimentalMessage as Schema_ExperimentalMessage
from pcapkit.protocols.schema.internet.mh import \
ExperimentalMobilityOption as Schema_ExperimentalMobilityOption
from pcapkit.protocols.schema.internet.mh import \
FastBindingAcknowledgmentMessage as Schema_FastBindingAcknowledgmentMessage
from pcapkit.protocols.schema.internet.mh import \
FastBindingUpdateMessage as Schema_FastBindingUpdateMessage
from pcapkit.protocols.schema.internet.mh import \
FastNeighborAdvertisementMessage as Schema_FastNeighborAdvertisementMessage
from pcapkit.protocols.schema.internet.mh import \
FlowBindingActionSuboption as Schema_FlowBindingActionSuboption
from pcapkit.protocols.schema.internet.mh import FlowBindingMessage as Schema_FlowBindingMessage
from pcapkit.protocols.schema.internet.mh import \
FlowIdentificationOption as Schema_FlowIdentificationOption
from pcapkit.protocols.schema.internet.mh import \
FlowIdentificationSuboption as Schema_FlowIdentificationSuboption
from pcapkit.protocols.schema.internet.mh import FlowSummaryOption as Schema_FlowSummaryOption
from pcapkit.protocols.schema.internet.mh import GREKeyOption as Schema_GREKeyOption
from pcapkit.protocols.schema.internet.mh import \
HandoffIndicatorOption as Schema_HandoffIndicatorOption
from pcapkit.protocols.schema.internet.mh import \
HandoverAcknowledgeMessage as Schema_HandoverAcknowledgeMessage
from pcapkit.protocols.schema.internet.mh import \
HandoverInitiateMessage as Schema_HandoverInitiateMessage
from pcapkit.protocols.schema.internet.mh import \
HeartbeatControlSuboption as Schema_HeartbeatControlSuboption
from pcapkit.protocols.schema.internet.mh import HeartbeatMessage as Schema_HeartbeatMessage
from pcapkit.protocols.schema.internet.mh import \
HomeAgentSwitchMessage as Schema_HomeAgentSwitchMessage
from pcapkit.protocols.schema.internet.mh import \
HomeNetworkPrefixOption as Schema_HomeNetworkPrefixOption
from pcapkit.protocols.schema.internet.mh import HomeTestInitMessage as Schema_HomeTestInitMessage
from pcapkit.protocols.schema.internet.mh import HomeTestMessage as Schema_HomeTestMessage
from pcapkit.protocols.schema.internet.mh import \
IPv4AddressAcknowledgementOption as Schema_IPv4AddressAcknowledgementOption
from pcapkit.protocols.schema.internet.mh import \
IPv4CareofAddressOption as Schema_IPv4CareofAddressOption
from pcapkit.protocols.schema.internet.mh import \
IPv4DefaultRouterAddressOption as Schema_IPv4DefaultRouterAddressOption
from pcapkit.protocols.schema.internet.mh import \
IPv4DHCPSupportModeOption as Schema_IPv4DHCPSupportModeOption
from pcapkit.protocols.schema.internet.mh import \
IPv4HomeAddressOption as Schema_IPv4HomeAddressOption
from pcapkit.protocols.schema.internet.mh import \
IPv4HomeAddressReplyOption as Schema_IPv4HomeAddressReplyOption
from pcapkit.protocols.schema.internet.mh import \
IPv4HomeAddressRequestOption as Schema_IPv4HomeAddressRequestOption
from pcapkit.protocols.schema.internet.mh import \
IPv4TrafficOffloadSelectorOption as Schema_IPv4TrafficOffloadSelectorOption
from pcapkit.protocols.schema.internet.mh import \
IPv6AddressPrefixOption as Schema_IPv6AddressPrefixOption
from pcapkit.protocols.schema.internet.mh import \
LinkLayerAddressOption as Schema_LinkLayerAddressOption
from pcapkit.protocols.schema.internet.mh import \
LinkLocalAddressOption as Schema_LinkLocalAddressOption
from pcapkit.protocols.schema.internet.mh import LMAAddressOption as Schema_LMAAddressOption
from pcapkit.protocols.schema.internet.mh import \
LMAControlledMAGParametersOption as Schema_LMAControlledMAGParametersOption
from pcapkit.protocols.schema.internet.mh import \
LMAControlledMAGSuboption as Schema_LMAControlledMAGSuboption
from pcapkit.protocols.schema.internet.mh import \
LMAUserPlaneAddressOption as Schema_LMAUserPlaneAddressOption
from pcapkit.protocols.schema.internet.mh import \
LoadInformationOption as Schema_LoadInformationOption
from pcapkit.protocols.schema.internet.mh import \
LocalizedRoutingAcknowledgmentMessage as Schema_LocalizedRoutingAcknowledgmentMessage
from pcapkit.protocols.schema.internet.mh import \
LocalizedRoutingInitiationMessage as Schema_LocalizedRoutingInitiationMessage
from pcapkit.protocols.schema.internet.mh import LocalPrefixOption as Schema_LocalPrefixOption
from pcapkit.protocols.schema.internet.mh import MAGIdentifierOption as Schema_MAGIdentifierOption
from pcapkit.protocols.schema.internet.mh import MAGIPv6AddressOption as Schema_MAGIPv6AddressOption
from pcapkit.protocols.schema.internet.mh import \
MAGMultipathBindingOption as Schema_MAGMultipathBindingOption
from pcapkit.protocols.schema.internet.mh import MesgIDOption as Schema_MesgIDOption
from pcapkit.protocols.schema.internet.mh import \
MNGroupIdentifierOption as Schema_MNGroupIdentifierOption
from pcapkit.protocols.schema.internet.mh import MNIDOption as Schema_MNIDOption
from pcapkit.protocols.schema.internet.mh import MNLLAIIDOption as Schema_MNLLAIIDOption
from pcapkit.protocols.schema.internet.mh import MNLLIdentifierOption as Schema_MNLLIdentifierOption
from pcapkit.protocols.schema.internet.mh import \
MobileNetworkPrefixOption as Schema_MobileNetworkPrefixOption
from pcapkit.protocols.schema.internet.mh import \
MulticastAcknowledgementOption as Schema_MulticastAcknowledgementOption
from pcapkit.protocols.schema.internet.mh import \
MulticastMobilityOption as Schema_MulticastMobilityOption
from pcapkit.protocols.schema.internet.mh import MultiPrefixExtension as Schema_MultiPrefixExtension
from pcapkit.protocols.schema.internet.mh import NATDetectionOption as Schema_NATDetectionOption
from pcapkit.protocols.schema.internet.mh import NonceIndicesOption as Schema_NonceIndicesOption
from pcapkit.protocols.schema.internet.mh import Packet as Schema_Packet
from pcapkit.protocols.schema.internet.mh import \
PadFlowIdentificationSuboption as Schema_PadFlowIdentificationSuboption
from pcapkit.protocols.schema.internet.mh import PadOption as Schema_PadOption
from pcapkit.protocols.schema.internet.mh import \
PermanentHomeKeygenTokenOption as Schema_PermanentHomeKeygenTokenOption
from pcapkit.protocols.schema.internet.mh import \
PerSessionBitRateAttribute as Schema_PerSessionBitRateAttribute
from pcapkit.protocols.schema.internet.mh import PreviousMAAROption as Schema_PreviousMAAROption
from pcapkit.protocols.schema.internet.mh import QoSAttribute as Schema_QoSAttribute
from pcapkit.protocols.schema.internet.mh import \
QoSTrafficSelectorAttribute as Schema_QoSTrafficSelectorAttribute
from pcapkit.protocols.schema.internet.mh import \
QoSVendorSpecificAttribute as Schema_QoSVendorSpecificAttribute
from pcapkit.protocols.schema.internet.mh import \
QualityOfServiceOption as Schema_QualityOfServiceOption
from pcapkit.protocols.schema.internet.mh import \
RedirectCapabilityOption as Schema_RedirectCapabilityOption
from pcapkit.protocols.schema.internet.mh import RedirectOption as Schema_RedirectOption
from pcapkit.protocols.schema.internet.mh import RestartCounterOption as Schema_RestartCounterOption
from pcapkit.protocols.schema.internet.mh import \
ServiceSelectionOption as Schema_ServiceSelectionOption
from pcapkit.protocols.schema.internet.mh import ServingMAAROption as Schema_ServingMAAROption
from pcapkit.protocols.schema.internet.mh import SignatureOption as Schema_SignatureOption
from pcapkit.protocols.schema.internet.mh import \
SubscriptionQueryMessage as Schema_SubscriptionQueryMessage
from pcapkit.protocols.schema.internet.mh import \
SubscriptionResponseMessage as Schema_SubscriptionResponseMessage
from pcapkit.protocols.schema.internet.mh import \
TargetCareofAddressSuboption as Schema_TargetCareofAddressSuboption
from pcapkit.protocols.schema.internet.mh import TimestampOption as Schema_TimestampOption
from pcapkit.protocols.schema.internet.mh import \
TrafficSelectorSuboption as Schema_TrafficSelectorSuboption
from pcapkit.protocols.schema.internet.mh import \
TransientBindingOption as Schema_TransientBindingOption
from pcapkit.protocols.schema.internet.mh import \
UnassignedANISuboption as Schema_UnassignedANISuboption
from pcapkit.protocols.schema.internet.mh import \
UnassignedFlowIdentificationSuboption as Schema_UnassignedFlowIdentificationSuboption
from pcapkit.protocols.schema.internet.mh import \
UnassignedLMAControlledMAGSuboption as Schema_UnassignedLMAControlledMAGSuboption
from pcapkit.protocols.schema.internet.mh import UnassignedOption as Schema_UnassignedOption
from pcapkit.protocols.schema.internet.mh import \
UnassignedQoSAttribute as Schema_UnassignedQoSAttribute
from pcapkit.protocols.schema.internet.mh import UnknownExtension as Schema_UnknownExtension
from pcapkit.protocols.schema.internet.mh import UnknownMessage as Schema_UnknownMessage
from pcapkit.protocols.schema.internet.mh import \
UpdateNotificationAcknowledgementMessage as Schema_UpdateNotificationAcknowledgementMessage
from pcapkit.protocols.schema.internet.mh import \
UpdateNotificationMessage as Schema_UpdateNotificationMessage
from pcapkit.protocols.schema.internet.mh import VendorSpecificOption as Schema_VendorSpecificOption
from pcapkit.protocols.schema.schema import Schema
from pcapkit.utilities.exceptions import 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 enum import IntEnum as StdlibEnum
from ipaddress import IPv4Address, IPv6Address, IPv6Network
from typing import IO, Any, Callable, DefaultDict, NoReturn, Optional, Type
from aenum import IntEnum as AenumEnum
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.data.internet.mh import Option as Data_Option
from pcapkit.protocols.protocol import ProtocolBase
from pcapkit.protocols.schema.internet.mh import Option as Schema_Option
from pcapkit.protocols.schema.internet.mh import Packet as Schema_Packet
from pcapkit.protocols.schema.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 NTPTimestamp(collections.namedtuple('NTPTimestamp', 'seconds fraction')):
"""NTP timestamp format, c.f., :rfc:`1305`."""
__slots__ = ()
#: Seconds since 1 January 1900.
seconds: int
#: Fraction of a second.
fraction: int
[docs]
class PMIPv6Timestamp(collections.namedtuple('PMIPv6Timestamp', 'seconds fraction')):
"""Proxy Mobile IPv6 timestamp format, c.f., :rfc:`5213#section-8.8`.
Note:
This is **not** an :rfc:`1305` NTP timestamp, which is why it is a type of
its own rather than a reuse of :class:`NTPTimestamp`. It counts from the
UNIX epoch rather than NTP's 1900 one, and splits its 64 bits 48/16
rather than 32/32, so each field means something different in the two.
"""
__slots__ = ()
#: Seconds since 1 January 1970, in the leading 48 bits.
seconds: int
#: Fraction of a second, in units of 1/65536, in the trailing 16 bits.
fraction: int
[docs]
class FastBindingAcknowledgmentStatus(IntEnum):
"""[FastBindingAcknowledgmentStatus] Fast Binding Acknowledgment Status Codes.
Status values of the fast binding acknowledgment (FBack) message, c.f.,
: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 two
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.
"""
#: 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]
@staticmethod
def get(key: 'int | str', default: 'int' = -1) -> 'FastBindingAcknowledgmentStatus':
"""Backport support for original codes.
Args:
key: Key to get enum item.
default: Default value if not found.
"""
if isinstance(key, int):
return FastBindingAcknowledgmentStatus(key)
if key not in FastBindingAcknowledgmentStatus._member_map_: # pylint: disable=no-member
extend_enum(FastBindingAcknowledgmentStatus, key, default)
return FastBindingAcknowledgmentStatus[key] # type: ignore[misc]
[docs]
@classmethod
def _missing_(cls, value: 'int') -> 'FastBindingAcknowledgmentStatus':
"""Lookup function used when value is not found.
Args:
value: Value to get enum item.
"""
if not (isinstance(value, int) and 0 <= value <= 255):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
#: Unassigned
return extend_enum(cls, 'Unassigned_%d' % value, value)
[docs]
class IPv6AddressPrefixCode(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, c.f.,
: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.
"""
#: 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]
@staticmethod
def get(key: 'int | str', default: 'int' = -1) -> 'IPv6AddressPrefixCode':
"""Backport support for original codes.
Args:
key: Key to get enum item.
default: Default value if not found.
"""
if isinstance(key, int):
return IPv6AddressPrefixCode(key)
if key not in IPv6AddressPrefixCode._member_map_: # pylint: disable=no-member
extend_enum(IPv6AddressPrefixCode, key, default)
return IPv6AddressPrefixCode[key] # type: ignore[misc]
[docs]
@classmethod
def _missing_(cls, value: 'int') -> 'IPv6AddressPrefixCode':
"""Lookup function used when value is not found.
Args:
value: Value to get enum item.
"""
if not (isinstance(value, int) and 0 <= value <= 255):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
#: Unassigned
return extend_enum(cls, 'Unassigned_%d' % value, value)
[docs]
class LocalizedRoutingStatus(IntEnum):
"""[LocalizedRoutingStatus] Localized Routing Acknowledgment Status Codes.
Status values of the localized routing acknowledgment (LRA) message, c.f.,
: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.
"""
#: 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]
@staticmethod
def get(key: 'int | str', default: 'int' = -1) -> 'LocalizedRoutingStatus':
"""Backport support for original codes.
Args:
key: Key to get enum item.
default: Default value if not found.
"""
if isinstance(key, int):
return LocalizedRoutingStatus(key)
if key not in LocalizedRoutingStatus._member_map_: # pylint: disable=no-member
extend_enum(LocalizedRoutingStatus, key, default)
return LocalizedRoutingStatus[key] # type: ignore[misc]
[docs]
@classmethod
def _missing_(cls, value: 'int') -> 'LocalizedRoutingStatus':
"""Lookup function used when value is not found.
Args:
value: Value to get enum item.
"""
if not (isinstance(value, int) and 0 <= value <= 255):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
#: Unassigned
return extend_enum(cls, 'Unassigned_%d' % value, value)
[docs]
class LMAAddressCode(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, c.f., :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`.
"""
#: 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]
@staticmethod
def get(key: 'int | str', default: 'int' = -1) -> 'LMAAddressCode':
"""Backport support for original codes.
Args:
key: Key to get enum item.
default: Default value if not found.
"""
if isinstance(key, int):
return LMAAddressCode(key)
if key not in LMAAddressCode._member_map_: # pylint: disable=no-member
extend_enum(LMAAddressCode, key, default)
return LMAAddressCode[key] # type: ignore[misc]
[docs]
@classmethod
def _missing_(cls, value: 'int') -> 'LMAAddressCode':
"""Lookup function used when value is not found.
Args:
value: Value to get enum item.
"""
if not (isinstance(value, int) and 0 <= value <= 255):
raise ValueError('%r is not a valid %s' % (value, cls.__name__))
#: Unassigned
return extend_enum(cls, 'Unassigned_%d' % value, value)
[docs]
class MH(Internet[Data_MH, Schema_MH],
schema=Schema_MH, data=Data_MH):
"""This class implements Mobility Header.
This class currently supports parsing of the following MH message types,
which are registered in the :attr:`self.__message__ <pcapkit.protocols.internet.mh.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.MH._read_msg_brr`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_brr`
* - :attr:`~pcapkit.const.mh.packet.Packet.Home_Test_Init`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_hoti`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_hoti`
* - :attr:`~pcapkit.const.mh.packet.Packet.Care_of_Test_Init`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_coti`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_coti`
* - :attr:`~pcapkit.const.mh.packet.Packet.Home_Test`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_hot`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_hot`
* - :attr:`~pcapkit.const.mh.packet.Packet.Care_of_Test`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_cot`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_cot`
* - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Update`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_bu`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_bu`
* - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Acknowledgement`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_ba`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_ba`
* - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Error`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_be`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_be`
* - :attr:`~pcapkit.const.mh.packet.Packet.Fast_Binding_Update`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_fbu`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_fbu`
* - :attr:`~pcapkit.const.mh.packet.Packet.Fast_Binding_Acknowledgment`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_fback`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_fback`
* - :attr:`~pcapkit.const.mh.packet.Packet.Fast_Neighbor_Advertisement`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_fna`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_fna`
* - :attr:`~pcapkit.const.mh.packet.Packet.Experimental_Mobility_Header`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_emh`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_emh`
* - :attr:`~pcapkit.const.mh.packet.Packet.Home_Agent_Switch_Message`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_has`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_has`
* - :attr:`~pcapkit.const.mh.packet.Packet.Heartbeat_Message`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_hb`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_hb`
* - :attr:`~pcapkit.const.mh.packet.Packet.Handover_Initiate_Message`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_hi`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_hi`
* - :attr:`~pcapkit.const.mh.packet.Packet.Handover_Acknowledge_Message`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_hack`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_hack`
* - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Revocation_Message`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_brm`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_brm`
* - :attr:`~pcapkit.const.mh.packet.Packet.Localized_Routing_Initiation`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_lri`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_lri`
* - :attr:`~pcapkit.const.mh.packet.Packet.Localized_Routing_Acknowledgment`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_lra`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_lra`
* - :attr:`~pcapkit.const.mh.packet.Packet.Update_Notification`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_upn`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_upn`
* - :attr:`~pcapkit.const.mh.packet.Packet.Update_Notification_Acknowledgement`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_upa`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_upa`
* - :attr:`~pcapkit.const.mh.packet.Packet.Flow_Binding_Message`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_fbm`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_fbm`
* - :attr:`~pcapkit.const.mh.packet.Packet.Subscription_Query`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_sq`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_sq`
* - :attr:`~pcapkit.const.mh.packet.Packet.Subscription_Response`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_msg_sr`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_msg_sr`
This class currently supports parsing the following MH options, which are
registered in the :attr:`self.__option__ <pcapkit.protocols.internet.mh.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.MH._read_opt_pad`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_pad`
* - :attr:`~pcapkit.const.mh.option.Option.PadN`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_pad`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_pad`
* - :attr:`~pcapkit.const.mh.option.Option.Binding_Refresh_Advice`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_bra`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_bra`
* - :attr:`~pcapkit.const.mh.option.Option.Alternate_Care_of_Address`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_aca`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_aca`
* - :attr:`~pcapkit.const.mh.option.Option.Nonce_Indices`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ni`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ni`
* - :attr:`~pcapkit.const.mh.option.Option.Authorization_Data`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_bad`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_bad`
* - :attr:`~pcapkit.const.mh.option.Option.Mobile_Network_Prefix_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mnp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mnp`
* - :attr:`~pcapkit.const.mh.option.Option.Mobility_Header_Link_Layer_Address_option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_lla`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_lla`
* - :attr:`~pcapkit.const.mh.option.Option.MN_ID_OPTION_TYPE`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mn_id`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mn_id`
* - :attr:`~pcapkit.const.mh.option.Option.AUTH_OPTION_TYPE`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_auth`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_auth`
* - :attr:`~pcapkit.const.mh.option.Option.MESG_ID_OPTION_TYPE`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mesg_id`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mesg_id`
* - :attr:`~pcapkit.const.mh.option.Option.CGA_Parameters_Request`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_cga_pr`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_cga_pr`
* - :attr:`~pcapkit.const.mh.option.Option.CGA_Parameters`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_cga_param`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_cga_param`
* - :attr:`~pcapkit.const.mh.option.Option.Signature`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_signature`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_signature`
* - :attr:`~pcapkit.const.mh.option.Option.Permanent_Home_Keygen_Token`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_phkt`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_phkt`
* - :attr:`~pcapkit.const.mh.option.Option.Care_of_Test_Init`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ct_init`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ct_init`
* - :attr:`~pcapkit.const.mh.option.Option.Care_of_Test`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ct`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ct`
* - :attr:`~pcapkit.const.mh.option.Option.DNS_UPDATE_TYPE`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_dns`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_dns`
* - :attr:`~pcapkit.const.mh.option.Option.Experimental_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_exp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_exp`
* - :attr:`~pcapkit.const.mh.option.Option.Vendor_Specific_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_vendor`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_vendor`
* - :attr:`~pcapkit.const.mh.option.Option.Service_Selection_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_service`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_service`
* - :attr:`~pcapkit.const.mh.option.Option.Binding_Authorization_Data_for_FMIPv6`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_badf`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_badf`
* - :attr:`~pcapkit.const.mh.option.Option.Home_Network_Prefix_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_hnp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_hnp`
* - :attr:`~pcapkit.const.mh.option.Option.Handoff_Indicator_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_hi`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_hi`
* - :attr:`~pcapkit.const.mh.option.Option.Access_Technology_Type_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_att`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_att`
* - :attr:`~pcapkit.const.mh.option.Option.Mobile_Node_Link_layer_Identifier_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mn_lli`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mn_lli`
* - :attr:`~pcapkit.const.mh.option.Option.Link_local_Address_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_lla_addr`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_lla_addr`
* - :attr:`~pcapkit.const.mh.option.Option.Timestamp_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_timestamp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_timestamp`
* - :attr:`~pcapkit.const.mh.option.Option.Restart_Counter`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_restart`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_restart`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Home_Address`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ipv4_hoa`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ipv4_hoa`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Address_Acknowledgement`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ipv4_ack`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ipv4_ack`
* - :attr:`~pcapkit.const.mh.option.Option.NAT_Detection`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_nat`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_nat`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Care_of_Address`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ipv4_coa`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ipv4_coa`
* - :attr:`~pcapkit.const.mh.option.Option.GRE_Key_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_gre`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_gre`
* - :attr:`~pcapkit.const.mh.option.Option.Mobility_Header_IPv6_Address_Prefix`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ipv6_ap`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ipv6_ap`
* - :attr:`~pcapkit.const.mh.option.Option.Binding_Identifier`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_bid`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_bid`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Home_Address_Request`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ipv4_hoa_req`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ipv4_hoa_req`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Home_Address_Reply`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ipv4_hoa_rep`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ipv4_hoa_rep`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Default_Router_Address`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ipv4_router`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ipv4_router`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_DHCP_Support_Mode`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ipv4_dhcp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ipv4_dhcp`
* - :attr:`~pcapkit.const.mh.option.Option.Context_Request_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_cr`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_cr`
* - :attr:`~pcapkit.const.mh.option.Option.Local_Mobility_Anchor_Address_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_lmaa`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_lmaa`
* - :attr:`~pcapkit.const.mh.option.Option.Mobile_Node_Link_local_Address_Interface_Identifier_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mn_lla_iid`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mn_lla_iid`
* - :attr:`~pcapkit.const.mh.option.Option.Transient_Binding`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_transient`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_transient`
* - :attr:`~pcapkit.const.mh.option.Option.Flow_Summary_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_fs`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_fs`
* - :attr:`~pcapkit.const.mh.option.Option.Flow_Identification_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_fid`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_fid`
* - :attr:`~pcapkit.const.mh.option.Option.Redirect_Capability_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_rc`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_rc`
* - :attr:`~pcapkit.const.mh.option.Option.Redirect_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_redirect`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_redirect`
* - :attr:`~pcapkit.const.mh.option.Option.Load_Information_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_load`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_load`
* - :attr:`~pcapkit.const.mh.option.Option.Alternate_IPv4_Care_of_Address`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_alt_ipv4_coa`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_alt_ipv4_coa`
* - :attr:`~pcapkit.const.mh.option.Option.Mobile_Node_Group_Identifier`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mn_group`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mn_group`
* - :attr:`~pcapkit.const.mh.option.Option.MAG_IPv6_Address`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mag_addr`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mag_addr`
* - :attr:`~pcapkit.const.mh.option.Option.Access_Network_Identifier`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ani`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ani`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Traffic_Offload_Selector`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_offload`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_offload`
* - :attr:`~pcapkit.const.mh.option.Option.Dynamic_IP_Multicast_Selector`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mcast_sel`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mcast_sel`
* - :attr:`~pcapkit.const.mh.option.Option.Delegated_Mobile_Network_Prefix`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_dmnp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_dmnp`
* - :attr:`~pcapkit.const.mh.option.Option.Active_Multicast_Subscription_IPv4`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ams4`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ams4`
* - :attr:`~pcapkit.const.mh.option.Option.Active_Multicast_Subscription_IPv6`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_ams6`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_ams6`
* - :attr:`~pcapkit.const.mh.option.Option.Quality_of_Service`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_qos`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_qos`
* - :attr:`~pcapkit.const.mh.option.Option.LMA_User_Plane_Address`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_lma_up`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_lma_up`
* - :attr:`~pcapkit.const.mh.option.Option.Multicast_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mcast`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mcast`
* - :attr:`~pcapkit.const.mh.option.Option.Multicast_Acknowledgement_Option`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mcast_ack`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mcast_ack`
* - :attr:`~pcapkit.const.mh.option.Option.LMA_Controlled_MAG_Parameters`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_lcmp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_lcmp`
* - :attr:`~pcapkit.const.mh.option.Option.MAG_Multipath_Binding`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mag_mp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mag_mp`
* - :attr:`~pcapkit.const.mh.option.Option.MAG_Identifier`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_mag_id`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_mag_id`
* - :attr:`~pcapkit.const.mh.option.Option.Anchored_Prefix`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_anchored`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_anchored`
* - :attr:`~pcapkit.const.mh.option.Option.Local_Prefix`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_local_prefix`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_local_prefix`
* - :attr:`~pcapkit.const.mh.option.Option.Previous_MAAR`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_prev_maar`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_prev_maar`
* - :attr:`~pcapkit.const.mh.option.Option.Serving_MAAR`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_serv_maar`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_serv_maar`
* - :attr:`~pcapkit.const.mh.option.Option.DLIF_Link_Local_Address`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_dlif_lla`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_dlif_lla`
* - :attr:`~pcapkit.const.mh.option.Option.DLIF_Link_Layer_Address`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_opt_dlif_lladdr`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_opt_dlif_lladdr`
This class currently supports parsing of the following MH CGA extensions,
which are registered in the :attr:`self.__extension__ <pcapkit.protocols.internet.mh.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.MH._read_ext_multiprefix`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_ext_multiprefix`
* - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Exp_FFFD`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_ext_exp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_ext_exp`
* - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Exp_FFFE`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_ext_exp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_ext_exp`
* - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Exp_FFFF`
- :meth:`~pcapkit.protocols.internet.mh.MH._read_ext_exp`
- :meth:`~pcapkit.protocols.internet.mh.MH._make_ext_exp`
"""
##########################################################################
# Defaults.
##########################################################################
#: DefaultDict[Enum_Packet, str | tuple[PacketParser, PacketConstructor]]:
#: Message type to method mapping. Method names are expected to be referred
#: to the class by ``_read_msg_${name}`` and/or ``_make_msg_${name}``,
#: and if such name not found, the value should then be a method that can
#: parse the message type by itself.
__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.Handover_Initiate_Message: 'hi',
Enum_Packet.Handover_Acknowledge_Message: 'hack',
Enum_Packet.Home_Agent_Switch_Message: 'has',
Enum_Packet.Heartbeat_Message: 'hb',
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]]
#: DefaultDict[Enum_Option, str | tuple[OptionParser, OptionConstructor]]:
#: Option type to method mapping. Method names are expected to be referred
#: to the class by ``_read_option_${name}`` and/or ``_make_opt_${name}``,
#: and if such name not found, the value should then be a method that can
#: parse the option by itself.
__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.Experimental_Mobility_Option: 'exp',
Enum_Option.Binding_Authorization_Data_for_FMIPv6: 'badf',
Enum_Option.Mobility_Header_IPv6_Address_Prefix: 'ipv6_ap',
Enum_Option.DNS_UPDATE_TYPE: 'dns',
Enum_Option.Vendor_Specific_Mobility_Option: 'vendor',
Enum_Option.Service_Selection_Mobility_Option: 'service',
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.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]]
#: DefaultDict[Enum_CGAExtension, str | tuple[ExtensionParser, ExtensionConstructor]]:
#: CGA extension type to method mapping. Method names are expected to be referred
#: to the class by ``_read_extension_${name}`` and/or ``_make_ext_${name}``,
#: and if such name not found, the value should then be a method that can
#: parse the CGA extension by itself.
__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 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.
"""
if length is None:
length = len(self)
schema = self.__header__
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:
return mh
return self._decode_next_layer(mh, schema.next, length - mh.length)
@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 back as the parsed message's own
``.length``, which :meth:`~pcapkit.protocols.internet.mh.MH.read`
then subtracts from the outer packet length to find the next
layer's length -- precisely the role ``Hdr Ext Len`` played in
#487, and the same read-side duplication :meth:`make`'s write-side
expression (``(len(data_val) + 6) // 8 - 1``, this formula's
inverse) had already been unified out of. 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
[docs]
def make(self,
next: 'Enum_TransType | StdlibEnum | AenumEnum | str | int' = Enum_TransType.UDP,
next_default: 'Optional[int]' = None,
next_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
next_reversed: 'bool' = False,
type: 'Enum_Packet | StdlibEnum | AenumEnum | str | int' = Enum_Packet.Binding_Refresh_Request,
type_default: 'Optional[int]' = None,
type_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
type_reversed: 'bool' = False,
chksum: 'bytes' = b'',
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.
next_default: Default value for next header type field.
next_namespace: Namespace of next header type field.
next_reversed: Whether the bits of next header type field is reversed.
type: Mobility Header type.
type_default: Default value for Mobility Header type field.
type_namespace: Namespace of Mobility Header type field.
type_reversed: Whether the bits of Mobility Header type field is reversed.
chksum: Checksum.
data: Message data.
payload: Payload of next layer protocol.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed packet data.
"""
next_val = self._make_index(next, next_default, namespace=next_namespace,
reversed=next_reversed, pack=False)
type_val = self._make_index(type, type_default, namespace=type_namespace,
reversed=type_reversed, pack=False)
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 format')
# NOTE: The header has to be a multiple of 8 octets, so the message data
# needs padding until ``len(data) + 6`` is aligned. Rounding ``length`` up
# without emitting that padding -- which is what ``math.ceil`` used to do
# here -- declares a header longer than the bytes that follow it, and the
# re-parse then reads whatever happens to be past the end of the buffer.
data_val = self._pad_mh_message(data_val)
return Schema_MH(
next=next_val,
length=(len(data_val) + 6) // 8 - 1,
type=type_val,
chksum=chksum,
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: Method name or callable to parse and/or construct the message.
"""
if code in cls.__message__:
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: Method name or callable to parse and/or construct the option.
"""
if code in cls.__option__:
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: Method name or callable to parse and/or construct the extension.
"""
if code in cls.__extension__:
warn(f'extension {code} already registered, overwriting', RegistryWarning)
cls.__extension__[code] = meth
##########################################################################
# 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 argument, 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[6]':
"""Return an estimated length for the object."""
return 6
[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,
'chksum': data.chksum,
'data': data,
'payload': cls._make_payload(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,
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,
chksum=header.chksum,
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,
chksum=header.chksum,
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,
chksum=header.chksum,
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,
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,
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 .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
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,
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']),
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 .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
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,
chksum=header.chksum,
status=schema.status,
key_mngt=bool(schema.flags['K']),
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,
chksum=header.chksum,
status=schema.status,
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,
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']),
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.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,
chksum=header.chksum,
status=FastBindingAcknowledgmentStatus(schema.status),
key_mngt=bool(schema.flags['K']),
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,
chksum=header.chksum,
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,
chksum=header.chksum,
data=schema.data,
)
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 four flags are registered in the IANA
*Handover Initiate Flags* registry, hence all four 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,
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']),
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``
and ``6``, are added by :rfc:`5949#section-6.1.2`. All three flags
are registered in the IANA *Handover Acknowledge Flags* registry,
hence all three 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,
chksum=header.chksum,
seq=schema.seq,
buffer=bool(schema.flags['U']),
proxy=bool(schema.flags['P']),
forward=bool(schema.flags['F']),
code=schema.code,
options=self._read_mh_options(schema.options),
)
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,
chksum=header.chksum,
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,
chksum=header.chksum,
unsolicited=bool(schema.flags['U']),
response=bool(schema.flags['R']),
seq=schema.seq,
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.data.internet.mh.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,
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']),
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,
chksum=header.chksum,
seq=schema.seq,
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.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,
chksum=header.chksum,
seq=schema.seq,
unsolicited=bool(schema.flags['U']),
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,
chksum=header.chksum,
seq=schema.seq,
reason=schema.reason,
ack=bool(schema.flags['A']),
retransmit=bool(schema.flags['D']),
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,
chksum=header.chksum,
seq=schema.seq,
status=schema.status,
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,
chksum=header.chksum,
fb_type=schema.fb_type,
seq=schema.seq,
code=schema.code,
ack=bool(schema.flags['A']),
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,
chksum=header.chksum,
seq=schema.seq,
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,
chksum=header.chksum,
seq=schema.seq,
info=bool(schema.flags['I']),
options=self._read_mh_options(schema.options),
)
return data
@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. This is the exact ``+2``/``-2``
mismatch #398 fixed independently in six places (see the ``Note:``
on :meth:`_read_opt_pad` below, which explains why a ``Pad1``
option -- the one option with no ``Option Length`` field at all --
is this helper's sole exception); collecting the read-side half of
it into one helper is so a future fix to this arithmetic only has
to happen once. Do NOT drop the ``+ 2``: that is precisely the
mismatch #398 fixed.
The ``+ 2`` is specific to an :rfc:`6275#section-6.2` mobility
option, whose Option Type and Option Length are one octet each. It
is **not** a universal contract for everything this class parses:
a CGA extension's Extension Type and Extension Data Length are two
octets each [:rfc:`4581#section-2`], so those readers use
:meth:`_mh_extension_length` instead. Reusing this helper for them
reported every parsed CGA extension two octets short (#512).
Note that only the *stored-length* read-side call sites are
collected here -- most ``_make_opt_*`` methods recompute the wire
``Option Length`` from ``len(value)`` rather than reading a parsed
``.length`` back, so they have nothing to unify against this helper.
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.
"""
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))
else:
meth = name[0]
data = meth(schema, options=options)
# record option data
options.add(type, data)
return options
[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.data.internet.mh.PadOption.length` is
``1`` rather than ``length + 2``. That one-octet wire shape is
enforced by
:class:`~pcapkit.protocols.schema.internet.mh.PadOption` itself,
which sizes both the length octet and the padding data from the
option type [:rfc:`6275#section-6.2.5`]; ``clen`` is therefore
always ``0`` here for a parsed ``Pad1``, and the check below only
guards a schema built by hand.
"""
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.PadN 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,
)
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')
prefix = cast('IPv6Network',
ipaddress.ip_network((schema.prefix, schema.prefix_length)))
data = Data_MobileNetworkPrefixOption(
type=schema.type,
length=self._mh_option_length(schema.length),
prefix=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.MH:
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=NTPTimestamp(schema.seconds, 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_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_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.data.internet.mh.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_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, c.f.,
:class:`~pcapkit.protocols.internet.mh.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_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']),
identity=schema.identity,
)
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_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')
if schema.prefix_length > 128:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_HomeNetworkPrefixOption(
type=schema.type,
length=self._mh_option_length(schema.length),
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),
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),
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),
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.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']
data = Data_TimestampOption(
type=schema.type,
length=self._mh_option_length(schema.length),
timestamp=datetime.datetime.fromtimestamp(
seconds + fraction / 65536, tz=datetime.timezone.utc),
pmip_timestamp=PMIPv6Timestamp(seconds, 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']),
)
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 more obvious trap of the three.
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'],
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']),
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),
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),
key=schema.key if schema.length == 6 else None,
)
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'],
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'],
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),
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 every other flag 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']),
)
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),
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.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),
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),
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']),
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_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**, never on ``isinstance`` of
the schema class, and that is load-bearing rather than a matter of
taste. Every schema class descends from
:class:`collections.abc.Mapping`, and on Python 3.10 and older they do
**not** each get their own ``_abc_impl`` -- they share
:class:`~pcapkit.protocols.schema.schema.Schema`'s. One
:class:`abc.ABCMeta` cache therefore serves the whole family, and it is
keyed only on the class being *tested*, not on the class being tested
*against*. So one ``issubclass`` answer poisons every later question
about the same class:
.. code-block:: python
>>> issubclass(ANIGeoLocationSuboption, Schema) # True, cached
True
>>> issubclass(ANIGeoLocationSuboption, ANINetworkIdentifierSuboption)
True # wrong -- the cached True for Schema is handed back
The wrong answer goes both ways: a correct ``False`` against a sibling
then makes ``isinstance(sub, Schema)`` ``False``, which is what
:meth:`ListField.pack
<pcapkit.corekit.fields.collections.ListField.pack>` consults, so
packing a perfectly good option fails with
:exc:`~pcapkit.utilities.exceptions.FieldValueError`. Python 3.11 and
newer give each class its own cache and the checks behave, which is why
this was invisible on a modern interpreter.
The code is on the wire and the registry keys on it, so it is both the
cheaper discriminator and the only one that cannot be poisoned. The
same reasoning applies to :meth:`_read_ani_suboptions`,
:meth:`_read_qos_attributes`, :meth:`_read_lcmp_suboptions` and their
four constructor counterparts.
Args:
suboptions_schema: Parsed sub-option schemas.
Returns:
Parsed sub-option data.
"""
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,
) # 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,
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),
action=action.action,
)
elif code == Enum_FlowIDSuboption.Target_Care_of_Address:
target = cast('Schema_TargetCareofAddressSuboption', schema)
data = Data_TargetCareofAddressSuboption(
type=code,
length=self._mh_option_length(target.length),
address=target.address,
)
else:
unknown = cast('Schema_UnassignedFlowIdentificationSuboption', schema)
data = Data_UnassignedFlowIdentificationSuboption(
type=code,
length=self._mh_option_length(unknown.length),
data=unknown.data,
)
suboptions.add(code, data)
return suboptions
[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 -- three 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,
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),
)
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,
)
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,
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),
address_length=schema.address_length,
address=schema.address,
)
return data
[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, 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.
"""
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']),
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._decode_signed(geo.location['latitude'], 24)
raw_lon = self._decode_signed(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,
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,
)
suboptions.add(code, data)
return suboptions
[docs]
@staticmethod
def _decode_signed(value: 'int', width: 'int') -> 'int':
"""Reinterpret an unsigned integer as a two's-complement signed one.
Args:
value: Unsigned value as read from the wire.
width: Field width, in bits.
Returns:
The signed value the same bits denote.
Note:
:class:`~pcapkit.corekit.fields.strings.BitField` reads a sub-field as
an unsigned integer, since that is what almost every bit-packed field
in the mobility header is. The geo-location degrees of
:rfc:`6757#section-3.1.2` are the exception, so they are converted
here rather than by teaching the field about signedness -- a change
that would touch every other user of it.
"""
sign_bit = 1 << (width - 1)
return value - (1 << width) if value & sign_bit else value
[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']),
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:`protocol` says which of
the two they are: ``143`` for MLDv2, ``131`` for MLDv1.
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']),
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,
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:`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:`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_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.
"""
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']),
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),
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),
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),
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),
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,
)
attributes.add(code, data)
return attributes
[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, so only the code point is carried into
the data model.
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_QualityOfServiceOption(
type=schema.type,
length=self._mh_option_length(schema.length),
sr_id=schema.sr_id,
dscp=schema.tc >> 2,
oc=schema.oc,
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),
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.data.internet.mh.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,
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_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.
"""
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,
)
suboptions.add(code, data)
return suboptions
[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,
)
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,
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),
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),
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),
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),
lla=schema.lla,
)
return data
@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` reported every
parsed CGA extension two octets short (#512): an 8-octet extension with
an ``Extension Data Length`` of ``4`` came back as ``6``. Note
:meth:`_make_cga_extensions` has always measured ``len(schema.pack())``
instead, so the write side was already right and only the read side
disagreed with the wire.
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.
"""
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)
# record extension data
extensions.add(type, data)
return extensions
[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']),
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, *,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_BindingRefreshRequestMessage':
"""Make MH binding refresh request (BRR) message type.
Args:
message: Message data model.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
options = message.options
else:
options = options or []
return Schema_BindingRefreshRequestMessage(
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, dict[str, Any]] | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_HomeTestInitMessage':
"""Make MH home test init (HoTI) message type.
Args:
message: Message data model.
cookie: Home test cookie.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
cookie = message.cookie
options = message.options
else:
options = options or []
return Schema_HomeTestInitMessage(
cookie=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, dict[str, Any]] | bytes]]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
cookie = message.cookie
options = message.options
else:
options = options or []
return Schema_CareofTestInitMessage(
cookie=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, dict[str, Any]] | bytes]]' = None,
**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.
"""
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=nonce_index,
cookie=cookie,
token=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, dict[str, Any]] | bytes]]' = None,
**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.
"""
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=nonce_index,
cookie=cookie,
token=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,
lifetime: 'int | timedelta' = 4, # reasonable default value
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**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.
lifetime: Lifetime in seconds or timedelta.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
seq = message.seq
ack = message.ack
home = message.home
lla_compat = message.lla_compat
key_mngt = message.key_mngt
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_BindingUpdateMessage(
seq=seq,
flags={
'A': ack,
'H': home,
'L': lla_compat,
'K': key_mngt,
},
lifetime=math.ceil(lifetime_val / 4),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_ba(self, message: 'Optional[Data_BindingAcknowledgementMessage]' = None, *,
status: 'Enum_StatusCode | StdlibEnum | AenumEnum | str | int' = Enum_StatusCode.Binding_Update_accepted_Proxy_Binding_Update_accepted,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
key_mngt: 'bool' = False,
seq: 'int' = 0,
lifetime: 'int | timedelta' = 4, # reasonable default value
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_BindingAcknowledgementMessage':
"""Make MH binding acknowledge (BA) message type.
Args:
message: Message data model.
status: Status code.
status_default: Default status code.
status_namespace: Status code namespace.
status_reversed: Reverse status code namespace.
key_mngt: Key management mobility option flag.
seq: Sequence number.
lifetime: Lifetime in seconds or timedelta.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
status_val = message.status
key_mngt = message.key_mngt
seq = message.seq
lifetime_val = math.ceil(message.lifetime.total_seconds())
options = message.options
else:
status_val = self._make_index(status, status_default, namespace=status_namespace, # type: ignore[assignment]
reversed=status_reversed, pack=False)
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds())
options = options or []
return Schema_BindingAcknowledgementMessage(
status=status_val,
flags={
'K': key_mngt,
},
seq=seq,
lifetime=math.ceil(lifetime_val / 4),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_be(self, message: 'Optional[Data_BindingErrorMessage]' = None, *,
status: 'Enum_StatusCode | StdlibEnum | AenumEnum | str | int' = Enum_StatusCode.Binding_Update_accepted_Proxy_Binding_Update_accepted,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
home: 'IPv6Address | int | str | bytes' = '::',
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_BindingErrorMessage':
"""Make MH binding error (BE) message type.
Args:
message: Message data model.
status: Status code.
status_default: Default status code.
status_namespace: Status code namespace.
status_reversed: Reverse status code namespace.
home: Home address.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
status_val = message.status
home = message.home
options = message.options
else:
status_val = self._make_index(status, status_default, namespace=status_namespace, # type: ignore[assignment]
reversed=status_reversed, pack=False)
options = options or []
return Schema_BindingErrorMessage(
status=status_val,
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, c.f., RFC 5568, section 6.2.2
home: 'bool' = True, # MUST be set, c.f., 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, dict[str, Any]] | bytes]]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
seq = message.seq
ack = message.ack
home = message.home
lla_compat = message.lla_compat
key_mngt = message.key_mngt
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=seq,
flags={
'A': ack,
'H': home,
'L': lla_compat,
'K': key_mngt,
},
lifetime=lifetime_val,
options=self._make_mh_options(options),
)
[docs]
def _make_msg_fback(self, message: 'Optional[Data_FastBindingAcknowledgmentMessage]' = None, *,
status: 'FastBindingAcknowledgmentStatus | StdlibEnum | AenumEnum | str | int' = FastBindingAcknowledgmentStatus.Fast_Binding_Update_accepted,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
key_mngt: 'bool' = False,
seq: 'int' = 0,
lifetime: 'int | timedelta' = 4, # reasonable default value
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_FastBindingAcknowledgmentMessage':
"""Make MH fast binding acknowledgment (FBack) message type.
Args:
message: Message data model.
status: Status code, c.f.,
:class:`~pcapkit.protocols.internet.mh.FastBindingAcknowledgmentStatus`.
status_default: Default status code.
status_namespace: Status code namespace.
status_reversed: Reverse status code namespace.
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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
status_val = message.status
key_mngt = message.key_mngt
seq = message.seq
lifetime_val = math.ceil(message.lifetime.total_seconds())
options = message.options
else:
status_val = self._make_index(status, status_default, namespace=status_namespace, # type: ignore[assignment]
reversed=status_reversed, pack=False)
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,
},
seq=seq,
lifetime=lifetime_val,
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, dict[str, Any]] | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_FastNeighborAdvertisementMessage':
"""Make MH fast neighbor advertisement (FNA) message type.
Args:
message: Message data model.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
options = message.options
else:
options = options or []
return Schema_FastNeighborAdvertisementMessage(
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, c.f., 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_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 | StdlibEnum | AenumEnum | str | int' = Enum_HandoverInitiateStatus.FBU_with_the_PCoA_as_source_IP_address,
code_default: 'Optional[int]' = None,
code_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
code_reversed: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**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.
code_default: Default code.
code_namespace: Code namespace.
code_reversed: Reverse code namespace.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
seq = message.seq
assign = message.assign
buffer = message.buffer
proxy = message.proxy
forward = message.forward
code_val = message.code
options = message.options
else:
code_val = self._make_index(code, code_default, namespace=code_namespace, # type: ignore[assignment]
reversed=code_reversed, pack=False)
options = options or []
return Schema_HandoverInitiateMessage(
seq=seq,
flags={
'S': assign,
'U': buffer,
'P': proxy,
'F': forward,
},
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 | StdlibEnum | AenumEnum | str | int' = Enum_HandoverACKStatus.Handover_Accepted_with_NCoA_valid,
code_default: 'Optional[int]' = None,
code_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
code_reversed: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**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.
code_default: Default code.
code_namespace: Code namespace.
code_reversed: Reverse code namespace.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
seq = message.seq
buffer = message.buffer
proxy = message.proxy
forward = message.forward
code_val = message.code
options = message.options
else:
code_val = self._make_index(code, code_default, namespace=code_namespace, # type: ignore[assignment]
reversed=code_reversed, pack=False)
options = options or []
return Schema_HandoverAcknowledgeMessage(
seq=seq,
flags={
'U': buffer,
'P': proxy,
'F': forward,
},
code=code_val,
options=self._make_mh_options(options),
)
[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, dict[str, Any]] | bytes]]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
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),
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, dict[str, Any]] | bytes]]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
unsolicited = message.unsolicited
response = message.response
seq = message.seq
options = message.options
else:
options = options or []
return Schema_HeartbeatMessage(
flags={
'U': unsolicited,
'R': response,
},
seq=seq,
options=self._make_mh_options(options),
)
[docs]
def _make_msg_brm(self, message: 'Optional[Data_BindingRevocationMessage]' = None, *,
br_type: 'Enum_BindingRevocation | StdlibEnum | AenumEnum | str | int' = Enum_BindingRevocation.Binding_Revocation_Indication,
br_type_default: 'Optional[int]' = None,
br_type_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
br_type_reversed: 'bool' = False,
code: 'Enum_RevocationTrigger | Enum_RevocationStatusCode | StdlibEnum | AenumEnum | str | int' = Enum_RevocationTrigger.Unspecified,
code_default: 'Optional[int]' = None,
code_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
code_reversed: 'bool' = False,
seq: 'int' = 0,
proxy: 'bool' = False,
ipv4_hoa: 'bool' = False,
global_revocation: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**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.
br_type_default: Default binding revocation type.
br_type_namespace: Binding revocation type namespace.
br_type_reversed: Reverse binding revocation type namespace.
code: Revocation trigger, for an indication, or acknowledgement
status, for an acknowledgement.
code_default: Default code.
code_namespace: Code namespace.
code_reversed: Reverse code namespace.
seq: Sequence number.
proxy: Proxy binding flag.
ipv4_hoa: IPv4 home address binding only flag.
global_revocation: Global revocation flag.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
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
options = message.options
else:
br_type_val = self._make_index(br_type, br_type_default, namespace=br_type_namespace, # type: ignore[assignment]
reversed=br_type_reversed, pack=False)
code_val = self._make_index(code, code_default, namespace=code_namespace, # type: ignore[assignment]
reversed=code_reversed, pack=False)
options = options or []
return Schema_BindingRevocationMessage(
br_type=br_type_val,
code=code_val,
seq=seq,
flags={
'P': proxy,
'V': ipv4_hoa,
'G': global_revocation,
},
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, dict[str, Any]] | bytes]]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
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=seq,
lifetime=lifetime_val,
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 | StdlibEnum | AenumEnum | str | int' = LocalizedRoutingStatus.Success,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
lifetime: 'int | timedelta' = 0,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**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, c.f.,
:class:`~pcapkit.protocols.internet.mh.LocalizedRoutingStatus`.
status_default: Default status.
status_namespace: Status namespace.
status_reversed: Reverse status namespace.
lifetime: Lifetime, in seconds or as a
:class:`~datetime.timedelta`.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
seq = message.seq
unsolicited = message.unsolicited
status_val = message.status # type: int
lifetime_val = math.ceil(message.lifetime.total_seconds())
options = message.options
else:
status_val = self._make_index(status, status_default, namespace=status_namespace,
reversed=status_reversed, pack=False)
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds())
options = options or []
return Schema_LocalizedRoutingAcknowledgmentMessage(
seq=seq,
flags={
'U': unsolicited,
},
status=status_val,
lifetime=lifetime_val,
options=self._make_mh_options(options),
)
[docs]
def _make_msg_upn(self, message: 'Optional[Data_UpdateNotificationMessage]' = None, *,
seq: 'int' = 0,
reason: 'Enum_UpdateNotificationReason | StdlibEnum | AenumEnum | str | int' = Enum_UpdateNotificationReason.FORCE_REREGISTRATION,
reason_default: 'Optional[int]' = None,
reason_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
reason_reversed: 'bool' = False,
ack: 'bool' = False,
retransmit: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_UpdateNotificationMessage':
"""Make MH update notification (UPN) message type.
Args:
message: Message data model.
seq: Sequence number.
reason: Notification reason.
reason_default: Default notification reason.
reason_namespace: Notification reason namespace.
reason_reversed: Reverse notification reason namespace.
ack: Acknowledgement requested flag.
retransmit: Retransmission flag.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
seq = message.seq
reason_val = message.reason # type: Enum_UpdateNotificationReason
ack = message.ack
retransmit = message.retransmit
options = message.options
else:
reason_val = self._make_index(reason, reason_default, namespace=reason_namespace, # type: ignore[assignment]
reversed=reason_reversed, pack=False)
options = options or []
return Schema_UpdateNotificationMessage(
seq=seq,
reason=reason_val,
flags={
'A': ack,
'D': retransmit,
},
options=self._make_mh_options(options),
)
[docs]
def _make_msg_upa(self, message: 'Optional[Data_UpdateNotificationAcknowledgementMessage]' = None, *,
seq: 'int' = 0,
status: 'Enum_UpdateNotificationACKStatus | StdlibEnum | AenumEnum | str | int' = Enum_UpdateNotificationACKStatus.SUCCESS,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_UpdateNotificationAcknowledgementMessage':
"""Make MH update notification acknowledgement (UPA) message type.
Args:
message: Message data model.
seq: Sequence number.
status: Status.
status_default: Default status.
status_namespace: Status namespace.
status_reversed: Reverse status namespace.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
seq = message.seq
status_val = message.status # type: Enum_UpdateNotificationACKStatus
options = message.options
else:
status_val = self._make_index(status, status_default, namespace=status_namespace, # type: ignore[assignment]
reversed=status_reversed, pack=False)
options = options or []
return Schema_UpdateNotificationAcknowledgementMessage(
seq=seq,
status=status_val,
options=self._make_mh_options(options),
)
[docs]
def _make_msg_fbm(self, message: 'Optional[Data_FlowBindingMessage]' = None, *,
fb_type: 'Enum_FlowBindingType | StdlibEnum | AenumEnum | str | int' = Enum_FlowBindingType.Indication,
fb_type_default: 'Optional[int]' = None,
fb_type_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
fb_type_reversed: 'bool' = False,
seq: 'int' = 0,
code: 'Enum_FlowBindingIndicationTrigger | Enum_FlowBindingACKStatus | StdlibEnum | AenumEnum | str | int' = Enum_FlowBindingIndicationTrigger.Unspecified,
code_default: 'Optional[int]' = None,
code_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
code_reversed: 'bool' = False,
ack: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]]' = None,
**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.
fb_type_default: Default flow binding type.
fb_type_namespace: Flow binding type namespace.
fb_type_reversed: Reverse flow binding type namespace.
seq: Sequence number.
code: Indication trigger, for an indication, or acknowledgement
status, for an acknowledgement.
code_default: Default code.
code_namespace: Code namespace.
code_reversed: Reverse code namespace.
ack: Acknowledgement requested flag. Meaningful only in an
indication.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
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
options = message.options
else:
fb_type_val = self._make_index(fb_type, fb_type_default, namespace=fb_type_namespace, # type: ignore[assignment]
reversed=fb_type_reversed, pack=False)
code_val = self._make_index(code, code_default, namespace=code_namespace, # type: ignore[assignment]
reversed=code_reversed, pack=False)
options = options or []
return Schema_FlowBindingMessage(
fb_type=fb_type_val,
seq=seq,
code=code_val,
flags={
'A': ack,
},
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, dict[str, Any]] | bytes]]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
seq = message.seq
options = message.options
else:
options = options or []
return Schema_SubscriptionQueryMessage(
seq=seq,
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, dict[str, Any]] | bytes]]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
seq = message.seq
info = message.info
options = message.options
else:
options = options or []
return Schema_SubscriptionResponseMessage(
seq=seq,
flags={
'I': info,
},
options=self._make_mh_options(options),
)
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.schema.internet.mh.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 or two octets are padded with ``Pad1`` options instead, that
being the only form which can occupy a single octet
[:rfc:`6275#section-6.2.5`]; two octets need two separate ``Pad1``
schemas, since a schema instance is mutable and must not be shared
between two entries of the option list.
"""
if offset % 8 == 0:
return [], 0
pad_len = 8 - (offset % 8)
if pad_len <= 2:
return [self._make_opt_pad(Enum_Option.Pad1, length=0) # type: ignore[arg-type]
for _ in range(pad_len)], pad_len
return [self._make_opt_pad(Enum_Option.PadN, length=pad_len - 2)], pad_len # type: ignore[arg-type]
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.2.5` puts it. A message whose body is opaque has
nowhere to put it --
:class:`~pcapkit.protocols.schema.internet.mh.UnknownMessage` and
:class:`~pcapkit.protocols.schema.internet.mh.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 emitted 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 comes first so that the type checker can
# narrow ``data`` for the assignment below; at runtime ``getattr`` has
# already covered the :obj:`bytes` case 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 rather than mutating the list in place is
# what marks the schema as updated, so that ``len()`` and ``pack()`` take
# the padding into account.
data.options = list(options) + pad_opts
return data
[docs]
def _make_mh_options(self, options: 'Option | list[Schema_Option | tuple[Enum_Option, dict[str, Any]] | bytes]') -> 'list[Schema_Option | bytes]':
"""Make options for MH.
Args:
options: MH options.
Returns:
Mobility options list.
"""
if isinstance(options, list):
options_list = [] # type: list[Schema_Option | bytes]
for schema in options:
if isinstance(schema, bytes):
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:
code, args = cast('tuple[Enum_Option, dict[str, Any]]', schema)
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 code, option in options.items(multi=True):
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_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.
"""
if option is not None:
data = option.data
return Schema_UnassignedOption(
type=type,
length=len(data),
data=data,
)
[docs]
def _make_opt_pad(self, type: 'Enum_Option', option: 'Optional[Data_PadOption]' = None, *,
length: 'int' = 0,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Note:
:attr:`Data_PadOption.length
<pcapkit.protocols.data.internet.mh.PadOption.length>` counts the
*whole* option, whereas :attr:`Schema_PadOption.length
<pcapkit.protocols.schema.internet.mh.PadOption.length>` is the
``Option Length`` field -- two octets fewer, and absent altogether
for a ``Pad1``. Copying one into the other unconverted is why
re-making a parsed ``PadN`` used to come back two octets too long.
"""
if option is not None:
length = 0 if option.type == Enum_Option.Pad1 else option.length - 2
if type == Enum_Option.Pad1 and length != 0:
# raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid format')
warn(f'{self.alias}: [OptNo {type}] invalid format', ProtocolWarning)
type = Enum_Option.PadN # type: ignore[assignment]
if type == Enum_Option.PadN and length == 0:
# raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid format')
warn(f'{self.alias}: [OptNo {type}] invalid format', ProtocolWarning)
type = Enum_Option.Pad1 # type: ignore[assignment]
return Schema_PadOption(
type=type,
length=length,
)
[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.
"""
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=interval_val,
)
[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.
"""
if option is not None:
home = option.home
careof = option.careof
return Schema_NonceIndicesOption(
type=type,
length=4,
home=home,
careof=careof,
)
[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.
"""
if option is not None:
data = option.data
if len(data) % 8 != 0:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid format')
return Schema_AuthorizationDataOption(
type=type,
length=len(data),
data=data,
)
[docs]
def _make_opt_mnp(self, type: 'Enum_Option', option: 'Optional[Data_MobileNetworkPrefixOption]' = None, *,
prefix: 'bytes | str | IPv6Network' = '::/0',
**kwargs: 'Any') -> 'Schema_MobileNetworkPrefixOption':
"""Make MH mobile network prefix option.
Args:
type: Option type.
option: Option data model.
prefix: Mobile network prefix.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
prefix = option.prefix
prefix_val = ipaddress.ip_network(prefix)
if prefix_val.version != 6:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid movile network prefix: {prefix!r}')
prefix_length = prefix_val.prefixlen
prefix_addr = prefix_val.network_address
return Schema_MobileNetworkPrefixOption(
type=type,
length=18,
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.
"""
if option is not None:
address = option.lla
return Schema_LinkLayerAddressOption(
type=type,
length=len(address) + 1,
code=Enum_LLACode.MH, # type: ignore[arg-type]
lla=address,
)
[docs]
def _make_opt_mn_id(self, type: 'Enum_Option', option: 'Optional[Data_MNIDOption]' = None, *,
subtype: 'Enum_MNIDSubtype | StdlibEnum | AenumEnum | str | int' = Enum_MNIDSubtype.IPv6_Address,
subtype_default: 'Optional[int]' = None,
subtype_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
subtype_reversed: 'bool' = False,
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.
subtype_default: MN-ID subtype default value.
subtype_namespace: MN-ID subtype namespace.
subtype_reversed: MN-ID subtype reversed flag.
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 the other six
subtypes -- all numeric identifiers (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 (c.f. #467, #468).
**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 by two different paths below, to
``::1`` for ``IPv6_Address`` and to a one-octet identifier for
the other six, neither of which a caller passing a flag can
plausibly have meant; pass ``int(...)`` to get the numeric
value (c.f. #469). 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 other six -- an :obj:`int`
is converted rather than rejected there, per #468 -- or anything
:class:`ipaddress.IPv6Address` itself does not accept for
``IPv6_Address`` (c.f. #469).
"""
if option is not None:
subtype_val = option.subtype
identifier = option.identifier
else:
subtype_val = self._make_index(subtype, subtype_default, namespace=subtype_namespace, # type: ignore[assignment]
reversed=subtype_reversed, pack=False)
if isinstance(identifier, int) and identifier < 0:
# NOTE: checked before the subtype dispatch below, not inside it,
# because *no* subtype has a wire form for a negative identifier and
# each one fails differently on its own: ``int.to_bytes`` raises
# ``OverflowError`` and ``ipaddress.IPv6Address`` an
# ``AddressValueError`` -- itself a bare :exc:`ValueError`, which is
# exactly the class of leak this handler exists to stop, and which a
# guard living inside the ``elif isinstance(identifier, int)`` branch
# could not catch, since the ``IPv6_Address`` dispatch never reaches
# it (c.f. #467, #468).
try:
# ``Enum_MNIDSubtype(subtype_val)`` round-trips a plain int back
# into a named member for the message below -- but its own
# ``_missing_`` only auto-extends 9-15 and 16-255, so 0,
# negatives and anything above 255 make the constructor itself
# raise a bare ``ValueError``, which would defeat the point of
# this guard (c.f. #468 review). Caught here and the raw value
# used instead rather than let it propagate.
subtype_repr = repr(Enum_MNIDSubtype(subtype_val))
except ValueError:
subtype_repr = repr(subtype_val)
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID subtype {subtype_repr} '
f'identifier must be a non-negative int, not {identifier!r}')
if isinstance(identifier, bool):
# NOTE: checked before the subtype dispatch, because ``bool`` is an
# ``int`` subclass and so would otherwise be converted by *two*
# different paths below -- ``IPv6Address(1)``, that is ``::1``, for
# ``IPv6_Address``, and a one-octet identifier for the six
# ``BytesField`` subtypes. An MN-ID of ``True`` is a caller mistake
# in every case rather than a value anyone means, so it is refused
# before either path can give it a plausible-looking wire form. A
# caller who genuinely wants the integer should pass ``int(flag)``
# (c.f. #469 review).
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: The wire format is chosen by ``subtype_val`` (c.f. ``mn_id_selector``),
# not by the Python type of ``identifier``, so the width has to be taken from
# the former. For the ``IPv6_Address`` subtype the schema always packs a fixed
# 16-octet address (:class:`~pcapkit.corekit.fields.ipaddress.IPv6AddressField`
# ignores any declared length), so ``identifier`` is normalised to that wire
# form here as well, keeping the packed bytes and the declared length derived
# from one value instead of two independent computations (c.f. #448).
if subtype_val == Enum_MNIDSubtype.IPv6_Address:
if isinstance(identifier, int) and identifier >= 1 << 128:
# NOTE: the upper-bound mirror of the negative-int guard above, and
# it belongs here rather than up there because this bound is
# subtype-*dependent*: ``2**140`` is a perfectly good identifier for
# the six ``BytesField`` subtypes -- it simply packs into more
# octets -- and only ``IPv6_Address`` caps at 128 bits. Left
# unguarded, :class:`ipaddress.IPv6Address` raises
# ``AddressValueError``, itself a bare :exc:`ValueError`, so this
# handler would otherwise ship with its lower bound guarded and its
# upper bound leaking (c.f. #467, #468). Checked explicitly rather
# than by wrapping the construction below, because that would also
# swallow the wrong-*type* ``AddressValueError`` -- a ``str`` or
# ``None`` reaching here -- which is #469's subject, not this one's.
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: catches ipaddress.AddressValueError -- itself a bare
# ValueError -- for every identifier ipaddress.IPv6Address
# cannot turn into an address: bytes of the wrong length, a
# str that is not an IPv6 literal, or a type it does not
# accept at all (float, None, list, dict, bytearray,
# memoryview -- ipaddress.IPv6Address only ever dispatches on
# bytes, int or str). The ``try`` wraps only this call, not
# the whole branch, so it cannot swallow the ProtocolError
# raised above for an out-of-range int, which is also a
# ValueError subclass (c.f. #467, #468, #469).
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: NAI's field is a StringField (c.f. mn_id_selector), so an
# int has to become text -- and unlike the numeric subtypes below,
# there is no non-arbitrary way to do that. str(identifier) packs
# and round-trips fine, but an NAI is a network access identifier
# ('user@realm', RFC 4283), and a bare decimal-digit string is not
# one: it is mechanically valid and semantically nonsense, exactly
# the "silently accepting a value that cannot pack" #467 removed,
# just relocated to "silently accepting a value that packs into
# the wrong thing". Rejected instead, with the explicit spelling
# a caller who really wants a decimal-digit NAI can use.
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(length=pkt['length'] - 1) (c.f. mn_id_selector) -- a
# numeric identifier, so unlike NAI there IS a non-arbitrary wire
# form: its own minimal big-endian encoding. That is self-consistent
# with the declared length by construction and round-trips exactly.
# ``id_len = math.ceil(identifier.bit_length() / 8)`` was the right
# width all along -- the pre-#467 defect was never the sizing, it
# was that ``identifier`` itself stayed an ``int`` afterwards and
# was handed to ``BytesField`` unconverted, which ``struct.pack()``
# cannot do anything with. #468 initially rejected outright instead
# of noticing that; converting is what this revision does (c.f.
# #467, #468). ``bit_length()`` is 0 for 0 itself, which would
# otherwise declare a zero-octet identifier -- collapsing "the
# identifier's value is 0" into "there is no identifier" -- so the
# width is floored at one octet, matching what any reasonable
# encoder would produce.
id_len = max(1, math.ceil(identifier.bit_length() / 8))
identifier = identifier.to_bytes(id_len, 'big')
elif subtype_val == Enum_MNIDSubtype.NAI:
# NOTE: NAI's field is a StringField (c.f. mn_id_selector), which
# calls ``identifier.encode(...)`` to pack -- so anything but a
# genuine str leaks a bare stdlib exception: AttributeError for
# bytes/list/dict (no ``.encode``), TypeError for float/None/an
# ipaddress.IPv6Address (no ``__len__`` either, so this branch's
# own ``len()`` call below would be the one to raise). Guarded
# here, before ``len()``, rather than relying on whichever of
# those two happens to fire first (c.f. #469). Deliberately not
# decoding a ``bytes`` identifier here: an NAI that happens to be
# ASCII-encodable is still the caller handing over the wrong
# representation, the same "accepts a value that means the wrong
# thing" #467 removed for int, just relocated to bytes.
if not isinstance(identifier, str):
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID subtype NAI '
f'identifier must be str, not {identifier!r}')
id_len = len(identifier)
else:
# NOTE: every other subtype's field is a
# BytesField(length=pkt['length'] - 1) (c.f. mn_id_selector),
# which hands ``identifier`` to ``struct.pack('Ns', ...)``
# unconverted -- so anything but bytes leaks a bare stdlib
# exception: struct.error for str/list/dict (struct's ``s``
# format demands a bytes object), TypeError for float/None/an
# ipaddress.IPv6Address (no ``__len__``, so this branch's own
# ``len()`` call below would raise instead). Guarded here,
# before ``len()``, for the same reason as the NAI branch above
# (c.f. #469).
#
# bytearray is accepted alongside bytes -- unlike every other
# wrong type here, it already round-trips correctly through
# ``struct.pack('Ns', ...)`` (measured), so rejecting it would
# be a gratuitous behaviour change to a type nothing here is
# actually broken for. memoryview looks equally bytes-like but
# does *not* survive struct's ``s`` format (measured: same
# ``struct.error`` as str/list/dict), so it is rejected with
# everything else rather than let through to leak anyway.
if not isinstance(identifier, (bytes, bytearray)):
try:
subtype_repr = repr(Enum_MNIDSubtype(subtype_val))
except ValueError:
subtype_repr = repr(subtype_val)
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID subtype {subtype_repr} '
f'identifier must be bytes, not {identifier!r}')
id_len = len(identifier)
return Schema_MNIDOption(
type=type,
length=1 + id_len,
subtype=subtype_val,
identifier=identifier,
)
[docs]
def _make_opt_auth(self, type: 'Enum_Option', option: 'Optional[Data_AuthOption]' = None, *,
subtype: 'Enum_AuthSubtype | StdlibEnum | AenumEnum | str | int' = Enum_AuthSubtype.MN_HA,
subtype_default: 'Optional[int]' = None,
subtype_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
subtype_reversed: 'bool' = False,
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.
subtype_default: Authentication subtype default value.
subtype_namespace: Authentication subtype namespace.
subtype_reversed: Authentication subtype reversed flag.
spi: Security parameter index.
data: Authentication data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
subtype_val = option.subtype
spi = option.spi
data = option.data
else:
subtype_val = self._make_index(subtype, subtype_default, namespace=subtype_namespace, # type: ignore[assignment]
reversed=subtype_reversed, pack=False)
if (len(data) + 6) % 4 != 0:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid format')
return Schema_AuthOption(
type=type,
length=5 + len(data),
subtype=subtype_val,
spi=spi,
data=data,
)
[docs]
def _make_opt_mesg_id(self, type: 'Enum_Option', option: 'Optional[Data_MesgIDOption]' = None, *,
timestamp: 'Optional[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, c.f., :rfc:`1305`.
interval: Timestamp interval (since UNIX-epoch).
**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 = interval.timestamp()
ts_sec = math.floor(int_ts)
ts_frc = math.ceil(((int_ts - ts_sec) * 1_000_000)) * 2**32
timestamp = 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 | dict[str, Any] | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_CGAParametersOption':
"""Make MH CGA paramters option.
Args:
type: Option type.
option: Option data model.
parameters: CGA parameters.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
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):
if not hasattr(data, 'public_key_test'):
data.public_key_test = {'len': max(len(data.public_key) - 2, 0)}
_, ext_len = self._make_cga_extensions(data.extensions)
length += 25 + len(data.public_key) + ext_len
param.append(data)
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: {data}')
return Schema_CGAParametersOption(
type=type,
length=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.
"""
if option is not None:
signature = option.signature
return Schema_SignatureOption(
type=type,
length=len(signature),
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.
"""
if option is not None:
token = option.token
return Schema_PermanentHomeKeygenTokenOption(
type=type,
length=len(token),
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.
"""
if option is not None:
token = option.token
return Schema_CareofTestOption(
type=type,
length=8,
token=token,
)
[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.
"""
if option is not None:
data = option.data
return Schema_ExperimentalMobilityOption(
type=type,
length=len(data),
data=data,
)
[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.
Note:
The ``length`` field counts the authenticator only, excluding the
SPI, c.f., :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 format')
return Schema_BADFOption(
type=type,
length=len(data),
spi=spi,
data=data,
)
[docs]
def _make_opt_ipv6_ap(self, type: 'Enum_Option', option: 'Optional[Data_IPv6AddressPrefixOption]' = None, *,
code: 'IPv6AddressPrefixCode | StdlibEnum | AenumEnum | str | int' = IPv6AddressPrefixCode.New_Care_of_Address,
code_default: 'Optional[int]' = None,
code_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
code_reversed: 'bool' = False,
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, c.f.,
:class:`~pcapkit.protocols.internet.mh.IPv6AddressPrefixCode`.
code_default: Default option code.
code_namespace: Option code namespace.
code_reversed: Reverse option code namespace.
prefix_length: Prefix length.
address: IPv6 address/prefix.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
code_val = option.code
prefix_length = option.prefix_length
address = option.address
else:
code_val = self._make_index(code, code_default, namespace=code_namespace, # type: ignore[assignment]
reversed=code_reversed, pack=False)
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_dns(self, type: 'Enum_Option', option: 'Optional[Data_DNSUpdateOption]' = None, *,
status: 'Enum_DNSStatusCode | StdlibEnum | AenumEnum | str | int' = Enum_DNSStatusCode.DNS_update_performed,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
remove: 'bool' = False,
identity: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_DNSUpdateOption':
"""Make MH DNS-UPDATE-TYPE option.
Args:
type: Option type.
option: Option data model.
status: Status.
status_default: Default status.
status_namespace: Status namespace.
status_reversed: Reverse status namespace.
remove: Remove flag.
identity: Mobile node identity, in FQDN form.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
status_val = option.status # type: Enum_DNSStatusCode
remove = option.remove
identity = option.identity
else:
status_val = self._make_index(status, status_default, namespace=status_namespace, # type: ignore[assignment]
reversed=status_reversed, pack=False)
return Schema_DNSUpdateOption(
type=type,
length=2 + len(identity),
status=status_val,
flags={'R': int(remove)},
identity=identity,
)
[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.
"""
if option is not None:
vendor = option.vendor
subtype = option.subtype
data = option.data
return Schema_VendorSpecificOption(
type=type,
length=5 + len(data),
vendor=vendor,
subtype=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.
"""
if option is not None:
identifier = option.identifier
encoded = identifier.encode()
if not encoded:
raise ProtocolError(f'{self.alias}: [OptNo {type}] service identifier must not be empty')
return Schema_ServiceSelectionOption(
type=type,
length=len(encoded),
identifier=identifier,
)
[docs]
def _make_opt_hnp(self, type: 'Enum_Option', option: 'Optional[Data_HomeNetworkPrefixOption]' = None, *,
prefix_length: 'int' = 64,
prefix: 'bytes | str | int | IPv6Address' = '::',
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
prefix_length = option.prefix_length
prefix = option.prefix
if prefix_length > 128:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_HomeNetworkPrefixOption(
type=type,
length=18,
prefix_length=prefix_length,
prefix=prefix,
)
[docs]
def _make_opt_hi(self, type: 'Enum_Option', option: 'Optional[Data_HandoffIndicatorOption]' = None, *,
hi: 'Enum_HandoffType | StdlibEnum | AenumEnum | str | int' = Enum_HandoffType.Attachment_over_a_new_interface,
hi_default: 'Optional[int]' = None,
hi_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
hi_reversed: 'bool' = False,
**kwargs: 'Any') -> 'Schema_HandoffIndicatorOption':
"""Make MH handoff indicator option.
Args:
type: Option type.
option: Option data model.
hi: Handoff indicator.
hi_default: Default handoff indicator.
hi_namespace: Handoff indicator namespace.
hi_reversed: Reverse handoff indicator namespace.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
hi_val = option.hi # type: Enum_HandoffType
else:
hi_val = self._make_index(hi, hi_default, namespace=hi_namespace, # type: ignore[assignment]
reversed=hi_reversed, pack=False)
return Schema_HandoffIndicatorOption(
type=type,
length=2,
hi=hi_val,
)
[docs]
def _make_opt_att(self, type: 'Enum_Option', option: 'Optional[Data_AccessTechnologyTypeOption]' = None, *,
att: 'Enum_AccessType | StdlibEnum | AenumEnum | str | int' = Enum_AccessType.Virtual,
att_default: 'Optional[int]' = None,
att_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
att_reversed: 'bool' = False,
**kwargs: 'Any') -> 'Schema_AccessTechnologyTypeOption':
"""Make MH access technology type option.
Args:
type: Option type.
option: Option data model.
att: Access technology type.
att_default: Default access technology type.
att_namespace: Access technology type namespace.
att_reversed: Reverse access technology type namespace.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
att_val = option.att # type: Enum_AccessType
else:
att_val = self._make_index(att, att_default, namespace=att_namespace, # type: ignore[assignment]
reversed=att_reversed, pack=False)
return Schema_AccessTechnologyTypeOption(
type=type,
length=2,
att=att_val,
)
[docs]
def _make_opt_mn_lli(self, type: 'Enum_Option', option: 'Optional[Data_MNLLIdentifierOption]' = None, *,
lli: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_MNLLIdentifierOption':
"""Make MH mobile node link-layer identifier option.
Args:
type: Option type.
option: Option data model.
lli: Link-layer identifier.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
lli = option.lli
return Schema_MNLLIdentifierOption(
type=type,
length=2 + len(lli),
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.
Note:
The fixed-point pair is taken from
:attr:`~pcapkit.protocols.data.internet.mh.TimestampOption.pmip_timestamp`
rather than recomputed from
:attr:`~pcapkit.protocols.data.internet.mh.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, fraction = option.pmip_timestamp
if not 0 <= seconds < 2 ** 48:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid timestamp seconds: {seconds}')
if not 0 <= fraction < 2 ** 16:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid timestamp fraction: {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.
"""
if option is not None:
counter = option.counter
return Schema_RestartCounterOption(
type=type,
length=4,
counter=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',
request_prefix: 'bool' = False,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
prefix_length = option.prefix_length
address = option.address
request_prefix = option.request_prefix
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),
},
address=address,
)
[docs]
def _make_opt_ipv4_ack(self, type: 'Enum_Option',
option: 'Optional[Data_IPv4AddressAcknowledgementOption]' = None, *,
status: 'Enum_DSMIPv6HomeAddress | StdlibEnum | AenumEnum | str | int' = Enum_DSMIPv6HomeAddress.Success,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
prefix_length: 'int' = 32,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0',
**kwargs: 'Any') -> 'Schema_IPv4AddressAcknowledgementOption':
"""Make MH IPv4 address acknowledgement option.
Args:
type: Option type.
option: Option data model.
status: Status.
status_default: Default status.
status_namespace: Status namespace.
status_reversed: Reverse status namespace.
prefix_length: Allocated prefix length.
address: Assigned IPv4 home address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
status_val = option.status # type: Enum_DSMIPv6HomeAddress
prefix_length = option.prefix_length
address = option.address
else:
status_val = self._make_index(status, status_default, namespace=status_namespace, # type: ignore[assignment]
reversed=status_reversed, pack=False)
if prefix_length > 32:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_IPv4AddressAcknowledgementOption(
type=type,
length=6,
status=status_val,
flags={'prefix_length': prefix_length},
address=address,
)
[docs]
def _make_opt_nat(self, type: 'Enum_Option', option: 'Optional[Data_NATDetectionOption]' = None, *,
force: 'bool' = False,
refresh: 'int | timedelta' = 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`.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
force = option.force
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)},
refresh=refresh_val,
)
[docs]
def _make_opt_ipv4_coa(self, type: 'Enum_Option', option: 'Optional[Data_IPv4CareofAddressOption]' = None, *,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0',
**kwargs: 'Any') -> 'Schema_IPv4CareofAddressOption':
"""Make MH IPv4 care-of address option.
Args:
type: Option type.
option: Option data model.
address: IPv4 care-of address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
address = option.address
return Schema_IPv4CareofAddressOption(
type=type,
length=6,
address=address,
)
[docs]
def _make_opt_gre(self, type: 'Enum_Option', option: 'Optional[Data_GREKeyOption]' = None, *,
key: 'Optional[int]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
key = option.key
return Schema_GREKeyOption(
type=type,
length=2 if key is None else 6,
key=key,
)
[docs]
def _make_opt_bid(self, type: 'Enum_Option', option: 'Optional[Data_BindingIdentifierOption]' = None, *,
bid: 'int' = 1,
status: 'Enum_StatusCode | StdlibEnum | AenumEnum | str | int' = Enum_StatusCode.Binding_Update_accepted_Proxy_Binding_Update_accepted,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
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.
status_default: Default status.
status_namespace: Status namespace.
status_reversed: Reverse status namespace.
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.
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
else:
status_val = self._make_index(status, status_default, namespace=status_namespace, # type: ignore[assignment]
reversed=status_reversed, pack=False)
if bid_pri > 0x7F:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid binding priority: {bid_pri}')
if address is None:
length = 4
else:
# NOTE: Converted through ``parse_ip_address`` rather than with
# ``ipaddress.ip_address``, because the conversion happens *here* --
# the option length below is derived from the family, so it cannot
# wait for the schema -- and a bare conversion therefore turns a
# ``bool`` into a perfectly ordinary ``IPv4Address`` that the
# schema's own guard can no longer tell from a real address. Before
# this, ``address=True`` packed as ``23080001000000000001``, i.e. a
# care-of address of ``0.0.0.1`` (c.f. #508).
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',
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
prefix_length = option.prefix_length
address = option.address
if prefix_length > 32:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_IPv4HomeAddressRequestOption(
type=type,
length=6,
flags={'prefix_length': prefix_length},
address=address,
)
[docs]
def _make_opt_ipv4_hoa_rep(self, type: 'Enum_Option', option: 'Optional[Data_IPv4HomeAddressReplyOption]' = None, *,
status: 'Enum_HomeAddressReply | StdlibEnum | AenumEnum | str | int' = Enum_HomeAddressReply.Success,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
prefix_length: 'int' = 32,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0',
**kwargs: 'Any') -> 'Schema_IPv4HomeAddressReplyOption':
"""Make MH IPv4 home address reply option.
Args:
type: Option type.
option: Option data model.
status: Status.
status_default: Default status.
status_namespace: Status namespace.
status_reversed: Reverse status namespace.
prefix_length: Prefix length of the assigned home network.
address: Assigned IPv4 home address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
status_val = option.status # type: Enum_HomeAddressReply
prefix_length = option.prefix_length
address = option.address
else:
status_val = self._make_index(status, status_default, namespace=status_namespace, # type: ignore[assignment]
reversed=status_reversed, pack=False)
if prefix_length > 32:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_IPv4HomeAddressReplyOption(
type=type,
length=6,
status=status_val,
flags={'prefix_length': prefix_length},
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',
**kwargs: 'Any') -> 'Schema_IPv4DefaultRouterAddressOption':
"""Make MH IPv4 default-router address option.
Args:
type: Option type.
option: Option data model.
address: IPv4 default-router address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
address = option.address
return Schema_IPv4DefaultRouterAddressOption(
type=type,
length=6,
address=address,
)
[docs]
def _make_opt_ipv4_dhcp(self, type: 'Enum_Option', option: 'Optional[Data_IPv4DHCPSupportModeOption]' = None, *,
mode: 'Enum_DHCPSupportMode | StdlibEnum | AenumEnum | str | int' = Enum_DHCPSupportMode.Unassigned_0x0,
mode_default: 'Optional[int]' = None,
mode_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
mode_reversed: 'bool' = False,
**kwargs: 'Any') -> 'Schema_IPv4DHCPSupportModeOption':
"""Make MH IPv4 DHCP support mode option.
Args:
type: Option type.
option: Option data model.
mode: DHCP support mode.
mode_default: Default DHCP support mode.
mode_namespace: DHCP support mode namespace.
mode_reversed: Reverse DHCP support mode namespace.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
mode_val = int(option.mode)
else:
mode_val = self._make_index(mode, mode_default, namespace=mode_namespace,
reversed=mode_reversed, pack=False)
if mode_val > 1:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid DHCP support mode: {mode_val}')
return Schema_IPv4DHCPSupportModeOption(
type=type,
length=2,
flags={'S': mode_val},
)
[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,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
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(raw_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=2 + len(buffer),
requests=bytes(buffer),
)
[docs]
def _make_opt_lmaa(self, type: 'Enum_Option', option: 'Optional[Data_LMAAddressOption]' = None, *,
code: 'LMAAddressCode | StdlibEnum | AenumEnum | str | int' = LMAAddressCode.IPv6_LMAA,
code_default: 'Optional[int]' = None,
code_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
code_reversed: 'bool' = False,
address: 'bytes | str | int | IPv4Address | IPv6Address' = '::',
**kwargs: 'Any') -> 'Schema_LMAAddressOption':
"""Make MH local mobility anchor address option.
Args:
type: Option type.
option: Option data model.
code: Option code, c.f.,
:class:`~pcapkit.protocols.internet.mh.LMAAddressCode`.
code_default: Default option code.
code_namespace: Option code namespace.
code_reversed: Reverse option code namespace.
address: Local mobility anchor address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Note:
The option length is derived from the address family rather than from
:attr:`code`, so that the two cannot be emitted disagreeing.
"""
if option is not None:
code_val = int(option.code)
address = option.address
else:
code_val = self._make_index(code, code_default, namespace=code_namespace,
reversed=code_reversed, pack=False)
if isinstance(address, bytes) and len(address) not in (4, 16):
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid address: {address!r}')
# NOTE: The address is normalised rather than passed through, so that the
# schema attribute holds the same type it would after a parse. The width is
# then taken from the address itself rather than from ``code``, so that the
# two cannot be emitted disagreeing. Normalising *here*, ahead of the
# schema, is also why the conversion goes through ``parse_ip_address``:
# ``ipaddress.ip_address(True)`` is ``0.0.0.1``, and the schema's own
# guard cannot see that it was ever a ``bool``. Before this,
# ``address=True`` packed as ``2906010000000001`` (c.f. #508).
addr = parse_ip_address(
address, f'{self.alias}: [OptNo {type}] invalid address')
return Schema_LMAAddressOption(
type=type,
length=6 if addr.version == 4 else 18,
code=code_val,
address=addr,
)
[docs]
def _make_opt_mn_lla_iid(self, type: 'Enum_Option', option: 'Optional[Data_MNLLAIIDOption]' = None, *,
iid: 'bytes' = b'\x00' * 8,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
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,
iid=iid,
)
[docs]
def _make_opt_transient(self, type: 'Enum_Option', option: 'Optional[Data_TransientBindingOption]' = None, *,
late: 'bool' = False,
lifetime: 'int | timedelta' = 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`.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
late = option.late
lifetime_val = math.ceil(option.lifetime.total_seconds() * 10)
else:
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(
lifetime.total_seconds() * 10)
if lifetime_val > 0xFF:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid lifetime: {lifetime_val}')
return Schema_TransientBindingOption(
type=type,
length=2,
flags={'L': int(late)},
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.
"""
if option is not None:
fid = list(option.fid)
else:
fid = fid or []
return Schema_FlowSummaryOption(
type=type,
length=len(fid) * 2,
fid=fid,
)
[docs]
def _make_fid_suboptions(
self,
suboptions: 'FlowIDSuboption | list[Schema_FlowIdentificationSuboption | tuple[Enum_FlowIDSuboption, dict[str, Any]] | bytes]',
) -> '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.
"""
if isinstance(suboptions, list):
entries = [] # type: list[Schema_FlowIdentificationSuboption | bytes]
for item in suboptions:
if isinstance(item, (bytes, Schema)):
entries.append(cast('Schema_FlowIdentificationSuboption | bytes', item))
else:
code, args = cast('tuple[Enum_FlowIDSuboption, dict[str, Any]]', item)
entries.append(self._make_fid_suboption(code, **args))
return entries
return [self._make_fid_suboption(code, option)
for code, 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.
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
naming it ``data`` made it **shadow a field**. The traffic selector and
unassigned sub-options both carry a field of their own called ``data``,
so a caller's ``data=`` bound to the model parameter instead of landing
in ``**kwargs``, and the ``kwargs.get('data')`` fallback below could
never see it: the payload was silently dropped and the length written as
though it were empty. No exception, just a lost field.
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))
return Schema_PadFlowIdentificationSuboption(type=code, length=pad_len)
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 [])
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
else:
ts_format = cast('Enum_TrafficSelector | int',
kwargs.get('ts_format',
Enum_TrafficSelector.IPv6_Binary_Traffic_Selector))
selector = cast('bytes', kwargs.get('selector', b''))
return Schema_TrafficSelectorSuboption(
type=code, length=2 + len(selector),
ts_format=cast('Enum_TrafficSelector', ts_format), selector=selector)
if code == Enum_FlowIDSuboption.Flow_Binding_Action:
if option is not None:
action = cast('Data_FlowBindingActionSuboption',
option).action # type: Enum_FlowBindingAction | int
else:
action = cast('Enum_FlowBindingAction | int',
kwargs.get('action', Enum_FlowBindingAction.Add))
return Schema_FlowBindingActionSuboption(
type=code, length=2, 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
else:
address = kwargs.get('address', '::')
# NOTE: Through ``parse_ip_address`` because the sub-option length
# below is derived from the family here, ahead of the schema, so a
# bare ``ipaddress.ip_address`` would launder a ``bool`` past the
# schema's guard. Before this, ``address=True`` packed as
# ``0506000000000001`` (c.f. #508).
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, 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_opt_fid(self, type: 'Enum_Option', option: 'Optional[Data_FlowIdentificationOption]' = None, *,
fid: 'int' = 1,
fid_pri: 'int' = 1,
status: 'Enum_FlowIDStatus | StdlibEnum | AenumEnum | str | int' = Enum_FlowIDStatus.Flow_binding_successful,
status_default: 'Optional[int]' = None,
status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
status_reversed: 'bool' = False,
suboptions: 'Optional[FlowIDSuboption | list[Schema_FlowIdentificationSuboption | tuple[Enum_FlowIDSuboption, dict[str, Any]] | bytes]]' = None,
**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.
status_default: Default status.
status_namespace: Status namespace.
status_reversed: Reverse status namespace.
suboptions: Sub-options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
fid = option.fid
fid_pri = option.fid_pri
status_val = option.status # type: Enum_FlowIDStatus
suboptions = option.suboptions
else:
status_val = self._make_index(status, status_default, namespace=status_namespace, # type: ignore[assignment]
reversed=status_reversed, pack=False)
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=length,
fid=fid,
fid_pri=fid_pri,
status=status_val,
suboptions=entries,
)
[docs]
def _make_opt_rc(self, type: 'Enum_Option', option: 'Optional[Data_RedirectCapabilityOption]' = None,
**kwargs: 'Any') -> 'Schema_RedirectCapabilityOption':
"""Make MH redirect-capability mobility option.
Args:
type: Option type.
option: Option data model.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
return Schema_RedirectCapabilityOption(
type=type,
length=2,
)
[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,
**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.
**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.
"""
if option is not None:
ipv6 = option.ipv6
ipv4 = option.ipv4
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),
},
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.
"""
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=priority,
sessions_in_use=sessions_in_use,
max_sessions=max_sessions,
used_capacity=used_capacity,
max_capacity=max_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',
**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 | StdlibEnum | AenumEnum | str | int' = Enum_MNGroupID.Bulk_Binding_Update_Group,
subtype_default: 'Optional[int]' = None,
subtype_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
subtype_reversed: 'bool' = False,
group_id: 'int' = 1,
**kwargs: 'Any') -> 'Schema_MNGroupIdentifierOption':
"""Make MH mobile node group identifier option.
Args:
type: Option type.
option: Option data model.
subtype: Sub-type.
subtype_default: Default sub-type.
subtype_namespace: Sub-type namespace.
subtype_reversed: Reverse sub-type namespace.
group_id: Mobile node group identifier.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
subtype_val = option.subtype # type: Enum_MNGroupID
group_id = option.group_id
else:
subtype_val = self._make_index(subtype, subtype_default, namespace=subtype_namespace, # type: ignore[assignment]
reversed=subtype_reversed, pack=False)
return Schema_MNGroupIdentifierOption(
type=type,
length=6,
subtype=subtype_val,
group_id=group_id,
)
[docs]
def _make_opt_mag_addr(self, type: 'Enum_Option', option: 'Optional[Data_MAGIPv6AddressOption]' = None, *,
address_length: 'int' = 128,
address: 'bytes | str | int | IPv6Address' = '::',
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
address_length = option.address_length
address = option.address
return Schema_MAGIPv6AddressOption(
type=type,
length=18,
address_length=address_length,
address=address,
)
[docs]
def _make_ani_suboptions(
self,
suboptions: 'ANISuboption | list[Schema_ANISuboption | tuple[Enum_ANISuboption, 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.
"""
if isinstance(suboptions, list):
entries = [] # type: list[Schema_ANISuboption | bytes]
for item in suboptions:
if isinstance(item, (bytes, Schema)):
entries.append(cast('Schema_ANISuboption | bytes', item))
else:
code, args = cast('tuple[Enum_ANISuboption, dict[str, Any]]', item)
entries.append(self._make_ani_suboption(code, **args))
return entries
return [self._make_ani_suboption(code, option)
for code, 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.
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.
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`, where it did. 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
else:
utf8 = cast('bool', kwargs.get('utf8', False))
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)}, 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': raw_lat & 0xFFFFFF,
'longitude': raw_lon & 0xFFFFFF,
})
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 = cast('Enum_OperatorID | int',
kwargs.get('op_id_type', Enum_OperatorID.Realm_of_the_Operator))
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
else:
fmt = cast('int', kwargs.get('format', 0))
location = cast('bytes', kwargs.get('location', b''))
return Schema_ANICivicLocationSuboption(
type=code, length=2 + len(location), format=fmt, 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=group_id)
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=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_opt_ani(self, type: 'Enum_Option', option: 'Optional[Data_AccessNetworkIdentifierOption]' = None, *,
suboptions: 'Optional[ANISuboption | list[Schema_ANISuboption | tuple[Enum_ANISuboption, dict[str, Any]] | bytes]]' = None,
**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.
"""
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=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, dict[str, Any]] | bytes]]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
mode = option.mode
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=length,
flags={'M': int(mode)},
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'',
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
protocol = option.protocol
mode = option.mode
records = option.records
data = option.data
return Schema_DynamicIPMulticastSelectorOption(
type=type,
length=4 + len(data),
protocol=protocol,
flags={'M': int(mode)},
records=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' = '::',
**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.
**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.
"""
if option is not None:
prefix_length = option.prefix_length
prefix = option.prefix
# NOTE: Through ``parse_ip_address`` because the ``V`` flag and the width
# are both derived from the family here, ahead of the schema, so a bare
# ``ipaddress.ip_address`` would launder a ``bool`` past the schema's
# guard. Before this, ``prefix=True`` with an IPv4-valid
# ``prefix_length`` packed as ``3706801800000001``; the default
# ``prefix_length=64`` masked it behind the range check below, which is
# why #508's own sweep read this site as already guarded (c.f. #508).
addr = parse_ip_address(
prefix, f'{self.alias}: [OptNo {type}] invalid mobile network prefix')
ipv4 = addr.version == 4
if prefix_length > (32 if ipv4 else 128):
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_DelegatedMNPOption(
type=type,
length=6 if ipv4 else 18,
flags={'V': int(ipv4)},
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.
"""
if option is not None:
igmp_type = option.igmp_type
context = option.context
return Schema_ActiveMulticastSubscriptionIPv4Option(
type=type,
length=1 + len(context),
igmp_type=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.
"""
if option is not None:
mld_type = option.mld_type
context = option.context
return Schema_ActiveMulticastSubscriptionIPv6Option(
type=type,
length=1 + len(context),
mld_type=mld_type,
context=context,
)
[docs]
def _make_qos_attributes(
self,
attributes: 'QoSAttribute | list[Schema_QoSAttribute | tuple[Enum_QoSAttribute, 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.
"""
if isinstance(attributes, list):
entries = [] # type: list[Schema_QoSAttribute | bytes]
for item in attributes:
if isinstance(item, (bytes, Schema)):
entries.append(cast('Schema_QoSAttribute | bytes', item))
else:
code, args = cast('tuple[Enum_QoSAttribute, dict[str, Any]]', item)
entries.append(self._make_qos_attribute(code, **args))
return entries
return [self._make_qos_attribute(code, option)
for code, 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.
Note:
The data model parameter is named ``option`` rather than ``data``, and
that is not cosmetic. The vendor-specific attribute of
:rfc:`7222#section-4.2.11` has a field of its own called ``data``, so
with the parameter named ``data`` a caller's ``data=`` bound to the
model parameter instead of reaching ``**kwargs`` -- and the
``kwargs.get('data')`` fallback then always saw nothing. Building the
attribute the natural way, mirroring the data model's own field names,
silently dropped the vendor payload and wrote the length as though it
were empty. ``vendor`` and ``subtype`` survived because those names do
not collide, which made the loss 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
else:
service = cast('bool', kwargs.get('service', False))
exclude = cast('bool', kwargs.get('exclude', False))
rate = cast('int', kwargs.get('rate', 0))
return Schema_PerSessionBitRateAttribute(
type=code, length=6,
flags={'S': int(service), 'E': int(exclude)}, rate=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
else:
rate = cast('int', kwargs.get('rate', 0))
return Schema_BitRateAttribute(type=code, length=6, rate=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
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))
return Schema_AllocationRetentionPriorityAttribute(
type=code, length=2,
priority={'PL': level, 'PC': capability, 'PV': 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
else:
ts_format = cast('Enum_TrafficSelector | int',
kwargs.get('ts_format',
Enum_TrafficSelector.IPv6_Binary_Traffic_Selector))
selector = cast('bytes', kwargs.get('selector', b''))
return Schema_QoSTrafficSelectorAttribute(
type=code, length=2 + len(selector),
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)
else:
vendor = cast('int', kwargs.get('vendor', 0))
subtype = cast('int', kwargs.get('subtype', 0))
payload = cast('bytes', kwargs.get('data', b''))
return Schema_QoSVendorSpecificAttribute(
type=code, length=7 + len(payload), vendor=vendor, subtype=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_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, dict[str, Any]] | bytes]]' = None,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
sr_id = option.sr_id
dscp = option.dscp
oc = option.oc
attributes = option.attributes
else:
attributes = attributes or []
if dscp > 0x3F:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid DSCP: {dscp}')
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=length,
sr_id=sr_id,
tc=dscp << 2,
oc=oc,
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,
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
address = option.address
if address is None:
return Schema_LMAUserPlaneAddressOption(type=type, length=2, address=b'')
# NOTE: Through ``parse_ip_address`` because the option length below is
# derived from the family here, ahead of the schema, so a bare
# ``ipaddress.ip_address`` would launder a ``bool`` past the schema's
# guard. Before this, ``address=True`` packed as ``3b06000000000001``.
# ``None`` is handled above and stays an absent address, which is a
# legitimate value here and not what is being rejected (c.f. #508).
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,
address=addr,
)
[docs]
def _make_opt_mcast(self, type: 'Enum_Option', option: 'Optional[Data_MulticastMobilityOption]' = None, *,
code: 'int' = 2,
data: 'bytes' = b'',
**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.
**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.
"""
if option is not None:
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=len(data) // 4,
code=code,
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.
"""
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=len(data) // 4,
code=code,
status=status,
data=data,
)
[docs]
def _make_lcmp_suboptions(
self,
suboptions: 'LMAControlledMAGSuboption | list[Schema_LMAControlledMAGSuboption | tuple[Enum_LMAControlledMAGSuboption, dict[str, Any]] | bytes]',
) -> '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.
"""
if isinstance(suboptions, list):
entries = [] # type: list[Schema_LMAControlledMAGSuboption | bytes]
for item in suboptions:
if isinstance(item, (bytes, Schema)):
entries.append(cast('Schema_LMAControlledMAGSuboption | bytes', item))
else:
code, args = cast(
'tuple[Enum_LMAControlledMAGSuboption, dict[str, Any]]', item)
entries.append(self._make_lcmp_suboption(code, **args))
return entries
return [self._make_lcmp_suboption(code, option)
for code, 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.
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`, where it did. 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=start,
initial_retransmission=initial, max_retransmission=maximum)
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=interval,
retransmission_delay=delay, max_retransmissions=count)
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_opt_lcmp(self, type: 'Enum_Option', option: 'Optional[Data_LMAControlledMAGParametersOption]' = None, *,
suboptions: 'Optional[LMAControlledMAGSuboption | list[Schema_LMAControlledMAGSuboption | tuple[Enum_LMAControlledMAGSuboption, dict[str, Any]] | bytes]]' = None,
**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.
"""
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=length,
suboptions=entries,
)
[docs]
def _make_opt_mag_mp(self, type: 'Enum_Option', option: 'Optional[Data_MAGMultipathBindingOption]' = None, *,
att: 'Enum_AccessType | StdlibEnum | AenumEnum | str | int' = Enum_AccessType.Virtual,
att_default: 'Optional[int]' = None,
att_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
att_reversed: 'bool' = False,
label: 'int' = 0,
bid: 'int' = 1,
bulk: 'bool' = False,
overwrite: 'bool' = False,
**kwargs: 'Any') -> 'Schema_MAGMultipathBindingOption':
"""Make MH MAG multipath binding option.
Args:
type: Option type.
option: Option data model.
att: Interface access-technology type.
att_default: Default access-technology type.
att_namespace: Access-technology type namespace.
att_reversed: Reverse access-technology type namespace.
label: Interface label.
bid: Binding identifier; ``0`` and ``255`` are reserved.
bulk: Bulk re-registration flag.
overwrite: Registration overwrite flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If both flags are set, which
:rfc:`8278#section-4.1` forbids.
"""
if option is not None:
att_val = option.att # type: Enum_AccessType
label = option.label
bid = option.bid
bulk = option.bulk
overwrite = option.overwrite
else:
att_val = self._make_index(att, att_default, namespace=att_namespace, # type: ignore[assignment]
reversed=att_reversed, pack=False)
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=label,
bid=bid,
flags={
'B': int(bulk),
'O': int(overwrite),
},
)
[docs]
def _make_opt_mag_id(self, type: 'Enum_Option', option: 'Optional[Data_MAGIdentifierOption]' = None, *,
subtype: 'Enum_MNIDSubtype | StdlibEnum | AenumEnum | str | int' = Enum_MNIDSubtype.NAI,
subtype_default: 'Optional[int]' = None,
subtype_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long
subtype_reversed: 'bool' = False,
identifier: 'bytes' = b'',
**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.
subtype_default: Default sub-type.
subtype_namespace: Sub-type namespace.
subtype_reversed: Reverse sub-type namespace.
identifier: Identifier.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
subtype_val = option.subtype # type: Enum_MNIDSubtype
identifier = option.identifier
else:
subtype_val = self._make_index(subtype, subtype_default, namespace=subtype_namespace, # type: ignore[assignment]
reversed=subtype_reversed, pack=False)
return Schema_MAGIdentifierOption(
type=type,
length=2 + len(identifier),
subtype=subtype_val,
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' = '::',
**kwargs: 'Any') -> 'Schema_AnchoredPrefixOption':
"""Make MH anchored prefix option.
Args:
type: Option type.
option: Option data model.
prefix_length: Prefix length.
prefix: Anchored prefix.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
prefix_length = option.prefix_length
prefix = option.prefix
if prefix_length > 128:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_AnchoredPrefixOption(
type=type,
length=18,
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' = '::',
**kwargs: 'Any') -> 'Schema_LocalPrefixOption':
"""Make MH local prefix option.
Args:
type: Option type.
option: Option data model.
prefix_length: Prefix length.
prefix: Local prefix.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
prefix_length = option.prefix_length
prefix = option.prefix
if prefix_length > 128:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_LocalPrefixOption(
type=type,
length=18,
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' = '::',
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
prefix_length = option.prefix_length
maar = option.maar
prefix = option.prefix
if prefix_length > 128:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_PreviousMAAROption(
type=type,
length=34,
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'',
**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.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
lla = option.lla
return Schema_DLIFLinkLayerAddressOption(
type=type,
length=2 + len(lla),
lla=lla,
)
[docs]
def _make_cga_extensions(self, extensions: 'Extension | list[Schema_CGAExtension | tuple[Enum_CGAExtension, dict[str, Any]] | bytes]') -> 'tuple[list[Schema_CGAExtension | bytes], int]':
"""Make CGA extensions for MH.
Args:
extensions: CGA extensions.
Returns:
Tuple of extensions and total length of extensions.
"""
total_length = 0
if isinstance(extensions, list):
extensions_list = [] # type: list[Schema_CGAExtension | bytes]
for schema in extensions:
if isinstance(schema, bytes):
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:
code, args = cast('tuple[Enum_CGAExtension, dict[str, Any]]', schema)
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 code, extension in extensions.items(multi=True):
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
[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,
**kwargs: 'Any') -> 'Schema_MultiPrefixExtension':
"""Make CGA multi-prefix extension.
Args:
type: Extension type.
option: Extension data model.
flag: Public key flag.
prefixes: Prefixes.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed extension schema.
"""
if option is not None:
flag = option.flag
# NOTE: ``list()`` rather than a cast: the data model stores the
# prefixes as a :obj:`tuple`, which
# :class:`~pcapkit.corekit.fields.collections.ListField` refuses to
# pack -- it raises ``ProtocolUnbound: unsupported type <class
# 'tuple'>``. The cast this replaced was a no-op at runtime, so
# re-making a parsed Multi-Prefix extension could not work at all.
prefixes = list(option.prefixes)
else:
prefixes = prefixes or []
return Schema_MultiPrefixExtension(
type=type,
# NOTE: The extension data is the 4-octet flags word followed by one
# **8**-octet prefix apiece, since
# :attr:`~pcapkit.protocols.schema.internet.mh.MultiPrefixExtension.prefixes`
# is a list of :class:`~pcapkit.corekit.fields.numbers.UInt64Field`.
# This used to read ``1 + len(prefixes) * 16``, which declared 33
# octets where 20 were emitted for two prefixes, so a re-parse ran off
# the end of the extension.
length=4 + len(prefixes) * 8,
flags={
'P': int(flag),
},
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,
)