# -*- coding: utf-8 -*-
# pylint: disable=fixme
"""MH - Mobility Header
==========================
.. module:: pcapkit.protocols.internet.mh.protocol
:mod:`pcapkit.protocols.internet.mh.protocol` contains
:class:`~pcapkit.protocols.internet.mh.protocol.MH`,
which implements the extractor for Mobility Header
(MH) [*]_, whose structure is described as below:
======= ========= ================== ===============================
Octets Bits Name Description
======= ========= ================== ===============================
0 0 ``mh.next`` Next Header
1 8 ``mh.length`` Header Length
2 16 ``mh.type`` Mobility Header Type
3 24 ``mh.reserved`` Reserved
4 32 ``mh.chksum`` Checksum
6 48 ``mh.data`` Message Data
======= ========= ================== ===============================
.. [*] https://en.wikipedia.org/wiki/Mobile_IP#Changes_in_IPv6_for_Mobile_IPv6
.. versionremoved:: 1.5.0
``pcapkit.protocols.internet.mh.NTPTimestamp``, with no alias; use
:class:`pcapkit.protocols.internet.mh.data.NTPTimestamp` instead
(:issue:`1886`).
"""
import collections
import datetime
import ipaddress
import math
from typing import TYPE_CHECKING, cast, overload
from aenum import IntEnum
from pcapkit.const.mh.access_type import AccessType as Enum_AccessType
from pcapkit.const.mh.ack_status_code import ACKStatusCode as Enum_ACKStatusCode
from pcapkit.const.mh.ani_suboption import ANISuboption as Enum_ANISuboption
from pcapkit.const.mh.auth_subtype import AuthSubtype as Enum_AuthSubtype
from pcapkit.const.mh.binding_ack_flag import BindingACKFlag as Enum_BindingACKFlag
from pcapkit.const.mh.binding_error import BindingError as Enum_BindingError
from pcapkit.const.mh.binding_revocation import BindingRevocation as Enum_BindingRevocation
from pcapkit.const.mh.binding_update_flag import BindingUpdateFlag as Enum_BindingUpdateFlag
from pcapkit.const.mh.cga_extension import CGAExtension as Enum_CGAExtension
from pcapkit.const.mh.cga_type import CGAType as Enum_CGAType
from pcapkit.const.mh.dhcp_support_mode import DHCPSupportMode as Enum_DHCPSupportMode
from pcapkit.const.mh.dns_status_code import DNSStatusCode as Enum_DNSStatusCode
from pcapkit.const.mh.dsmip6_tls_packet import DSMIP6TLSPacket as Enum_DSMIP6TLSPacket
from pcapkit.const.mh.dsmipv6_home_address import DSMIPv6HomeAddress as Enum_DSMIPv6HomeAddress
from pcapkit.const.mh.enumerating_algorithm import EnumeratingAlgorithm as Enum_EnumeratingAlgorithm
from pcapkit.const.mh.fb_ack_status import FlowBindingACKStatus as Enum_FlowBindingACKStatus
from pcapkit.const.mh.fb_action import FlowBindingAction as Enum_FlowBindingAction
from pcapkit.const.mh.fb_indication_trigger import \
FlowBindingIndicationTrigger as Enum_FlowBindingIndicationTrigger
from pcapkit.const.mh.fb_type import FlowBindingType as Enum_FlowBindingType
from pcapkit.const.mh.flow_id_status import FlowIDStatus as Enum_FlowIDStatus
from pcapkit.const.mh.flow_id_suboption import FlowIDSuboption as Enum_FlowIDSuboption
from pcapkit.const.mh.handoff_type import HandoffType as Enum_HandoffType
from pcapkit.const.mh.handover_ack_flag import HandoverACKFlag as Enum_HandoverACKFlag
from pcapkit.const.mh.handover_ack_status import HandoverACKStatus as Enum_HandoverACKStatus
from pcapkit.const.mh.handover_initiate_flag import \
HandoverInitiateFlag as Enum_HandoverInitiateFlag
from pcapkit.const.mh.handover_initiate_status import \
HandoverInitiateStatus as Enum_HandoverInitiateStatus
from pcapkit.const.mh.home_address_reply import HomeAddressReply as Enum_HomeAddressReply
from pcapkit.const.mh.lla_code import LLACode as Enum_LLACode
from pcapkit.const.mh.lma_mag_suboption import \
LMAControlledMAGSuboption as Enum_LMAControlledMAGSuboption
from pcapkit.const.mh.mn_group_id import MNGroupID as Enum_MNGroupID
from pcapkit.const.mh.mn_id_subtype import MNIDSubtype as Enum_MNIDSubtype
from pcapkit.const.mh.operator_id import OperatorID as Enum_OperatorID
from pcapkit.const.mh.option import Option as Enum_Option
from pcapkit.const.mh.packet import Packet as Enum_Packet
from pcapkit.const.mh.qos_attribute import QoSAttribute as Enum_QoSAttribute
from pcapkit.const.mh.revocation_status_code import \
RevocationStatusCode as Enum_RevocationStatusCode
from pcapkit.const.mh.revocation_trigger import RevocationTrigger as Enum_RevocationTrigger
from pcapkit.const.mh.status_code import StatusCode as Enum_StatusCode
from pcapkit.const.mh.traffic_selector import TrafficSelector as Enum_TrafficSelector
from pcapkit.const.mh.upa_status import \
UpdateNotificationACKStatus as Enum_UpdateNotificationACKStatus
from pcapkit.const.mh.upn_reason import UpdateNotificationReason as Enum_UpdateNotificationReason
from pcapkit.const.reg.transtype import TransType as Enum_TransType
from pcapkit.corekit.enum import EnumLookup
from pcapkit.corekit.multidict import OrderedMultiDict
from pcapkit.corekit.schema.ipaddress import parse_ip_address
from pcapkit.corekit.schema.padding import pad_length
from pcapkit.protocols.internet.ipv6_ext.protocol import IPv6_Ext
from pcapkit.protocols.internet.mh.data import MH as Data_MH
from pcapkit.protocols.internet.mh.data import \
AccessNetworkIdentifierOption as Data_AccessNetworkIdentifierOption
from pcapkit.protocols.internet.mh.data import \
AccessTechnologyTypeOption as Data_AccessTechnologyTypeOption
from pcapkit.protocols.internet.mh.data import \
ActiveMulticastSubscriptionIPv4Option as Data_ActiveMulticastSubscriptionIPv4Option
from pcapkit.protocols.internet.mh.data import \
ActiveMulticastSubscriptionIPv6Option as Data_ActiveMulticastSubscriptionIPv6Option
from pcapkit.protocols.internet.mh.data import \
AllocationRetentionPriorityAttribute as Data_AllocationRetentionPriorityAttribute
from pcapkit.protocols.internet.mh.data import \
AlternateCareofAddressOption as Data_AlternateCareofAddressOption
from pcapkit.protocols.internet.mh.data import \
AlternateIPv4CareofAddressOption as Data_AlternateIPv4CareofAddressOption
from pcapkit.protocols.internet.mh.data import AnchoredPrefixOption as Data_AnchoredPrefixOption
from pcapkit.protocols.internet.mh.data import \
ANICivicLocationSuboption as Data_ANICivicLocationSuboption
from pcapkit.protocols.internet.mh.data import \
ANIGeoLocationSuboption as Data_ANIGeoLocationSuboption
from pcapkit.protocols.internet.mh.data import \
ANIMAGGroupIdentifierSuboption as Data_ANIMAGGroupIdentifierSuboption
from pcapkit.protocols.internet.mh.data import \
ANINetworkIdentifierSuboption as Data_ANINetworkIdentifierSuboption
from pcapkit.protocols.internet.mh.data import \
ANIOperatorIdentifierSuboption as Data_ANIOperatorIdentifierSuboption
from pcapkit.protocols.internet.mh.data import ANISuboption as Data_ANISuboption
from pcapkit.protocols.internet.mh.data import \
ANIUpdateTimerSuboption as Data_ANIUpdateTimerSuboption
from pcapkit.protocols.internet.mh.data import AuthOption as Data_AuthOption
from pcapkit.protocols.internet.mh.data import \
AuthorizationDataOption as Data_AuthorizationDataOption
from pcapkit.protocols.internet.mh.data import BADFOption as Data_BADFOption
from pcapkit.protocols.internet.mh.data import BIDReferenceSuboption as Data_BIDReferenceSuboption
from pcapkit.protocols.internet.mh.data import \
BindingAcknowledgementMessage as Data_BindingAcknowledgementMessage
from pcapkit.protocols.internet.mh.data import BindingErrorMessage as Data_BindingErrorMessage
from pcapkit.protocols.internet.mh.data import \
BindingIdentifierOption as Data_BindingIdentifierOption
from pcapkit.protocols.internet.mh.data import \
BindingRefreshAdviceOption as Data_BindingRefreshAdviceOption
from pcapkit.protocols.internet.mh.data import \
BindingRefreshRequestMessage as Data_BindingRefreshRequestMessage
from pcapkit.protocols.internet.mh.data import \
BindingReregistrationControlSuboption as Data_BindingReregistrationControlSuboption
from pcapkit.protocols.internet.mh.data import \
BindingRevocationMessage as Data_BindingRevocationMessage
from pcapkit.protocols.internet.mh.data import BindingUpdateMessage as Data_BindingUpdateMessage
from pcapkit.protocols.internet.mh.data import BitRateAttribute as Data_BitRateAttribute
from pcapkit.protocols.internet.mh.data import CareofTestInitMessage as Data_CareofTestInitMessage
from pcapkit.protocols.internet.mh.data import CareofTestInitOption as Data_CareofTestInitOption
from pcapkit.protocols.internet.mh.data import CareofTestMessage as Data_CareofTestMessage
from pcapkit.protocols.internet.mh.data import CareofTestOption as Data_CareofTestOption
from pcapkit.protocols.internet.mh.data import CGAExtension as Data_CGAExtension
from pcapkit.protocols.internet.mh.data import CGAParameter as Data_CGAParameter
from pcapkit.protocols.internet.mh.data import CGAParametersOption as Data_CGAParametersOption
from pcapkit.protocols.internet.mh.data import \
CGAParametersRequestOption as Data_CGAParametersRequestOption
from pcapkit.protocols.internet.mh.data import ContextRequest as Data_ContextRequest
from pcapkit.protocols.internet.mh.data import ContextRequestOption as Data_ContextRequestOption
from pcapkit.protocols.internet.mh.data import DelegatedMNPOption as Data_DelegatedMNPOption
from pcapkit.protocols.internet.mh.data import \
DLIFLinkLayerAddressOption as Data_DLIFLinkLayerAddressOption
from pcapkit.protocols.internet.mh.data import \
DLIFLinkLocalAddressOption as Data_DLIFLinkLocalAddressOption
from pcapkit.protocols.internet.mh.data import DNSUpdateOption as Data_DNSUpdateOption
from pcapkit.protocols.internet.mh.data import \
DynamicIPMulticastSelectorOption as Data_DynamicIPMulticastSelectorOption
from pcapkit.protocols.internet.mh.data import ExperimentalExtension as Data_ExperimentalExtension
from pcapkit.protocols.internet.mh.data import ExperimentalMessage as Data_ExperimentalMessage
from pcapkit.protocols.internet.mh.data import \
ExperimentalMobilityOption as Data_ExperimentalMobilityOption
from pcapkit.protocols.internet.mh.data import \
FastBindingAcknowledgmentMessage as Data_FastBindingAcknowledgmentMessage
from pcapkit.protocols.internet.mh.data import \
FastBindingUpdateMessage as Data_FastBindingUpdateMessage
from pcapkit.protocols.internet.mh.data import \
FastNeighborAdvertisementMessage as Data_FastNeighborAdvertisementMessage
from pcapkit.protocols.internet.mh.data import \
FlowBindingActionSuboption as Data_FlowBindingActionSuboption
from pcapkit.protocols.internet.mh.data import FlowBindingMessage as Data_FlowBindingMessage
from pcapkit.protocols.internet.mh.data import \
FlowIdentificationOption as Data_FlowIdentificationOption
from pcapkit.protocols.internet.mh.data import \
FlowIdentificationSuboption as Data_FlowIdentificationSuboption
from pcapkit.protocols.internet.mh.data import FlowSummaryOption as Data_FlowSummaryOption
from pcapkit.protocols.internet.mh.data import GREKeyOption as Data_GREKeyOption
from pcapkit.protocols.internet.mh.data import HandoffIndicatorOption as Data_HandoffIndicatorOption
from pcapkit.protocols.internet.mh.data import \
HandoverAcknowledgeMessage as Data_HandoverAcknowledgeMessage
from pcapkit.protocols.internet.mh.data import \
HandoverInitiateMessage as Data_HandoverInitiateMessage
from pcapkit.protocols.internet.mh.data import \
HeartbeatControlSuboption as Data_HeartbeatControlSuboption
from pcapkit.protocols.internet.mh.data import HeartbeatMessage as Data_HeartbeatMessage
from pcapkit.protocols.internet.mh.data import HomeAgentSwitchMessage as Data_HomeAgentSwitchMessage
from pcapkit.protocols.internet.mh.data import \
HomeNetworkPrefixOption as Data_HomeNetworkPrefixOption
from pcapkit.protocols.internet.mh.data import HomeTestInitMessage as Data_HomeTestInitMessage
from pcapkit.protocols.internet.mh.data import HomeTestMessage as Data_HomeTestMessage
from pcapkit.protocols.internet.mh.data import \
IPv4AddressAcknowledgementOption as Data_IPv4AddressAcknowledgementOption
from pcapkit.protocols.internet.mh.data import \
IPv4CareofAddressOption as Data_IPv4CareofAddressOption
from pcapkit.protocols.internet.mh.data import \
IPv4DefaultRouterAddressOption as Data_IPv4DefaultRouterAddressOption
from pcapkit.protocols.internet.mh.data import \
IPv4DHCPSupportModeOption as Data_IPv4DHCPSupportModeOption
from pcapkit.protocols.internet.mh.data import IPv4HomeAddressOption as Data_IPv4HomeAddressOption
from pcapkit.protocols.internet.mh.data import \
IPv4HomeAddressReplyOption as Data_IPv4HomeAddressReplyOption
from pcapkit.protocols.internet.mh.data import \
IPv4HomeAddressRequestOption as Data_IPv4HomeAddressRequestOption
from pcapkit.protocols.internet.mh.data import \
IPv4TrafficOffloadSelectorOption as Data_IPv4TrafficOffloadSelectorOption
from pcapkit.protocols.internet.mh.data import \
IPv6AddressPrefixOption as Data_IPv6AddressPrefixOption
from pcapkit.protocols.internet.mh.data import LinkLayerAddressOption as Data_LinkLayerAddressOption
from pcapkit.protocols.internet.mh.data import LinkLocalAddressOption as Data_LinkLocalAddressOption
from pcapkit.protocols.internet.mh.data import LMAAddressOption as Data_LMAAddressOption
from pcapkit.protocols.internet.mh.data import \
LMAControlledMAGParametersOption as Data_LMAControlledMAGParametersOption
from pcapkit.protocols.internet.mh.data import \
LMAControlledMAGSuboption as Data_LMAControlledMAGSuboption
from pcapkit.protocols.internet.mh.data import \
LMAUserPlaneAddressOption as Data_LMAUserPlaneAddressOption
from pcapkit.protocols.internet.mh.data import LoadInformationOption as Data_LoadInformationOption
from pcapkit.protocols.internet.mh.data import \
LocalizedRoutingAcknowledgmentMessage as Data_LocalizedRoutingAcknowledgmentMessage
from pcapkit.protocols.internet.mh.data import \
LocalizedRoutingInitiationMessage as Data_LocalizedRoutingInitiationMessage
from pcapkit.protocols.internet.mh.data import LocalPrefixOption as Data_LocalPrefixOption
from pcapkit.protocols.internet.mh.data import MAGIdentifierOption as Data_MAGIdentifierOption
from pcapkit.protocols.internet.mh.data import MAGIPv6AddressOption as Data_MAGIPv6AddressOption
from pcapkit.protocols.internet.mh.data import \
MAGMultipathBindingOption as Data_MAGMultipathBindingOption
from pcapkit.protocols.internet.mh.data import MesgIDOption as Data_MesgIDOption
from pcapkit.protocols.internet.mh.data import \
MNGroupIdentifierOption as Data_MNGroupIdentifierOption
from pcapkit.protocols.internet.mh.data import MNIDOption as Data_MNIDOption
from pcapkit.protocols.internet.mh.data import MNLLAIIDOption as Data_MNLLAIIDOption
from pcapkit.protocols.internet.mh.data import MNLLIdentifierOption as Data_MNLLIdentifierOption
from pcapkit.protocols.internet.mh.data import \
MobileNetworkPrefixOption as Data_MobileNetworkPrefixOption
from pcapkit.protocols.internet.mh.data import \
MulticastAcknowledgementOption as Data_MulticastAcknowledgementOption
from pcapkit.protocols.internet.mh.data import \
MulticastMobilityOption as Data_MulticastMobilityOption
from pcapkit.protocols.internet.mh.data import MultiPrefixExtension as Data_MultiPrefixExtension
from pcapkit.protocols.internet.mh.data import NATDetectionOption as Data_NATDetectionOption
from pcapkit.protocols.internet.mh.data import NonceIndicesOption as Data_NonceIndicesOption
from pcapkit.protocols.internet.mh.data import NTPTimestamp as Data_NTPTimestamp
from pcapkit.protocols.internet.mh.data import \
PadFlowIdentificationSuboption as Data_PadFlowIdentificationSuboption
from pcapkit.protocols.internet.mh.data import PadOption as Data_PadOption
from pcapkit.protocols.internet.mh.data import \
PermanentHomeKeygenTokenOption as Data_PermanentHomeKeygenTokenOption
from pcapkit.protocols.internet.mh.data import \
PerSessionBitRateAttribute as Data_PerSessionBitRateAttribute
from pcapkit.protocols.internet.mh.data import PMIPv6Timestamp as Data_PMIPv6Timestamp
from pcapkit.protocols.internet.mh.data import PreviousMAAROption as Data_PreviousMAAROption
from pcapkit.protocols.internet.mh.data import QoSAttribute as Data_QoSAttribute
from pcapkit.protocols.internet.mh.data import \
QoSTrafficSelectorAttribute as Data_QoSTrafficSelectorAttribute
from pcapkit.protocols.internet.mh.data import \
QoSVendorSpecificAttribute as Data_QoSVendorSpecificAttribute
from pcapkit.protocols.internet.mh.data import QualityOfServiceOption as Data_QualityOfServiceOption
from pcapkit.protocols.internet.mh.data import \
RedirectCapabilityOption as Data_RedirectCapabilityOption
from pcapkit.protocols.internet.mh.data import RedirectOption as Data_RedirectOption
from pcapkit.protocols.internet.mh.data import RestartCounterOption as Data_RestartCounterOption
from pcapkit.protocols.internet.mh.data import ServiceSelectionOption as Data_ServiceSelectionOption
from pcapkit.protocols.internet.mh.data import ServingMAAROption as Data_ServingMAAROption
from pcapkit.protocols.internet.mh.data import SignatureOption as Data_SignatureOption
from pcapkit.protocols.internet.mh.data import \
SubscriptionQueryMessage as Data_SubscriptionQueryMessage
from pcapkit.protocols.internet.mh.data import \
SubscriptionResponseMessage as Data_SubscriptionResponseMessage
from pcapkit.protocols.internet.mh.data import \
TargetCareofAddressSuboption as Data_TargetCareofAddressSuboption
from pcapkit.protocols.internet.mh.data import TimestampOption as Data_TimestampOption
from pcapkit.protocols.internet.mh.data import \
TrafficSelectorSuboption as Data_TrafficSelectorSuboption
from pcapkit.protocols.internet.mh.data import TransientBindingOption as Data_TransientBindingOption
from pcapkit.protocols.internet.mh.data import UnassignedANISuboption as Data_UnassignedANISuboption
from pcapkit.protocols.internet.mh.data import \
UnassignedFlowIdentificationSuboption as Data_UnassignedFlowIdentificationSuboption
from pcapkit.protocols.internet.mh.data import \
UnassignedLMAControlledMAGSuboption as Data_UnassignedLMAControlledMAGSuboption
from pcapkit.protocols.internet.mh.data import UnassignedOption as Data_UnassignedOption
from pcapkit.protocols.internet.mh.data import UnassignedQoSAttribute as Data_UnassignedQoSAttribute
from pcapkit.protocols.internet.mh.data import UnknownExtension as Data_UnknownExtension
from pcapkit.protocols.internet.mh.data import UnknownMessage as Data_UnknownMessage
from pcapkit.protocols.internet.mh.data import \
UpdateNotificationAcknowledgementMessage as Data_UpdateNotificationAcknowledgementMessage
from pcapkit.protocols.internet.mh.data import \
UpdateNotificationMessage as Data_UpdateNotificationMessage
from pcapkit.protocols.internet.mh.data import VendorSpecificOption as Data_VendorSpecificOption
from pcapkit.protocols.internet.mh.schema import MH as Schema_MH
from pcapkit.protocols.internet.mh.schema import \
AccessNetworkIdentifierOption as Schema_AccessNetworkIdentifierOption
from pcapkit.protocols.internet.mh.schema import \
AccessTechnologyTypeOption as Schema_AccessTechnologyTypeOption
from pcapkit.protocols.internet.mh.schema import \
ActiveMulticastSubscriptionIPv4Option as Schema_ActiveMulticastSubscriptionIPv4Option
from pcapkit.protocols.internet.mh.schema import \
ActiveMulticastSubscriptionIPv6Option as Schema_ActiveMulticastSubscriptionIPv6Option
from pcapkit.protocols.internet.mh.schema import \
AllocationRetentionPriorityAttribute as Schema_AllocationRetentionPriorityAttribute
from pcapkit.protocols.internet.mh.schema import \
AlternateCareofAddressOption as Schema_AlternateCareofAddressOption
from pcapkit.protocols.internet.mh.schema import \
AlternateIPv4CareofAddressOption as Schema_AlternateIPv4CareofAddressOption
from pcapkit.protocols.internet.mh.schema import AnchoredPrefixOption as Schema_AnchoredPrefixOption
from pcapkit.protocols.internet.mh.schema import \
ANICivicLocationSuboption as Schema_ANICivicLocationSuboption
from pcapkit.protocols.internet.mh.schema import \
ANIGeoLocationSuboption as Schema_ANIGeoLocationSuboption
from pcapkit.protocols.internet.mh.schema import \
ANIMAGGroupIdentifierSuboption as Schema_ANIMAGGroupIdentifierSuboption
from pcapkit.protocols.internet.mh.schema import \
ANINetworkIdentifierSuboption as Schema_ANINetworkIdentifierSuboption
from pcapkit.protocols.internet.mh.schema import \
ANIOperatorIdentifierSuboption as Schema_ANIOperatorIdentifierSuboption
from pcapkit.protocols.internet.mh.schema import ANISuboption as Schema_ANISuboption
from pcapkit.protocols.internet.mh.schema import \
ANIUpdateTimerSuboption as Schema_ANIUpdateTimerSuboption
from pcapkit.protocols.internet.mh.schema import AuthOption as Schema_AuthOption
from pcapkit.protocols.internet.mh.schema import \
AuthorizationDataOption as Schema_AuthorizationDataOption
from pcapkit.protocols.internet.mh.schema import BADFOption as Schema_BADFOption
from pcapkit.protocols.internet.mh.schema import \
BIDReferenceSuboption as Schema_BIDReferenceSuboption
from pcapkit.protocols.internet.mh.schema import \
BindingAcknowledgementMessage as Schema_BindingAcknowledgementMessage
from pcapkit.protocols.internet.mh.schema import BindingErrorMessage as Schema_BindingErrorMessage
from pcapkit.protocols.internet.mh.schema import \
BindingIdentifierOption as Schema_BindingIdentifierOption
from pcapkit.protocols.internet.mh.schema import \
BindingRefreshAdviceOption as Schema_BindingRefreshAdviceOption
from pcapkit.protocols.internet.mh.schema import \
BindingRefreshRequestMessage as Schema_BindingRefreshRequestMessage
from pcapkit.protocols.internet.mh.schema import \
BindingReregistrationControlSuboption as Schema_BindingReregistrationControlSuboption
from pcapkit.protocols.internet.mh.schema import \
BindingRevocationMessage as Schema_BindingRevocationMessage
from pcapkit.protocols.internet.mh.schema import BindingUpdateMessage as Schema_BindingUpdateMessage
from pcapkit.protocols.internet.mh.schema import BitRateAttribute as Schema_BitRateAttribute
from pcapkit.protocols.internet.mh.schema import \
CareofTestInitMessage as Schema_CareofTestInitMessage
from pcapkit.protocols.internet.mh.schema import CareofTestInitOption as Schema_CareofTestInitOption
from pcapkit.protocols.internet.mh.schema import CareofTestMessage as Schema_CareofTestMessage
from pcapkit.protocols.internet.mh.schema import CareofTestOption as Schema_CareofTestOption
from pcapkit.protocols.internet.mh.schema import CGAExtension as Schema_CGAExtension
from pcapkit.protocols.internet.mh.schema import CGAParameter as Schema_CGAParameter
from pcapkit.protocols.internet.mh.schema import CGAParametersOption as Schema_CGAParametersOption
from pcapkit.protocols.internet.mh.schema import \
CGAParametersRequestOption as Schema_CGAParametersRequestOption
from pcapkit.protocols.internet.mh.schema import ContextRequestOption as Schema_ContextRequestOption
from pcapkit.protocols.internet.mh.schema import DelegatedMNPOption as Schema_DelegatedMNPOption
from pcapkit.protocols.internet.mh.schema import \
DLIFLinkLayerAddressOption as Schema_DLIFLinkLayerAddressOption
from pcapkit.protocols.internet.mh.schema import \
DLIFLinkLocalAddressOption as Schema_DLIFLinkLocalAddressOption
from pcapkit.protocols.internet.mh.schema import DNSUpdateOption as Schema_DNSUpdateOption
from pcapkit.protocols.internet.mh.schema import \
DynamicIPMulticastSelectorOption as Schema_DynamicIPMulticastSelectorOption
from pcapkit.protocols.internet.mh.schema import \
ExperimentalExtension as Schema_ExperimentalExtension
from pcapkit.protocols.internet.mh.schema import ExperimentalMessage as Schema_ExperimentalMessage
from pcapkit.protocols.internet.mh.schema import \
ExperimentalMobilityOption as Schema_ExperimentalMobilityOption
from pcapkit.protocols.internet.mh.schema import \
FastBindingAcknowledgmentMessage as Schema_FastBindingAcknowledgmentMessage
from pcapkit.protocols.internet.mh.schema import \
FastBindingUpdateMessage as Schema_FastBindingUpdateMessage
from pcapkit.protocols.internet.mh.schema import \
FastNeighborAdvertisementMessage as Schema_FastNeighborAdvertisementMessage
from pcapkit.protocols.internet.mh.schema import \
FlowBindingActionSuboption as Schema_FlowBindingActionSuboption
from pcapkit.protocols.internet.mh.schema import FlowBindingMessage as Schema_FlowBindingMessage
from pcapkit.protocols.internet.mh.schema import \
FlowIdentificationOption as Schema_FlowIdentificationOption
from pcapkit.protocols.internet.mh.schema import \
FlowIdentificationSuboption as Schema_FlowIdentificationSuboption
from pcapkit.protocols.internet.mh.schema import FlowSummaryOption as Schema_FlowSummaryOption
from pcapkit.protocols.internet.mh.schema import GREKeyOption as Schema_GREKeyOption
from pcapkit.protocols.internet.mh.schema import \
HandoffIndicatorOption as Schema_HandoffIndicatorOption
from pcapkit.protocols.internet.mh.schema import \
HandoverAcknowledgeMessage as Schema_HandoverAcknowledgeMessage
from pcapkit.protocols.internet.mh.schema import \
HandoverInitiateMessage as Schema_HandoverInitiateMessage
from pcapkit.protocols.internet.mh.schema import \
HeartbeatControlSuboption as Schema_HeartbeatControlSuboption
from pcapkit.protocols.internet.mh.schema import HeartbeatMessage as Schema_HeartbeatMessage
from pcapkit.protocols.internet.mh.schema import \
HomeAgentSwitchMessage as Schema_HomeAgentSwitchMessage
from pcapkit.protocols.internet.mh.schema import \
HomeNetworkPrefixOption as Schema_HomeNetworkPrefixOption
from pcapkit.protocols.internet.mh.schema import HomeTestInitMessage as Schema_HomeTestInitMessage
from pcapkit.protocols.internet.mh.schema import HomeTestMessage as Schema_HomeTestMessage
from pcapkit.protocols.internet.mh.schema import \
IPv4AddressAcknowledgementOption as Schema_IPv4AddressAcknowledgementOption
from pcapkit.protocols.internet.mh.schema import \
IPv4CareofAddressOption as Schema_IPv4CareofAddressOption
from pcapkit.protocols.internet.mh.schema import \
IPv4DefaultRouterAddressOption as Schema_IPv4DefaultRouterAddressOption
from pcapkit.protocols.internet.mh.schema import \
IPv4DHCPSupportModeOption as Schema_IPv4DHCPSupportModeOption
from pcapkit.protocols.internet.mh.schema import \
IPv4HomeAddressOption as Schema_IPv4HomeAddressOption
from pcapkit.protocols.internet.mh.schema import \
IPv4HomeAddressReplyOption as Schema_IPv4HomeAddressReplyOption
from pcapkit.protocols.internet.mh.schema import \
IPv4HomeAddressRequestOption as Schema_IPv4HomeAddressRequestOption
from pcapkit.protocols.internet.mh.schema import \
IPv4TrafficOffloadSelectorOption as Schema_IPv4TrafficOffloadSelectorOption
from pcapkit.protocols.internet.mh.schema import \
IPv6AddressPrefixOption as Schema_IPv6AddressPrefixOption
from pcapkit.protocols.internet.mh.schema import \
LinkLayerAddressOption as Schema_LinkLayerAddressOption
from pcapkit.protocols.internet.mh.schema import \
LinkLocalAddressOption as Schema_LinkLocalAddressOption
from pcapkit.protocols.internet.mh.schema import LMAAddressOption as Schema_LMAAddressOption
from pcapkit.protocols.internet.mh.schema import \
LMAControlledMAGParametersOption as Schema_LMAControlledMAGParametersOption
from pcapkit.protocols.internet.mh.schema import \
LMAControlledMAGSuboption as Schema_LMAControlledMAGSuboption
from pcapkit.protocols.internet.mh.schema import \
LMAUserPlaneAddressOption as Schema_LMAUserPlaneAddressOption
from pcapkit.protocols.internet.mh.schema import \
LoadInformationOption as Schema_LoadInformationOption
from pcapkit.protocols.internet.mh.schema import \
LocalizedRoutingAcknowledgmentMessage as Schema_LocalizedRoutingAcknowledgmentMessage
from pcapkit.protocols.internet.mh.schema import \
LocalizedRoutingInitiationMessage as Schema_LocalizedRoutingInitiationMessage
from pcapkit.protocols.internet.mh.schema import LocalPrefixOption as Schema_LocalPrefixOption
from pcapkit.protocols.internet.mh.schema import MAGIdentifierOption as Schema_MAGIdentifierOption
from pcapkit.protocols.internet.mh.schema import MAGIPv6AddressOption as Schema_MAGIPv6AddressOption
from pcapkit.protocols.internet.mh.schema import \
MAGMultipathBindingOption as Schema_MAGMultipathBindingOption
from pcapkit.protocols.internet.mh.schema import MesgIDOption as Schema_MesgIDOption
from pcapkit.protocols.internet.mh.schema import \
MNGroupIdentifierOption as Schema_MNGroupIdentifierOption
from pcapkit.protocols.internet.mh.schema import MNIDOption as Schema_MNIDOption
from pcapkit.protocols.internet.mh.schema import MNLLAIIDOption as Schema_MNLLAIIDOption
from pcapkit.protocols.internet.mh.schema import MNLLIdentifierOption as Schema_MNLLIdentifierOption
from pcapkit.protocols.internet.mh.schema import \
MobileNetworkPrefixOption as Schema_MobileNetworkPrefixOption
from pcapkit.protocols.internet.mh.schema import \
MulticastAcknowledgementOption as Schema_MulticastAcknowledgementOption
from pcapkit.protocols.internet.mh.schema import \
MulticastMobilityOption as Schema_MulticastMobilityOption
from pcapkit.protocols.internet.mh.schema import MultiPrefixExtension as Schema_MultiPrefixExtension
from pcapkit.protocols.internet.mh.schema import NATDetectionOption as Schema_NATDetectionOption
from pcapkit.protocols.internet.mh.schema import NonceIndicesOption as Schema_NonceIndicesOption
from pcapkit.protocols.internet.mh.schema import Packet as Schema_Packet
from pcapkit.protocols.internet.mh.schema import \
PadFlowIdentificationSuboption as Schema_PadFlowIdentificationSuboption
from pcapkit.protocols.internet.mh.schema import PadOption as Schema_PadOption
from pcapkit.protocols.internet.mh.schema import \
PermanentHomeKeygenTokenOption as Schema_PermanentHomeKeygenTokenOption
from pcapkit.protocols.internet.mh.schema import \
PerSessionBitRateAttribute as Schema_PerSessionBitRateAttribute
from pcapkit.protocols.internet.mh.schema import PreviousMAAROption as Schema_PreviousMAAROption
from pcapkit.protocols.internet.mh.schema import QoSAttribute as Schema_QoSAttribute
from pcapkit.protocols.internet.mh.schema import \
QoSTrafficSelectorAttribute as Schema_QoSTrafficSelectorAttribute
from pcapkit.protocols.internet.mh.schema import \
QoSVendorSpecificAttribute as Schema_QoSVendorSpecificAttribute
from pcapkit.protocols.internet.mh.schema import \
QualityOfServiceOption as Schema_QualityOfServiceOption
from pcapkit.protocols.internet.mh.schema import \
RedirectCapabilityOption as Schema_RedirectCapabilityOption
from pcapkit.protocols.internet.mh.schema import RedirectOption as Schema_RedirectOption
from pcapkit.protocols.internet.mh.schema import RestartCounterOption as Schema_RestartCounterOption
from pcapkit.protocols.internet.mh.schema import \
ServiceSelectionOption as Schema_ServiceSelectionOption
from pcapkit.protocols.internet.mh.schema import ServingMAAROption as Schema_ServingMAAROption
from pcapkit.protocols.internet.mh.schema import SignatureOption as Schema_SignatureOption
from pcapkit.protocols.internet.mh.schema import \
SubscriptionQueryMessage as Schema_SubscriptionQueryMessage
from pcapkit.protocols.internet.mh.schema import \
SubscriptionResponseMessage as Schema_SubscriptionResponseMessage
from pcapkit.protocols.internet.mh.schema import \
TargetCareofAddressSuboption as Schema_TargetCareofAddressSuboption
from pcapkit.protocols.internet.mh.schema import TimestampOption as Schema_TimestampOption
from pcapkit.protocols.internet.mh.schema import \
TrafficSelectorSuboption as Schema_TrafficSelectorSuboption
from pcapkit.protocols.internet.mh.schema import \
TransientBindingOption as Schema_TransientBindingOption
from pcapkit.protocols.internet.mh.schema import \
UnassignedANISuboption as Schema_UnassignedANISuboption
from pcapkit.protocols.internet.mh.schema import \
UnassignedFlowIdentificationSuboption as Schema_UnassignedFlowIdentificationSuboption
from pcapkit.protocols.internet.mh.schema import \
UnassignedLMAControlledMAGSuboption as Schema_UnassignedLMAControlledMAGSuboption
from pcapkit.protocols.internet.mh.schema import UnassignedOption as Schema_UnassignedOption
from pcapkit.protocols.internet.mh.schema import \
UnassignedQoSAttribute as Schema_UnassignedQoSAttribute
from pcapkit.protocols.internet.mh.schema import UnknownExtension as Schema_UnknownExtension
from pcapkit.protocols.internet.mh.schema import UnknownMessage as Schema_UnknownMessage
from pcapkit.protocols.internet.mh.schema import \
UpdateNotificationAcknowledgementMessage as Schema_UpdateNotificationAcknowledgementMessage
from pcapkit.protocols.internet.mh.schema import \
UpdateNotificationMessage as Schema_UpdateNotificationMessage
from pcapkit.protocols.internet.mh.schema import VendorSpecificOption as Schema_VendorSpecificOption
from pcapkit.protocols.misc.no_payload.protocol import NoPayload
from pcapkit.protocols.schema import Schema
from pcapkit.utilities.datetime import fromtimestamp, totimestamp
from pcapkit.utilities.exceptions import (BaseError, CaughtByCaller, EnumValueError, ProtocolError,
UnsupportedCall)
from pcapkit.utilities.warnings import ProtocolWarning, RegistryWarning, warn
if TYPE_CHECKING:
from datetime import datetime as dt_type
from datetime import timedelta
from ipaddress import IPv4Address, IPv6Address, IPv6Network
from typing import IO, Any, Callable, DefaultDict, NoReturn, Optional
from mypy_extensions import DefaultArg, KwArg, NamedArg
from typing_extensions import Literal
from pcapkit.corekit.multidict import OrderedMultiDict
from pcapkit.corekit.protochain import ProtoChain
from pcapkit.protocols.internet.mh.data import Option as Data_Option
from pcapkit.protocols.internet.mh.schema import Option as Schema_Option
from pcapkit.protocols.internet.mh.schema import Packet as Schema_Packet
from pcapkit.protocols.protocol import ProtocolBase
from pcapkit.protocols.schema import Schema
Option = OrderedMultiDict[Enum_Option, Data_Option]
Extension = OrderedMultiDict[Enum_CGAExtension, Data_CGAExtension]
FlowIDSuboption = OrderedMultiDict[Enum_FlowIDSuboption,
Data_FlowIdentificationSuboption]
ANISuboption = OrderedMultiDict[Enum_ANISuboption, Data_ANISuboption]
QoSAttribute = OrderedMultiDict[Enum_QoSAttribute, Data_QoSAttribute]
LMAControlledMAGSuboption = OrderedMultiDict[Enum_LMAControlledMAGSuboption,
Data_LMAControlledMAGSuboption]
PacketParser = Callable[[Schema_Packet, NamedArg(Schema_MH, 'header')], Data_MH]
PacketConstructor = Callable[[DefaultArg(Optional[Data_MH]),
KwArg(Any)], Schema_Packet]
OptionParser = Callable[[Schema_Option, NamedArg(Option, 'options')], Data_Option]
OptionConstructor = Callable[[Enum_Option, DefaultArg(Optional[Data_Option]),
KwArg(Any)], Schema_Option]
ExtensionParser = Callable[[Schema_CGAExtension, NamedArg(Extension, 'extensions')], Data_CGAExtension]
ExtensionConstructor = Callable[[Enum_CGAExtension, DefaultArg(Optional[Data_CGAExtension]),
KwArg(Any)], Schema_CGAExtension]
__all__ = ['MH']
[docs]
class FastBindingAcknowledgmentStatus(EnumLookup, IntEnum):
"""[FastBindingAcknowledgmentStatus] Fast Binding Acknowledgment Status Codes.
Status values of the fast binding acknowledgment (FBack) message, see
:rfc:`5568#section-6.2.3`. Values below ``128`` indicate that the fast
binding update was accepted by the receiving node, values of ``128`` and
above that it was rejected.
Note:
:rfc:`5568#section-6.2.3` defines these values inline and IANA keeps no
registry of them, so the enumeration lives here rather than in
:mod:`pcapkit.const.mh`. It is also **not** interchangeable with the
registered :class:`~pcapkit.const.mh.status_code.StatusCode`, since some
of its values collide semantically: ``1`` means *NCoA is invalid* here
but *accepted but prefix discovery necessary* there, and ``131`` means
*incorrect interface identifier length* here but *home registration not
supported* there.
The enumeration is **closed**: an in-range value :rfc:`5568` leaves
unassigned raises :exc:`~pcapkit.utilities.exceptions.EnumValueError`
from :meth:`_missing_` instead of minting an ``Unassigned_N`` member,
because this RFC-inline value set stays immutable (decided on
:issue:`877`). Within an IPv6 chain the Mobility Header and everything
after it are then :class:`~pcapkit.protocols.misc.raw.protocol.Raw`, as
:rfc:`8200#section-4` has a node discard such a packet (:issue:`2477`).
The class defines no ``get`` of its own: ``get``/``get_all`` come from
:class:`~pcapkit.corekit.enum.EnumLookup`, like every other
:class:`int`-valued registry here, so a key that is neither an
:class:`int` nor a :class:`str` raises
:exc:`~pcapkit.utilities.exceptions.EnumValueError`. A ``get`` override
was rejected (decided on :issue:`935`; the override was deleted, not
widened to accept ``default``): this class mints no alias
(``__members__`` and ``list(cls)`` hold the same members), so an override
could only repeat the base's dual :class:`int`/name resolution.
"""
#: Fast Binding Update accepted [:rfc:`5568#section-6.2.3`]
Fast_Binding_Update_accepted = 0
#: Fast Binding Update accepted but NCoA is invalid; use the NCoA supplied in
#: the "alternate" care-of address option [:rfc:`5568#section-6.2.3`]
Fast_Binding_Update_accepted_but_NCoA_is_invalid = 1
#: Reason unspecified [:rfc:`5568#section-6.2.3`]
Reason_unspecified = 128
#: Administratively prohibited [:rfc:`5568#section-6.2.3`]
Administratively_prohibited = 129
#: Insufficient resources [:rfc:`5568#section-6.2.3`]
Insufficient_resources = 130
#: Incorrect interface identifier length [:rfc:`5568#section-6.2.3`]
Incorrect_interface_identifier_length = 131
[docs]
@classmethod
def _missing_(cls, value: 'int') -> 'NoReturn':
"""Lookup function used when value is not found.
Args:
value: Value to get enum item.
Raises:
EnumValueError: Always. :rfc:`5568#section-6.2.3` names this value
set inline with no IANA registry behind it, and it stays
immutable (decided on :issue:`877`) rather than minting an
``Unassigned_N`` placeholder member.
"""
raise EnumValueError('%r is not a valid %s' % (value, cls.__name__))
[docs]
class IPv6AddressPrefixCode(EnumLookup, IntEnum):
"""[IPv6AddressPrefixCode] Mobility Header IPv6 Address/Prefix Option Codes.
Option codes of the mobility header IPv6 address/prefix option, which
identify which address the option carries, see
:rfc:`5568#section-6.4.2`.
Note:
:rfc:`5568#section-6.4.2` defines these values inline and IANA keeps no
registry of them, so the enumeration lives here rather than in
:mod:`pcapkit.const.mh`. The identical code space of the neighbor
discovery IP address/prefix option of :rfc:`5568#section-6.4.1` is
likewise unregistered.
The enumeration is **closed**: an in-range value :rfc:`5568` leaves
unassigned raises :exc:`~pcapkit.utilities.exceptions.EnumValueError`
from :meth:`_missing_` instead of minting an ``Unassigned_N`` member,
because this RFC-inline value set stays immutable (decided on
:issue:`877`). Within an IPv6 chain the Mobility Header and everything
after it are then :class:`~pcapkit.protocols.misc.raw.protocol.Raw`, as
:rfc:`8200#section-4` has a node discard such a packet (:issue:`2477`).
The class defines no ``get`` of its own: ``get``/``get_all`` come from
:class:`~pcapkit.corekit.enum.EnumLookup`, like every other
:class:`int`-valued registry here, so a key that is neither an
:class:`int` nor a :class:`str` raises
:exc:`~pcapkit.utilities.exceptions.EnumValueError`. A ``get`` override
was rejected (decided on :issue:`935`; the override was deleted, not
widened to accept ``default``): this class mints no alias
(``__members__`` and ``list(cls)`` hold the same members), so an override
could only repeat the base's dual :class:`int`/name resolution.
"""
#: Old Care-of Address [:rfc:`5568#section-6.4.2`]
Old_Care_of_Address = 1
#: New Care-of Address [:rfc:`5568#section-6.4.2`]
New_Care_of_Address = 2
#: NAR's IP address [:rfc:`5568#section-6.4.2`]
NAR_IP_address = 3
#: NAR's Prefix, sent in PrRtAdv; the prefix length field contains the number
#: of valid leading bits in the prefix [:rfc:`5568#section-6.4.2`]
NAR_Prefix = 4
[docs]
@classmethod
def _missing_(cls, value: 'int') -> 'NoReturn':
"""Lookup function used when value is not found.
Args:
value: Value to get enum item.
Raises:
EnumValueError: Always. :rfc:`5568#section-6.4.2` names this value
set inline with no IANA registry behind it, and it stays
immutable (decided on :issue:`877`) rather than minting an
``Unassigned_N`` placeholder member.
"""
raise EnumValueError('%r is not a valid %s' % (value, cls.__name__))
[docs]
class LocalizedRoutingStatus(EnumLookup, IntEnum):
"""[LocalizedRoutingStatus] Localized Routing Acknowledgment Status Codes.
Status values of the localized routing acknowledgment (LRA) message, see
:rfc:`6705#section-10.2`. Values below ``128`` indicate that the initiation
was processed successfully, values of ``128`` and above that it was rejected.
Note:
:rfc:`6705#section-10.2` defines these values inline and IANA keeps no
registry of them -- neither a dedicated one nor entries in the general
*Status Codes* registry -- so the enumeration lives here rather than in
:mod:`pcapkit.const.mh`. It is **not** interchangeable with the
registered :class:`~pcapkit.const.mh.status_code.StatusCode`, whose
``128`` and ``129`` mean something else entirely.
The enumeration is **closed**: an in-range value :rfc:`6705` leaves
unassigned raises :exc:`~pcapkit.utilities.exceptions.EnumValueError`
from :meth:`_missing_` instead of minting an ``Unassigned_N`` member,
because this RFC-inline value set stays immutable (decided on
:issue:`877`). Within an IPv6 chain the Mobility Header and everything
after it are then :class:`~pcapkit.protocols.misc.raw.protocol.Raw`, as
:rfc:`8200#section-4` has a node discard such a packet (:issue:`2477`).
The class defines no ``get`` of its own: ``get``/``get_all`` come from
:class:`~pcapkit.corekit.enum.EnumLookup`, and since it cannot mint, an
unassigned value raises through ``get`` exactly as it does through the
bare constructor. A ``get`` override was rejected (decided on
:issue:`880`): it had no callers, and rebuilding it on the immutable
contract would add nothing to the base.
"""
#: Success [:rfc:`6705#section-10.2`]
Success = 0
#: Localized Routing Not Allowed [:rfc:`6705#section-10.2`]
Localized_Routing_Not_Allowed = 128
#: MN Not Attached [:rfc:`6705#section-10.2`]
MN_Not_Attached = 129
[docs]
@classmethod
def _missing_(cls, value: 'int') -> 'NoReturn':
"""Lookup function used when value is not found.
Args:
value: Value to get enum item.
Raises:
EnumValueError: Always. :rfc:`6705#section-10.2` names this value
set inline with no IANA registry behind it, and it stays
immutable (decided on :issue:`877`) rather than minting an
``Unassigned_N`` placeholder member.
"""
raise EnumValueError('%r is not a valid %s' % (value, cls.__name__))
[docs]
class LMAAddressCode(EnumLookup, IntEnum):
"""[LMAAddressCode] Local Mobility Anchor Address Option Codes.
Option codes of the local mobility anchor address option, which say which
address family the option carries, see :rfc:`5949#section-6.2.2`.
Note:
:rfc:`5949#section-6.2.2` defines these values inline and IANA keeps no
registry of them, so the enumeration lives here rather than in
:mod:`pcapkit.const.mh`.
The enumeration is **closed**: an in-range value :rfc:`5949` leaves
unassigned raises :exc:`~pcapkit.utilities.exceptions.EnumValueError`
from :meth:`_missing_` instead of minting an ``Unassigned_N`` member,
because this RFC-inline value set stays immutable (decided on
:issue:`877`). Within an IPv6 chain the Mobility Header and everything
after it are then :class:`~pcapkit.protocols.misc.raw.protocol.Raw`, as
:rfc:`8200#section-4` has a node discard such a packet (:issue:`2477`).
The class defines no ``get`` of its own: ``get``/``get_all`` come from
:class:`~pcapkit.corekit.enum.EnumLookup`, and since it cannot mint, an
unassigned value raises through ``get`` exactly as it does through the
bare constructor. A ``get`` override was rejected (decided on
:issue:`880`): it had no callers, and rebuilding it on the immutable
contract would add nothing to the base.
"""
#: Reserved [:rfc:`5949#section-6.2.2`]
Reserved = 0
#: IPv6 local mobility anchor address [:rfc:`5949#section-6.2.2`]
IPv6_LMAA = 1
#: IPv4 local mobility anchor address [:rfc:`5949#section-6.2.2`]
IPv4_LMAA = 2
[docs]
@classmethod
def _missing_(cls, value: 'int') -> 'NoReturn':
"""Lookup function used when value is not found.
Args:
value: Value to get enum item.
Raises:
EnumValueError: Always. :rfc:`5949#section-6.2.2` names this value
set inline with no IANA registry behind it, and it stays
immutable (decided on :issue:`877`) rather than minting an
``Unassigned_N`` placeholder member.
"""
raise EnumValueError('%r is not a valid %s' % (value, cls.__name__))
[docs]
class MH(IPv6_Ext[Data_MH, Schema_MH],
schema=Schema_MH, data=Data_MH):
"""This class implements Mobility Header.
This class supports parsing of the following MH message types,
which are registered in the :attr:`self.__message__ <pcapkit.protocols.internet.mh.protocol.MH.__message__>`
attribute:
.. list-table::
:header-rows: 1
* - Message Type
- Message Parser
- Message Constructor
* - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Refresh_Request`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_brr`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_brr`
* - :attr:`~pcapkit.const.mh.packet.Packet.Home_Test_Init`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hoti`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hoti`
* - :attr:`~pcapkit.const.mh.packet.Packet.Care_of_Test_Init`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_coti`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_coti`
* - :attr:`~pcapkit.const.mh.packet.Packet.Home_Test`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hot`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hot`
* - :attr:`~pcapkit.const.mh.packet.Packet.Care_of_Test`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_cot`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_cot`
* - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Update`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_bu`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_bu`
* - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Acknowledgement`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_ba`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_ba`
* - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Error`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_be`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_be`
* - :attr:`~pcapkit.const.mh.packet.Packet.Fast_Binding_Update`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_fbu`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_fbu`
* - :attr:`~pcapkit.const.mh.packet.Packet.Fast_Binding_Acknowledgment`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_fback`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_fback`
* - :attr:`~pcapkit.const.mh.packet.Packet.Fast_Neighbor_Advertisement`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_fna`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_fna`
* - :attr:`~pcapkit.const.mh.packet.Packet.Experimental_Mobility_Header`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_emh`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_emh`
* - :attr:`~pcapkit.const.mh.packet.Packet.Home_Agent_Switch_Message`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_has`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_has`
* - :attr:`~pcapkit.const.mh.packet.Packet.Heartbeat_Message`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hb`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hb`
* - :attr:`~pcapkit.const.mh.packet.Packet.Handover_Initiate_Message`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hi`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hi`
* - :attr:`~pcapkit.const.mh.packet.Packet.Handover_Acknowledge_Message`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_hack`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_hack`
* - :attr:`~pcapkit.const.mh.packet.Packet.Binding_Revocation_Message`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_brm`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_brm`
* - :attr:`~pcapkit.const.mh.packet.Packet.Localized_Routing_Initiation`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_lri`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_lri`
* - :attr:`~pcapkit.const.mh.packet.Packet.Localized_Routing_Acknowledgment`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_lra`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_lra`
* - :attr:`~pcapkit.const.mh.packet.Packet.Update_Notification`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_upn`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_upn`
* - :attr:`~pcapkit.const.mh.packet.Packet.Update_Notification_Acknowledgement`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_upa`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_upa`
* - :attr:`~pcapkit.const.mh.packet.Packet.Flow_Binding_Message`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_fbm`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_fbm`
* - :attr:`~pcapkit.const.mh.packet.Packet.Subscription_Query`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_sq`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_sq`
* - :attr:`~pcapkit.const.mh.packet.Packet.Subscription_Response`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_msg_sr`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_msg_sr`
This class supports parsing the following MH options, which are
registered in the :attr:`self.__option__ <pcapkit.protocols.internet.mh.protocol.MH.__option__>`
attribute:
.. list-table::
:header-rows: 1
* - Option Code
- Option Parser
- Option Constructor
* - :attr:`~pcapkit.const.mh.option.Option.Pad1`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_pad`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_pad`
* - :attr:`~pcapkit.const.mh.option.Option.PadN`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_pad`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_pad`
* - :attr:`~pcapkit.const.mh.option.Option.Binding_Refresh_Advice`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_bra`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_bra`
* - :attr:`~pcapkit.const.mh.option.Option.Alternate_Care_of_Address`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_aca`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_aca`
* - :attr:`~pcapkit.const.mh.option.Option.Nonce_Indices`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ni`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ni`
* - :attr:`~pcapkit.const.mh.option.Option.Authorization_Data`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_bad`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_bad`
* - :attr:`~pcapkit.const.mh.option.Option.Mobile_Network_Prefix_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mnp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mnp`
* - :attr:`~pcapkit.const.mh.option.Option.Mobility_Header_Link_Layer_Address_option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lla`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lla`
* - :attr:`~pcapkit.const.mh.option.Option.MN_ID_OPTION_TYPE`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mn_id`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mn_id`
* - :attr:`~pcapkit.const.mh.option.Option.AUTH_OPTION_TYPE`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_auth`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_auth`
* - :attr:`~pcapkit.const.mh.option.Option.MESG_ID_OPTION_TYPE`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mesg_id`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mesg_id`
* - :attr:`~pcapkit.const.mh.option.Option.CGA_Parameters_Request`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_cga_pr`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_cga_pr`
* - :attr:`~pcapkit.const.mh.option.Option.CGA_Parameters`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_cga_param`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_cga_param`
* - :attr:`~pcapkit.const.mh.option.Option.Signature`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_signature`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_signature`
* - :attr:`~pcapkit.const.mh.option.Option.Permanent_Home_Keygen_Token`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_phkt`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_phkt`
* - :attr:`~pcapkit.const.mh.option.Option.Care_of_Test_Init`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ct_init`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ct_init`
* - :attr:`~pcapkit.const.mh.option.Option.Care_of_Test`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ct`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ct`
* - :attr:`~pcapkit.const.mh.option.Option.DNS_UPDATE_TYPE`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_dns`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_dns`
* - :attr:`~pcapkit.const.mh.option.Option.Experimental_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_exp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_exp`
* - :attr:`~pcapkit.const.mh.option.Option.Vendor_Specific_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_vendor`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_vendor`
* - :attr:`~pcapkit.const.mh.option.Option.Service_Selection_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_service`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_service`
* - :attr:`~pcapkit.const.mh.option.Option.Binding_Authorization_Data_for_FMIPv6`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_badf`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_badf`
* - :attr:`~pcapkit.const.mh.option.Option.Home_Network_Prefix_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_hnp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_hnp`
* - :attr:`~pcapkit.const.mh.option.Option.Handoff_Indicator_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_hi`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_hi`
* - :attr:`~pcapkit.const.mh.option.Option.Access_Technology_Type_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_att`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_att`
* - :attr:`~pcapkit.const.mh.option.Option.Mobile_Node_Link_layer_Identifier_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mn_lli`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mn_lli`
* - :attr:`~pcapkit.const.mh.option.Option.Link_local_Address_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lla_addr`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lla_addr`
* - :attr:`~pcapkit.const.mh.option.Option.Timestamp_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_timestamp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_timestamp`
* - :attr:`~pcapkit.const.mh.option.Option.Restart_Counter`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_restart`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_restart`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Home_Address`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_hoa`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_hoa`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Address_Acknowledgement`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_ack`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_ack`
* - :attr:`~pcapkit.const.mh.option.Option.NAT_Detection`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_nat`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_nat`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Care_of_Address`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_coa`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_coa`
* - :attr:`~pcapkit.const.mh.option.Option.GRE_Key_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_gre`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_gre`
* - :attr:`~pcapkit.const.mh.option.Option.Mobility_Header_IPv6_Address_Prefix`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv6_ap`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv6_ap`
* - :attr:`~pcapkit.const.mh.option.Option.Binding_Identifier`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_bid`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_bid`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Home_Address_Request`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_hoa_req`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_hoa_req`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Home_Address_Reply`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_hoa_rep`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_hoa_rep`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Default_Router_Address`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_router`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_router`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_DHCP_Support_Mode`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ipv4_dhcp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ipv4_dhcp`
* - :attr:`~pcapkit.const.mh.option.Option.Context_Request_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_cr`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_cr`
* - :attr:`~pcapkit.const.mh.option.Option.Local_Mobility_Anchor_Address_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lmaa`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lmaa`
* - :attr:`~pcapkit.const.mh.option.Option.Mobile_Node_Link_local_Address_Interface_Identifier_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mn_lla_iid`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mn_lla_iid`
* - :attr:`~pcapkit.const.mh.option.Option.Transient_Binding`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_transient`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_transient`
* - :attr:`~pcapkit.const.mh.option.Option.Flow_Summary_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_fs`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_fs`
* - :attr:`~pcapkit.const.mh.option.Option.Flow_Identification_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_fid`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_fid`
* - :attr:`~pcapkit.const.mh.option.Option.Redirect_Capability_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_rc`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_rc`
* - :attr:`~pcapkit.const.mh.option.Option.Redirect_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_redirect`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_redirect`
* - :attr:`~pcapkit.const.mh.option.Option.Load_Information_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_load`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_load`
* - :attr:`~pcapkit.const.mh.option.Option.Alternate_IPv4_Care_of_Address`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_alt_ipv4_coa`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_alt_ipv4_coa`
* - :attr:`~pcapkit.const.mh.option.Option.Mobile_Node_Group_Identifier`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mn_group`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mn_group`
* - :attr:`~pcapkit.const.mh.option.Option.MAG_IPv6_Address`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mag_addr`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mag_addr`
* - :attr:`~pcapkit.const.mh.option.Option.Access_Network_Identifier`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ani`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ani`
* - :attr:`~pcapkit.const.mh.option.Option.IPv4_Traffic_Offload_Selector`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_offload`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_offload`
* - :attr:`~pcapkit.const.mh.option.Option.Dynamic_IP_Multicast_Selector`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mcast_sel`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mcast_sel`
* - :attr:`~pcapkit.const.mh.option.Option.Delegated_Mobile_Network_Prefix`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_dmnp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_dmnp`
* - :attr:`~pcapkit.const.mh.option.Option.Active_Multicast_Subscription_IPv4`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ams4`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ams4`
* - :attr:`~pcapkit.const.mh.option.Option.Active_Multicast_Subscription_IPv6`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_ams6`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_ams6`
* - :attr:`~pcapkit.const.mh.option.Option.Quality_of_Service`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_qos`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_qos`
* - :attr:`~pcapkit.const.mh.option.Option.LMA_User_Plane_Address`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lma_up`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lma_up`
* - :attr:`~pcapkit.const.mh.option.Option.Multicast_Mobility_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mcast`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mcast`
* - :attr:`~pcapkit.const.mh.option.Option.Multicast_Acknowledgement_Option`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mcast_ack`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mcast_ack`
* - :attr:`~pcapkit.const.mh.option.Option.LMA_Controlled_MAG_Parameters`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_lcmp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_lcmp`
* - :attr:`~pcapkit.const.mh.option.Option.MAG_Multipath_Binding`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mag_mp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mag_mp`
* - :attr:`~pcapkit.const.mh.option.Option.MAG_Identifier`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_mag_id`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_mag_id`
* - :attr:`~pcapkit.const.mh.option.Option.Anchored_Prefix`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_anchored`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_anchored`
* - :attr:`~pcapkit.const.mh.option.Option.Local_Prefix`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_local_prefix`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_local_prefix`
* - :attr:`~pcapkit.const.mh.option.Option.Previous_MAAR`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_prev_maar`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_prev_maar`
* - :attr:`~pcapkit.const.mh.option.Option.Serving_MAAR`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_serv_maar`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_serv_maar`
* - :attr:`~pcapkit.const.mh.option.Option.DLIF_Link_Local_Address`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_dlif_lla`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_dlif_lla`
* - :attr:`~pcapkit.const.mh.option.Option.DLIF_Link_Layer_Address`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_opt_dlif_lladdr`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_opt_dlif_lladdr`
This class supports parsing of the following MH CGA extensions,
which are registered in the :attr:`self.__extension__ <pcapkit.protocols.internet.mh.protocol.MH.__extension__>`
attribute:
.. list-table::
:header-rows: 1
* - CGA Extension Code
- CGA Extension Parser
- CGA Extension Constructor
* - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Multi_Prefix`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_ext_multiprefix`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_ext_multiprefix`
* - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Exp_FFFD`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_ext_exp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_ext_exp`
* - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Exp_FFFE`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_ext_exp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_ext_exp`
* - :attr:`~pcapkit.const.mh.cga_extension.CGAExtension.Exp_FFFF`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._read_ext_exp`
- :meth:`~pcapkit.protocols.internet.mh.protocol.MH._make_ext_exp`
.. versionchanged:: 1.5.0
* The FBU and FBack Lifetime is read and built as plain seconds, per
:rfc:`5568` (:issue:`493`).
* A rebuild keeps a PadN with no data, the reserved fields, the reserved
bits of the flag fields and the octets each padding field captured
(:issue:`1223`, :issue:`1324`, :issue:`1329`, :issue:`1418`,
:issue:`1437`).
.. versionchanged:: 1.6.0
* :meth:`make` and the message, option and sub-option makers, given an
integer argument its field cannot carry -- negative, too wide, or a
:obj:`bool` for a field wider than one bit -- raise
:exc:`~pcapkit.utilities.exceptions.ProtocolError`, rather than
writing a different value or raising another exception; so does the
access network identifier sub-option maker for a raw geo-location
degree that does not fit 24 bits as two's complement, where it masked
it. The option makers check their registry codes and their one-octet
``Option Length`` too (:issue:`2614`).
* An option, sub-option, QoS attribute or CGA extension given to a
maker as empty raw octets, which hold no type, raises
:exc:`~pcapkit.utilities.exceptions.ProtocolError`, rather than being
dropped beside another item or, for most, raising an
:exc:`AttributeError` alone (:issue:`2723`).
* A CGA parameter builds from a data model holding raw extensions, where
it raised an :exc:`AttributeError`, and from a schema without
``public_key_test``, where it raised a :exc:`KeyError`
(:issue:`2725`).
* Each enumeration field of :meth:`make` and of the message, option and
sub-option makers, and the type of each option, sub-option, QoS
attribute and CGA extension given to them, takes a member or an
:class:`int` code, resolved with the enumeration's ``get``; a name, a
:obj:`bool` for a field wider than one bit, or a code the field cannot
carry raises :exc:`~pcapkit.utilities.exceptions.ProtocolError`,
rather than resolving against an enumeration inferred from the
caller's frame or writing a different value. The Binding Error
maker resolves ``status`` with
:class:`~pcapkit.const.mh.binding_error.BindingError`, the registry
its schema holds, rather than with
:class:`~pcapkit.const.mh.status_code.StatusCode` (:issue:`2594`).
The same makers raise it for a fixed-size octet argument of the wrong
width -- the checksum, a cookie or keygen token, or reserved octets --
rather than padding or cutting it to the field. The checksum defaults
to two zero octets (:issue:`2676`).
.. versionremoved:: 1.6.0
* ``_decode_signed``, the two's-complement decoder of the geo-location
degrees; :meth:`~pcapkit.protocols.protocol.Protocol._read_sint` reads
them instead (:issue:`2614`).
* The ``X_default``, ``X_namespace`` and ``X_reversed`` arguments that
went with each enumeration field ``X`` of :meth:`make` and of the
message and option makers; resolve a name with
:meth:`~pcapkit.protocols.protocol.Protocol.resolve_index` first
(:issue:`2594`).
"""
##########################################################################
# Defaults.
##########################################################################
#: Message type to method mapping. A :obj:`str` value names the methods
#: ``_read_msg_${name}`` and ``_make_msg_${name}``, falling back to
#: ``_read_msg_unknown`` / ``_make_msg_unknown`` when they do not exist; any
#: other value is a ``(parser, constructor)`` pair of callables.
__message__ = collections.defaultdict(
lambda: 'unknown',
{
Enum_Packet.Binding_Refresh_Request: 'brr',
Enum_Packet.Home_Test_Init: 'hoti',
Enum_Packet.Care_of_Test_Init: 'coti',
Enum_Packet.Home_Test: 'hot',
Enum_Packet.Care_of_Test: 'cot',
Enum_Packet.Binding_Update: 'bu',
Enum_Packet.Binding_Acknowledgement: 'ba',
Enum_Packet.Binding_Error: 'be',
Enum_Packet.Fast_Binding_Update: 'fbu',
Enum_Packet.Fast_Binding_Acknowledgment: 'fback',
Enum_Packet.Fast_Neighbor_Advertisement: 'fna',
Enum_Packet.Experimental_Mobility_Header: 'emh',
Enum_Packet.Home_Agent_Switch_Message: 'has',
Enum_Packet.Heartbeat_Message: 'hb',
Enum_Packet.Handover_Initiate_Message: 'hi',
Enum_Packet.Handover_Acknowledge_Message: 'hack',
Enum_Packet.Binding_Revocation_Message: 'brm',
Enum_Packet.Localized_Routing_Initiation: 'lri',
Enum_Packet.Localized_Routing_Acknowledgment: 'lra',
Enum_Packet.Update_Notification: 'upn',
Enum_Packet.Update_Notification_Acknowledgement: 'upa',
Enum_Packet.Flow_Binding_Message: 'fbm',
Enum_Packet.Subscription_Query: 'sq',
Enum_Packet.Subscription_Response: 'sr',
},
) # type: DefaultDict[Enum_Packet | int, str | tuple[PacketParser, PacketConstructor]]
#: Option type to method mapping. A :obj:`str` value names the methods
#: ``_read_opt_${name}`` and ``_make_opt_${name}``, falling back to
#: ``_read_opt_none`` / ``_make_opt_none`` when they do not exist; any other
#: value is a ``(parser, constructor)`` pair of callables.
__option__ = collections.defaultdict(
lambda: 'none',
{
Enum_Option.Pad1: 'pad',
Enum_Option.PadN: 'pad',
Enum_Option.Binding_Refresh_Advice: 'bra',
Enum_Option.Alternate_Care_of_Address: 'aca',
Enum_Option.Nonce_Indices: 'ni',
Enum_Option.Authorization_Data: 'bad',
Enum_Option.Mobile_Network_Prefix_Option: 'mnp',
Enum_Option.Mobility_Header_Link_Layer_Address_option: 'lla',
Enum_Option.MN_ID_OPTION_TYPE: 'mn_id',
Enum_Option.AUTH_OPTION_TYPE: 'auth',
Enum_Option.MESG_ID_OPTION_TYPE: 'mesg_id',
Enum_Option.CGA_Parameters_Request: 'cga_pr',
Enum_Option.CGA_Parameters: 'cga_param',
Enum_Option.Signature: 'signature',
Enum_Option.Permanent_Home_Keygen_Token: 'phkt',
Enum_Option.Care_of_Test_Init: 'ct_init',
Enum_Option.Care_of_Test: 'ct',
Enum_Option.DNS_UPDATE_TYPE: 'dns',
Enum_Option.Experimental_Mobility_Option: 'exp',
Enum_Option.Vendor_Specific_Mobility_Option: 'vendor',
Enum_Option.Service_Selection_Mobility_Option: 'service',
Enum_Option.Binding_Authorization_Data_for_FMIPv6: 'badf',
Enum_Option.Home_Network_Prefix_Option: 'hnp',
Enum_Option.Handoff_Indicator_Option: 'hi',
Enum_Option.Access_Technology_Type_Option: 'att',
Enum_Option.Mobile_Node_Link_layer_Identifier_Option: 'mn_lli',
Enum_Option.Link_local_Address_Option: 'lla_addr',
Enum_Option.Timestamp_Option: 'timestamp',
Enum_Option.Restart_Counter: 'restart',
Enum_Option.IPv4_Home_Address: 'ipv4_hoa',
Enum_Option.IPv4_Address_Acknowledgement: 'ipv4_ack',
Enum_Option.NAT_Detection: 'nat',
Enum_Option.IPv4_Care_of_Address: 'ipv4_coa',
Enum_Option.GRE_Key_Option: 'gre',
Enum_Option.Mobility_Header_IPv6_Address_Prefix: 'ipv6_ap',
Enum_Option.Binding_Identifier: 'bid',
Enum_Option.IPv4_Home_Address_Request: 'ipv4_hoa_req',
Enum_Option.IPv4_Home_Address_Reply: 'ipv4_hoa_rep',
Enum_Option.IPv4_Default_Router_Address: 'ipv4_router',
Enum_Option.IPv4_DHCP_Support_Mode: 'ipv4_dhcp',
Enum_Option.Context_Request_Option: 'cr',
Enum_Option.Local_Mobility_Anchor_Address_Option: 'lmaa',
Enum_Option.Mobile_Node_Link_local_Address_Interface_Identifier_Option: 'mn_lla_iid',
Enum_Option.Transient_Binding: 'transient',
Enum_Option.Flow_Summary_Mobility_Option: 'fs',
Enum_Option.Flow_Identification_Mobility_Option: 'fid',
Enum_Option.Redirect_Capability_Mobility_Option: 'rc',
Enum_Option.Redirect_Mobility_Option: 'redirect',
Enum_Option.Load_Information_Mobility_Option: 'load',
Enum_Option.Alternate_IPv4_Care_of_Address: 'alt_ipv4_coa',
Enum_Option.Mobile_Node_Group_Identifier: 'mn_group',
Enum_Option.MAG_IPv6_Address: 'mag_addr',
Enum_Option.Access_Network_Identifier: 'ani',
Enum_Option.IPv4_Traffic_Offload_Selector: 'offload',
Enum_Option.Dynamic_IP_Multicast_Selector: 'mcast_sel',
Enum_Option.Delegated_Mobile_Network_Prefix: 'dmnp',
Enum_Option.Active_Multicast_Subscription_IPv4: 'ams4',
Enum_Option.Active_Multicast_Subscription_IPv6: 'ams6',
Enum_Option.Quality_of_Service: 'qos',
Enum_Option.LMA_User_Plane_Address: 'lma_up',
Enum_Option.Multicast_Mobility_Option: 'mcast',
Enum_Option.Multicast_Acknowledgement_Option: 'mcast_ack',
Enum_Option.LMA_Controlled_MAG_Parameters: 'lcmp',
Enum_Option.MAG_Multipath_Binding: 'mag_mp',
Enum_Option.MAG_Identifier: 'mag_id',
Enum_Option.Anchored_Prefix: 'anchored',
Enum_Option.Local_Prefix: 'local_prefix',
Enum_Option.Previous_MAAR: 'prev_maar',
Enum_Option.Serving_MAAR: 'serv_maar',
Enum_Option.DLIF_Link_Local_Address: 'dlif_lla',
Enum_Option.DLIF_Link_Layer_Address: 'dlif_lladdr',
},
) # type: DefaultDict[Enum_Option | int, str | tuple[OptionParser, OptionConstructor]]
#: CGA extension type to method mapping. A :obj:`str` value names the
#: methods ``_read_ext_${name}`` and ``_make_ext_${name}``, falling back to
#: ``_read_ext_none`` / ``_make_ext_none`` when they do not exist; any other
#: value is a ``(parser, constructor)`` pair of callables.
__extension__ = collections.defaultdict(
lambda: 'none',
{
Enum_CGAExtension.Multi_Prefix: 'multiprefix',
Enum_CGAExtension.Exp_FFFD: 'exp',
Enum_CGAExtension.Exp_FFFE: 'exp',
Enum_CGAExtension.Exp_FFFF: 'exp',
},
) # type: DefaultDict[Enum_CGAExtension | int, str | tuple[ExtensionParser, ExtensionConstructor]]
##########################################################################
# Properties.
##########################################################################
@property
def name(self) -> 'Literal["Mobility Header"]':
"""Name of current protocol."""
return 'Mobility Header'
@property
def alias(self) -> 'Literal["MH"]':
"""Acronym of corresponding protocol.
Spelled out rather than left to
:attr:`Protocol.alias <pcapkit.protocols.protocol.Protocol.alias>`'s
class-name default, because
:class:`~pcapkit.protocols.internet.ipv6_ext.protocol.IPv6_Ext` sits
between this class and that default in the MRO and carries a concrete
``'IPv6-Ext'`` of its own. Inheriting it would
rename this header in every
:class:`~pcapkit.corekit.protochain.ProtoChain` string and in
:meth:`IPv6._decode_next_layer
<pcapkit.protocols.internet.ipv6.protocol.IPv6._decode_next_layer>`'s packet
dict key. The value equals the class-name default.
"""
return 'MH'
@property
def length(self) -> 'int':
"""Header length of current protocol."""
return self._info.length
@property
def payload(self) -> 'ProtocolBase | NoReturn':
"""Payload of current instance.
Raises:
UnsupportedCall: if the protocol is used as an IPv6 extension header
"""
if self._extf:
raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'payload'")
return super().payload
@property
def protocol(self) -> 'Optional[str] | NoReturn':
"""Name of next layer protocol (if any).
Raises:
UnsupportedCall: if the protocol is used as an IPv6 extension header
"""
if self._extf:
raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'protocol'")
return super().protocol
@property
def protochain(self) -> 'ProtoChain | NoReturn':
"""Protocol chain of current instance.
Raises:
UnsupportedCall: if the protocol is used as an IPv6 extension header
"""
if self._extf:
raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'protochain'")
return super().protochain
##########################################################################
# Methods.
##########################################################################
[docs]
def read(self, length: 'Optional[int]' = None, *, version: 'Literal[4, 6]' = 4, # pylint: disable=arguments-differ,unused-argument
extension: bool = False, **kwargs: 'Any') -> 'Data_MH': # pylint: disable=unused-argument
"""Read Mobility Header.
Structure of MH header [:rfc:`6275`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Proto | Header Len | MH Type | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
. .
. Message Data .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
length: Length of packet data.
version: IP protocol version.
extension: If the protocol is used as an IPv6 extension header.
**kwargs: Arbitrary keyword arguments.
Returns:
Parsed packet data.
Raises:
ProtocolError: If ``Header Len`` declares fewer octets than the fixed
fields of the message type take, a message :rfc:`6275#section-9.2`
has the receiver discard.
.. versionchanged:: 1.6.0
* Such a message is refused, rather than read with the missing fields
as zeros and rebuilt longer than captured (:issue:`2669`).
* A header cut short is rebuilt with the ``Header Len`` it declared,
rather than one worked out from the message (:issue:`2669`).
"""
if length is None:
length = len(self)
schema = self.__header__
# NOTE: A whole message whose fields run past its Header Len reads the rest as
# zeros, and its rebuild writes them and a longer Header Len back (#2669).
if '__short_read__' not in schema.__dict__ and '__short_read__' in schema.data.__dict__:
raise ProtocolError(f'{self.alias}: [Type {schema.type}] invalid header length: {schema.length}')
name = self._lookup_registry(self.__message__, schema.type)
if isinstance(name, str):
meth_name = f'_read_msg_{name}'
meth = cast('PacketParser',
getattr(self, meth_name, self._read_msg_unknown))
else:
meth = name[0]
mh = meth(schema.data, header=schema)
if extension:
# NOTE: As an IPv6 extension header the upper layer belongs to the enclosing
# IPv6 header, so hold NoPayload here so that membership does not raise (#2487).
self._next = NoPayload() # pylint: disable=attribute-defined-outside-init
return mh
return self._decode_next_layer(mh, schema.next, length - mh.length)
[docs]
def make(self,
next: 'Enum_TransType | int' = Enum_TransType.UDP,
type: 'Enum_Packet | int' = Enum_Packet.Binding_Refresh_Request,
reserved: 'int' = 0,
chksum: 'bytes' = b'\x00\x00',
data: 'bytes | Data_MH | Schema_Packet | dict[str, Any]' = b'\x00\x00', # minimum length
payload: 'ProtocolBase | Schema | bytes' = b'',
**kwargs: 'Any') -> 'Schema_MH':
"""Make (construct) packet data.
Args:
next: Next header type.
type: Mobility Header type.
reserved: Reserved octet after the MH type.
chksum: Checksum, two octets.
data: Message data.
payload: Payload of next layer protocol.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed packet data.
Raises:
ProtocolError: If ``data`` is none of the forms this maker reads --
raw :obj:`bytes`, a :obj:`dict` of message arguments, an
:class:`~pcapkit.protocols.internet.mh.data.MH` data model or a
:class:`~pcapkit.protocols.internet.mh.schema.Packet` schema;
or if ``reserved``, or the header length of the message, does
not fit in one octet; if ``chksum`` is not two octets; or if
``next`` or ``type`` is a name, a :obj:`bool` or a code its
field cannot carry.
.. versionchanged:: 1.5.0
Takes ``reserved`` ahead of ``chksum`` (:issue:`1329`), which shifts
every later positional argument.
"""
next_val = self._make_index(next, Enum_TransType, bits=8, name='next header')
type_val = self._make_index(type, Enum_Packet, bits=8, name='MH type')
if isinstance(data, bytes):
data_val = data # type: bytes | Schema_Packet
elif isinstance(data, (dict, Data_MH)):
name = self._lookup_registry(self.__message__, type_val)
if isinstance(name, str):
meth_name = f'_make_msg_{name}'
meth = cast('PacketConstructor',
getattr(self, meth_name, self._make_msg_unknown))
else:
meth = name[1]
if isinstance(data, dict):
data_val = meth(**data)
else:
data_val = meth(data)
elif isinstance(data, Schema_Packet):
data_val = data
else:
raise ProtocolError(f'MH: [Type {type_val}] invalid data: must be bytes, a dict, '
f'an MH data model or a Packet schema, not {data!r}')
# NOTE: Padded until ``len(data) + 6`` is a multiple of 8; rounding ``length`` up alone
# would declare octets never emitted, and the re-parse would read past the buffer.
data_val = self._pad_mh_message(data_val)
# NOTE: A capture cut short rebuilds with the Header Len it declared, not one
# worked out from the message, and its short read cuts the rest (#2669).
header_len = (len(data_val) + 6) // 8 - 1
if isinstance(data, Data_MH) and data.get('__short_read__') is not None:
header_len = data.length // 8 - 1
return Schema_MH(
next=next_val,
length=self._make_uint(header_len, bits=8, name='header length'),
type=type_val,
reserved=self._make_uint(reserved, bits=8, name='reserved octet'),
chksum=self._make_octets(chksum, size=2, name='checksum'),
data=data_val,
payload=payload,
)
[docs]
@classmethod
def register_message(cls, code: 'Enum_Packet', meth: 'str | tuple[PacketParser, PacketConstructor]') -> 'None':
"""Register a message parser.
Args:
code: MH message type code.
meth: Name ``X`` of the ``_read_msg_X`` and ``_make_msg_X``
methods, or a ``(parser, constructor)`` pair of callables, see
:attr:`__message__`.
Warns:
RegistryWarning: If a different entry is already registered for
``code``; it is overwritten. Handing back the entry already
stored, or one equal to it, is a silent no-op. The entry
``code`` shipped with is accepted like any other, so an
override can be undone (:issue:`1362`, :issue:`1363`).
"""
incumbent = cls.__message__.get(code)
if incumbent is not None and incumbent == meth:
return
if incumbent is not None:
warn(f'message type {code} already registered, overwriting', RegistryWarning)
cls.__message__[code] = meth
[docs]
@classmethod
def register_option(cls, code: 'Enum_Option', meth: 'str | tuple[OptionParser, OptionConstructor]') -> 'None':
"""Register an option parser.
Args:
code: MH option code.
meth: Name ``X`` of the ``_read_opt_X`` and ``_make_opt_X``
methods, or a ``(parser, constructor)`` pair of callables, see
:attr:`__option__`.
Warns:
RegistryWarning: If a different entry is already registered for
``code``; it is overwritten. Handing back the entry already
stored, or one equal to it, is a silent no-op. The entry
``code`` shipped with is accepted like any other, so an
override can be undone (:issue:`1362`, :issue:`1363`).
"""
incumbent = cls.__option__.get(code)
if incumbent is not None and incumbent == meth:
return
if incumbent is not None:
warn(f'option {code} already registered, overwriting', RegistryWarning)
cls.__option__[code] = meth
[docs]
@classmethod
def register_extension(cls, code: 'Enum_CGAExtension', meth: 'str | tuple[ExtensionParser, ExtensionConstructor]') -> 'None':
"""Register a CGA extension parser.
Args:
code: CGA extension code.
meth: Name ``X`` of the ``_read_ext_X`` and ``_make_ext_X``
methods, or a ``(parser, constructor)`` pair of callables, see
:attr:`__extension__`.
Warns:
RegistryWarning: If a different entry is already registered for
``code``; it is overwritten. Handing back the entry already
stored, or one equal to it, is a silent no-op. The entry
``code`` shipped with is accepted like any other, so an
override can be undone (:issue:`1362`, :issue:`1363`).
"""
incumbent = cls.__extension__.get(code)
if incumbent is not None and incumbent == meth:
return
if incumbent is not None:
warn(f'extension {code} already registered, overwriting', RegistryWarning)
cls.__extension__[code] = meth
[docs]
@classmethod
def id(cls) -> 'tuple[Literal["MH"]]': # type: ignore[override]
"""Index ID of the protocol.
Returns:
Index ID of the protocol.
"""
return ('MH',)
##########################################################################
# Data models.
##########################################################################
@overload
def __post_init__(self, file: 'IO[bytes] | bytes', length: 'Optional[int]' = ..., *, # pylint: disable=arguments-differ
extension: 'bool' = ..., **kwargs: 'Any') -> 'None': ...
@overload
def __post_init__(self, **kwargs: 'Any') -> 'None': ... # pylint: disable=arguments-differ
[docs]
def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Optional[int]' = None, *, # pylint: disable=arguments-differ
extension: 'bool' = False, **kwargs: 'Any') -> 'None':
"""Post initialisation hook.
Args:
file: Source packet stream.
length: Length of packet data.
extension: If the protocol is used as an IPv6 extension header.
**kwargs: Arbitrary keyword arguments.
See Also:
For construction arguments, please refer to :meth:`self.make <MH.make>`.
"""
#: bool: If the protocol is used as an IPv6 extension header.
self._extf = extension
# call super __post_init__
super().__post_init__(file, length, extension=extension, **kwargs) # type: ignore[arg-type]
def __length_hint__(self) -> 'Literal[8]':
"""Return an estimated length for the object.
Eight octets: the six fixed ones and the message data that
:rfc:`6275#section-6.1.1` counts in the first 8-octet unit.
"""
return 8
[docs]
@classmethod
def __index__(cls) -> 'Enum_TransType': # pylint: disable=invalid-index-returned
"""Numeral registry index of the protocol.
Returns:
Numeral registry index of the protocol in `IANA`_.
.. _IANA: https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
"""
return Enum_TransType.Mobility_Header # type: ignore[return-value]
##########################################################################
# Utilities.
##########################################################################
[docs]
@classmethod
def _make_data(cls, data: 'Data_MH') -> 'dict[str, Any]': # type: ignore[override]
"""Create key-value pairs from ``data`` for protocol construction.
Args:
data: protocol data
Returns:
Key-value pairs for protocol construction.
"""
return {
'next': data.next,
'type': data.type,
'reserved': data.reserved,
'chksum': data.chksum,
'data': data,
'payload': cls._make_payload(data),
}
def _pad_mh_message(self, data: 'Schema_Packet | bytes') -> 'Schema_Packet | bytes':
"""Pad an MH message so that the Mobility Header aligns to 8 octets.
Args:
data: Constructed message data.
Returns:
The message data, with padding options appended if any were needed.
Note:
The padding goes into the message's mobility options, which is where
:rfc:`6275#section-6.1.1` puts it. A message whose body is opaque has
nowhere to put it --
:class:`~pcapkit.protocols.internet.mh.schema.UnknownMessage` and
:class:`~pcapkit.protocols.internet.mh.schema.ExperimentalMessage`
carry raw bytes and no options, as does a ``data`` argument given
directly as :obj:`bytes` -- so for those the padding is appended to the
message body itself and a
:class:`~pcapkit.utilities.warnings.ProtocolWarning` says so.
Appending is necessary rather than optional: ``length`` is
``(len(data) + 6) // 8 - 1``, which floors, so leaving an opaque body
short would emit 10, 12 or 14 octets while declaring 8, and a parser reads
8 and misinterprets the remainder. Since the caller asked for a packet
to be built and the shortfall is recoverable, completing it beats
refusing -- the warning is there because the emitted body is then not
byte-for-byte what was handed in.
"""
pad_opts, pad_len = self._make_pad_options(len(data) + 6)
if pad_len == 0:
return data
options = getattr(data, 'options', None)
# NOTE: The ``isinstance`` test narrows ``data`` for the type checker; at runtime ``getattr``
# has already covered :obj:`bytes` by returning :obj:`None`.
if isinstance(data, bytes) or options is None:
warn(f'{self.alias}: message data of {len(data)} octets carries no '
f'mobility options to hold padding, so {pad_len} octet(s) were '
'appended to the message body to align the header',
ProtocolWarning)
if isinstance(data, bytes):
return data + b'\x00' * pad_len
# An opaque schema body -- UnknownMessage, ExperimentalMessage -- keeps
# its content in ``data`` rather than in options, so that is where the
# octets go. Rebound rather than mutated in place so that ``len()`` and
# ``pack()`` see the change, exactly as for the options branch below.
body = getattr(data, 'data', None)
if not isinstance(body, bytes):
raise ProtocolError(
f'{self.alias}: message data of {len(data)} octets needs '
f'{pad_len} octet(s) of padding, but the body is neither bytes '
'nor a schema carrying bytes, so there is nowhere to put it')
data.data = body + b'\x00' * pad_len
return data
# NOTE: Rebinding the attribute, not mutating the list, marks the schema updated, so ``len()``
# and ``pack()`` count the padding.
data.options = list(options) + pad_opts
return data
[docs]
def _read_msg_unknown(self, schema: 'Schema_UnknownMessage', *,
header: 'Schema_MH') -> 'Data_UnknownMessage':
"""Read unknown MH message type.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_UnknownMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
data=schema.data,
)
return data
[docs]
def _read_msg_brr(self, schema: 'Schema_BindingRefreshRequestMessage', *,
header: 'Schema_MH') -> 'Data_BindingRefreshRequestMessage':
"""Read MH binding refresh request (BRR) message type.
Structure of MH Binding Refresh Request Message [:rfc:`6275`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_BindingRefreshRequestMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
msg_reserved=schema.reserved,
options=self._read_mh_options(schema.options)
)
return data
[docs]
def _read_msg_hoti(self, schema: 'Schema_HomeTestInitMessage', *,
header: 'Schema_MH') -> 'Data_HomeTestInitMessage':
"""Read MH home test initiation (HoTI) message type.
Structure of MH Home Test Initiation Message [:rfc:`6275`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Home Init Cookie +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_HomeTestInitMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
msg_reserved=schema.reserved,
cookie=schema.cookie,
options=self._read_mh_options(schema.options)
)
return data
[docs]
def _read_msg_coti(self, schema: 'Schema_CareofTestInitMessage', *,
header: 'Schema_MH') -> 'Data_CareofTestInitMessage':
"""Read MH care-of test initiation (CoTI) message type.
Structure of MH Care-of Test Initiation Message [:rfc:`6275`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Care-of Init Cookie +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_CareofTestInitMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
msg_reserved=schema.reserved,
cookie=schema.cookie,
options=self._read_mh_options(schema.options)
)
return data
[docs]
def _read_msg_hot(self, schema: 'Schema_HomeTestMessage', *,
header: 'Schema_MH') -> 'Data_HomeTestMessage':
"""Read MH home test (HoT) message type.
Structure of MH Home Test Message [:rfc:`6275`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Home Nonce Index |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Home Init Cookie +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Home Keygen Token +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_HomeTestMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
nonce_index=schema.nonce_index,
cookie=schema.cookie,
token=schema.token,
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_cot(self, schema: 'Schema_CareofTestMessage', *,
header: 'Schema_MH') -> 'Data_CareofTestMessage':
"""Read MH care-of test (CoT) message type.
Structure of MH Care-of Test Message [:rfc:`6275`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Care-of Nonce Index |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Care-of Init Cookie +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Care-of Keygen Token +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_CareofTestMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
nonce_index=schema.nonce_index,
cookie=schema.cookie,
token=schema.token,
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_bu(self, schema: 'Schema_BindingUpdateMessage', *,
header: 'Schema_MH') -> 'Data_BindingUpdateMessage':
"""Read MH binding update (BU) message type.
Structure of MH Binding Update Message [:rfc:`6275`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|A|H|L|K| Reserved | Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Later RFCs assign more of the flag bits, ``M``, ``R``, ``P``,
``F``, ``T``, ``B``, ``S`` and ``D`` in that order (IANA *Binding
Update Flags*); the low four bits remain reserved.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_BindingUpdateMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
ack=bool(schema.flags['A']),
home=bool(schema.flags['H']),
lla_compat=bool(schema.flags['L']),
key_mngt=bool(schema.flags['K']),
map_reg=bool(schema.flags['M']),
mobile_router=bool(schema.flags['R']),
proxy_reg=bool(schema.flags['P']),
udp_encap=bool(schema.flags['F']),
tlv_format=bool(schema.flags['T']),
bulk_binding=bool(schema.flags['B']),
multicast=bool(schema.flags['S']),
dmm=bool(schema.flags['D']),
flags_reserved=schema.flags['reserved'],
lifetime=datetime.timedelta(seconds=schema.lifetime * 4),
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_ba(self, schema: 'Schema_BindingAcknowledgementMessage', *,
header: 'Schema_MH') -> 'Data_BindingAcknowledgementMessage':
"""Read MH binding acknowledgement (BA) message type.
Structure of MH Binding Acknowledgement Message [:rfc:`6275`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Status |K| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # | Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Later RFCs assign more of the flag bits, ``R``, ``P``, ``T``,
``B``, ``S`` and ``D`` in that order (IANA *Binding Acknowledgment
Flags*); the lowest bit remains reserved.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_BindingAcknowledgementMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
status=schema.status,
key_mngt=bool(schema.flags['K']),
mobile_router=bool(schema.flags['R']),
proxy_reg=bool(schema.flags['P']),
tlv_format=bool(schema.flags['T']),
bulk_binding=bool(schema.flags['B']),
multicast=bool(schema.flags['S']),
dmm=bool(schema.flags['D']),
flags_reserved=schema.flags['reserved'],
seq=schema.seq,
lifetime=datetime.timedelta(seconds=schema.lifetime * 4),
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_be(self, schema: 'Schema_BindingErrorMessage', *,
header: 'Schema_MH') -> 'Data_BindingErrorMessage':
"""Read MH binding error (BE) message type.
Structure of MH Binding Error Message [:rfc:`6275`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Status | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Home Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_BindingErrorMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
status=schema.status,
msg_reserved=schema.reserved,
home=schema.home,
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_fbu(self, schema: 'Schema_FastBindingUpdateMessage', *,
header: 'Schema_MH') -> 'Data_FastBindingUpdateMessage':
"""Read MH fast binding update (FBU) message type.
Structure of MH Fast Binding Update Message [:rfc:`5568#section-6.2.2`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|A|H|L|K| Reserved | Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`5568#section-6.2.2` states that the FBU is *identical* to the
Mobile IPv6 binding update (BU) message -- but that statement is
about **message layout**, not field units. The same section defines
the Lifetime as "the requested time in seconds", unlike the BU
lifetime's units of 4 seconds [:rfc:`6275#section-6.1.7`], so it is
not scaled on the way in.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_FastBindingUpdateMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
ack=bool(schema.flags['A']),
home=bool(schema.flags['H']),
lla_compat=bool(schema.flags['L']),
key_mngt=bool(schema.flags['K']),
flags_reserved=schema.flags['reserved'],
lifetime=datetime.timedelta(seconds=schema.lifetime),
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_fback(self, schema: 'Schema_FastBindingAcknowledgmentMessage', *,
header: 'Schema_MH') -> 'Data_FastBindingAcknowledgmentMessage':
"""Read MH fast binding acknowledgment (FBack) message type.
Structure of MH Fast Binding Acknowledgment Message [:rfc:`5568#section-6.2.3`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Status |K| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # | Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`5568#section-6.2.3` defines the FBack status values inline
instead of drawing them from the IANA *Status Codes* registry, whose
values ``1`` and ``131`` mean something else entirely. The status is
therefore reported as a
:class:`~pcapkit.protocols.internet.mh.protocol.FastBindingAcknowledgmentStatus`,
which is local to this module, rather than being mislabelled as a
:class:`~pcapkit.const.mh.status_code.StatusCode`.
The Lifetime is likewise defined by :rfc:`5568#section-6.2.3` as
"the granted lifetime ... in seconds", unlike the BA lifetime's
units of 4 seconds [:rfc:`6275#section-6.1.7`], so it is not scaled
on the way in.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_FastBindingAcknowledgmentMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
status=FastBindingAcknowledgmentStatus(schema.status),
key_mngt=bool(schema.flags['K']),
flags_reserved=schema.flags['reserved'],
seq=schema.seq,
lifetime=datetime.timedelta(seconds=schema.lifetime),
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_fna(self, schema: 'Schema_FastNeighborAdvertisementMessage', *,
header: 'Schema_MH') -> 'Data_FastNeighborAdvertisementMessage':
"""Read MH fast neighbor advertisement (FNA) message type.
Structure of MH Fast Neighbor Advertisement Message [:rfc:`4068#section-6.3.3`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The FNA message is **deprecated** by :rfc:`5568#section-8`, which
replaces it with the unsolicited neighbor advertisement (UNA) of
:rfc:`4861`. Its wire format is therefore taken from :rfc:`4068`,
the specification :rfc:`5568` obsoletes, since a NAR is still
permitted to process the message as specified there.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_FastNeighborAdvertisementMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
msg_reserved=schema.reserved,
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_emh(self, schema: 'Schema_ExperimentalMessage', *,
header: 'Schema_MH') -> 'Data_ExperimentalMessage':
"""Read MH experimental mobility header message type.
Structure of MH Experimental Mobility Header Message [:rfc:`5096#section-3`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Proto | Header Len | MH Type | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
. .
. Message Data .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`5096#section-3` defines no fields beyond those of the mobility
header itself, so the message data is carried opaquely -- its layout
is whatever the experiment in question defines. Two bytes of padding
are required when no data is present.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_ExperimentalMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
data=schema.data,
)
return data
[docs]
def _read_msg_has(self, schema: 'Schema_HomeAgentSwitchMessage', *,
header: 'Schema_MH') -> 'Data_HomeAgentSwitchMessage':
"""Read MH home agent switch (HAS) message type.
Structure of MH Home Agent Switch Message [:rfc:`5142#section-4`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|# of Addresses | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
. .
. Home Agent Addresses .
. .
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
. .
. Mobility Options .
. .
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The ``# of Addresses`` field is not carried into the data model,
since it is exactly ``len(addresses)`` and a second copy of a length
is a second thing to keep in step. An empty list is meaningful rather
than degenerate: :rfc:`5142#section-4` uses a count of zero to tell
the mobile node to run home agent discovery instead.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
if schema.count != len(schema.addresses):
raise ProtocolError(f'{self.alias}: [Type {header.type}] invalid format')
data = Data_HomeAgentSwitchMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
msg_reserved=schema.reserved,
addresses=tuple(schema.addresses),
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_hb(self, schema: 'Schema_HeartbeatMessage', *,
header: 'Schema_MH') -> 'Data_HeartbeatMessage':
"""Read MH heartbeat message type.
Structure of MH Heartbeat Message [:rfc:`5847#section-3.3`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved |U|R|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The ``U`` and ``R`` flags are the two **least** significant bits of
the first half-word, not the most significant ones the other mobility
messages put their flags in [:rfc:`5847#section-3.3`]. The sequence
number is 32 bits wide, also unlike the 16-bit ones elsewhere.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_HeartbeatMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
unsolicited=bool(schema.flags['U']),
response=bool(schema.flags['R']),
flags_reserved=schema.flags['reserved'],
seq=schema.seq,
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_hi(self, schema: 'Schema_HandoverInitiateMessage', *,
header: 'Schema_MH') -> 'Data_HandoverInitiateMessage':
"""Read MH handover initiate (HI) message type.
Structure of MH Handover Initiate Message [:rfc:`5568#section-6.2.1.1`,
:rfc:`5949#section-6.1.1`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|S|U|P|F| Resv | Code | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .
| |
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`5568` defines only the ``S`` and ``U`` flags; the ``P`` and
``F`` flags, and code values ``2`` and ``3``, are added by
:rfc:`5949#section-6.1.1`. All of these flags are registered in the IANA
*Handover Initiate Flags* registry, hence all are parsed here.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_HandoverInitiateMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
assign=bool(schema.flags['S']),
buffer=bool(schema.flags['U']),
proxy=bool(schema.flags['P']),
forward=bool(schema.flags['F']),
flags_reserved=schema.flags['reserved'],
code=schema.code,
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_hack(self, schema: 'Schema_HandoverAcknowledgeMessage', *,
header: 'Schema_MH') -> 'Data_HandoverAcknowledgeMessage':
"""Read MH handover acknowledge (HAck) message type.
Structure of MH Handover Acknowledge Message [:rfc:`5568#section-6.2.1.2`,
:rfc:`5949#section-6.1.2`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|U|P|F| Reserved| Code | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .
| |
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`5568` reserves the whole first octet of the message data; the
``U``, ``P`` and ``F`` flags carved out of it, and code values ``5``,
``6``, ``131`` and ``132``, are added by :rfc:`5949#section-6.1.2`.
All of these flags are registered in the IANA *Handover Acknowledge
Flags* registry, hence all are parsed here.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_HandoverAcknowledgeMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
buffer=bool(schema.flags['U']),
proxy=bool(schema.flags['P']),
forward=bool(schema.flags['F']),
flags_reserved=schema.flags['reserved'],
code=schema.code,
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_brm(self, schema: 'Schema_BindingRevocationMessage', *,
header: 'Schema_MH') -> 'Data_BindingRevocationMessage':
"""Read MH binding revocation (BRM) message type.
Structure of MH Binding Revocation Indication Message
[:rfc:`5846#section-5.1`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| B.R. Type = 1 | R. Trigger |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |P|V|G| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility options .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Structure of MH Binding Revocation Acknowledgement Message
[:rfc:`5846#section-5.2`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| B.R. Type = 2 | Status |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |P|V|G| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
One Mobility Header type carries both forms, told apart by the
``B.R. Type`` octet **inside** the message rather than by the header
type. The two have identical geometry, and the only field whose
meaning differs is the octet after it -- a revocation trigger in an
indication and a status code in an acknowledgement, drawn from two
different registries -- so a single data model carries both, with
:attr:`~pcapkit.protocols.internet.mh.data.BindingRevocationMessage.br_type`
saying which it is.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_BindingRevocationMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
br_type=schema.br_type,
code=schema.code,
seq=schema.seq,
proxy=bool(schema.flags['P']),
ipv4_hoa=bool(schema.flags['V']),
global_revocation=bool(schema.flags['G']),
flags_reserved=schema.flags['reserved'],
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_lri(self, schema: 'Schema_LocalizedRoutingInitiationMessage', *,
header: 'Schema_MH') -> 'Data_LocalizedRoutingInitiationMessage':
"""Read MH localized routing initiation (LRI) message type.
Structure of MH Localized Routing Initiation Message
[:rfc:`6705#section-10.1`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved | Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The lifetime is in **seconds** here, not in the units of 4 seconds
that :rfc:`6275` uses for the binding messages
[:rfc:`6705#section-10.1`], so it is not scaled on the way in.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_LocalizedRoutingInitiationMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
msg_reserved=schema.reserved,
lifetime=datetime.timedelta(seconds=schema.lifetime),
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_lra(self, schema: 'Schema_LocalizedRoutingAcknowledgmentMessage', *,
header: 'Schema_MH') -> 'Data_LocalizedRoutingAcknowledgmentMessage':
"""Read MH localized routing acknowledgment (LRA) message type.
Structure of MH Localized Routing Acknowledgment Message
[:rfc:`6705#section-10.2`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|U| Reserved | Status | Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The status codes are enumerated in
:class:`~pcapkit.protocols.internet.mh.protocol.LocalizedRoutingStatus`, which is
local to this module because :rfc:`6705` defines them inline and IANA
registers them nowhere -- not even in the general *Status Codes*
registry.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_LocalizedRoutingAcknowledgmentMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
unsolicited=bool(schema.flags['U']),
flags_reserved=schema.flags['reserved'],
status=LocalizedRoutingStatus(schema.status),
lifetime=datetime.timedelta(seconds=schema.lifetime),
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_upn(self, schema: 'Schema_UpdateNotificationMessage', *,
header: 'Schema_MH') -> 'Data_UpdateNotificationMessage':
"""Read MH update notification (UPN) message type.
Structure of MH Update Notification Message [:rfc:`7077#section-4.1`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Notification Reason |A|D| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The notification reason is **16** bits wide, not the 8 that a status
octet elsewhere in the Mobility Header would be.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_UpdateNotificationMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
reason=schema.reason,
ack=bool(schema.flags['A']),
retransmit=bool(schema.flags['D']),
flags_reserved=schema.flags['reserved'],
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_upa(self, schema: 'Schema_UpdateNotificationAcknowledgementMessage', *,
header: 'Schema_MH') -> 'Data_UpdateNotificationAcknowledgementMessage':
"""Read MH update notification acknowledgement (UPA) message type.
Structure of MH Update Notification Acknowledgement Message
[:rfc:`7077#section-4.2`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Status Code | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_UpdateNotificationAcknowledgementMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
status=schema.status,
msg_reserved=schema.reserved,
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_fbm(self, schema: 'Schema_FlowBindingMessage', *,
header: 'Schema_MH') -> 'Data_FlowBindingMessage':
"""Read MH flow binding (FB) message type.
Structure of MH Flow Binding Indication Message
[:rfc:`7109#section-6.1.1`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flow Binding Type = 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # | Trigger |A| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility options .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Structure of MH Flow Binding Acknowledgement Message
[:rfc:`7109#section-6.1.2`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flow Binding Type = 2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # | Status | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility options .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
As with the binding revocation message, one Mobility Header type
carries both forms and the ``Flow Binding Type`` field inside the
message says which. The ``A`` flag exists only in the indication; the
acknowledgement reserves the whole octet it is taken from, so a
well-formed acknowledgement reads back as ``ack=False``.
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_FlowBindingMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
fb_type=schema.fb_type,
seq=schema.seq,
code=schema.code,
ack=bool(schema.flags['A']),
flags_reserved=schema.flags['reserved'],
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_sq(self, schema: 'Schema_SubscriptionQueryMessage', *,
header: 'Schema_MH') -> 'Data_SubscriptionQueryMessage':
"""Read MH subscription query (SQ) message type.
Structure of MH Subscription Query Message [:rfc:`7161#section-4.3.1.2`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The sequence number is a single octet, counted modulo 256, unlike the
16-bit ones of most other mobility messages
[:rfc:`7161#section-4.3.1.2`].
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_SubscriptionQueryMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
msg_reserved=schema.reserved,
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_msg_sr(self, schema: 'Schema_SubscriptionResponseMessage', *,
header: 'Schema_MH') -> 'Data_SubscriptionResponseMessage':
"""Read MH subscription response (SR) message type.
Structure of MH Subscription Response Message
[:rfc:`7161#section-4.3.2.2`]:
.. code-block:: text
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence # |I| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Mobility Options .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed message type schema.
header: Parsed MH header schema.
Returns:
Parsed message type data.
"""
data = Data_SubscriptionResponseMessage(
next=header.next,
length=self._mh_message_length(header.length),
type=header.type,
reserved=header.reserved,
chksum=header.chksum,
seq=schema.seq,
info=bool(schema.flags['I']),
flags_reserved=schema.flags['reserved'],
options=self._read_mh_options(schema.options),
)
return data
[docs]
def _read_opt_none(self, schema: 'Schema_UnassignedOption', *,
options: 'Option') -> 'Data_UnassignedOption':
"""Read MH unassigned option.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
data = Data_UnassignedOption(
type=schema.type,
length=self._mh_option_length(schema.length),
data=schema.data,
)
return data
[docs]
def _read_opt_pad(self, schema: 'Schema_PadOption', *,
options: 'Option') -> 'Data_PadOption':
"""Read MH padding option.
Structure of MH padding option [:rfc:`6275`]:
* ``Pad1`` option:
.. code-block:: text
0
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
| Type = 0 |
+-+-+-+-+-+-+-+-+
* ``PadN`` option:
.. code-block:: text
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
| Type = 1 | Option Length | Option Data
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
Note:
A ``Pad1`` option occupies a single octet and carries no
``Option Length`` field, so its
:attr:`~pcapkit.protocols.internet.mh.data.Option.length` is ``1``
rather than ``length + 2``. That one-octet wire shape is enforced
by :class:`~pcapkit.protocols.internet.mh.schema.PadOption` itself,
which sizes both the length octet and the padding data from the
option type [:rfc:`6275#section-6.2.2`]; ``clen`` is therefore
always ``0`` here for a parsed ``Pad1``, and the length check this
method makes only guards a schema built by hand. A ``PadN`` whose
``Option Length`` is ``0`` is a valid two-octet padding option
[:rfc:`6275#section-6.2.3`].
"""
code, clen = schema.type, schema.length
if code not in (Enum_Option.Pad1, Enum_Option.PadN):
raise ProtocolError(f'{self.alias}: [OptNo {code}] invalid format')
if code == Enum_Option.Pad1 and clen != 0:
raise ProtocolError(f'{self.alias}: [OptNo {code}] invalid format')
if code == Enum_Option.Pad1:
size = 1
else:
size = self._mh_option_length(clen)
data = Data_PadOption(
type=schema.type,
length=size,
data=schema.data,
)
return data
[docs]
def _read_opt_bra(self, schema: 'Schema_BindingRefreshAdviceOption', *,
options: 'Option') -> 'Data_BindingRefreshAdviceOption':
"""Read MH binding refresh advice option.
Structure of MH Binding Refresh Advice option [:rfc:`6275`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 2 | Length = 2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Refresh Interval |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The Refresh Interval is measured in units of four seconds
[:rfc:`6275#section-6.2.4`], the same unit the BU/BA lifetime uses,
so it is scaled here.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_BindingRefreshAdviceOption(
type=schema.type,
length=self._mh_option_length(schema.length),
interval=datetime.timedelta(seconds=schema.interval * 4),
)
return data
[docs]
def _read_opt_aca(self, schema: 'Schema_AlternateCareofAddressOption', *,
options: 'Option') -> 'Data_AlternateCareofAddressOption':
"""Read MH alternate care-of address option.
Structure of MH Alternate Care-of Address option [:rfc:`6275`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 3 | Length = 16 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Alternate Care-of Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 16:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_AlternateCareofAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
address=schema.address,
)
return data
[docs]
def _read_opt_ni(self, schema: 'Schema_NonceIndicesOption', *,
options: 'Option') -> 'Data_NonceIndicesOption':
"""Read MH nonce indices option.
Structure of MH Nonce Indices option [:rfc:`6275`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 4 | Length = 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Home Nonce Index | Care-of Nonce Index |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 4:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_NonceIndicesOption(
type=schema.type,
length=self._mh_option_length(schema.length),
home=schema.home,
careof=schema.careof,
)
return data
[docs]
def _read_opt_bad(self, schema: 'Schema_AuthorizationDataOption', *,
options: 'Option') -> 'Data_AuthorizationDataOption':
"""Read MH binding authorization data option.
Structure of MH Binding Authorization Data option [:rfc:`6275`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 5 | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| Authenticator |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length % 8 != 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_AuthorizationDataOption(
type=schema.type,
length=self._mh_option_length(schema.length),
data=schema.data,
)
return data
[docs]
def _read_opt_mnp(self, schema: 'Schema_MobileNetworkPrefixOption', *,
options: 'Option') -> 'Data_MobileNetworkPrefixOption':
"""Read MH mobile network prefix option.
Structure of MH Mobile Network Prefix option [:rfc:`3963`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved | Prefix Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Mobile Network Prefix +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 18:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
# NOTE: Prefix and length stay apart, as captured: an IPv6Network holds neither bits past the
# length nor a length past 128; RFC 3963 section 6.2 answers a bad prefix with a status (#2702).
data = Data_MobileNetworkPrefixOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
prefix_length=schema.prefix_length,
prefix=schema.prefix,
)
return data
[docs]
def _read_opt_lla(self, schema: 'Schema_LinkLayerAddressOption', *,
options: 'Option') -> 'Data_LinkLayerAddressOption':
"""Read MH link-layer address (MH-LLA) option.
Structure of MH Link-Layer Address option [:rfc:`5568`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option-Code | LLA ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.code != Enum_LLACode.MN:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_LinkLayerAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
code=schema.code,
lla=schema.lla,
)
return data
[docs]
def _read_opt_mn_id(self, schema: 'Schema_MNIDOption', *,
options: 'Option') -> 'Data_MNIDOption':
"""Read MH mobile node identifier option.
Structure of MH Mobile Node Identifier option [:rfc:`4283`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Subtype | Identifier ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
data = Data_MNIDOption(
type=schema.type,
length=self._mh_option_length(schema.length),
subtype=schema.subtype,
identifier=schema.identifier,
)
return data
[docs]
def _read_opt_auth(self, schema: 'Schema_AuthOption', *,
options: 'Option') -> 'Data_AuthOption':
"""Read MH mobility message authentication option.
Structure of MH Mobility Message Authentication option [:rfc:`4285`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length | Subtype |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Mobility SPI |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Authentication Data ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if (schema.length + 1) % 4 != 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_AuthOption(
type=schema.type,
length=self._mh_option_length(schema.length),
subtype=schema.subtype,
spi=schema.spi,
data=schema.data,
)
return data
[docs]
def _read_opt_mesg_id(self, schema: 'Schema_MesgIDOption', *,
options: 'Option') -> 'Data_MesgIDOption':
"""Read MH mobility message replay protection option.
Structure of MH Mobility Message Replay Protection option [:rfc:`4285`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if (schema.length) % 8 != 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_MesgIDOption(
type=schema.type,
length=self._mh_option_length(schema.length),
timestamp=schema.timestamp,
ntp_timestamp=Data_NTPTimestamp(seconds=schema.seconds, fraction=schema.fraction),
)
return data
[docs]
def _read_opt_cga_pr(self, schema: 'Schema_CGAParametersRequestOption', *,
options: 'Option') -> 'Data_CGAParametersRequestOption':
"""Read MH CGA parameters request option.
Structure of MH CGA Parameters Request option [:rfc:`4866`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_CGAParametersRequestOption(
type=schema.type,
length=self._mh_option_length(schema.length),
)
return data
[docs]
def _read_opt_cga_param(self, schema: 'Schema_CGAParametersOption', *,
options: 'Option') -> 'Data_CGAParametersOption':
"""Read MH CGA parameters option.
Structure of MH CGA Parameters option [:rfc:`4866`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
: :
: CGA Parameters :
: :
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
for param in schema.parameters:
if param.collision_count not in (0, 1, 2):
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_CGAParametersOption(
type=schema.type,
length=self._mh_option_length(schema.length),
parameters=tuple(Data_CGAParameter(
modifier=param.modifier,
prefix=param.prefix,
collision_count=param.collision_count,
public_key=param.public_key,
extensions=self._read_cga_extensions(param.extensions),
) for param in schema.parameters),
)
return data
[docs]
def _read_opt_signature(self, schema: 'Schema_SignatureOption', *,
options: 'Option') -> 'Data_SignatureOption':
"""Read MH signature option.
Structure of MH Signature option [:rfc:`4866`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
: :
: Signature :
: :
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
data = Data_SignatureOption(
type=schema.type,
length=self._mh_option_length(schema.length),
signature=schema.signature,
)
return data
[docs]
def _read_opt_phkt(self, schema: 'Schema_PermanentHomeKeygenTokenOption', *,
options: 'Option') -> 'Data_PermanentHomeKeygenTokenOption':
"""Read MH permanent home keygen token option.
Structure of MH Permanent Home Keygen Token option [:rfc:`4866`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
: :
: Permanent Home Keygen Token :
: :
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
data = Data_PermanentHomeKeygenTokenOption(
type=schema.type,
length=self._mh_option_length(schema.length),
token=schema.token,
)
return data
[docs]
def _read_opt_ct_init(self, schema: 'Schema_CareofTestInitOption', *,
options: 'Option') -> 'Data_CareofTestInitOption':
"""Read MH Care-of Test Init option.
Structure of MH Care-of Test Init option [:rfc:`4866`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_CareofTestInitOption(
type=schema.type,
length=self._mh_option_length(schema.length),
)
return data
[docs]
def _read_opt_ct(self, schema: 'Schema_CareofTestOption', *,
options: 'Option') -> 'Data_CareofTestOption':
"""Read MH Care-of Test option.
Structure of MH Care-of Test option [:rfc:`4866`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Care-of Keygen Token +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 8:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_CareofTestOption(
type=schema.type,
length=self._mh_option_length(schema.length),
token=schema.token,
)
return data
[docs]
def _read_opt_dns(self, schema: 'Schema_DNSUpdateOption', *,
options: 'Option') -> 'Data_DNSUpdateOption':
"""Read MH DNS-UPDATE-TYPE option.
Structure of MH DNS-UPDATE-TYPE option [:rfc:`5026#section-8.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Status |R| Reserved | MN identity (FQDN) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The status field draws from the *Status Codes (DNS Update Mobility
Option)* registry that :rfc:`5026#section-10` creates, **not** from
the general mobility status codes, which is why it is typed
:class:`~pcapkit.const.mh.dns_status_code.DNSStatusCode`.
The identity is kept as :obj:`bytes` rather than decoded to
:class:`str`, because the RFC says only "FQDN format" without saying
whether that means the presentation form or the length-prefixed
label form of :rfc:`1035`.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_DNSUpdateOption(
type=schema.type,
length=self._mh_option_length(schema.length),
status=schema.status,
remove=bool(schema.flags['R']),
reserved=schema.flags['reserved'],
identity=schema.identity,
)
return data
[docs]
def _read_opt_exp(self, schema: 'Schema_ExperimentalMobilityOption', *,
options: 'Option') -> 'Data_ExperimentalMobilityOption':
"""Read MH experimental mobility option.
Structure of MH Experimental Mobility option [:rfc:`5096#section-4`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Data .....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
data = Data_ExperimentalMobilityOption(
type=schema.type,
length=self._mh_option_length(schema.length),
data=schema.data,
)
return data
[docs]
def _read_opt_vendor(self, schema: 'Schema_VendorSpecificOption', *,
options: 'Option') -> 'Data_VendorSpecificOption':
"""Read MH vendor specific mobility option.
Structure of MH Vendor Specific mobility option [:rfc:`5094#section-3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Vendor ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sub-Type | Data.......
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The vendor ID is an SMI Network Management Private Enterprise Number,
whose space is unbounded, so it is not enumerated. The sub-type is
administered by that vendor rather than by IANA, so it is not
enumerated either.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 5:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_VendorSpecificOption(
type=schema.type,
length=self._mh_option_length(schema.length),
vendor=schema.vendor,
subtype=schema.subtype,
data=schema.data,
)
return data
[docs]
def _read_opt_service(self, schema: 'Schema_ServiceSelectionOption', *,
options: 'Option') -> 'Data_ServiceSelectionOption':
"""Read MH service selection mobility option.
Structure of MH Service Selection mobility option [:rfc:`5149#section-3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 20 | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
Unlike the DNS identity of :meth:`_read_opt_dns`, this identifier is
decoded to :class:`str`: :rfc:`5149#section-3` states outright that it
is UTF-8 and NFKC-normalised. A length of zero is invalid.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length == 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_ServiceSelectionOption(
type=schema.type,
length=self._mh_option_length(schema.length),
identifier=schema.identifier,
)
return data
[docs]
def _read_opt_badf(self, schema: 'Schema_BADFOption', *,
options: 'Option') -> 'Data_BADFOption':
"""Read MH binding authorization data for FMIPv6 (BADF) option.
Structure of MH Binding Authorization Data for FMIPv6 option
[:rfc:`5568#section-6.4.5`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 21 | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SPI |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| Authenticator |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`5568#section-6.4.5` defines the option length as *the length of
the Authenticator in bytes*, i.e. it does **not** cover the 4-byte
SPI, unlike every other mobility option, whose length covers all of
its option data. The wording is inherited from the :rfc:`6275`
binding authorization data option (type ``5``), which carries no SPI
and for which the two readings coincide. Because :rfc:`5568` also
requires this option to be the **last** mobility option present, the
discrepancy never has to be resolved in order to find the following
option, so the literal reading is used here and the reported
:attr:`~pcapkit.protocols.internet.mh.data.Option.length` accounts
for the extra 4 bytes.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length == 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_BADFOption(
type=schema.type,
length=schema.length + 6, # 2 bytes for type & length, 4 bytes for SPI
spi=schema.spi,
data=schema.data,
)
return data
[docs]
def _read_opt_hnp(self, schema: 'Schema_HomeNetworkPrefixOption', *,
options: 'Option') -> 'Data_HomeNetworkPrefixOption':
"""Read MH home network prefix option.
Structure of MH Home Network Prefix option [:rfc:`5213#section-8.3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved | Prefix Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Home Network Prefix +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 18:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
# NOTE: A prefix length past 128 is kept, as for option 6: RFC 5213 section 5.3.2 answers an
# unauthorisable prefix with a status rather than discarding the option (#2702).
data = Data_HomeNetworkPrefixOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
prefix_length=schema.prefix_length,
prefix=schema.prefix,
)
return data
[docs]
def _read_opt_hi(self, schema: 'Schema_HandoffIndicatorOption', *,
options: 'Option') -> 'Data_HandoffIndicatorOption':
"""Read MH handoff indicator option.
Structure of MH Handoff Indicator option [:rfc:`5213#section-8.4`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved (R) | HI |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_HandoffIndicatorOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
hi=schema.hi,
)
return data
[docs]
def _read_opt_att(self, schema: 'Schema_AccessTechnologyTypeOption', *,
options: 'Option') -> 'Data_AccessTechnologyTypeOption':
"""Read MH access technology type option.
Structure of MH Access Technology Type option [:rfc:`5213#section-8.5`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved (R) | ATT |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`5213` itself defines only access types ``0`` through ``5``.
Values ``6`` through ``13`` were registered directly with IANA against
3GPP and 3GPP2 specifications rather than through an updating RFC, and
:class:`~pcapkit.const.mh.access_type.AccessType` carries all of them.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_AccessTechnologyTypeOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
att=schema.att,
)
return data
[docs]
def _read_opt_mn_lli(self, schema: 'Schema_MNLLIdentifierOption', *,
options: 'Option') -> 'Data_MNLLIdentifierOption':
"""Read MH mobile node link-layer identifier option.
Structure of MH Mobile Node Link-layer Identifier option
[:rfc:`5213#section-8.6`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Link-layer Identifier +
. ... .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The identifier's octet and bit ordering is that of
:rfc:`4861#section-4.6`, but only the *address encoding* is borrowed
from there -- the option keeps its own mobility-option framing, in
which the length is in octets rather than in the 8-octet units
neighbour discovery uses.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_MNLLIdentifierOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
lli=schema.lli,
)
return data
[docs]
def _read_opt_lla_addr(self, schema: 'Schema_LinkLocalAddressOption', *,
options: 'Option') -> 'Data_LinkLocalAddressOption':
"""Read MH link-local address option.
Structure of MH Link-local Address option [:rfc:`5213#section-8.7`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Link-local Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`6543` reserves ``0200:5EFF:FE00:5213`` as the interface
identifier a mobile access gateway may use here when it does not vary
the address per access link. That changes which values are expected,
not the option's shape.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 16:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_LinkLocalAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
address=schema.address,
)
return data
[docs]
def _read_opt_timestamp(self, schema: 'Schema_TimestampOption', *,
options: 'Option') -> 'Data_TimestampOption':
"""Read MH timestamp option.
Structure of MH Timestamp option [:rfc:`5213#section-8.8`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Timestamp +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
This is **not** the :rfc:`1305` NTP timestamp that
:meth:`_read_opt_mesg_id` reads, and the two must not be conflated.
:rfc:`5213#section-8.8` counts seconds from the UNIX epoch, not NTP's
1900 one, and splits the 64 bits 48/16 rather than 32/32. Reading it
as an NTP timestamp would be wrong in both the epoch and the field
widths, which is why it gets its own
:class:`~pcapkit.protocols.internet.mh.data.PMIPv6Timestamp`.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 8:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
seconds = schema.timestamp['seconds']
fraction = schema.timestamp['fraction']
try:
# NOTE: Not the stdlib's, which stops at 3000 on Windows (#2099); the whole seconds
# go in as an int, so the fraction cannot carry the last second of 9999 past it.
timestamp = (fromtimestamp(seconds, datetime.timezone.utc)
+ datetime.timedelta(seconds=fraction / 65536)) # type: Optional[dt_type]
except ValueError:
# past the year 9999, which the 48-bit count reaches and datetime
# cannot hold, so pmip_timestamp alone keeps it (#2702)
timestamp = None
data = Data_TimestampOption(
type=schema.type,
length=self._mh_option_length(schema.length),
timestamp=timestamp,
pmip_timestamp=Data_PMIPv6Timestamp(seconds=seconds, fraction=fraction),
)
return data
[docs]
def _read_opt_restart(self, schema: 'Schema_RestartCounterOption', *,
options: 'Option') -> 'Data_RestartCounterOption':
"""Read MH restart counter option.
Structure of MH Restart Counter option [:rfc:`5847#section-3.4`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Restart Counter |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`5847#section-3.4` says this option is valid only in a heartbeat
*response*. That is a constraint on the sender rather than on the wire
format, so it is not enforced here -- a capture carries whatever it
carries.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 4:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_RestartCounterOption(
type=schema.type,
length=self._mh_option_length(schema.length),
counter=schema.counter,
)
return data
[docs]
def _read_opt_ipv4_hoa(self, schema: 'Schema_IPv4HomeAddressOption', *,
options: 'Option') -> 'Data_IPv4HomeAddressOption':
"""Read MH IPv4 home address option.
Structure of MH IPv4 Home Address option [:rfc:`5555#section-3.1.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |Prefix-len |P| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IPv4 home address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
A prefix length of ``32`` means a single address rather than a prefix,
and ``0`` is invalid [:rfc:`5555#section-3.1.1`].
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
prefix_length = schema.flags['prefix_length']
if prefix_length == 0 or prefix_length > 32:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_IPv4HomeAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
prefix_length=prefix_length,
address=schema.address,
request_prefix=bool(schema.flags['P']),
reserved=schema.flags['reserved'],
)
return data
[docs]
def _read_opt_ipv4_ack(self, schema: 'Schema_IPv4AddressAcknowledgementOption', *,
options: 'Option') -> 'Data_IPv4AddressAcknowledgementOption':
"""Read MH IPv4 address acknowledgement option.
Structure of MH IPv4 Address Acknowledgement option
[:rfc:`5555#section-3.2.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Status |Pref-len |Res|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IPv4 home address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The status field draws from the *DSMIPv6 IPv4 Home Address Option
Status Codes* registry of :rfc:`5555#section-8`, which despite its
name governs **this** option rather than the IPv4 home address option.
It is not the registry the similarly shaped IPv4 Home Address Reply
option of :rfc:`5844` uses: that one has no value ``133``. Nor is it
the pseudo-home-address registry of :rfc:`5726` that
:class:`~pcapkit.const.mh.ack_status_code.ACKStatusCode` carries,
whose name is the most obvious trap of these.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_IPv4AddressAcknowledgementOption(
type=schema.type,
length=self._mh_option_length(schema.length),
status=schema.status,
prefix_length=schema.flags['prefix_length'],
reserved=schema.flags['reserved'],
address=schema.address,
)
return data
[docs]
def _read_opt_nat(self, schema: 'Schema_NATDetectionOption', *,
options: 'Option') -> 'Data_NATDetectionOption':
"""Read MH NAT detection option.
Structure of MH NAT Detection option [:rfc:`5555#section-3.2.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |F| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Refresh time |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
A refresh time of ``0`` means the field is to be ignored, and all ones
that no keep-alives are needed [:rfc:`5555#section-3.2.2`]. Both are
representable as a :class:`~datetime.timedelta`, so the raw value is
recoverable from it without being stored twice.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_NATDetectionOption(
type=schema.type,
length=self._mh_option_length(schema.length),
force=bool(schema.flags['F']),
reserved=schema.flags['reserved'],
refresh=datetime.timedelta(seconds=schema.refresh),
)
return data
[docs]
def _read_opt_ipv4_coa(self, schema: 'Schema_IPv4CareofAddressOption', *,
options: 'Option') -> 'Data_IPv4CareofAddressOption':
"""Read MH IPv4 care-of address option.
Structure of MH IPv4 Care-of Address option [:rfc:`5555#section-3.1.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IPv4 Care-of address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
Unlike the IPv4 home address option, the whole half-word before the
address is reserved -- there is no prefix length and no ``P`` flag
carved out of it.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_IPv4CareofAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
address=schema.address,
)
return data
[docs]
def _read_opt_gre(self, schema: 'Schema_GREKeyOption', *,
options: 'Option') -> 'Data_GREKeyOption':
"""Read MH GRE key option.
Structure of MH GRE Key option [:rfc:`5845#section-6.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| GRE Key Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The key identifier is present only when the option length is ``6``; a
length of ``2`` says the option carries none
[:rfc:`5845#section-6.1`]. No flag announces that, so the length is
the only thing to branch on, and any other length is malformed.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length not in (2, 6):
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_GREKeyOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
key=schema.key if schema.length == 6 else None,
)
return data
[docs]
def _read_opt_ipv6_ap(self, schema: 'Schema_IPv6AddressPrefixOption', *,
options: 'Option') -> 'Data_IPv6AddressPrefixOption':
"""Read MH mobility header IPv6 address/prefix option.
Structure of MH Mobility Header IPv6 Address/Prefix option
[:rfc:`5568#section-6.4.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Option-Code | Prefix Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ IPv6 Address/Prefix +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The option code identifies which address the option carries, see
:class:`~pcapkit.protocols.internet.mh.protocol.IPv6AddressPrefixCode`. It, too,
is defined inline by :rfc:`5568#section-6.4.2` with no IANA registry
behind it, hence the enumeration is local to this module.
Note:
:rfc:`5568` prints the type as ``17``, which is the *neighbor
discovery* option type of its sibling in
:rfc:`5568#section-6.4.1`. Errata ID 1816 (verified) corrects it to
the IANA-assigned mobility option type ``34``, which is what this
handler is registered against.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 18:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
if schema.prefix_length > 128:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_IPv6AddressPrefixOption(
type=schema.type,
length=self._mh_option_length(schema.length),
code=IPv6AddressPrefixCode(schema.code),
prefix_length=schema.prefix_length,
address=schema.address,
)
return data
[docs]
def _read_opt_bid(self, schema: 'Schema_BindingIdentifierOption', *,
options: 'Option') -> 'Data_BindingIdentifierOption':
"""Read MH binding identifier (BID) option.
Structure of MH Binding Identifier option [:rfc:`5648#section-4.3`, as
updated by :rfc:`6089#section-4.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 35 | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Binding ID (BID) | Status |H| BID-PRI |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------------------------+
+ +
: IPv4 or IPv6 Care-of Address (CoA) :
+ +
+---------------------------------------------------------------+
Note:
The care-of address is absent for a length of ``4``, an IPv4 address
for ``8`` and an IPv6 address for ``20``; no other length is valid and
the option carries no address-family flag, so the length is the only
thing to branch on [:rfc:`5648#section-4.3`].
:rfc:`6089#section-4.1` renames the seven bits below the ``H`` flag
from ``Reserved`` to ``BID-PRI``, a binding priority in which ``0``
marks a sender predating :rfc:`6089`. The newer reading is used, since
a zero reads identically either way.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length not in (4, 8, 20):
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_BindingIdentifierOption(
type=schema.type,
length=self._mh_option_length(schema.length),
bid=schema.bid,
status=schema.status,
simultaneous=bool(schema.flags['H']),
bid_pri=schema.flags['BID_PRI'],
address=cast('Optional[IPv4Address | IPv6Address]', schema.address),
)
return data
[docs]
def _read_opt_ipv4_hoa_req(self, schema: 'Schema_IPv4HomeAddressRequestOption', *,
options: 'Option') -> 'Data_IPv4HomeAddressRequestOption':
"""Read MH IPv4 home address request option.
Structure of MH IPv4 Home Address Request option
[:rfc:`5844#section-3.3.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |Prefix-len | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IPv4 home address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
Unlike the IPv4 home address option of :rfc:`5555#section-3.1.1`,
which this otherwise resembles, there is no ``P`` flag -- the reserved
field is 10 bits rather than 9.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_IPv4HomeAddressRequestOption(
type=schema.type,
length=self._mh_option_length(schema.length),
prefix_length=schema.flags['prefix_length'],
reserved=schema.flags['reserved'],
address=schema.address,
)
return data
[docs]
def _read_opt_ipv4_hoa_rep(self, schema: 'Schema_IPv4HomeAddressReplyOption', *,
options: 'Option') -> 'Data_IPv4HomeAddressReplyOption':
"""Read MH IPv4 home address reply option.
Structure of MH IPv4 Home Address Reply option [:rfc:`5844#section-3.3.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Status |Pref-len |Res|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IPv4 home address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The status draws from the *IPv4 Home Address Reply Status Codes*
registry of :rfc:`5844#section-6`, which is **not** the DSMIPv6 one the
identically shaped IPv4 address acknowledgement option of :rfc:`5555`
uses -- that registry additionally defines ``133``.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_IPv4HomeAddressReplyOption(
type=schema.type,
length=self._mh_option_length(schema.length),
status=schema.status,
prefix_length=schema.flags['prefix_length'],
reserved=schema.flags['reserved'],
address=schema.address,
)
return data
[docs]
def _read_opt_ipv4_router(self, schema: 'Schema_IPv4DefaultRouterAddressOption', *,
options: 'Option') -> 'Data_IPv4DefaultRouterAddressOption':
"""Read MH IPv4 default-router address option.
Structure of MH IPv4 Default-Router Address option
[:rfc:`5844#section-3.3.3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved (R) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IPv4 Default-Router Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_IPv4DefaultRouterAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
address=schema.address,
)
return data
[docs]
def _read_opt_ipv4_dhcp(self, schema: 'Schema_IPv4DHCPSupportModeOption', *,
options: 'Option') -> 'Data_IPv4DHCPSupportModeOption':
"""Read MH IPv4 DHCP support mode option.
Structure of MH IPv4 DHCP Support Mode option [:rfc:`5844#section-3.3.4`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved (R) |S|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The ``S`` bit is the **last** bit of the option, not the first -- the
reserved field precedes it rather than following it, which is the
other way round from most flags in this module.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_IPv4DHCPSupportModeOption(
type=schema.type,
length=self._mh_option_length(schema.length),
mode=Enum_DHCPSupportMode(schema.flags['S']),
reserved=schema.flags['reserved'],
)
return data
[docs]
def _read_opt_cr(self, schema: 'Schema_ContextRequestOption', *,
options: 'Option') -> 'Data_ContextRequestOption':
"""Read MH context request option.
Structure of MH Context Request option [:rfc:`5949#section-6.2.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+---------------+---------------+
| Option-Type | Option-Length | Reserved |
+---------------+---------------+-------------------------------+
| Req-type-1 | Req-length-1 | Req-type-2 | Req-length-2 |
+---------------------------------------------------------------+
| Req-type-3 | Req-length-3 | Req-option-3 |
+---------------------------------------------------------------+
| ... |
Note:
The requested types are drawn from the mobility option registry
itself, so a request for a vendor specific option (type ``19``) is
followed by five octets naming the vendor and sub-type, whereas a
request for a home network prefix (type ``22``) carries a request
length of zero. There is neither a count field nor a terminator: the
list runs to the end of the option.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
requests = [] # type: list[Data_ContextRequest]
buffer = schema.requests
index = 0
while index < len(buffer):
if index + 2 > len(buffer):
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
req_type, req_len = buffer[index], buffer[index + 1]
value = buffer[index + 2:index + 2 + req_len]
if len(value) != req_len:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
requests.append(Data_ContextRequest(
type=Enum_Option.get(req_type),
length=req_len,
value=value,
))
index += 2 + req_len
data = Data_ContextRequestOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
requests=tuple(requests),
)
return data
[docs]
def _read_opt_lmaa(self, schema: 'Schema_LMAAddressOption', *,
options: 'Option') -> 'Data_LMAAddressOption':
"""Read MH local mobility anchor address option.
Structure of MH Local Mobility Anchor Address option
[:rfc:`5949#section-6.2.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option-Type | Option-Length | Option-Code | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Local Mobility Anchor Address ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The option codes are enumerated in
:class:`~pcapkit.protocols.internet.mh.protocol.LMAAddressCode`, which is local to
this module because :rfc:`5949` defines them inline with no IANA registry
behind them.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length not in (6, 18):
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_LMAAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
code=LMAAddressCode(schema.code),
reserved=schema.reserved,
address=schema.address,
)
return data
[docs]
def _read_opt_mn_lla_iid(self, schema: 'Schema_MNLLAIIDOption', *,
options: 'Option') -> 'Data_MNLLAIIDOption':
"""Read MH mobile node link-local address interface identifier option.
Structure of MH Mobile Node Link-local Address Interface Identifier option
[:rfc:`5949#section-6.2.3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option-Type | Option-Length | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Interface Identifier +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 10:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_MNLLAIIDOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
iid=schema.iid,
)
return data
[docs]
def _read_opt_transient(self, schema: 'Schema_TransientBindingOption', *,
options: 'Option') -> 'Data_TransientBindingOption':
"""Read MH transient binding option.
Structure of MH Transient Binding option [:rfc:`6058#section-5.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved |L| Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The lifetime is in units of **100 milliseconds**, which is unlike every
other lifetime in this module [:rfc:`6058#section-5.1`]. The ``L`` flag
is the last bit of the third octet, with the seven reserved bits above
rather than below it.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_TransientBindingOption(
type=schema.type,
length=self._mh_option_length(schema.length),
late=bool(schema.flags['L']),
reserved=schema.flags['reserved'],
lifetime=datetime.timedelta(milliseconds=schema.lifetime * 100),
)
return data
[docs]
def _read_opt_fs(self, schema: 'Schema_FlowSummaryOption', *,
options: 'Option') -> 'Data_FlowSummaryOption':
"""Read MH flow summary mobility option.
Structure of MH Flow Summary mobility option [:rfc:`6089#section-4.2.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Len | FID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FID ........
+-+-+-+-+-+-+-+-+-+-+-
Note:
:rfc:`6089#section-8` prints the type codes of this option and the flow
identification option the wrong way round, contradicting both its own
figures and the IANA registry. The figures and the registry agree that
the flow summary option is type ``44``, which is what is registered
here.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length % 2 != 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_FlowSummaryOption(
type=schema.type,
length=self._mh_option_length(schema.length),
fid=tuple(schema.fid),
)
return data
[docs]
def _read_opt_fid(self, schema: 'Schema_FlowIdentificationOption', *,
options: 'Option') -> 'Data_FlowIdentificationOption':
"""Read MH flow identification mobility option.
Structure of MH Flow Identification mobility option
[:rfc:`6089#section-4.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Len | FID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FID-PRI | Reserved | Status |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sub-options (optional) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The status draws from the *Flow Identification Mobility Option Status
Codes* registry, which is neither the general mobility status codes nor
the flow binding acknowledgement ones -- similarly named
registries that must not be confused.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_FlowIdentificationOption(
type=schema.type,
length=self._mh_option_length(schema.length),
fid=schema.fid,
fid_pri=schema.fid_pri,
reserved=schema.reserved,
status=schema.status,
suboptions=self._read_fid_suboptions(schema.suboptions),
)
return data
[docs]
def _read_opt_rc(self, schema: 'Schema_RedirectCapabilityOption', *,
options: 'Option') -> 'Data_RedirectCapabilityOption':
"""Read MH redirect-capability mobility option.
Structure of MH Redirect-Capability mobility option
[:rfc:`6463#section-4.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_RedirectCapabilityOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
)
return data
[docs]
def _read_opt_redirect(self, schema: 'Schema_RedirectOption', *,
options: 'Option') -> 'Data_RedirectOption':
"""Read MH redirect mobility option.
Structure of MH Redirect mobility option [:rfc:`6463#section-4.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length |K|N| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Optional IPv6 r2LMA Address |
| |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Optional IPv4 r2LMA Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`6463#section-4.2` forbids ``K`` and ``N`` from being both set and
both clear, so exactly one address is present and the option length is
18 or 6 accordingly. The two encodings of the same fact are checked
against each other here, since a disagreement means the option cannot
be read either way.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
ipv6_flag = bool(schema.flags['K'])
ipv4_flag = bool(schema.flags['N'])
if ipv6_flag == ipv4_flag:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
if schema.length != (18 if ipv6_flag else 6):
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_RedirectOption(
type=schema.type,
length=self._mh_option_length(schema.length),
ipv6=schema.ipv6,
ipv4=schema.ipv4,
reserved=schema.flags['reserved'],
)
return data
[docs]
def _read_opt_load(self, schema: 'Schema_LoadInformationOption', *,
options: 'Option') -> 'Data_LoadInformationOption':
"""Read MH load information mobility option.
Structure of MH Load Information mobility option [:rfc:`6463#section-4.3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length | Priority |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sessions in Use |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Maximum Sessions |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Used Capacity |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Maximum Capacity |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The two capacity fields are in **kilobytes** per second, unlike the
quality-of-service bit rates, which are in bits per second
[:rfc:`6463#section-4.3`].
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 18:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_LoadInformationOption(
type=schema.type,
length=self._mh_option_length(schema.length),
priority=schema.priority,
sessions_in_use=schema.sessions_in_use,
max_sessions=schema.max_sessions,
used_capacity=schema.used_capacity,
max_capacity=schema.max_capacity,
)
return data
[docs]
def _read_opt_alt_ipv4_coa(self, schema: 'Schema_AlternateIPv4CareofAddressOption', *,
options: 'Option') -> 'Data_AlternateIPv4CareofAddressOption':
"""Read MH alternate IPv4 care-of address option.
Structure of MH Alternate IPv4 Care-of Address option
[:rfc:`6463#section-4.4`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
| Option Type | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Alternate IPv4 Care-of Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 4:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_AlternateIPv4CareofAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
address=schema.address,
)
return data
[docs]
def _read_opt_mn_group(self, schema: 'Schema_MNGroupIdentifierOption', *,
options: 'Option') -> 'Data_MNGroupIdentifierOption':
"""Read MH mobile node group identifier option.
Structure of MH Mobile Node Group Identifier option
[:rfc:`6602#section-4.3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Sub-type | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Mobile Node Group Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_MNGroupIdentifierOption(
type=schema.type,
length=self._mh_option_length(schema.length),
subtype=schema.subtype,
reserved=schema.reserved,
group_id=schema.group_id,
)
return data
[docs]
def _read_opt_mag_addr(self, schema: 'Schema_MAGIPv6AddressOption', *,
options: 'Option') -> 'Data_MAGIPv6AddressOption':
"""Read MH MAG IPv6 address option.
Structure of MH MAG IPv6 Address option [:rfc:`6705#section-11.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved | Address Length|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ MAG IPv6 Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The option is modelled on the home network prefix option, which is why
it carries an address *length* field at all; :rfc:`6705#section-11.1`
requires it to be ``128``, since a full address is always carried.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 18:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_MAGIPv6AddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
address_length=schema.address_length,
address=schema.address,
)
return data
[docs]
def _read_opt_ani(self, schema: 'Schema_AccessNetworkIdentifierOption', *,
options: 'Option') -> 'Data_AccessNetworkIdentifierOption':
"""Read MH access network identifier option.
Structure of MH Access Network Identifier option [:rfc:`6757#section-3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
... ANI Sub-option(s) ... ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
Unlike most options carrying sub-options, this one has **no** reserved
octet between its own length and the first sub-option
[:rfc:`6757#section-3`], and it must carry at least one.
IANA names the sub-option registry "Access Network *Information*"
while the RFC's prose says "Identifier"; they are the same registry.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length == 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_AccessNetworkIdentifierOption(
type=schema.type,
length=self._mh_option_length(schema.length),
suboptions=self._read_ani_suboptions(schema.suboptions),
)
return data
[docs]
def _read_opt_offload(self, schema: 'Schema_IPv4TrafficOffloadSelectorOption', *,
options: 'Option') -> 'Data_IPv4TrafficOffloadSelectorOption':
"""Read MH IPv4 traffic offload selector option.
Structure of MH IPv4 Traffic Offload Selector option
[:rfc:`6909#section-3.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|M| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Traffic Selector Sub-option ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The traffic selector it carries is the *flow identification*
sub-option of :rfc:`6089#section-4.2.1.4`, so it is read through the
same sub-option registry rather than through one of its own. It is
optional in a proxy binding update and mandatory in a proxy binding
acknowledgement, hence a collection rather than a single value.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 4:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_IPv4TrafficOffloadSelectorOption(
type=schema.type,
length=self._mh_option_length(schema.length),
mode=bool(schema.flags['M']),
reserved=schema.flags['reserved'],
selector=self._read_fid_suboptions(schema.selector),
)
return data
[docs]
def _read_opt_mcast_sel(self, schema: 'Schema_DynamicIPMulticastSelectorOption', *,
options: 'Option') -> 'Data_DynamicIPMulticastSelectorOption':
"""Read MH dynamic IP multicast selector option.
Structure of MH Dynamic IP Multicast Selector option
[:rfc:`7028#section-5.1.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Protocol |M| Reserved |Nr of Mcast Address Records (N)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Multicast Address Record [1] +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Multicast Address Record [N] +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The address records are :rfc:`3810#section-5.2` MLD or
:rfc:`3376#section-4.2` IGMP structures. They belong to those protocols
rather than to the mobility header, and each is self-describing through
its own auxiliary-data length and source count, so they are recorded
opaquely here rather than half-decoded.
:attr:`~pcapkit.protocols.internet.mh.data.DynamicIPMulticastSelectorOption.protocol`
says which of the two they are: ``143`` for MLDv2 and ``131`` for MLDv1,
or ``0x12``, ``0x16`` and ``0x22`` for IGMPv1 to IGMPv3
[:rfc:`7028#section-8`].
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 4:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_DynamicIPMulticastSelectorOption(
type=schema.type,
length=self._mh_option_length(schema.length),
protocol=schema.protocol,
mode=bool(schema.flags['M']),
reserved=schema.flags['reserved'],
records=schema.records,
data=schema.data,
)
return data
[docs]
def _read_opt_dmnp(self, schema: 'Schema_DelegatedMNPOption', *,
options: 'Option') -> 'Data_DelegatedMNPOption':
"""Read MH delegated mobile network prefix option.
Structure of MH Delegated Mobile Network Prefix option
[:rfc:`7148#section-4.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |V| Reserved | Prefix Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
. .
+ IPv4 or IPv6 Delegated Mobile Network Prefix +
| (DMNP) |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
Unlike the binding identifier and target care-of address, this option
*does* carry an address-family flag, so the ``V`` bit rather than the
option length is what selects the prefix width
[:rfc:`7148#section-4.1`]. The two are checked against each other,
since a disagreement leaves the option unreadable.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
ipv4 = bool(schema.flags['V'])
if schema.length != (6 if ipv4 else 18):
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_DelegatedMNPOption(
type=schema.type,
length=self._mh_option_length(schema.length),
ipv4=ipv4,
reserved=schema.flags['reserved'],
prefix_length=schema.prefix_length,
prefix=schema.prefix,
)
return data
[docs]
def _read_opt_ams4(self, schema: 'Schema_ActiveMulticastSubscriptionIPv4Option', *,
options: 'Option') -> 'Data_ActiveMulticastSubscriptionIPv4Option':
"""Read MH active multicast subscription IPv4 option.
Structure of MH Active Multicast Subscription IPv4 option
[:rfc:`7161#section-6.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | IGMP Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Multicast Membership Context +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The membership context is an IGMP group address (:rfc:`1112`,
:rfc:`2236`) or an IGMPv3 group record (:rfc:`3376#section-4.2`),
which belong to IGMP rather than to the mobility header, so they are
recorded opaquely.
:attr:`~pcapkit.protocols.internet.mh.data.ActiveMulticastSubscriptionIPv4Option.igmp_type`
says which: ``0x12`` IGMPv1,
``0x16`` IGMPv2, ``0x22`` IGMPv3.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 1:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_ActiveMulticastSubscriptionIPv4Option(
type=schema.type,
length=self._mh_option_length(schema.length),
igmp_type=schema.igmp_type,
context=schema.context,
)
return data
[docs]
def _read_opt_ams6(self, schema: 'Schema_ActiveMulticastSubscriptionIPv6Option', *,
options: 'Option') -> 'Data_ActiveMulticastSubscriptionIPv6Option':
"""Read MH active multicast subscription IPv6 option.
Structure of MH Active Multicast Subscription IPv6 option
[:rfc:`7161#section-4.1.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | MLD Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Multicast Membership Context +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
For MLDv2 the context is an :rfc:`3810#section-5.2` multicast address
record; for MLDv1 it is a reserved word followed by a multicast address
[:rfc:`7161#section-4.1.3`]. Both belong to MLD, so the context is
recorded opaquely and
:attr:`~pcapkit.protocols.internet.mh.data.ActiveMulticastSubscriptionIPv6Option.mld_type`
says which form it is in.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 1:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_ActiveMulticastSubscriptionIPv6Option(
type=schema.type,
length=self._mh_option_length(schema.length),
mld_type=schema.mld_type,
context=schema.context,
)
return data
[docs]
def _read_opt_qos(self, schema: 'Schema_QualityOfServiceOption', *,
options: 'Option') -> 'Data_QualityOfServiceOption':
"""Read MH quality-of-service option.
Structure of MH Quality-of-Service option [:rfc:`7222#section-4.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | SR-ID | TC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| OC | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ QoS Attribute(s) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The traffic class octet is a 6-bit differentiated services code point
with two reserved bits below it, which the receiver ignores
[:rfc:`7222#section-4.1`]; both are carried into the data model, so
that the octet rebuilds as captured.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
.. versionchanged:: 1.6.0
The two reserved bits of the traffic class are kept, as
``tc_reserved``, rather than dropped (:issue:`2684`).
"""
if schema.length < 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_QualityOfServiceOption(
type=schema.type,
length=self._mh_option_length(schema.length),
sr_id=schema.sr_id,
dscp=schema.tc >> 2,
tc_reserved=schema.tc & 0b11,
oc=schema.oc,
reserved=schema.reserved,
attributes=self._read_qos_attributes(schema.attributes),
)
return data
[docs]
def _read_opt_lma_up(self, schema: 'Schema_LMAUserPlaneAddressOption', *,
options: 'Option') -> 'Data_LMAUserPlaneAddressOption':
"""Read MH LMA user-plane address option.
Structure of MH LMA User-Plane Address option [:rfc:`7389#section-4`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
. .
+ LMA User-Plane Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The address may legitimately be **absent**: a mobile access gateway
sends the option with no address, or with an all-zero one, purely to
say which transport it wants [:rfc:`7389#section-4`]. Only a local
mobility anchor's reply is required to carry a real address.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length not in (2, 6, 18):
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_LMAUserPlaneAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
address=cast('Optional[IPv4Address | IPv6Address]',
schema.address if schema.length != 2 else None),
)
return data
[docs]
def _read_opt_mcast(self, schema: 'Schema_MulticastMobilityOption', *,
options: 'Option') -> 'Data_MulticastMobilityOption':
"""Read MH multicast mobility option.
Structure of MH Multicast Mobility option [:rfc:`7411#section-5.3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Option-Code | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| MLD or IGMP Report Payload |
~ ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
This option's length field is **not** the usual octet count.
:rfc:`7411#section-5.3` measures it in 32-bit *words* and excludes the
option code and reserved octets as well as the type and length ones, so
the option occupies ``4 + length * 4`` octets rather than
``length + 2``. The option-collection machinery advances by the octets
the schema actually consumed rather than by the declared length, so the
unusual unit does not misframe the options that follow -- but it does
mean :attr:`~pcapkit.protocols.internet.mh.data.Option.length` here is
the true octet count, computed rather than copied.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
data = Data_MulticastMobilityOption(
type=schema.type,
length=schema.length * 4 + 4,
code=schema.code,
reserved=schema.reserved,
data=schema.data,
)
return data
[docs]
def _read_opt_mcast_ack(self, schema: 'Schema_MulticastAcknowledgementOption', *,
options: 'Option') -> 'Data_MulticastAcknowledgementOption':
"""Read MH multicast acknowledgement option.
Structure of MH Multicast Acknowledgement option [:rfc:`7411#section-5.4`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Option-Code | Status |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| MLD or IGMP Unsupported Report Payload |
~ ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
As with :meth:`_read_opt_mcast`, the length counts 32-bit words and
excludes the option code and status octets [:rfc:`7411#section-5.4`].
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
data = Data_MulticastAcknowledgementOption(
type=schema.type,
length=schema.length * 4 + 4,
code=schema.code,
status=schema.status,
data=schema.data,
)
return data
[docs]
def _read_opt_lcmp(self, schema: 'Schema_LMAControlledMAGParametersOption', *,
options: 'Option') -> 'Data_LMAControlledMAGParametersOption':
"""Read MH LMA-controlled MAG parameters option.
Structure of MH LMA-Controlled MAG Parameters option
[:rfc:`8127#section-3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| LCMP Sub-Option(s) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
This option is registered to :rfc:`8127`, **not** to :rfc:`7864` --
the latter defines the flow-mobility sub-options and the
``FLOW-MOBILITY`` update notification reason instead. The IANA registry
is the authority, and it cites :rfc:`8127`.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length == 0:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_LMAControlledMAGParametersOption(
type=schema.type,
length=self._mh_option_length(schema.length),
suboptions=self._read_lcmp_suboptions(schema.suboptions),
)
return data
[docs]
def _read_opt_mag_mp(self, schema: 'Schema_MAGMultipathBindingOption', *,
options: 'Option') -> 'Data_MAGMultipathBindingOption':
"""Read MH MAG multipath binding option.
Structure of MH MAG Multipath Binding option [:rfc:`8278#section-4.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | If-ATT | If-Label |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Binding ID |B|O| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`8278#section-4.1` makes the ``B`` and ``O`` flags mutually
exclusive -- neither may be set while the other is -- and reserves
binding identifiers ``0`` and ``255``.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 6:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
bulk = bool(schema.flags['B'])
overwrite = bool(schema.flags['O'])
if bulk and overwrite:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_MAGMultipathBindingOption(
type=schema.type,
length=self._mh_option_length(schema.length),
att=schema.att,
label=schema.label,
bid=schema.bid,
bulk=bulk,
overwrite=overwrite,
reserved=schema.flags['reserved'],
)
return data
[docs]
def _read_opt_mag_id(self, schema: 'Schema_MAGIdentifierOption', *,
options: 'Option') -> 'Data_MAGIdentifierOption':
"""Read MH MAG identifier option.
Structure of MH MAG Identifier option [:rfc:`8278#section-4.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Subtype | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier ... ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The sub-type is borrowed from the *mobile node* identifier subtype
registry [:rfc:`4283`, :rfc:`8371`], so the identifier's encoding is
whatever that subtype prescribes. :rfc:`8278` does not restate those
encodings, so the identifier is kept as :obj:`bytes`.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_MAGIdentifierOption(
type=schema.type,
length=self._mh_option_length(schema.length),
subtype=schema.subtype,
reserved=schema.reserved,
identifier=schema.identifier,
)
return data
[docs]
def _read_opt_anchored(self, schema: 'Schema_AnchoredPrefixOption', *,
options: 'Option') -> 'Data_AnchoredPrefixOption':
"""Read MH anchored prefix option.
Structure of MH Anchored Prefix option [:rfc:`8885#section-4.3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved | Prefix Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Anchored Prefix +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 18:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
if schema.prefix_length > 128:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_AnchoredPrefixOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
prefix_length=schema.prefix_length,
prefix=schema.prefix,
)
return data
[docs]
def _read_opt_local_prefix(self, schema: 'Schema_LocalPrefixOption', *,
options: 'Option') -> 'Data_LocalPrefixOption':
"""Read MH local prefix option.
Structure of MH Local Prefix option [:rfc:`8885#section-4.4`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved | Prefix Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Local Prefix +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 18:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
if schema.prefix_length > 128:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_LocalPrefixOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
prefix_length=schema.prefix_length,
prefix=schema.prefix,
)
return data
[docs]
def _read_opt_prev_maar(self, schema: 'Schema_PreviousMAAROption', *,
options: 'Option') -> 'Data_PreviousMAAROption':
"""Read MH previous MAAR option.
Structure of MH Previous MAAR option [:rfc:`8885#section-4.5`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved | Prefix Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Previous MAAR +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Home Network Prefix +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The prefix length applies to the *home network prefix* only; the
previous MAAR's own field is a full address
[:rfc:`8885#section-4.5`].
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 34:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
if schema.prefix_length > 128:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_PreviousMAAROption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
prefix_length=schema.prefix_length,
maar=schema.maar,
prefix=schema.prefix,
)
return data
[docs]
def _read_opt_serv_maar(self, schema: 'Schema_ServingMAAROption', *,
options: 'Option') -> 'Data_ServingMAAROption':
"""Read MH serving MAAR option.
Structure of MH Serving MAAR option [:rfc:`8885#section-4.6`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ S-MAAR's Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
Unlike the anchored and local prefix options, this one has neither a
reserved octet nor a prefix length -- it carries a full address and
nothing else.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 16:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_ServingMAAROption(
type=schema.type,
length=self._mh_option_length(schema.length),
address=schema.address,
)
return data
[docs]
def _read_opt_dlif_lla(self, schema: 'Schema_DLIFLinkLocalAddressOption', *,
options: 'Option') -> 'Data_DLIFLinkLocalAddressOption':
"""Read MH DLIF link-local address option.
Structure of MH DLIF Link-Local Address option [:rfc:`8885#section-4.7`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ DLIF Link-Local Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length != 16:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_DLIFLinkLocalAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
address=schema.address,
)
return data
[docs]
def _read_opt_dlif_lladdr(self, schema: 'Schema_DLIFLinkLayerAddressOption', *,
options: 'Option') -> 'Data_DLIFLinkLayerAddressOption':
"""Read MH DLIF link-layer address option.
Structure of MH DLIF Link-Layer Address option [:rfc:`8885#section-4.8`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ DLIF Link-Layer Address +
. ... .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
The address is encoded as in :rfc:`4861#section-4.6.2`, whose width is
link-type dependent, so no fixed length can be checked here.
:rfc:`8885#section-4.8` says outright that the option cannot be used on
links where a link-layer address is unavailable.
Args:
schema: Parsed option schema.
options: Parsed MH options.
Returns:
Constructed option data.
"""
if schema.length < 2:
raise ProtocolError(f'{self.alias}: [Opt {schema.type}] invalid format')
data = Data_DLIFLinkLayerAddressOption(
type=schema.type,
length=self._mh_option_length(schema.length),
reserved=schema.reserved,
lla=schema.lla,
)
return data
[docs]
def _read_ext_none(self, schema: 'Schema_UnknownExtension', *,
extensions: 'Extension') -> 'Data_UnknownExtension':
"""Read unknown CGA extension.
Args:
schema: Parsed extension schema.
extensions: Parsed MH CGA extensions.
Returns:
Constructed extension data.
"""
data = Data_UnknownExtension(
type=schema.type,
length=self._mh_extension_length(schema.length),
data=schema.data,
)
return data
[docs]
def _read_ext_multiprefix(self, schema: 'Schema_MultiPrefixExtension', *,
extensions: 'Extension') -> 'Data_MultiPrefixExtension':
"""Read multi-prefix CGA extension.
Structure of Multi-Prefix CGA extension [:rfc:`5535`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Extension Type | Extension Data Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|P| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Prefix[1] +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Prefix[2] +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. . .
. . .
. . .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Prefix[n] +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
schema: Parsed extension schema.
extensions: Parsed MH CGA extensions.
Returns:
Constructed extension data.
"""
data = Data_MultiPrefixExtension(
type=schema.type,
length=self._mh_extension_length(schema.length),
flag=bool(schema.flags['P']),
reserved=schema.flags['reserved'],
prefixes=tuple(schema.prefixes),
)
return data
[docs]
def _read_ext_exp(self, schema: 'Schema_ExperimentalExtension', *,
extensions: 'Extension') -> 'Data_ExperimentalExtension':
"""Read experimental CGA extension.
Structure of an experimental CGA extension [:rfc:`4581#section-2`,
:rfc:`4581#section-3`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Extension Type | Extension Data Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
~ Extension Data ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
:rfc:`4581#section-3` assigns extension types ``0xFFFD``, ``0xFFFE``
and ``0xFFFF`` for experimental use as :rfc:`3692` recommends, and
defines **no** structure for their extension data -- neither it nor
:rfc:`5535` gives those three a layout, and the IANA registry records
only their names. So the data is opaque *by specification* here rather
than merely left undecoded, and no better parse is available to be
written later. One handler serves all three, since they share a shape
and differ only in their type code.
Args:
schema: Parsed extension schema.
extensions: Parsed MH CGA extensions.
Returns:
Constructed extension data.
"""
data = Data_ExperimentalExtension(
type=schema.type,
length=self._mh_extension_length(schema.length),
data=schema.data,
)
return data
[docs]
def _make_msg_unknown(self, message: 'Optional[Data_UnknownMessage]' = None, *,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_UnknownMessage':
"""Make MH unknown message type.
Args:
message: Message data model.
data: Raw message data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
data = message.data
return Schema_UnknownMessage(
data=data,
)
[docs]
def _make_msg_brr(self, message: 'Optional[Data_BindingRefreshRequestMessage]' = None, *,
msg_reserved: 'int' = 0,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
**kwargs: 'Any') -> 'Schema_BindingRefreshRequestMessage':
"""Make MH binding refresh request (BRR) message type.
Args:
message: Message data model.
msg_reserved: Reserved field before the mobility options.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``msg_reserved`` does not fit its field.
"""
if message is not None:
msg_reserved = message.msg_reserved
options = message.options
else:
options = options or []
return Schema_BindingRefreshRequestMessage(
reserved=self._make_uint(msg_reserved, bits=16, name='reserved bits'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_hoti(self, message: 'Optional[Data_HomeTestInitMessage]' = None, *,
cookie: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00',
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
msg_reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_HomeTestInitMessage':
"""Make MH home test init (HoTI) message type.
Args:
message: Message data model.
cookie: Home test cookie.
options: Mobility options.
msg_reserved: The two reserved octets of the message body as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``cookie`` is not eight octets, or ``msg_reserved``
is not two.
"""
if message is not None:
msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00'),
size=2, model=message, field='data')
cookie = message.cookie
options = message.options
else:
options = options or []
return Schema_HomeTestInitMessage(
reserved=self._make_octets(msg_reserved, size=2, name='reserved octets'),
cookie=self._make_octets(cookie, size=8, name='cookie'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_coti(self, message: 'Optional[Data_CareofTestInitMessage]' = None, *,
cookie: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00',
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
msg_reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_CareofTestInitMessage':
"""Make MH care-of test init (CoTI) message type.
Args:
message: Message data model.
cookie: Care-of test cookie.
options: Mobility options.
msg_reserved: The two reserved octets of the message body as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``cookie`` is not eight octets, or ``msg_reserved``
is not two.
"""
if message is not None:
msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00'),
size=2, model=message, field='data')
cookie = message.cookie
options = message.options
else:
options = options or []
return Schema_CareofTestInitMessage(
reserved=self._make_octets(msg_reserved, size=2, name='reserved octets'),
cookie=self._make_octets(cookie, size=8, name='cookie'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_hot(self, message: 'Optional[Data_HomeTestMessage]' = None, *,
nonce_index: 'int' = 0,
cookie: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00',
token: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00',
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
**kwargs: 'Any') -> 'Schema_HomeTestMessage':
"""Make MH home test (HoT) message type.
Args:
message: Message data model.
nonce_index: Home nonce index.
cookie: Home test cookie.
token: Home test token.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``nonce_index`` does not fit its field.
Also if ``cookie`` or ``token`` is not eight octets.
"""
if message is not None:
nonce_index = message.nonce_index
cookie = message.cookie
token = message.token
options = message.options
else:
options = options or []
return Schema_HomeTestMessage(
nonce_index=self._make_uint(nonce_index, bits=16, name='nonce index'),
cookie=self._make_octets(cookie, size=8, name='cookie'),
token=self._make_octets(token, size=8, name='token'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_cot(self, message: 'Optional[Data_CareofTestMessage]' = None, *,
nonce_index: 'int' = 0,
cookie: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00',
token: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00',
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
**kwargs: 'Any') -> 'Schema_CareofTestMessage':
"""Make MH care-of test (CoT) message type.
Args:
message: Message data model.
nonce_index: Care-of nonce index.
cookie: Care-of test cookie.
token: Care-of test token.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``nonce_index`` does not fit its field.
Also if ``cookie`` or ``token`` is not eight octets.
"""
if message is not None:
nonce_index = message.nonce_index
cookie = message.cookie
token = message.token
options = message.options
else:
options = options or []
return Schema_CareofTestMessage(
nonce_index=self._make_uint(nonce_index, bits=16, name='nonce index'),
cookie=self._make_octets(cookie, size=8, name='cookie'),
token=self._make_octets(token, size=8, name='token'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_bu(self, message: 'Optional[Data_BindingUpdateMessage]' = None, *,
seq: 'int' = 0,
ack: 'bool' = False,
home: 'bool' = False,
lla_compat: 'bool' = False,
key_mngt: 'bool' = False,
map_reg: 'bool' = False,
mobile_router: 'bool' = False,
proxy_reg: 'bool' = False,
udp_encap: 'bool' = False,
tlv_format: 'bool' = False,
bulk_binding: 'bool' = False,
multicast: 'bool' = False,
dmm: 'bool' = False,
flags_reserved: 'int' = 0,
lifetime: 'int | timedelta' = 4, # reasonable default value
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
**kwargs: 'Any') -> 'Schema_BindingUpdateMessage':
"""Make MH binding update (BU) message type.
Args:
message: Message data model.
seq: Sequence number.
ack: Acknowledgement flag.
home: Home registration flag.
lla_compat: LLA compatibility flag.
key_mngt: Key management mobility option flag.
map_reg: MAP registration flag (``M``).
mobile_router: Mobile router flag (``R``).
proxy_reg: Proxy registration flag (``P``).
udp_encap: Forcing UDP encapsulation flag (``F``).
tlv_format: TLV-header format flag (``T``).
bulk_binding: Bulk binding update flag (``B``).
multicast: Multicast signaling flag (``S``).
dmm: DMM flag (``D``).
flags_reserved: Reserved bits, i.e. the low four bits of the flags
word.
lifetime: Lifetime in seconds or timedelta.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit
its field.
"""
if message is not None:
seq = message.seq
ack = message.ack
home = message.home
lla_compat = message.lla_compat
key_mngt = message.key_mngt
map_reg = message.map_reg
mobile_router = message.mobile_router
proxy_reg = message.proxy_reg
udp_encap = message.udp_encap
tlv_format = message.tlv_format
bulk_binding = message.bulk_binding
multicast = message.multicast
dmm = message.dmm
flags_reserved = message.flags_reserved
lifetime_val = math.ceil(message.lifetime.total_seconds())
options = message.options
else:
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds())
options = options or []
# NOTE: The field counts units of 4 seconds, so the seconds are checked
# before they are rounded up to units, and the units after.
lifetime_val = self._make_uint(lifetime_val, bits=18, name='lifetime')
return Schema_BindingUpdateMessage(
seq=self._make_uint(seq, bits=16, name='sequence number'),
flags={
'A': ack,
'H': home,
'L': lla_compat,
'K': key_mngt,
'M': map_reg,
'R': mobile_router,
'P': proxy_reg,
'F': udp_encap,
'T': tlv_format,
'B': bulk_binding,
'S': multicast,
'D': dmm,
'reserved': self._make_uint(flags_reserved, bits=4, name='reserved bits'),
},
lifetime=self._make_uint(math.ceil(lifetime_val / 4), bits=16,
name='lifetime in units of 4 seconds'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_ba(self, message: 'Optional[Data_BindingAcknowledgementMessage]' = None, *,
status: 'Enum_StatusCode | int' = Enum_StatusCode.Binding_Update_accepted_Proxy_Binding_Update_accepted, # pylint: disable=line-too-long
key_mngt: 'bool' = False,
mobile_router: 'bool' = False,
proxy_reg: 'bool' = False,
tlv_format: 'bool' = False,
bulk_binding: 'bool' = False,
multicast: 'bool' = False,
dmm: 'bool' = False,
flags_reserved: 'int' = 0,
seq: 'int' = 0,
lifetime: 'int | timedelta' = 4, # reasonable default value
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
**kwargs: 'Any') -> 'Schema_BindingAcknowledgementMessage':
"""Make MH binding acknowledge (BA) message type.
Args:
message: Message data model.
status: Status code.
key_mngt: Key management mobility option flag.
mobile_router: Mobile router flag (``R``).
proxy_reg: Proxy registration flag (``P``).
tlv_format: TLV-header format flag (``T``).
bulk_binding: Bulk binding update flag (``B``).
multicast: Multicast signaling flag (``S``).
dmm: DMM flag (``D``).
flags_reserved: Reserved bit, i.e. the lowest bit of the flags
octet.
seq: Sequence number.
lifetime: Lifetime in seconds or timedelta.
options: Mobility options.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit
its field, or ``status`` is a name, a :obj:`bool` or a code its
field cannot carry.
"""
if message is not None:
status_val = message.status
key_mngt = message.key_mngt
mobile_router = message.mobile_router
proxy_reg = message.proxy_reg
tlv_format = message.tlv_format
bulk_binding = message.bulk_binding
multicast = message.multicast
dmm = message.dmm
flags_reserved = message.flags_reserved
seq = message.seq
lifetime_val = math.ceil(message.lifetime.total_seconds())
options = message.options
else:
status_val = self._make_index(status, Enum_StatusCode, bits=8, name='status')
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds())
options = options or []
# NOTE: The field counts units of 4 seconds, so the seconds are checked
# before they are rounded up to units, and the units after.
lifetime_val = self._make_uint(lifetime_val, bits=18, name='lifetime')
return Schema_BindingAcknowledgementMessage(
status=status_val,
flags={
'K': key_mngt,
'R': mobile_router,
'P': proxy_reg,
'T': tlv_format,
'B': bulk_binding,
'S': multicast,
'D': dmm,
'reserved': self._make_uint(flags_reserved, bits=1, name='reserved bits'),
},
seq=self._make_uint(seq, bits=16, name='sequence number'),
lifetime=self._make_uint(math.ceil(lifetime_val / 4), bits=16,
name='lifetime in units of 4 seconds'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_be(self, message: 'Optional[Data_BindingErrorMessage]' = None, *,
status: 'Enum_BindingError | int' = 0,
home: 'IPv6Address | int | str | bytes' = '::',
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
msg_reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_BindingErrorMessage':
"""Make MH binding error (BE) message type.
Args:
message: Message data model.
status: Status [:rfc:`6275#section-6.1.9`]; ``0`` by default, a
code no status is assigned.
home: Home address.
options: Mobility options.
msg_reserved: The reserved octet of the message body as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``status`` is a name, a :obj:`bool` or a code its
field cannot carry, or ``msg_reserved`` is not one octet.
"""
if message is not None:
msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00'),
size=1, model=message, field='data', offset=1)
status_val = message.status
home = message.home
options = message.options
else:
status_val = self._make_index(status, Enum_BindingError, bits=8, name='status')
options = options or []
return Schema_BindingErrorMessage(
status=status_val,
reserved=self._make_octets(msg_reserved, size=1, name='reserved octets'),
home=home,
options=self._make_mh_options(options),
)
[docs]
def _make_msg_fbu(self, message: 'Optional[Data_FastBindingUpdateMessage]' = None, *,
seq: 'int' = 0,
ack: 'bool' = True, # MUST be set, see RFC 5568, section 6.2.2
home: 'bool' = True, # MUST be set, see RFC 5568, section 6.2.2
lla_compat: 'bool' = False,
key_mngt: 'bool' = False,
lifetime: 'int | timedelta' = 4, # reasonable default value
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_FastBindingUpdateMessage':
"""Make MH fast binding update (FBU) message type.
Args:
message: Message data model.
seq: Sequence number.
ack: Acknowledgement flag.
home: Home registration flag.
lla_compat: LLA compatibility flag.
key_mngt: Key management mobility option flag.
lifetime: Lifetime, in seconds or as a
:class:`~datetime.timedelta`. Unlike the BU/BA lifetime, this
one counts seconds rather than units of 4 seconds
[:rfc:`5568#section-6.2.2`], so the value is not scaled.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the 12 bits after the ``K`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit
its field.
"""
if message is not None:
seq = message.seq
ack = message.ack
home = message.home
lla_compat = message.lla_compat
key_mngt = message.key_mngt
flags_reserved = message.flags_reserved
lifetime_val = math.ceil(message.lifetime.total_seconds())
options = message.options
else:
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds())
options = options or []
return Schema_FastBindingUpdateMessage(
seq=self._make_uint(seq, bits=16, name='sequence number'),
flags={
'A': ack,
'H': home,
'L': lla_compat,
'K': key_mngt,
'reserved': self._make_uint(flags_reserved, bits=12, name='reserved bits'),
},
lifetime=self._make_uint(lifetime_val, bits=16, name='lifetime'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_fback(self, message: 'Optional[Data_FastBindingAcknowledgmentMessage]' = None, *,
status: 'FastBindingAcknowledgmentStatus | int' = FastBindingAcknowledgmentStatus.Fast_Binding_Update_accepted, # pylint: disable=line-too-long
key_mngt: 'bool' = False,
seq: 'int' = 0,
lifetime: 'int | timedelta' = 4, # reasonable default value
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_FastBindingAcknowledgmentMessage':
"""Make MH fast binding acknowledgment (FBack) message type.
Args:
message: Message data model.
status: Status code, see
:class:`~pcapkit.protocols.internet.mh.protocol.FastBindingAcknowledgmentStatus`.
key_mngt: Key management mobility option flag.
seq: Sequence number.
lifetime: Lifetime, in seconds or as a
:class:`~datetime.timedelta`. Unlike the BU/BA lifetime, this
one counts seconds rather than units of 4 seconds
[:rfc:`5568#section-6.2.3`], so the value is not scaled.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the seven bits after the ``K`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit
its field, or ``status`` is a name, a :obj:`bool` or a code its
field cannot carry.
"""
if message is not None:
status_val = message.status
key_mngt = message.key_mngt
flags_reserved = message.flags_reserved
seq = message.seq
lifetime_val = math.ceil(message.lifetime.total_seconds())
options = message.options
else:
status_val = self._make_index(status, FastBindingAcknowledgmentStatus, bits=8,
name='status')
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds())
options = options or []
return Schema_FastBindingAcknowledgmentMessage(
status=status_val,
flags={
'K': key_mngt,
'reserved': self._make_uint(flags_reserved, bits=7, name='reserved bits'),
},
seq=self._make_uint(seq, bits=16, name='sequence number'),
lifetime=self._make_uint(lifetime_val, bits=16, name='lifetime'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_fna(self, message: 'Optional[Data_FastNeighborAdvertisementMessage]' = None, *,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
msg_reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_FastNeighborAdvertisementMessage':
"""Make MH fast neighbor advertisement (FNA) message type.
Args:
message: Message data model.
options: Mobility options.
msg_reserved: The two reserved octets of the message body as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``msg_reserved`` is not two octets.
"""
if message is not None:
msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00'),
size=2, model=message, field='data')
options = message.options
else:
options = options or []
return Schema_FastNeighborAdvertisementMessage(
reserved=self._make_octets(msg_reserved, size=2, name='reserved octets'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_emh(self, message: 'Optional[Data_ExperimentalMessage]' = None, *,
data: 'bytes' = b'\x00\x00', # 2 bytes of padding, see RFC 5096, section 3
**kwargs: 'Any') -> 'Schema_ExperimentalMessage':
"""Make MH experimental mobility header message type.
Args:
message: Message data model.
data: Experimental message data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
"""
if message is not None:
data = message.data
return Schema_ExperimentalMessage(
data=data,
)
[docs]
def _make_msg_has(self, message: 'Optional[Data_HomeAgentSwitchMessage]' = None, *,
addresses: 'Optional[list[IPv6Address | bytes | str | int]]' = None,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
msg_reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_HomeAgentSwitchMessage':
"""Make MH home agent switch (HAS) message type.
Args:
message: Message data model.
addresses: Alternate home agent addresses. An empty list is valid and
asks the mobile node to run home agent discovery.
options: Mobility options.
msg_reserved: The reserved octet of the message body as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``msg_reserved`` is not one octet.
"""
if message is not None:
msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00'),
size=1, model=message, field='data', offset=1)
addresses = cast('list[IPv6Address | bytes | str | int]', list(message.addresses))
options = message.options
else:
addresses = addresses or []
options = options or []
return Schema_HomeAgentSwitchMessage(
count=len(addresses),
reserved=self._make_octets(msg_reserved, size=1, name='reserved octets'),
addresses=addresses,
options=self._make_mh_options(options),
)
[docs]
def _make_msg_hb(self, message: 'Optional[Data_HeartbeatMessage]' = None, *,
unsolicited: 'bool' = False,
response: 'bool' = False,
seq: 'int' = 0,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_HeartbeatMessage':
"""Make MH heartbeat message type.
Args:
message: Message data model.
unsolicited: Unsolicited flag.
response: Response flag; unset makes this a heartbeat request.
seq: Sequence number.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the 14 bits before the ``U`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field.
"""
if message is not None:
unsolicited = message.unsolicited
response = message.response
flags_reserved = message.flags_reserved
seq = message.seq
options = message.options
else:
options = options or []
return Schema_HeartbeatMessage(
flags={
'U': unsolicited,
'R': response,
'reserved': self._make_uint(flags_reserved, bits=14, name='reserved bits'),
},
seq=self._make_uint(seq, bits=32, name='sequence number'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_hi(self, message: 'Optional[Data_HandoverInitiateMessage]' = None, *,
seq: 'int' = 0,
assign: 'bool' = False,
buffer: 'bool' = False,
proxy: 'bool' = False,
forward: 'bool' = False,
code: 'Enum_HandoverInitiateStatus | int' = Enum_HandoverInitiateStatus.FBU_with_the_PCoA_as_source_IP_address, # pylint: disable=line-too-long
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_HandoverInitiateMessage':
"""Make MH handover initiate (HI) message type.
Args:
message: Message data model.
seq: Sequence number.
assign: Assigned address configuration flag.
buffer: Buffer flag.
proxy: Proxy flag.
forward: Forwarding flag.
code: Code.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the four bits after the ``F`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field,
or ``code`` is a name, a :obj:`bool` or a code its field cannot
carry.
"""
if message is not None:
seq = message.seq
assign = message.assign
buffer = message.buffer
proxy = message.proxy
forward = message.forward
flags_reserved = message.flags_reserved
code_val = message.code
options = message.options
else:
code_val = self._make_index(code, Enum_HandoverInitiateStatus, bits=8, name='code')
options = options or []
return Schema_HandoverInitiateMessage(
seq=self._make_uint(seq, bits=16, name='sequence number'),
flags={
'S': assign,
'U': buffer,
'P': proxy,
'F': forward,
'reserved': self._make_uint(flags_reserved, bits=4, name='reserved bits'),
},
code=code_val,
options=self._make_mh_options(options),
)
[docs]
def _make_msg_hack(self, message: 'Optional[Data_HandoverAcknowledgeMessage]' = None, *,
seq: 'int' = 0,
buffer: 'bool' = False,
proxy: 'bool' = False,
forward: 'bool' = False,
code: 'Enum_HandoverACKStatus | int' = Enum_HandoverACKStatus.Handover_Accepted_with_NCoA_valid,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_HandoverAcknowledgeMessage':
"""Make MH handover acknowledge (HAck) message type.
Args:
message: Message data model.
seq: Sequence number.
buffer: Buffer flag.
proxy: Proxy flag.
forward: Forwarding flag.
code: Code.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the five bits after the ``F`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field,
or ``code`` is a name, a :obj:`bool` or a code its field cannot
carry.
"""
if message is not None:
seq = message.seq
buffer = message.buffer
proxy = message.proxy
forward = message.forward
flags_reserved = message.flags_reserved
code_val = message.code
options = message.options
else:
code_val = self._make_index(code, Enum_HandoverACKStatus, bits=8, name='code')
options = options or []
return Schema_HandoverAcknowledgeMessage(
seq=self._make_uint(seq, bits=16, name='sequence number'),
flags={
'U': buffer,
'P': proxy,
'F': forward,
'reserved': self._make_uint(flags_reserved, bits=5, name='reserved bits'),
},
code=code_val,
options=self._make_mh_options(options),
)
[docs]
def _make_msg_brm(self, message: 'Optional[Data_BindingRevocationMessage]' = None, *,
br_type: 'Enum_BindingRevocation | int' = Enum_BindingRevocation.Binding_Revocation_Indication,
code: 'Enum_RevocationTrigger | Enum_RevocationStatusCode | int' = Enum_RevocationTrigger.Unspecified, # pylint: disable=line-too-long
seq: 'int' = 0,
proxy: 'bool' = False,
ipv4_hoa: 'bool' = False,
global_revocation: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_BindingRevocationMessage':
"""Make MH binding revocation (BRM) message type.
Args:
message: Message data model.
br_type: Binding revocation type, saying which form of the message
this is.
code: Revocation trigger, for an indication, or acknowledgement
status, for an acknowledgement; resolved in the registry of the
form ``br_type`` names.
seq: Sequence number.
proxy: Proxy binding flag.
ipv4_hoa: IPv4 home address binding only flag.
global_revocation: Global revocation flag.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the 13 bits after the ``G`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field,
or ``br_type`` or ``code`` is a name, a :obj:`bool` or a code its
field cannot carry.
"""
if message is not None:
br_type_val = message.br_type # type: Enum_BindingRevocation
code_val = message.code # type: Enum_RevocationTrigger | Enum_RevocationStatusCode
seq = message.seq
proxy = message.proxy
ipv4_hoa = message.ipv4_hoa
global_revocation = message.global_revocation
flags_reserved = message.flags_reserved
options = message.options
else:
br_type_val = self._make_index(br_type, Enum_BindingRevocation, bits=8,
name='binding revocation type')
# NOTE: The registry ``br_code_selector`` reads the octet from.
if br_type_val == Enum_BindingRevocation.Binding_Revocation_Acknowledgement:
code_val = self._make_index(code, Enum_RevocationStatusCode, bits=8, name='status')
else:
code_val = self._make_index(code, Enum_RevocationTrigger, bits=8,
name='revocation trigger')
options = options or []
return Schema_BindingRevocationMessage(
br_type=br_type_val,
code=code_val,
seq=self._make_uint(seq, bits=16, name='sequence number'),
flags={
'P': proxy,
'V': ipv4_hoa,
'G': global_revocation,
'reserved': self._make_uint(flags_reserved, bits=13, name='reserved bits'),
},
options=self._make_mh_options(options),
)
[docs]
def _make_msg_lri(self, message: 'Optional[Data_LocalizedRoutingInitiationMessage]' = None, *,
seq: 'int' = 0,
lifetime: 'int | timedelta' = 0,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
msg_reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_LocalizedRoutingInitiationMessage':
"""Make MH localized routing initiation (LRI) message type.
Args:
message: Message data model.
seq: Sequence number.
lifetime: Lifetime, in seconds or as a
:class:`~datetime.timedelta`. Unlike the binding messages, this
one counts seconds rather than units of 4 seconds, so the value is
not scaled.
options: Mobility options.
msg_reserved: The two reserved octets of the message body as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` or the lifetime does not fit its field.
Also if ``msg_reserved`` is not two octets.
"""
if message is not None:
msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00'),
size=2, model=message, field='data', offset=2)
seq = message.seq
lifetime_val = math.ceil(message.lifetime.total_seconds())
options = message.options
else:
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds())
options = options or []
return Schema_LocalizedRoutingInitiationMessage(
seq=self._make_uint(seq, bits=16, name='sequence number'),
reserved=self._make_octets(msg_reserved, size=2, name='reserved octets'),
lifetime=self._make_uint(lifetime_val, bits=16, name='lifetime'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_lra(self, message: 'Optional[Data_LocalizedRoutingAcknowledgmentMessage]' = None, *,
seq: 'int' = 0,
unsolicited: 'bool' = False,
status: 'LocalizedRoutingStatus | int' = LocalizedRoutingStatus.Success,
lifetime: 'int | timedelta' = 0,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_LocalizedRoutingAcknowledgmentMessage':
"""Make MH localized routing acknowledgment (LRA) message type.
Args:
message: Message data model.
seq: Sequence number.
unsolicited: Unsolicited flag.
status: Status, see
:class:`~pcapkit.protocols.internet.mh.protocol.LocalizedRoutingStatus`.
lifetime: Lifetime, in seconds or as a
:class:`~datetime.timedelta`.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the seven bits after the ``U`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq``, ``flags_reserved`` or the lifetime does not fit
its field, or ``status`` is a name, a :obj:`bool` or a code its
field cannot carry.
"""
if message is not None:
seq = message.seq
unsolicited = message.unsolicited
flags_reserved = message.flags_reserved
status_val = message.status # type: int
lifetime_val = math.ceil(message.lifetime.total_seconds())
options = message.options
else:
status_val = self._make_index(status, LocalizedRoutingStatus, bits=8, name='status')
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(lifetime.total_seconds())
options = options or []
return Schema_LocalizedRoutingAcknowledgmentMessage(
seq=self._make_uint(seq, bits=16, name='sequence number'),
flags={
'U': unsolicited,
'reserved': self._make_uint(flags_reserved, bits=7, name='reserved bits'),
},
status=status_val,
lifetime=self._make_uint(lifetime_val, bits=16, name='lifetime'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_upn(self, message: 'Optional[Data_UpdateNotificationMessage]' = None, *,
seq: 'int' = 0,
reason: 'Enum_UpdateNotificationReason | int' = Enum_UpdateNotificationReason.FORCE_REREGISTRATION, # pylint: disable=line-too-long
ack: 'bool' = False,
retransmit: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_UpdateNotificationMessage':
"""Make MH update notification (UPN) message type.
Args:
message: Message data model.
seq: Sequence number.
reason: Notification reason.
ack: Acknowledgement requested flag.
retransmit: Retransmission flag.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the 14 bits after the ``D`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field,
or ``reason`` is a name, a :obj:`bool` or a code its field cannot
carry.
"""
if message is not None:
seq = message.seq
reason_val = message.reason # type: Enum_UpdateNotificationReason
ack = message.ack
retransmit = message.retransmit
flags_reserved = message.flags_reserved
options = message.options
else:
reason_val = self._make_index(reason, Enum_UpdateNotificationReason, bits=16,
name='notification reason')
options = options or []
return Schema_UpdateNotificationMessage(
seq=self._make_uint(seq, bits=16, name='sequence number'),
reason=reason_val,
flags={
'A': ack,
'D': retransmit,
'reserved': self._make_uint(flags_reserved, bits=14, name='reserved bits'),
},
options=self._make_mh_options(options),
)
[docs]
def _make_msg_upa(self, message: 'Optional[Data_UpdateNotificationAcknowledgementMessage]' = None, *,
seq: 'int' = 0,
status: 'Enum_UpdateNotificationACKStatus | int' = Enum_UpdateNotificationACKStatus.SUCCESS,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
msg_reserved: 'bytes' = b'\x00\x00\x00',
**kwargs: 'Any') -> 'Schema_UpdateNotificationAcknowledgementMessage':
"""Make MH update notification acknowledgement (UPA) message type.
Args:
message: Message data model.
seq: Sequence number.
status: Status.
options: Mobility options.
msg_reserved: The three reserved octets of the message body as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` does not fit its field, or ``status`` is a
name, a :obj:`bool` or a code its field cannot carry.
Also if ``msg_reserved`` is not three octets.
"""
if message is not None:
msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00\x00\x00'),
size=3, model=message, field='data', offset=3)
seq = message.seq
status_val = message.status # type: Enum_UpdateNotificationACKStatus
options = message.options
else:
status_val = self._make_index(status, Enum_UpdateNotificationACKStatus, bits=8,
name='status')
options = options or []
return Schema_UpdateNotificationAcknowledgementMessage(
seq=self._make_uint(seq, bits=16, name='sequence number'),
status=status_val,
reserved=self._make_octets(msg_reserved, size=3, name='reserved octets'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_fbm(self, message: 'Optional[Data_FlowBindingMessage]' = None, *,
fb_type: 'Enum_FlowBindingType | int' = Enum_FlowBindingType.Indication,
seq: 'int' = 0,
code: 'Enum_FlowBindingIndicationTrigger | Enum_FlowBindingACKStatus | int' = Enum_FlowBindingIndicationTrigger.Unspecified, # pylint: disable=line-too-long
ack: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_FlowBindingMessage':
"""Make MH flow binding (FB) message type.
Args:
message: Message data model.
fb_type: Flow binding type, saying which form of the message this is.
seq: Sequence number.
code: Indication trigger, for an indication, or acknowledgement
status, for an acknowledgement; resolved in the registry of the
form ``fb_type`` names.
ack: Acknowledgement requested flag. Meaningful only in an
indication.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the seven bits after the ``A`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field,
or ``fb_type`` or ``code`` is a name, a :obj:`bool` or a code its
field cannot carry.
"""
if message is not None:
fb_type_val = message.fb_type # type: Enum_FlowBindingType
seq = message.seq
code_val = message.code # type: Enum_FlowBindingIndicationTrigger | Enum_FlowBindingACKStatus
ack = message.ack
flags_reserved = message.flags_reserved
options = message.options
else:
fb_type_val = self._make_index(fb_type, Enum_FlowBindingType, bits=16,
name='flow binding type')
# NOTE: The registry ``fb_code_selector`` reads the octet from.
if fb_type_val == Enum_FlowBindingType.Acknowledgement:
code_val = self._make_index(code, Enum_FlowBindingACKStatus, bits=8, name='status')
else:
code_val = self._make_index(code, Enum_FlowBindingIndicationTrigger, bits=8,
name='trigger')
options = options or []
return Schema_FlowBindingMessage(
fb_type=fb_type_val,
seq=self._make_uint(seq, bits=16, name='sequence number'),
code=code_val,
flags={
'A': ack,
'reserved': self._make_uint(flags_reserved, bits=7, name='reserved bits'),
},
options=self._make_mh_options(options),
)
[docs]
def _make_msg_sq(self, message: 'Optional[Data_SubscriptionQueryMessage]' = None, *,
seq: 'int' = 0,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
msg_reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_SubscriptionQueryMessage':
"""Make MH subscription query (SQ) message type.
Args:
message: Message data model.
seq: Sequence number, counted modulo 256.
options: Mobility options.
msg_reserved: The reserved octet of the message body as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` does not fit its field.
Also if ``msg_reserved`` is not one octet.
"""
if message is not None:
msg_reserved = self._fill_short_read(getattr(message, 'msg_reserved', b'\x00'),
size=1, model=message, field='data', offset=1)
seq = message.seq
options = message.options
else:
options = options or []
return Schema_SubscriptionQueryMessage(
seq=self._make_uint(seq, bits=8, name='sequence number'),
reserved=self._make_octets(msg_reserved, size=1, name='reserved octets'),
options=self._make_mh_options(options),
)
[docs]
def _make_msg_sr(self, message: 'Optional[Data_SubscriptionResponseMessage]' = None, *,
seq: 'int' = 0,
info: 'bool' = False,
options: 'Optional[Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
flags_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_SubscriptionResponseMessage':
"""Make MH subscription response (SR) message type.
Args:
message: Message data model.
seq: Sequence number, echoed from the query.
info: Multicast information flag.
options: Mobility options.
flags_reserved: Reserved bits, i.e. the seven bits after the ``I`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed message type.
Raises:
ProtocolError: If ``seq`` or ``flags_reserved`` does not fit its field.
"""
if message is not None:
seq = message.seq
info = message.info
flags_reserved = message.flags_reserved
options = message.options
else:
options = options or []
return Schema_SubscriptionResponseMessage(
seq=self._make_uint(seq, bits=8, name='sequence number'),
flags={
'I': info,
'reserved': self._make_uint(flags_reserved, bits=7, name='reserved bits'),
},
options=self._make_mh_options(options),
)
[docs]
def _make_opt_none(self, type: 'Enum_Option', option: 'Optional[Data_UnassignedOption]' = None, *,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_UnassignedOption':
"""Make MH unassigned option.
Args:
type: Option type.
option: Option data model.
data: Option data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``.
"""
if option is not None:
data = option.data
return Schema_UnassignedOption(
type=type,
length=self._make_uint(len(data), bits=8, name='Option Length'),
data=data,
)
[docs]
def _make_opt_pad(self, type: 'Enum_Option', option: 'Optional[Data_PadOption]' = None, *,
length: 'int' = 0,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_PadOption':
"""Make MH pad option.
Args:
type: Option type.
option: Option data model.
length: Value of the ``Option Length`` field, i.e. the number of
padding octets *after* the two octets of the option header.
data: The ``PadN`` option data, truncated or zero-filled to
``length`` octets.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``length`` does not fit the one-octet
``Option Length``.
Warns:
ProtocolWarning: If ``length`` is non-zero for a ``Pad1``, which
has no ``Option Length`` field to carry it; the option is made
as a ``PadN`` instead.
Note:
:attr:`Data_PadOption.length
<pcapkit.protocols.internet.mh.data.Option.length>` counts the
*whole* option, whereas :attr:`Schema_PadOption.length
<pcapkit.protocols.internet.mh.schema.Option.length>` is the
``Option Length`` field -- two octets fewer, and absent altogether
for a ``Pad1``. Copying one into the other unconverted would make a
re-made parsed ``PadN`` come back two octets too long.
A ``PadN`` with ``length`` ``0`` is a valid two-octet option
[:rfc:`6275#section-6.2.3`] and is kept as one.
"""
if option is not None:
length = 0 if option.type == Enum_Option.Pad1 else option.length - 2
data = getattr(option, 'data', b'')
if type == Enum_Option.Pad1 and length != 0:
warn(f'{self.alias}: [OptNo {type}] invalid length: must be 0 for a Pad1 option, '
f'not {length!r}; making a PadN', ProtocolWarning)
type = Enum_Option.PadN # type: ignore[assignment]
return Schema_PadOption(
type=type,
length=self._make_uint(length, bits=8, name='Option Length'),
data=data,
)
[docs]
def _make_opt_bra(self, type: 'Enum_Option', option: 'Optional[Data_BindingRefreshAdviceOption]' = None, *,
interval: 'int | timedelta' = 0,
**kwargs: 'Any') -> 'Schema_BindingRefreshAdviceOption':
"""Make MH binding refresh advice option.
Args:
type: Option type.
option: Option data model.
interval: Refresh interval before re-registration, in units of 4
seconds or as a :class:`~datetime.timedelta`
[:rfc:`6275#section-6.2.4`].
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the interval does not fit its field.
"""
if option is not None:
interval_val = math.ceil(option.interval.total_seconds() / 4)
else:
interval_val = self._seconds(interval, 4)
return Schema_BindingRefreshAdviceOption(
type=type,
length=2,
interval=self._make_uint(interval_val, bits=16, name='refresh interval'),
)
[docs]
def _make_opt_aca(self, type: 'Enum_Option', option: 'Optional[Data_AlternateCareofAddressOption]' = None, *,
address: 'bytes | str | int | IPv6Address' = '::',
**kwargs: 'Any') -> 'Schema_AlternateCareofAddressOption':
"""Make MH alternate care-of address option.
Args:
type: Option type.
option: Option data model.
address: Alternate care-of address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
address = option.address
return Schema_AlternateCareofAddressOption(
type=type,
length=16,
address=address,
)
[docs]
def _make_opt_ni(self, type: 'Enum_Option', option: 'Optional[Data_NonceIndicesOption]' = None, *,
home: 'int' = 0,
careof: 'int' = 0,
**kwargs: 'Any') -> 'Schema_NonceIndicesOption':
"""Make MH nonce indices option.
Args:
type: Option type.
option: Option data model.
home: Home nonce index.
careof: Care-of nonce index.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``home`` or ``careof`` does not fit its field.
"""
if option is not None:
home = option.home
careof = option.careof
return Schema_NonceIndicesOption(
type=type,
length=4,
home=self._make_uint(home, bits=16, name='home nonce index'),
careof=self._make_uint(careof, bits=16, name='care-of nonce index'),
)
[docs]
def _make_opt_bad(self, type: 'Enum_Option', option: 'Optional[Data_AuthorizationDataOption]' = None, *,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_AuthorizationDataOption':
"""Make MH binding authorization data option.
Args:
type: Option type.
option: Option data model.
data: Authenticator.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``data`` is not a multiple of eight octets, the
length :meth:`_read_opt_bad` accepts for an :rfc:`6275`
authenticator.
Also if the option is too long for its one-octet ``Option Length``.
"""
if option is not None:
data = option.data
if len(data) % 8 != 0:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid data: '
f'must be a multiple of eight octets, not {data!r}')
return Schema_AuthorizationDataOption(
type=type,
length=self._make_uint(len(data), bits=8, name='Option Length'),
data=data,
)
[docs]
def _make_opt_mnp(self, type: 'Enum_Option', option: 'Optional[Data_MobileNetworkPrefixOption]' = None, *,
prefix: 'bytes | str | int | IPv6Address | IPv6Network' = '::/0',
prefix_length: 'Optional[int]' = None,
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_MobileNetworkPrefixOption':
"""Make MH mobile network prefix option.
Args:
type: Option type.
option: Option data model.
prefix: Mobile network prefix, an IPv6 address or network, whose
bits past ``prefix_length`` are written as given.
prefix_length: Prefix length; defaults to that of a network or of
text ending in ``/<length>``, and to 128 for an address.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix`` is not an IPv6 address or network, or
``prefix_length`` is negative, a :obj:`bool`, or does not fit in
one octet.
Also if ``reserved`` is not one octet.
Note:
The prefix length is checked against its 8-bit field alone, as
:rfc:`3963#section-6.2` has a home agent answer a prefix it cannot
use with a status rather than discard the option, so whatever the
reader keeps is written back.
.. versionchanged:: 1.6.0
New argument ``prefix_length``. ``prefix`` keeps the bits set past
its length, rather than raising :exc:`ValueError`, and a prefix
length past 128 is written (:issue:`2702`).
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
prefix_length = option.prefix_length
prefix_addr = option.prefix
else:
try:
# NOTE: An interface keeps bits past its length, unlike a network, and takes an address,
# network or text alike; a bool is refused first, as it would pass for ``::1``.
prefix_val = None if isinstance(prefix, bool) else ipaddress.IPv6Interface(prefix)
except ValueError:
prefix_val = None
if prefix_val is None:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid mobile network prefix: {prefix!r}')
if prefix_length is None:
prefix_length = prefix_val.network.prefixlen
prefix_addr = prefix_val.ip
prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length')
return Schema_MobileNetworkPrefixOption(
type=type,
length=18,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
prefix_length=prefix_length,
prefix=prefix_addr,
)
[docs]
def _make_opt_lla(self, type: 'Enum_Option', option: 'Optional[Data_LinkLayerAddressOption]' = None, *,
address: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_LinkLayerAddressOption':
"""Make MH link-layer address option.
Args:
type: Option type.
option: Option data model.
address: Link-layer address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``.
"""
if option is not None:
address = option.lla
return Schema_LinkLayerAddressOption(
type=type,
length=self._make_uint(len(address) + 1, bits=8, name='Option Length'),
code=Enum_LLACode.MN, # type: ignore[arg-type]
lla=address,
)
[docs]
def _make_opt_mn_id(self, type: 'Enum_Option', option: 'Optional[Data_MNIDOption]' = None, *,
subtype: 'Enum_MNIDSubtype | int' = Enum_MNIDSubtype.IPv6_Address,
identifier: 'bytes | str | IPv6Address | int' = '::',
**kwargs: 'Any') -> 'Schema_MNIDOption':
"""Make MH mobile node identifier option.
Args:
type: Option type.
option: Option data model.
subtype: MN-ID subtype.
identifier: Identifier. An :obj:`int` is accepted for every
subtype except ``NAI``, with the sole exception of a
:obj:`bool`, which is rejected for every subtype -- see
``Raises`` below. For ``IPv6_Address`` it is converted
and validated the same way as any other value
:class:`ipaddress.IPv6Address` accepts. For every other
subtype -- each a numeric identifier (an IMSI, a P-TMSI, an
EUI-48/64 address, a GUTI, a DUID) -- it is converted to its
own minimal big-endian octets, at least one. ``NAI`` is text
(RFC 4283's ``user@realm`` form) rather than a numeric
identifier, so there is no non-arbitrary int-to-text mapping
the way there is int-to-address or int-to-octets, and an
:obj:`int` is rejected there.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``identifier`` is a :obj:`bool`, for any subtype
-- :obj:`bool` is an :obj:`int` subclass, so ``True`` would
otherwise be converted, to ``::1`` for ``IPv6_Address`` and to
a one-octet identifier for the numeric subtypes, neither of
which a caller passing a flag can plausibly have meant; pass
``int(...)`` to get the numeric value. If ``identifier`` is a
negative :obj:`int` (no subtype has a wire form for one), an
:obj:`int` of any value with the ``NAI`` subtype, an :obj:`int`
of ``2**128`` or above with the ``IPv6_Address`` subtype (whose
wire form is a fixed 16 octets, unlike the other subtypes,
which have no ceiling and simply pack into more), or
``identifier`` is of a type its subtype's field cannot hold at
all: anything but :obj:`str` for ``NAI``, anything but
:obj:`bytes`/:obj:`bytearray`/:obj:`int` for the remaining
subtypes -- an :obj:`int` is converted rather than rejected
there -- or anything :class:`ipaddress.IPv6Address` itself does
not accept for ``IPv6_Address``.
Also if ``subtype`` is a name, a :obj:`bool` or a code its field
cannot carry, or the option is too long for its one-octet
``Option Length``.
.. versionchanged:: 1.6.0
An ``NAI`` identifier's length counts the octets it packs as, rather
than its characters (:issue:`2684`).
"""
if option is not None:
subtype_val = option.subtype
identifier = option.identifier
self._make_uint(subtype_val, bits=8, name='MN-ID subtype')
else:
subtype_val = self._make_index(subtype, Enum_MNIDSubtype, bits=8, name='MN-ID subtype')
if isinstance(identifier, int) and identifier < 0:
# NOTE: Checked before the subtype dispatch, since no subtype can pack a
# negative identifier and each would leak a different bare stdlib error.
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID subtype {subtype_val!r} '
f'identifier must be a non-negative int, not {identifier!r}')
if isinstance(identifier, bool):
# NOTE: Checked before the dispatch, since a ``bool`` would otherwise pack as
# ``::1`` or as one octet; a caller wanting the integer passes ``int(flag)``.
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID identifier must not be a '
f'bool, not {identifier!r} -- pass int({identifier!r}) if the '
f'numeric value is what is wanted')
# NOTE: ``subtype_val`` picks the wire format (see ``mn_id_selector``), not the type
# of ``identifier``; an ``IPv6_Address`` always packs 16 octets, so it is normalised.
if subtype_val == Enum_MNIDSubtype.IPv6_Address:
if isinstance(identifier, int) and identifier >= 1 << 128:
# NOTE: Only ``IPv6_Address`` caps at 128 bits, so this bound lives here, and is
# checked explicitly so the ``try`` below handles only unconvertible types.
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID subtype IPv6_Address '
f'identifier must be an int below 2**128, not {identifier!r}')
if not isinstance(identifier, ipaddress.IPv6Address):
# NOTE: Wraps only this call, turning ``AddressValueError`` for anything
# ``IPv6Address`` cannot convert into a ``ProtocolError``.
try:
identifier = ipaddress.IPv6Address(identifier)
except ValueError as error:
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID subtype IPv6_Address '
f'identifier must be an ipaddress.IPv6Address, an int, or '
f'bytes/str it accepts, not {identifier!r}') from error
id_len = 16
elif isinstance(identifier, int):
if subtype_val == Enum_MNIDSubtype.NAI:
# NOTE: A decimal-digit string is not an NAI (``user@realm``, RFC 4283), so an
# int is refused rather than packed as text.
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID subtype NAI identifier '
f'must be str, not int -- pass str({identifier!r}) if a '
f'decimal-digit NAI is really what is wanted')
# NOTE: Every other subtype's field is a BytesField (see mn_id_selector),
# so the identifier is written as its minimal big-endian octets, at least one.
id_len = self._uint_size(identifier, minimum=1)
identifier = identifier.to_bytes(id_len, 'big')
elif subtype_val == Enum_MNIDSubtype.NAI:
# NOTE: The ``StringField`` packs with ``.encode``, so a non-str would leak a bare
# stdlib error; ``bytes`` is refused rather than decoded, as the wrong representation.
if not isinstance(identifier, str):
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID subtype NAI '
f'identifier must be str, not {identifier!r}')
# NOTE: The field packs UTF-8 octets, or a DecodedString's ``raw``
# ones, so the length counts those rather than characters (#2684).
id_len = len(getattr(identifier, 'raw', None) or identifier.encode())
else:
# NOTE: The ``BytesField`` packs with ``struct``'s ``s`` format, which takes only
# ``bytes`` and ``bytearray``; anything else would leak a bare stdlib error.
if not isinstance(identifier, (bytes, bytearray)):
raise ProtocolError(
f'{self.alias}: [OptNo {type}] MN-ID subtype {subtype_val!r} '
f'identifier must be bytes, not {identifier!r}')
id_len = len(identifier)
return Schema_MNIDOption(
type=type,
length=self._make_uint(1 + id_len, bits=8, name='Option Length'),
subtype=subtype_val,
identifier=identifier,
)
[docs]
def _make_opt_auth(self, type: 'Enum_Option', option: 'Optional[Data_AuthOption]' = None, *,
subtype: 'Enum_AuthSubtype | int' = Enum_AuthSubtype.MN_HA,
spi: 'int' = 0,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_AuthOption':
"""Make MH authentication option.
Args:
type: Option type.
option: Option data model.
subtype: Authentication subtype.
spi: Security parameter index.
data: Authentication data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``data`` is not two octets more than a multiple of
four, which is the only length leaving an ``Option Length`` of
``5 + len(data)`` that :meth:`_read_opt_auth` accepts; or if
``spi`` does not fit in four octets.
Also if ``subtype`` is a name, a :obj:`bool` or a code its field
cannot carry, or the option is too long for its one-octet
``Option Length``.
"""
if option is not None:
subtype_val = option.subtype
spi = option.spi
data = option.data
else:
subtype_val = self._make_index(subtype, Enum_AuthSubtype, bits=8, name='subtype')
if (len(data) + 6) % 4 != 0:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid data: must be two octets '
f'more than a multiple of four, not {data!r}')
return Schema_AuthOption(
type=type,
length=self._make_uint(5 + len(data), bits=8, name='Option Length'),
subtype=subtype_val,
spi=self._make_uint(spi, bits=32, name='SPI'),
data=data,
)
[docs]
def _make_opt_mesg_id(self, type: 'Enum_Option', option: 'Optional[Data_MesgIDOption]' = None, *,
timestamp: 'Optional[Data_NTPTimestamp]' = None,
interval: 'Optional[dt_type]' = None,
**kwargs: 'Any') -> 'Schema_MesgIDOption':
"""Make MH mobility message replay protection option.
Args:
type: Option type.
option: Option data model.
timestamp: NTP timestamp, see :rfc:`1305`.
interval: Moment to encode when neither ``option`` nor
``timestamp`` is given (``option`` supplies its own
``ntp_timestamp``); defaults to the current time.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
timestamp = option.ntp_timestamp
if timestamp is None:
interval = interval or datetime.datetime.now(datetime.timezone.utc)
int_ts = totimestamp(interval)
ts_sec = math.floor(int_ts)
ts_frc = min(round((int_ts - ts_sec) * 2**32), 0xFFFF_FFFF) # 1/2**32 seconds per unit
timestamp = Data_NTPTimestamp(seconds=ts_sec + 2_208_988_800, # 70 years
fraction=ts_frc)
return Schema_MesgIDOption(
type=type,
length=8,
seconds=timestamp.seconds,
fraction=timestamp.fraction,
)
[docs]
def _make_opt_cga_pr(self, type: 'Enum_Option', option: 'Optional[Data_CGAParametersRequestOption]' = None,
**kwargs: 'Any') -> 'Schema_CGAParametersRequestOption':
"""Make MH CGA parameters request option.
Args:
type: Option type.
option: Option data model.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
return Schema_CGAParametersRequestOption(
type=type,
length=0,
)
[docs]
def _make_opt_cga_param(self, type: 'Enum_Option', option: 'Optional[Data_CGAParametersOption]' = None, *,
parameters: 'Optional[list[Schema_CGAParameter | Data_CGAParameter | bytes]]' = None,
**kwargs: 'Any') -> 'Schema_CGAParametersOption':
"""Make MH CGA parameters option.
Args:
type: Option type.
option: Option data model.
parameters: CGA parameters, each as octets, a schema or a data
model. A :obj:`dict` is refused: a mapping stands for a
maker's keyword arguments, and no maker takes a CGA
parameter's fields, so build the data model from them. A
schema without ``public_key_test`` is built from a copy that
takes one from its key, and is left as given.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``, or an item of ``parameters`` is not octets,
a schema or a data model, such as a :obj:`dict`.
"""
if option is not None:
parameters = cast('list[Data_CGAParameter]', option.parameters) # type: ignore[assignment]
if parameters is None:
parameters = []
param = [] # type: list[Schema_CGAParameter | bytes]
length = 0
for data in parameters:
if isinstance(data, bytes):
length += len(data)
param.append(data)
elif isinstance(data, Schema_CGAParameter):
schema = data
# NOTE: Test the schema, not ``hasattr``: an unset field is not
# stored, so ``hasattr`` finds the class's field object (#2725).
if 'public_key_test' not in data:
# A copy takes the key test, leaving the caller's schema as given.
schema = Schema_CGAParameter.from_dict(
data.to_dict(), public_key_test={'len': max(len(data.public_key) - 2, 0)})
_, ext_len = self._make_cga_extensions(schema.extensions)
length += 25 + len(schema.public_key) + ext_len
param.append(schema)
elif isinstance(data, Data_CGAParameter):
ext, ext_len = self._make_cga_extensions(data.extensions)
schema = Schema_CGAParameter(
modifier=data.modifier,
prefix=data.prefix,
collision_count=data.collision_count,
public_key=data.public_key,
extensions=ext,
)
schema.public_key_test = {'len': max(len(data.public_key) - 2, 0)}
length += 25 + len(data.public_key) + ext_len
param.append(schema)
else:
raise ProtocolError(f'{self.alias}: [OptNo {type}] unknown CGA parameter format: must be bytes, '
f'a CGA parameter schema or data model, not {data!r}')
return Schema_CGAParametersOption(
type=type,
length=self._make_uint(length, bits=8, name='Option Length'),
parameters=param,
)
[docs]
def _make_opt_signature(self, type: 'Enum_Option', option: 'Optional[Data_SignatureOption]' = None, *,
signature: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_SignatureOption':
"""Make MH signature option.
Args:
type: Option type.
option: Option data model.
signature: Signature data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``.
"""
if option is not None:
signature = option.signature
return Schema_SignatureOption(
type=type,
length=self._make_uint(len(signature), bits=8, name='Option Length'),
signature=signature,
)
[docs]
def _make_opt_phkt(self, type: 'Enum_Option', option: 'Optional[Data_PermanentHomeKeygenTokenOption]' = None, *,
token: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_PermanentHomeKeygenTokenOption':
"""Make MH permanent home keygen token option.
Args:
type: Option type.
option: Option data model.
token: Token data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``.
"""
if option is not None:
token = option.token
return Schema_PermanentHomeKeygenTokenOption(
type=type,
length=self._make_uint(len(token), bits=8, name='Option Length'),
token=token,
)
[docs]
def _make_opt_ct_init(self, type: 'Enum_Option', option: 'Optional[Data_CareofTestInitOption]' = None,
**kwargs: 'Any') -> 'Schema_CareofTestInitOption':
"""Make MH Care-of Test Init option.
Args:
type: Option type.
option: Option data model.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
return Schema_CareofTestInitOption(
type=type,
length=0,
)
[docs]
def _make_opt_ct(self, type: 'Enum_Option', option: 'Optional[Data_CareofTestOption]' = None,
token: 'bytes' = b'\x00\x00\x00\x00\x00\x00\x00\x00',
**kwargs: 'Any') -> 'Schema_CareofTestOption':
"""Make MH Care-of Test option.
Args:
type: Option type.
option: Option data model.
token: Care-of keygen token.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``token`` is not eight octets.
"""
if option is not None:
token = option.token
return Schema_CareofTestOption(
type=type,
length=8,
token=self._make_octets(token, size=8, name='token'),
)
[docs]
def _make_opt_dns(self, type: 'Enum_Option', option: 'Optional[Data_DNSUpdateOption]' = None, *,
status: 'Enum_DNSStatusCode | int' = Enum_DNSStatusCode.DNS_update_performed,
remove: 'bool' = False,
identity: 'bytes' = b'',
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_DNSUpdateOption':
"""Make MH DNS-UPDATE-TYPE option.
Args:
type: Option type.
option: Option data model.
status: Status.
remove: Remove flag.
identity: Mobile node identity, in FQDN form.
reserved: Reserved bits, i.e. the seven bits after the ``R`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``reserved`` does not fit its field.
Also if ``status`` is a name, a :obj:`bool` or a code its field
cannot carry, or the option is too long for its one-octet
``Option Length``.
"""
if option is not None:
status_val = option.status # type: Enum_DNSStatusCode
remove = option.remove
reserved = option.reserved
identity = option.identity
self._make_uint(status_val, bits=8, name='status')
else:
status_val = self._make_index(status, Enum_DNSStatusCode, bits=8, name='status')
return Schema_DNSUpdateOption(
type=type,
length=self._make_uint(2 + len(identity), bits=8, name='Option Length'),
status=status_val,
flags={'R': int(remove), 'reserved': self._make_uint(reserved, bits=7,
name='reserved bits')},
identity=identity,
)
[docs]
def _make_opt_exp(self, type: 'Enum_Option', option: 'Optional[Data_ExperimentalMobilityOption]' = None, *,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_ExperimentalMobilityOption':
"""Make MH experimental mobility option.
Args:
type: Option type.
option: Option data model.
data: Experimental data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``.
"""
if option is not None:
data = option.data
return Schema_ExperimentalMobilityOption(
type=type,
length=self._make_uint(len(data), bits=8, name='Option Length'),
data=data,
)
[docs]
def _make_opt_vendor(self, type: 'Enum_Option', option: 'Optional[Data_VendorSpecificOption]' = None, *,
vendor: 'int' = 0,
subtype: 'int' = 0,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_VendorSpecificOption':
"""Make MH vendor specific mobility option.
Args:
type: Option type.
option: Option data model.
vendor: Vendor ID, an SMI Network Management Private Enterprise
Number.
subtype: Vendor-administered sub-type.
data: Vendor-specific data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``vendor`` or ``subtype`` does not fit its field.
Also if the option is too long for its one-octet ``Option Length``.
"""
if option is not None:
vendor = option.vendor
subtype = option.subtype
data = option.data
return Schema_VendorSpecificOption(
type=type,
length=self._make_uint(5 + len(data), bits=8, name='Option Length'),
vendor=self._make_uint(vendor, bits=32, name='vendor ID'),
subtype=self._make_uint(subtype, bits=8, name='vendor subtype'),
data=data,
)
[docs]
def _make_opt_service(self, type: 'Enum_Option', option: 'Optional[Data_ServiceSelectionOption]' = None, *,
identifier: 'str' = '',
**kwargs: 'Any') -> 'Schema_ServiceSelectionOption':
"""Make MH service selection mobility option.
Args:
type: Option type.
option: Option data model.
identifier: Service identifier.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``.
.. versionchanged:: 1.6.0
The length counts the octets a
:class:`~pcapkit.corekit.fields.strings.DecodedString` identifier
packs back as, rather than its text re-encoded (:issue:`2684`).
"""
if option is not None:
identifier = option.identifier
# NOTE: An identifier that is not UTF-8 parses as a DecodedString, whose
# ``raw`` octets are what packs back, so the length counts those (#2684).
encoded = getattr(identifier, 'raw', None) or identifier.encode()
if not encoded:
raise ProtocolError(f'{self.alias}: [OptNo {type}] service identifier must not be empty')
return Schema_ServiceSelectionOption(
type=type,
length=self._make_uint(len(encoded), bits=8, name='Option Length'),
identifier=identifier,
)
[docs]
def _make_opt_badf(self, type: 'Enum_Option', option: 'Optional[Data_BADFOption]' = None, *,
spi: 'int' = 0,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_BADFOption':
"""Make MH binding authorization data for FMIPv6 (BADF) option.
Args:
type: Option type.
option: Option data model.
spi: Security parameter index.
data: Authenticator.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``data`` is empty, since the ``length`` field then
reads as ``0``, which :meth:`_read_opt_badf` rejects; or if
``spi`` does not fit in four octets.
Also if the option is too long for its one-octet ``Option Length``.
Note:
The ``length`` field counts the authenticator only, excluding the
SPI, see :meth:`_read_opt_badf`.
"""
if option is not None:
spi = option.spi
data = option.data
if not data:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid data: '
f'must be at least one octet, not {data!r}')
return Schema_BADFOption(
type=type,
length=self._make_uint(len(data), bits=8, name='Option Length'),
spi=self._make_uint(spi, bits=32, name='SPI'),
data=data,
)
[docs]
def _make_opt_hnp(self, type: 'Enum_Option', option: 'Optional[Data_HomeNetworkPrefixOption]' = None, *,
prefix_length: 'int' = 64,
prefix: 'bytes | str | int | IPv6Address' = '::',
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_HomeNetworkPrefixOption':
"""Make MH home network prefix option.
Args:
type: Option type.
option: Option data model.
prefix_length: Prefix length.
prefix: Home network prefix.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`,
or does not fit in one octet. Also if ``reserved`` is not one octet.
Note:
The prefix length is checked against its 8-bit field alone, as
:rfc:`5213#section-5.3.2` has a local mobility anchor answer a
prefix it cannot authorise with a status rather than discard the
option, so whatever the reader keeps is written back.
.. versionchanged:: 1.6.0
A prefix length longer than the address is written, rather than
refused, as the reader keeps one (:issue:`2702`).
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
prefix_length = option.prefix_length
prefix = option.prefix
prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length')
return Schema_HomeNetworkPrefixOption(
type=type,
length=18,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
prefix_length=prefix_length,
prefix=prefix,
)
[docs]
def _make_opt_hi(self, type: 'Enum_Option', option: 'Optional[Data_HandoffIndicatorOption]' = None, *,
hi: 'Enum_HandoffType | int' = Enum_HandoffType.Attachment_over_a_new_interface,
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_HandoffIndicatorOption':
"""Make MH handoff indicator option.
Args:
type: Option type.
option: Option data model.
hi: Handoff indicator.
reserved: Reserved octet before the handoff indicator.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``reserved`` does not fit its field.
Also if ``hi`` is a name, a :obj:`bool` or a code its field
cannot carry.
"""
if option is not None:
hi_val = option.hi # type: Enum_HandoffType
reserved = option.reserved
self._make_uint(hi_val, bits=8, name='handoff type')
else:
hi_val = self._make_index(hi, Enum_HandoffType, bits=8, name='handoff type')
return Schema_HandoffIndicatorOption(
type=type,
length=2,
reserved=self._make_uint(reserved, bits=8, name='reserved octet'),
hi=hi_val,
)
[docs]
def _make_opt_att(self, type: 'Enum_Option', option: 'Optional[Data_AccessTechnologyTypeOption]' = None, *,
att: 'Enum_AccessType | int' = Enum_AccessType.Virtual,
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_AccessTechnologyTypeOption':
"""Make MH access technology type option.
Args:
type: Option type.
option: Option data model.
att: Access technology type.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``att`` is a name, a :obj:`bool` or a code its
field cannot carry.
Also if ``reserved`` is not one octet.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
att_val = option.att # type: Enum_AccessType
self._make_uint(att_val, bits=8, name='access technology type')
else:
att_val = self._make_index(att, Enum_AccessType, bits=8, name='access technology type')
return Schema_AccessTechnologyTypeOption(
type=type,
length=2,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
att=att_val,
)
[docs]
def _make_opt_mn_lli(self, type: 'Enum_Option', option: 'Optional[Data_MNLLIdentifierOption]' = None, *,
lli: 'bytes' = b'',
reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_MNLLIdentifierOption':
"""Make MH mobile node link-layer identifier option.
Args:
type: Option type.
option: Option data model.
lli: Link-layer identifier.
reserved: The two reserved octets as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``. Also if ``reserved`` is not two octets.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00')
lli = option.lli
return Schema_MNLLIdentifierOption(
type=type,
length=self._make_uint(2 + len(lli), bits=8, name='Option Length'),
reserved=self._make_octets(reserved, size=2, name='reserved octets'),
lli=lli,
)
[docs]
def _make_opt_lla_addr(self, type: 'Enum_Option', option: 'Optional[Data_LinkLocalAddressOption]' = None, *,
address: 'bytes | str | int | IPv6Address' = '::',
**kwargs: 'Any') -> 'Schema_LinkLocalAddressOption':
"""Make MH link-local address option.
Args:
type: Option type.
option: Option data model.
address: Link-local address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
address = option.address
return Schema_LinkLocalAddressOption(
type=type,
length=16,
address=address,
)
[docs]
def _make_opt_timestamp(self, type: 'Enum_Option', option: 'Optional[Data_TimestampOption]' = None, *,
seconds: 'int' = 0,
fraction: 'int' = 0,
**kwargs: 'Any') -> 'Schema_TimestampOption':
"""Make MH timestamp option.
Args:
type: Option type.
option: Option data model.
seconds: Seconds since 1 January 1970, in the leading 48 bits.
fraction: Fraction of a second, in units of 1/65536.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``seconds`` or ``fraction`` does not fit its field.
Note:
The fixed-point pair is taken from
:attr:`~pcapkit.protocols.internet.mh.data.TimestampOption.pmip_timestamp`
rather than recomputed from
:attr:`~pcapkit.protocols.internet.mh.data.TimestampOption.timestamp`,
since a :class:`~datetime.datetime` holds microseconds and the wire
format holds 1/65536ths -- converting between them does not round-trip.
"""
if option is not None:
seconds = option.pmip_timestamp.seconds
fraction = option.pmip_timestamp.fraction
seconds = self._make_uint(seconds, bits=48, name='timestamp seconds')
fraction = self._make_uint(fraction, bits=16, name='timestamp fraction')
return Schema_TimestampOption(
type=type,
length=8,
timestamp={
'seconds': seconds,
'fraction': fraction,
},
)
[docs]
def _make_opt_restart(self, type: 'Enum_Option', option: 'Optional[Data_RestartCounterOption]' = None, *,
counter: 'int' = 0,
**kwargs: 'Any') -> 'Schema_RestartCounterOption':
"""Make MH restart counter option.
Args:
type: Option type.
option: Option data model.
counter: Restart counter.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``counter`` does not fit its field.
"""
if option is not None:
counter = option.counter
return Schema_RestartCounterOption(
type=type,
length=4,
counter=self._make_uint(counter, bits=32, name='restart counter'),
)
[docs]
def _make_opt_ipv4_hoa(self, type: 'Enum_Option', option: 'Optional[Data_IPv4HomeAddressOption]' = None, *,
prefix_length: 'int' = 32,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind
request_prefix: 'bool' = False,
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_IPv4HomeAddressOption':
"""Make MH IPv4 home address option.
Args:
type: Option type.
option: Option data model.
prefix_length: Prefix length; ``32`` for a single address.
address: IPv4 home address.
request_prefix: Mobile network prefix request flag.
reserved: Reserved bits, i.e. the nine bits after the ``P`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix_length`` is zero, negative, a
:obj:`bool`, or longer than the address, or ``reserved``
does not fit its field.
"""
if option is not None:
prefix_length = option.prefix_length
address = option.address
request_prefix = option.request_prefix
reserved = option.reserved
prefix_length = self._make_uint(prefix_length, bits=6, name='prefix length')
if prefix_length == 0 or prefix_length > 32:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_IPv4HomeAddressOption(
type=type,
length=6,
flags={
'prefix_length': prefix_length,
'P': int(request_prefix),
'reserved': self._make_uint(reserved, bits=9, name='reserved bits'),
},
address=address,
)
[docs]
def _make_opt_ipv4_ack(self, type: 'Enum_Option',
option: 'Optional[Data_IPv4AddressAcknowledgementOption]' = None, *,
status: 'Enum_DSMIPv6HomeAddress | int' = Enum_DSMIPv6HomeAddress.Success,
prefix_length: 'int' = 32,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_IPv4AddressAcknowledgementOption':
"""Make MH IPv4 address acknowledgement option.
Args:
type: Option type.
option: Option data model.
status: Status.
prefix_length: Allocated prefix length.
address: Assigned IPv4 home address.
reserved: Reserved bits, i.e. the two bits after the prefix length.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix_length`` or ``reserved`` is negative, a
:obj:`bool`, or does not fit its field.
Also if ``status`` is a name, a :obj:`bool` or a code its field
cannot carry.
Note:
The prefix length is checked against its 6-bit field alone, as
:rfc:`5555#section-3.2.1` does not have the receiver discard one longer
than the address, so whatever the reader keeps is written back.
.. versionchanged:: 1.6.0
A prefix length longer than the address is written, rather than
refused, so that every one the reader keeps rebuilds (:issue:`2684`).
"""
if option is not None:
status_val = option.status # type: Enum_DSMIPv6HomeAddress
prefix_length = option.prefix_length
reserved = option.reserved
address = option.address
self._make_uint(status_val, bits=8, name='status')
else:
status_val = self._make_index(status, Enum_DSMIPv6HomeAddress, bits=8, name='status')
prefix_length = self._make_uint(prefix_length, bits=6, name='prefix length')
return Schema_IPv4AddressAcknowledgementOption(
type=type,
length=6,
status=status_val,
flags={'prefix_length': prefix_length,
'reserved': self._make_uint(reserved, bits=2, name='reserved bits')},
address=address,
)
[docs]
def _make_opt_nat(self, type: 'Enum_Option', option: 'Optional[Data_NATDetectionOption]' = None, *,
force: 'bool' = False,
refresh: 'int | timedelta' = 0,
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_NATDetectionOption':
"""Make MH NAT detection option.
Args:
type: Option type.
option: Option data model.
force: Force UDP encapsulation flag.
refresh: Suggested NAT binding refresh interval, in seconds or as a
:class:`~datetime.timedelta`.
reserved: Reserved bits, i.e. the 15 bits after the ``F`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``reserved`` or the refresh interval does not fit its
field.
"""
if option is not None:
force = option.force
reserved = option.reserved
refresh_val = math.ceil(option.refresh.total_seconds())
else:
refresh_val = refresh if isinstance(refresh, int) else math.ceil(refresh.total_seconds())
return Schema_NATDetectionOption(
type=type,
length=6,
flags={'F': int(force), 'reserved': self._make_uint(reserved, bits=15,
name='reserved bits')},
refresh=self._make_uint(refresh_val, bits=32, name='refresh interval'),
)
[docs]
def _make_opt_ipv4_coa(self, type: 'Enum_Option', option: 'Optional[Data_IPv4CareofAddressOption]' = None, *,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind
reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_IPv4CareofAddressOption':
"""Make MH IPv4 care-of address option.
Args:
type: Option type.
option: Option data model.
address: IPv4 care-of address.
reserved: The two reserved octets as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``reserved`` is not two octets.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00')
address = option.address
return Schema_IPv4CareofAddressOption(
type=type,
length=6,
reserved=self._make_octets(reserved, size=2, name='reserved octets'),
address=address,
)
[docs]
def _make_opt_gre(self, type: 'Enum_Option', option: 'Optional[Data_GREKeyOption]' = None, *,
key: 'Optional[int]' = None,
reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_GREKeyOption':
"""Make MH GRE key option.
Args:
type: Option type.
option: Option data model.
key: GRE key identifier, or :obj:`None` to omit it -- which is what an
option length of 2 means on the wire.
reserved: The two reserved octets as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``key`` does not fit its field.
Also if ``reserved`` is not two octets.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00')
key = option.key
return Schema_GREKeyOption(
type=type,
length=2 if key is None else 6,
reserved=self._make_octets(reserved, size=2, name='reserved octets'),
key=None if key is None else self._make_uint(key, bits=32, name='GRE key'),
)
[docs]
def _make_opt_ipv6_ap(self, type: 'Enum_Option', option: 'Optional[Data_IPv6AddressPrefixOption]' = None, *,
code: 'IPv6AddressPrefixCode | int' = IPv6AddressPrefixCode.New_Care_of_Address,
prefix_length: 'int' = 128,
address: 'bytes | str | int | IPv6Address' = '::',
**kwargs: 'Any') -> 'Schema_IPv6AddressPrefixOption':
"""Make MH mobility header IPv6 address/prefix option.
Args:
type: Option type.
option: Option data model.
code: Option code, see
:class:`~pcapkit.protocols.internet.mh.protocol.IPv6AddressPrefixCode`.
prefix_length: Prefix length.
address: IPv6 address/prefix.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`,
or longer than the address, or ``code`` is a name, a
:obj:`bool` or a code its field cannot carry.
"""
if option is not None:
code_val = option.code
prefix_length = option.prefix_length
address = option.address
else:
code_val = self._make_index(code, IPv6AddressPrefixCode, bits=8, name='code')
prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length')
if prefix_length > 128:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_IPv6AddressPrefixOption(
type=type,
length=18,
code=code_val,
prefix_length=prefix_length,
address=address,
)
[docs]
def _make_opt_bid(self, type: 'Enum_Option', option: 'Optional[Data_BindingIdentifierOption]' = None, *,
bid: 'int' = 1,
status: 'Enum_StatusCode | int' = Enum_StatusCode.Binding_Update_accepted_Proxy_Binding_Update_accepted, # pylint: disable=line-too-long
simultaneous: 'bool' = False,
bid_pri: 'int' = 0,
address: 'Optional[bytes | str | int | IPv4Address | IPv6Address]' = None,
**kwargs: 'Any') -> 'Schema_BindingIdentifierOption':
"""Make MH binding identifier (BID) option.
Args:
type: Option type.
option: Option data model.
bid: Binding identifier.
status: Status, overriding the message status for this binding alone.
simultaneous: Simultaneous home and foreign binding flag.
bid_pri: Binding priority.
address: Care-of address, or :obj:`None` to omit it.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``bid`` or ``bid_pri`` does not fit its field.
Also if ``status`` is a name, a :obj:`bool` or a code its field
cannot carry.
Note:
The option length is derived from the address family, since that is
the only thing that carries it on the wire: 4 with no address, 8 for
an IPv4 one and 20 for an IPv6 one [:rfc:`5648#section-4.3`].
"""
if option is not None:
bid = option.bid
status_val = option.status # type: Enum_StatusCode
simultaneous = option.simultaneous
bid_pri = option.bid_pri
address = option.address
self._make_uint(status_val, bits=8, name='status')
else:
status_val = self._make_index(status, Enum_StatusCode, bits=8, name='status')
bid = self._make_uint(bid, bits=16, name='binding identifier')
bid_pri = self._make_uint(bid_pri, bits=7, name='binding priority')
if address is None:
length = 4
else:
# NOTE: Converted here, as the length follows the family, so ``parse_ip_address``
# refuses a ``bool`` that would otherwise pack as ``0.0.0.1``.
addr = parse_ip_address(
address, f'{self.alias}: [OptNo {type}] invalid care-of address')
length = 8 if addr.version == 4 else 20
address = addr
return Schema_BindingIdentifierOption(
type=type,
length=length,
bid=bid,
status=status_val,
flags={
'H': int(simultaneous),
'BID_PRI': bid_pri,
},
address=address,
)
[docs]
def _make_opt_ipv4_hoa_req(self, type: 'Enum_Option',
option: 'Optional[Data_IPv4HomeAddressRequestOption]' = None, *,
prefix_length: 'int' = 32,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_IPv4HomeAddressRequestOption':
"""Make MH IPv4 home address request option.
Args:
type: Option type.
option: Option data model.
prefix_length: Prefix length of the requested home network.
address: Requested IPv4 home address.
reserved: Reserved bits, i.e. the ten bits after the prefix length.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix_length`` or ``reserved`` is negative, a
:obj:`bool`, or does not fit its field.
Note:
The prefix length is checked against its 6-bit field alone, as
:rfc:`5844#section-3.3.1` does not have the receiver discard one longer
than the address, so whatever the reader keeps is written back.
.. versionchanged:: 1.6.0
A prefix length longer than the address is written, rather than
refused, so that every one the reader keeps rebuilds (:issue:`2684`).
"""
if option is not None:
prefix_length = option.prefix_length
reserved = option.reserved
address = option.address
prefix_length = self._make_uint(prefix_length, bits=6, name='prefix length')
return Schema_IPv4HomeAddressRequestOption(
type=type,
length=6,
flags={'prefix_length': prefix_length,
'reserved': self._make_uint(reserved, bits=10, name='reserved bits')},
address=address,
)
[docs]
def _make_opt_ipv4_hoa_rep(self, type: 'Enum_Option', option: 'Optional[Data_IPv4HomeAddressReplyOption]' = None, *,
status: 'Enum_HomeAddressReply | int' = Enum_HomeAddressReply.Success,
prefix_length: 'int' = 32,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_IPv4HomeAddressReplyOption':
"""Make MH IPv4 home address reply option.
Args:
type: Option type.
option: Option data model.
status: Status.
prefix_length: Prefix length of the assigned home network.
address: Assigned IPv4 home address.
reserved: Reserved bits, i.e. the two bits after the prefix length.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix_length`` or ``reserved`` is negative, a
:obj:`bool`, or does not fit its field.
Also if ``status`` is a name, a :obj:`bool` or a code its field
cannot carry.
Note:
The prefix length is checked against its 6-bit field alone, as
:rfc:`5844#section-3.3.2` does not have the receiver discard one longer
than the address, so whatever the reader keeps is written back.
.. versionchanged:: 1.6.0
A prefix length longer than the address is written, rather than
refused, so that every one the reader keeps rebuilds (:issue:`2684`).
"""
if option is not None:
status_val = option.status # type: Enum_HomeAddressReply
prefix_length = option.prefix_length
reserved = option.reserved
address = option.address
self._make_uint(status_val, bits=8, name='status')
else:
status_val = self._make_index(status, Enum_HomeAddressReply, bits=8, name='status')
prefix_length = self._make_uint(prefix_length, bits=6, name='prefix length')
return Schema_IPv4HomeAddressReplyOption(
type=type,
length=6,
status=status_val,
flags={'prefix_length': prefix_length,
'reserved': self._make_uint(reserved, bits=2, name='reserved bits')},
address=address,
)
[docs]
def _make_opt_ipv4_router(self, type: 'Enum_Option',
option: 'Optional[Data_IPv4DefaultRouterAddressOption]' = None, *,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind
reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_IPv4DefaultRouterAddressOption':
"""Make MH IPv4 default-router address option.
Args:
type: Option type.
option: Option data model.
address: IPv4 default-router address.
reserved: The two reserved octets as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``reserved`` is not two octets.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00')
address = option.address
return Schema_IPv4DefaultRouterAddressOption(
type=type,
length=6,
reserved=self._make_octets(reserved, size=2, name='reserved octets'),
address=address,
)
[docs]
def _make_opt_ipv4_dhcp(self, type: 'Enum_Option', option: 'Optional[Data_IPv4DHCPSupportModeOption]' = None, *,
mode: 'Enum_DHCPSupportMode | int' = Enum_DHCPSupportMode.Unassigned_0x0,
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_IPv4DHCPSupportModeOption':
"""Make MH IPv4 DHCP support mode option.
Args:
type: Option type.
option: Option data model.
mode: DHCP support mode.
reserved: Reserved bits, i.e. the 15 bits before the ``S`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the mode does not fit the one-bit ``S`` flag, or
``reserved`` does not fit its field; or if ``mode`` is a name.
"""
if option is not None:
mode_val = self._make_uint(int(option.mode), bits=1, name='DHCP support mode')
reserved = option.reserved
else:
mode_val = int(self._make_index(mode, Enum_DHCPSupportMode, bits=1,
name='DHCP support mode'))
return Schema_IPv4DHCPSupportModeOption(
type=type,
length=2,
flags={'S': mode_val, 'reserved': self._make_uint(reserved, bits=15,
name='reserved bits')},
)
[docs]
def _make_opt_cr(self, type: 'Enum_Option', option: 'Optional[Data_ContextRequestOption]' = None, *,
requests: 'Optional[list[Data_ContextRequest | tuple[Enum_Option | int, bytes]]]' = None,
reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_ContextRequestOption':
"""Make MH context request option.
Args:
type: Option type.
option: Option data model.
requests: Requested contexts, each either a data model or a
``(mobility option type, extra data)`` pair.
reserved: The two reserved octets as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``, or the mobility option type of a pair is a
name, a :obj:`bool` or a code its field cannot carry.
Also if ``reserved`` is not two octets.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00')
requests = cast('list[Data_ContextRequest | tuple[Enum_Option | int, bytes]]',
list(option.requests))
else:
requests = requests or []
buffer = bytearray()
for request in requests:
if isinstance(request, Data_ContextRequest):
req_type, value = int(request.type), request.value
else:
raw_type, value = request
req_type = int(self._make_index(raw_type, Enum_Option, bits=8, name='request type'))
if len(value) > 0xFF:
raise ProtocolError(f'{self.alias}: [OptNo {type}] context request value of '
f'{len(value)} octets does not fit its length field')
buffer.append(req_type)
buffer.append(len(value))
buffer.extend(value)
return Schema_ContextRequestOption(
type=type,
length=self._make_uint(2 + len(buffer), bits=8, name='Option Length'),
reserved=self._make_octets(reserved, size=2, name='reserved octets'),
requests=bytes(buffer),
)
[docs]
def _make_opt_lmaa(self, type: 'Enum_Option', option: 'Optional[Data_LMAAddressOption]' = None, *,
code: 'Optional[LMAAddressCode | int]' = None,
address: 'bytes | str | int | IPv4Address | IPv6Address' = '::',
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_LMAAddressOption':
"""Make MH local mobility anchor address option.
Args:
type: Option type.
option: Option data model.
code: Option code, see
:class:`~pcapkit.protocols.internet.mh.protocol.LMAAddressCode`. If not
given, the code of the address family of ``address``.
address: Local mobility anchor address.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If an explicit ``code`` is not the code
:rfc:`5949#section-6.2.2` gives the address family of
``address`` (:issue:`2280`), or is a name, a :obj:`bool` or a
code its field cannot carry.
Also if ``reserved`` is not one octet.
Note:
The option length is derived from the address family rather than from
``code``, so that the two cannot be emitted disagreeing. A data model
passed as ``option`` keeps its code as captured, even where it
disagrees with the family, so that the capture rebuilds byte for byte.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
code_val = self._make_uint(int(option.code), bits=8, name='code') # type: Optional[int]
address = option.address
elif code is None:
code_val = None
else:
code_val = self._make_index(code, LMAAddressCode, bits=8, name='code')
if isinstance(address, bytes) and len(address) not in (4, 16):
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid address: {address!r}')
# NOTE: Normalised here, so the attribute matches a parse and the width follows the
# address; ``parse_ip_address`` refuses a ``bool`` the schema's guard would never see.
addr = parse_ip_address(
address, f'{self.alias}: [OptNo {type}] invalid address')
# NOTE: RFC 5949 ties each code to one address family, so an unset code is the family's and a
# set one must agree (#2280). A data model as ``option`` skips this, so a captured mismatch rebuilds.
family_code = LMAAddressCode.IPv4_LMAA if addr.version == 4 else LMAAddressCode.IPv6_LMAA
if code_val is None:
code_val = int(family_code)
elif option is None and code_val != family_code:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid code: '
f'code does not match address family: {code_val}')
return Schema_LMAAddressOption(
type=type,
length=6 if addr.version == 4 else 18,
code=code_val,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
address=addr,
)
[docs]
def _make_opt_mn_lla_iid(self, type: 'Enum_Option', option: 'Optional[Data_MNLLAIIDOption]' = None, *,
iid: 'bytes' = b'\x00' * 8,
reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_MNLLAIIDOption':
"""Make MH mobile node link-local address interface identifier option.
Args:
type: Option type.
option: Option data model.
iid: Interface identifier; exactly 8 octets.
reserved: The two reserved octets as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``iid`` is not eight octets, or ``reserved`` is not
two.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00')
iid = option.iid
if len(iid) != 8:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid interface identifier: {iid!r}')
return Schema_MNLLAIIDOption(
type=type,
length=10,
reserved=self._make_octets(reserved, size=2, name='reserved octets'),
iid=iid,
)
[docs]
def _make_opt_transient(self, type: 'Enum_Option', option: 'Optional[Data_TransientBindingOption]' = None, *,
late: 'bool' = False,
lifetime: 'int | timedelta' = 0,
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_TransientBindingOption':
"""Make MH transient binding option.
Args:
type: Option type.
option: Option data model.
late: Late path switch flag.
lifetime: Maximum lifetime of the transient state, in units of 100
milliseconds or as a :class:`~datetime.timedelta`.
reserved: Reserved bits, i.e. the seven bits before the ``L`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the lifetime does not fit in one octet, or
``reserved`` does not fit its field.
"""
if option is not None:
late = option.late
reserved = option.reserved
lifetime_val = math.ceil(option.lifetime.total_seconds() * 10)
else:
lifetime_val = lifetime if isinstance(lifetime, int) else math.ceil(
lifetime.total_seconds() * 10)
lifetime_val = self._make_uint(lifetime_val, bits=8, name='lifetime')
return Schema_TransientBindingOption(
type=type,
length=2,
flags={'L': int(late), 'reserved': self._make_uint(reserved, bits=7,
name='reserved bits')},
lifetime=lifetime_val,
)
[docs]
def _make_opt_fs(self, type: 'Enum_Option', option: 'Optional[Data_FlowSummaryOption]' = None, *,
fid: 'Optional[list[int]]' = None,
**kwargs: 'Any') -> 'Schema_FlowSummaryOption':
"""Make MH flow summary mobility option.
Args:
type: Option type.
option: Option data model.
fid: Flow identifiers being refreshed.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If an entry of ``fid`` does not fit its field.
Also if the option is too long for its one-octet ``Option Length``.
"""
if option is not None:
fid = list(option.fid)
else:
fid = fid or []
for item in fid:
self._make_uint(item, bits=16, name='flow identifier')
return Schema_FlowSummaryOption(
type=type,
length=self._make_uint(len(fid) * 2, bits=8, name='Option Length'),
fid=fid,
)
[docs]
def _make_opt_fid(self, type: 'Enum_Option', option: 'Optional[Data_FlowIdentificationOption]' = None, *,
fid: 'int' = 1,
fid_pri: 'int' = 1,
status: 'Enum_FlowIDStatus | int' = Enum_FlowIDStatus.Flow_binding_successful,
suboptions: 'Optional[FlowIDSuboption | list[Schema_FlowIdentificationSuboption | tuple[Enum_FlowIDSuboption | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_FlowIdentificationOption':
"""Make MH flow identification mobility option.
Args:
type: Option type.
option: Option data model.
fid: Flow identifier.
fid_pri: Flow priority.
status: Status.
suboptions: Sub-options.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``fid`` or ``fid_pri`` does not fit its field.
Also if ``status`` is a name, a :obj:`bool` or a code its field
cannot carry, or the option is too long for its one-octet
``Option Length``.
Also if ``reserved`` is not one octet.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
fid = option.fid
fid_pri = option.fid_pri
status_val = option.status # type: Enum_FlowIDStatus
suboptions = option.suboptions
self._make_uint(status_val, bits=8, name='status')
else:
status_val = self._make_index(status, Enum_FlowIDStatus, bits=8, name='status')
suboptions = suboptions or []
entries = self._make_fid_suboptions(suboptions)
length = 6 + sum(len(entry) if isinstance(entry, bytes) else len(entry.pack())
for entry in entries)
return Schema_FlowIdentificationOption(
type=type,
length=self._make_uint(length, bits=8, name='Option Length'),
fid=self._make_uint(fid, bits=16, name='flow identifier'),
fid_pri=self._make_uint(fid_pri, bits=16, name='flow identifier priority'),
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
status=status_val,
suboptions=entries,
)
[docs]
def _make_opt_rc(self, type: 'Enum_Option', option: 'Optional[Data_RedirectCapabilityOption]' = None, *,
reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_RedirectCapabilityOption':
"""Make MH redirect-capability mobility option.
Args:
type: Option type.
option: Option data model.
reserved: The two reserved octets as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``reserved`` is not two octets.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00')
return Schema_RedirectCapabilityOption(
type=type,
length=2,
reserved=self._make_octets(reserved, size=2, name='reserved octets'),
)
[docs]
def _make_opt_redirect(self, type: 'Enum_Option', option: 'Optional[Data_RedirectOption]' = None, *,
ipv6: 'Optional[bytes | str | int | IPv6Address]' = None,
ipv4: 'Optional[bytes | str | int | IPv4Address]' = None,
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_RedirectOption':
"""Make MH redirect mobility option.
Args:
type: Option type.
option: Option data model.
ipv6: IPv6 address of the redirected-to local mobility anchor.
ipv4: IPv4 address of the redirected-to local mobility anchor.
reserved: Reserved bits, i.e. the 14 bits after the ``N`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If neither or both addresses are given.
:rfc:`6463#section-4.2` requires exactly one. Also if
``reserved`` does not fit its field.
"""
if option is not None:
ipv6 = option.ipv6
ipv4 = option.ipv4
reserved = option.reserved
if (ipv6 is None) == (ipv4 is None):
raise ProtocolError(f'{self.alias}: [OptNo {type}] exactly one of the IPv6 and '
'IPv4 addresses must be given')
return Schema_RedirectOption(
type=type,
length=18 if ipv6 is not None else 6,
flags={
'K': int(ipv6 is not None),
'N': int(ipv4 is not None),
'reserved': self._make_uint(reserved, bits=14, name='reserved bits'),
},
ipv6=ipv6,
ipv4=ipv4,
)
[docs]
def _make_opt_load(self, type: 'Enum_Option', option: 'Optional[Data_LoadInformationOption]' = None, *,
priority: 'int' = 0,
sessions_in_use: 'int' = 0,
max_sessions: 'int' = 0,
used_capacity: 'int' = 0,
max_capacity: 'int' = 0,
**kwargs: 'Any') -> 'Schema_LoadInformationOption':
"""Make MH load information mobility option.
Args:
type: Option type.
option: Option data model.
priority: Priority; a lower value is a higher priority.
sessions_in_use: Mobility sessions currently in use.
max_sessions: Maximum number of mobility sessions accepted.
used_capacity: Used capacity, in kilobytes per second.
max_capacity: Maximum capacity, in kilobytes per second.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If a priority, session or capacity value does not fit its
field.
"""
if option is not None:
priority = option.priority
sessions_in_use = option.sessions_in_use
max_sessions = option.max_sessions
used_capacity = option.used_capacity
max_capacity = option.max_capacity
return Schema_LoadInformationOption(
type=type,
length=18,
priority=self._make_uint(priority, bits=16, name='priority'),
sessions_in_use=self._make_uint(sessions_in_use, bits=32, name='sessions in use'),
max_sessions=self._make_uint(max_sessions, bits=32, name='maximum sessions'),
used_capacity=self._make_uint(used_capacity, bits=32, name='used capacity'),
max_capacity=self._make_uint(max_capacity, bits=32, name='maximum capacity'),
)
[docs]
def _make_opt_alt_ipv4_coa(self, type: 'Enum_Option',
option: 'Optional[Data_AlternateIPv4CareofAddressOption]' = None, *,
address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind
**kwargs: 'Any') -> 'Schema_AlternateIPv4CareofAddressOption':
"""Make MH alternate IPv4 care-of address option.
Args:
type: Option type.
option: Option data model.
address: Alternate IPv4 care-of address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
address = option.address
return Schema_AlternateIPv4CareofAddressOption(
type=type,
length=4,
address=address,
)
[docs]
def _make_opt_mn_group(self, type: 'Enum_Option', option: 'Optional[Data_MNGroupIdentifierOption]' = None, *,
subtype: 'Enum_MNGroupID | int' = Enum_MNGroupID.Bulk_Binding_Update_Group,
group_id: 'int' = 1,
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_MNGroupIdentifierOption':
"""Make MH mobile node group identifier option.
Args:
type: Option type.
option: Option data model.
subtype: Sub-type.
group_id: Mobile node group identifier.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``group_id`` does not fit its field.
Also if ``subtype`` is a name, a :obj:`bool` or a code its field
cannot carry.
Also if ``reserved`` is not one octet.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
subtype_val = option.subtype # type: Enum_MNGroupID
group_id = option.group_id
self._make_uint(subtype_val, bits=8, name='group identifier subtype')
else:
subtype_val = self._make_index(subtype, Enum_MNGroupID, bits=8,
name='group identifier subtype')
return Schema_MNGroupIdentifierOption(
type=type,
length=6,
subtype=subtype_val,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
group_id=self._make_uint(group_id, bits=32, name='group identifier'),
)
[docs]
def _make_opt_mag_addr(self, type: 'Enum_Option', option: 'Optional[Data_MAGIPv6AddressOption]' = None, *,
address_length: 'int' = 128,
address: 'bytes | str | int | IPv6Address' = '::',
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_MAGIPv6AddressOption':
"""Make MH MAG IPv6 address option.
Args:
type: Option type.
option: Option data model.
address_length: Address length, in bits; :rfc:`6705#section-11.1`
requires 128.
address: MAG IPv6 address.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``address_length`` does not fit its field.
Also if ``reserved`` is not one octet.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
address_length = option.address_length
address = option.address
return Schema_MAGIPv6AddressOption(
type=type,
length=18,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
address_length=self._make_uint(address_length, bits=8, name='address length'),
address=address,
)
[docs]
def _make_opt_ani(self, type: 'Enum_Option', option: 'Optional[Data_AccessNetworkIdentifierOption]' = None, *,
suboptions: 'Optional[ANISuboption | list[Schema_ANISuboption | tuple[Enum_ANISuboption | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
**kwargs: 'Any') -> 'Schema_AccessNetworkIdentifierOption':
"""Make MH access network identifier option.
Args:
type: Option type.
option: Option data model.
suboptions: Sub-options; at least one is required.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``.
"""
if option is not None:
suboptions = option.suboptions
else:
suboptions = suboptions or []
entries = self._make_ani_suboptions(suboptions)
if not entries:
raise ProtocolError(f'{self.alias}: [OptNo {type}] at least one sub-option is required')
length = sum(len(entry) if isinstance(entry, bytes) else len(entry.pack())
for entry in entries)
return Schema_AccessNetworkIdentifierOption(
type=type,
length=self._make_uint(length, bits=8, name='Option Length'),
suboptions=entries,
)
[docs]
def _make_opt_offload(self, type: 'Enum_Option',
option: 'Optional[Data_IPv4TrafficOffloadSelectorOption]' = None, *,
mode: 'bool' = False,
selector: 'Optional[FlowIDSuboption | list[Schema_FlowIdentificationSuboption | tuple[Enum_FlowIDSuboption | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_IPv4TrafficOffloadSelectorOption':
"""Make MH IPv4 traffic offload selector option.
Args:
type: Option type.
option: Option data model.
mode: Offload mode flag.
selector: Traffic selector sub-options.
reserved: Reserved bits, i.e. the 31 bits after the ``M`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``reserved`` does not fit its field.
Also if the option is too long for its one-octet ``Option Length``.
"""
if option is not None:
mode = option.mode
reserved = option.reserved
selector = option.selector
else:
selector = selector or []
entries = self._make_fid_suboptions(selector)
length = 4 + sum(len(entry) if isinstance(entry, bytes) else len(entry.pack())
for entry in entries)
return Schema_IPv4TrafficOffloadSelectorOption(
type=type,
length=self._make_uint(length, bits=8, name='Option Length'),
flags={'M': int(mode), 'reserved': self._make_uint(reserved, bits=31,
name='reserved bits')},
selector=entries,
)
[docs]
def _make_opt_mcast_sel(self, type: 'Enum_Option',
option: 'Optional[Data_DynamicIPMulticastSelectorOption]' = None, *,
protocol: 'int' = 143,
mode: 'bool' = False,
records: 'int' = 0,
data: 'bytes' = b'',
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_DynamicIPMulticastSelectorOption':
"""Make MH dynamic IP multicast selector option.
Args:
type: Option type.
option: Option data model.
protocol: MLD or IGMP protocol number identifying the record format.
mode: Selector mode flag.
records: Number of multicast address records in ``data``.
data: Multicast address records, opaque to this module.
reserved: Reserved bits, i.e. the seven bits after the ``M`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``protocol``, ``reserved`` or ``records`` does not fit its
field.
Also if the option is too long for its one-octet ``Option Length``.
"""
if option is not None:
protocol = option.protocol
mode = option.mode
reserved = option.reserved
records = option.records
data = option.data
return Schema_DynamicIPMulticastSelectorOption(
type=type,
length=self._make_uint(4 + len(data), bits=8, name='Option Length'),
protocol=self._make_uint(protocol, bits=8, name='protocol'),
flags={'M': int(mode), 'reserved': self._make_uint(reserved, bits=7,
name='reserved bits')},
records=self._make_uint(records, bits=16, name='number of records'),
data=data,
)
[docs]
def _make_opt_dmnp(self, type: 'Enum_Option', option: 'Optional[Data_DelegatedMNPOption]' = None, *,
prefix_length: 'int' = 64,
prefix: 'bytes | str | int | IPv4Address | IPv6Address' = '::',
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_DelegatedMNPOption':
"""Make MH delegated mobile network prefix option.
Args:
type: Option type.
option: Option data model.
prefix_length: Prefix length.
prefix: Delegated mobile network prefix.
reserved: Reserved bits, i.e. the seven bits after the ``V`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Note:
The ``V`` flag is derived from the prefix's own family rather than
taken as an argument, so that the flag and the emitted width cannot
disagree. The prefix length is checked against its 8-bit field alone,
as :rfc:`7148#section-4.1` does not have the receiver discard one
longer than the address, so whatever the reader keeps is written back.
Raises:
ProtocolError: If ``prefix_length`` or ``reserved`` is negative, a
:obj:`bool`, or does not fit its field.
.. versionchanged:: 1.6.0
A prefix length longer than the address is written, rather than
refused, so that every one the reader keeps rebuilds (:issue:`2684`).
"""
if option is not None:
reserved = option.reserved
prefix_length = option.prefix_length
prefix = option.prefix
# NOTE: The ``V`` flag and width follow the family here, so ``parse_ip_address`` refuses
# a ``bool``; the default ``prefix_length`` only hides it behind the range check.
addr = parse_ip_address(
prefix, f'{self.alias}: [OptNo {type}] invalid mobile network prefix')
ipv4 = addr.version == 4
prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length')
return Schema_DelegatedMNPOption(
type=type,
length=6 if ipv4 else 18,
flags={'V': int(ipv4), 'reserved': self._make_uint(reserved, bits=7,
name='reserved bits')},
prefix_length=prefix_length,
prefix=addr,
)
[docs]
def _make_opt_ams4(self, type: 'Enum_Option',
option: 'Optional[Data_ActiveMulticastSubscriptionIPv4Option]' = None, *,
igmp_type: 'int' = 0x22,
context: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_ActiveMulticastSubscriptionIPv4Option':
"""Make MH active multicast subscription IPv4 option.
Args:
type: Option type.
option: Option data model.
igmp_type: IGMP message type identifying the context format.
context: Multicast membership context, opaque to this module.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``igmp_type`` does not fit its field.
Also if the option is too long for its one-octet ``Option Length``.
"""
if option is not None:
igmp_type = option.igmp_type
context = option.context
return Schema_ActiveMulticastSubscriptionIPv4Option(
type=type,
length=self._make_uint(1 + len(context), bits=8, name='Option Length'),
igmp_type=self._make_uint(igmp_type, bits=8, name='IGMP type'),
context=context,
)
[docs]
def _make_opt_ams6(self, type: 'Enum_Option',
option: 'Optional[Data_ActiveMulticastSubscriptionIPv6Option]' = None, *,
mld_type: 'int' = 143,
context: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_ActiveMulticastSubscriptionIPv6Option':
"""Make MH active multicast subscription IPv6 option.
Args:
type: Option type.
option: Option data model.
mld_type: MLD message type identifying the context format.
context: Multicast membership context, opaque to this module.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``mld_type`` does not fit its field.
Also if the option is too long for its one-octet ``Option Length``.
"""
if option is not None:
mld_type = option.mld_type
context = option.context
return Schema_ActiveMulticastSubscriptionIPv6Option(
type=type,
length=self._make_uint(1 + len(context), bits=8, name='Option Length'),
mld_type=self._make_uint(mld_type, bits=8, name='MLD type'),
context=context,
)
[docs]
def _make_opt_qos(self, type: 'Enum_Option', option: 'Optional[Data_QualityOfServiceOption]' = None, *,
sr_id: 'int' = 0,
dscp: 'int' = 0,
oc: 'int' = 0,
attributes: 'Optional[QoSAttribute | list[Schema_QoSAttribute | tuple[Enum_QoSAttribute | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
reserved: 'bytes' = b'\x00\x00\x00',
tc_reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_QualityOfServiceOption':
"""Make MH quality-of-service option.
Args:
type: Option type.
option: Option data model.
sr_id: Service request identifier.
dscp: Differentiated services code point, 6 bits.
oc: Operational code.
attributes: Quality-of-service attributes.
reserved: The three reserved octets as captured.
tc_reserved: The 2 reserved bits of the traffic class, after ``dscp``.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``sr_id``, ``dscp``, ``tc_reserved`` or ``oc`` does
not fit its field.
Also if the option is too long for its one-octet ``Option Length``.
Also if ``reserved`` is not three octets.
.. versionchanged:: 1.6.0
Takes ``tc_reserved``, which a data model passed as ``option`` carries
as well (:issue:`2684`).
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00\x00')
sr_id = option.sr_id
dscp = option.dscp
tc_reserved = getattr(option, 'tc_reserved', 0)
oc = option.oc
attributes = option.attributes
else:
attributes = attributes or []
dscp = self._make_uint(dscp, bits=6, name='DSCP')
tc_reserved = self._make_uint(tc_reserved, bits=2, name='traffic class reserved bits')
entries = self._make_qos_attributes(attributes)
length = 6 + sum(len(entry) if isinstance(entry, bytes) else len(entry.pack())
for entry in entries)
return Schema_QualityOfServiceOption(
type=type,
length=self._make_uint(length, bits=8, name='Option Length'),
sr_id=self._make_uint(sr_id, bits=8, name='service request identifier'),
tc=dscp << 2 | tc_reserved,
oc=self._make_uint(oc, bits=8, name='operational code'),
reserved=self._make_octets(reserved, size=3, name='reserved octets'),
attributes=entries,
)
[docs]
def _make_opt_lma_up(self, type: 'Enum_Option', option: 'Optional[Data_LMAUserPlaneAddressOption]' = None, *,
address: 'Optional[bytes | str | int | IPv4Address | IPv6Address]' = None,
reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_LMAUserPlaneAddressOption':
"""Make MH LMA user-plane address option.
Args:
type: Option type.
option: Option data model.
address: LMA user-plane address, or :obj:`None` to omit it -- which is
how a mobile access gateway asks for a transport without naming an
address.
reserved: The two reserved octets as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``reserved`` is not two octets.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00')
address = option.address
reserved = self._make_octets(reserved, size=2, name='reserved octets')
if address is None:
return Schema_LMAUserPlaneAddressOption(type=type, length=2, reserved=reserved, address=b'')
# NOTE: The length follows the family here, so ``parse_ip_address`` refuses a ``bool``
# the schema's guard would never see; ``None`` stays an absent address.
addr = parse_ip_address(
address, f'{self.alias}: [OptNo {type}] invalid LMA user-plane address')
return Schema_LMAUserPlaneAddressOption(
type=type,
length=6 if addr.version == 4 else 18,
reserved=reserved,
address=addr,
)
[docs]
def _make_opt_mcast(self, type: 'Enum_Option', option: 'Optional[Data_MulticastMobilityOption]' = None, *,
code: 'int' = 2,
data: 'bytes' = b'',
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_MulticastMobilityOption':
"""Make MH multicast mobility option.
Args:
type: Option type.
option: Option data model.
code: Option code.
data: MLD or IGMP report payload, opaque to this module.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the payload is not a whole number of 32-bit words.
:rfc:`7411#section-5.3` measures this option's length in words, so
a payload that is not a multiple of 4 octets cannot be described by
it at all. Also if ``code`` does not fit in one octet, or the option is
too long for its one-octet ``Option Length``.
Also if ``reserved`` is not one octet.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
code = option.code
data = option.data
if len(data) % 4 != 0:
raise ProtocolError(f'{self.alias}: [OptNo {type}] payload of {len(data)} octets is '
'not a whole number of 32-bit words')
return Schema_MulticastMobilityOption(
type=type,
length=self._make_uint(len(data) // 4, bits=8, name='Option Length'),
code=self._make_uint(code, bits=8, name='code'),
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
data=data,
)
[docs]
def _make_opt_mcast_ack(self, type: 'Enum_Option',
option: 'Optional[Data_MulticastAcknowledgementOption]' = None, *,
code: 'int' = 0,
status: 'int' = 1,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_MulticastAcknowledgementOption':
"""Make MH multicast acknowledgement option.
Args:
type: Option type.
option: Option data model.
code: Option code; always ``0``.
status: Status.
data: MLD or IGMP unsupported report payload, opaque to this module.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the payload is not a whole number of 32-bit words,
or ``code`` or ``status`` does not fit in one octet.
Also if the option is too long for its one-octet ``Option Length``.
"""
if option is not None:
code = option.code
status = option.status
data = option.data
if len(data) % 4 != 0:
raise ProtocolError(f'{self.alias}: [OptNo {type}] payload of {len(data)} octets is '
'not a whole number of 32-bit words')
return Schema_MulticastAcknowledgementOption(
type=type,
length=self._make_uint(len(data) // 4, bits=8, name='Option Length'),
code=self._make_uint(code, bits=8, name='code'),
status=self._make_uint(status, bits=8, name='status'),
data=data,
)
[docs]
def _make_opt_lcmp(self, type: 'Enum_Option', option: 'Optional[Data_LMAControlledMAGParametersOption]' = None, *,
suboptions: 'Optional[LMAControlledMAGSuboption | list[Schema_LMAControlledMAGSuboption | tuple[Enum_LMAControlledMAGSuboption | int, dict[str, Any]] | bytes]]' = None, # pylint: disable=line-too-long
**kwargs: 'Any') -> 'Schema_LMAControlledMAGParametersOption':
"""Make MH LMA-controlled MAG parameters option.
Args:
type: Option type.
option: Option data model.
suboptions: Sub-options; at least one is required.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``.
"""
if option is not None:
suboptions = option.suboptions
else:
suboptions = suboptions or []
entries = self._make_lcmp_suboptions(suboptions)
if not entries:
raise ProtocolError(f'{self.alias}: [OptNo {type}] at least one sub-option is required')
length = sum(len(entry) if isinstance(entry, bytes) else len(entry.pack())
for entry in entries)
return Schema_LMAControlledMAGParametersOption(
type=type,
length=self._make_uint(length, bits=8, name='Option Length'),
suboptions=entries,
)
[docs]
def _make_opt_mag_mp(self, type: 'Enum_Option', option: 'Optional[Data_MAGMultipathBindingOption]' = None, *,
att: 'Enum_AccessType | int' = Enum_AccessType.Virtual,
label: 'int' = 0,
bid: 'int' = 1,
bulk: 'bool' = False,
overwrite: 'bool' = False,
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_MAGMultipathBindingOption':
"""Make MH MAG multipath binding option.
Args:
type: Option type.
option: Option data model.
att: Interface access-technology type.
label: Interface label.
bid: Binding identifier; ``0`` and ``255`` are reserved.
bulk: Bulk re-registration flag.
overwrite: Registration overwrite flag.
reserved: Reserved bits, i.e. the 22 bits after the ``O`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If both flags are set, which
:rfc:`8278#section-4.1` forbids, or if ``label``, ``bid`` or
``reserved`` does not fit its field.
Also if ``att`` is a name, a :obj:`bool` or a code its field
cannot carry.
"""
if option is not None:
att_val = option.att # type: Enum_AccessType
label = option.label
bid = option.bid
bulk = option.bulk
overwrite = option.overwrite
reserved = option.reserved
self._make_uint(att_val, bits=8, name='access technology type')
else:
att_val = self._make_index(att, Enum_AccessType, bits=8, name='access technology type')
if bulk and overwrite:
raise ProtocolError(f'{self.alias}: [OptNo {type}] the bulk re-registration and '
'registration overwrite flags are mutually exclusive')
return Schema_MAGMultipathBindingOption(
type=type,
length=6,
att=att_val,
label=self._make_uint(label, bits=8, name='interface label'),
bid=self._make_uint(bid, bits=8, name='binding identifier'),
flags={
'B': int(bulk),
'O': int(overwrite),
'reserved': self._make_uint(reserved, bits=22, name='reserved bits'),
},
)
[docs]
def _make_opt_mag_id(self, type: 'Enum_Option', option: 'Optional[Data_MAGIdentifierOption]' = None, *,
subtype: 'Enum_MNIDSubtype | int' = Enum_MNIDSubtype.NAI,
identifier: 'bytes' = b'',
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_MAGIdentifierOption':
"""Make MH MAG identifier option.
Args:
type: Option type.
option: Option data model.
subtype: Sub-type, from the mobile node identifier subtype registry.
identifier: Identifier.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``subtype`` is a name, a :obj:`bool` or a code its
field cannot carry, or the option is too long for its one-octet
``Option Length``.
Also if ``reserved`` is not one octet.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
subtype_val = option.subtype # type: Enum_MNIDSubtype
identifier = option.identifier
self._make_uint(subtype_val, bits=8, name='MAG identifier subtype')
else:
subtype_val = self._make_index(subtype, Enum_MNIDSubtype, bits=8,
name='MAG identifier subtype')
return Schema_MAGIdentifierOption(
type=type,
length=self._make_uint(2 + len(identifier), bits=8, name='Option Length'),
subtype=subtype_val,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
identifier=identifier,
)
[docs]
def _make_opt_anchored(self, type: 'Enum_Option', option: 'Optional[Data_AnchoredPrefixOption]' = None, *,
prefix_length: 'int' = 64,
prefix: 'bytes | str | int | IPv6Address' = '::',
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_AnchoredPrefixOption':
"""Make MH anchored prefix option.
Args:
type: Option type.
option: Option data model.
prefix_length: Prefix length.
prefix: Anchored prefix.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`,
or longer than the address. Also if ``reserved`` is not one octet.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
prefix_length = option.prefix_length
prefix = option.prefix
prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length')
if prefix_length > 128:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_AnchoredPrefixOption(
type=type,
length=18,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
prefix_length=prefix_length,
prefix=prefix,
)
[docs]
def _make_opt_local_prefix(self, type: 'Enum_Option', option: 'Optional[Data_LocalPrefixOption]' = None, *,
prefix_length: 'int' = 64,
prefix: 'bytes | str | int | IPv6Address' = '::',
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_LocalPrefixOption':
"""Make MH local prefix option.
Args:
type: Option type.
option: Option data model.
prefix_length: Prefix length.
prefix: Local prefix.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`,
or longer than the address. Also if ``reserved`` is not one octet.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
prefix_length = option.prefix_length
prefix = option.prefix
prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length')
if prefix_length > 128:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_LocalPrefixOption(
type=type,
length=18,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
prefix_length=prefix_length,
prefix=prefix,
)
[docs]
def _make_opt_prev_maar(self, type: 'Enum_Option', option: 'Optional[Data_PreviousMAAROption]' = None, *,
prefix_length: 'int' = 64,
maar: 'bytes | str | int | IPv6Address' = '::',
prefix: 'bytes | str | int | IPv6Address' = '::',
reserved: 'bytes' = b'\x00',
**kwargs: 'Any') -> 'Schema_PreviousMAAROption':
"""Make MH previous MAAR option.
Args:
type: Option type.
option: Option data model.
prefix_length: Prefix length of ``prefix``.
maar: Previous MAAR's global address.
prefix: Mobile node's home network prefix.
reserved: The reserved octet as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If ``prefix_length`` is negative, a :obj:`bool`,
or longer than the address. Also if ``reserved`` is not one octet.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00')
prefix_length = option.prefix_length
maar = option.maar
prefix = option.prefix
prefix_length = self._make_uint(prefix_length, bits=8, name='prefix length')
if prefix_length > 128:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid prefix length: {prefix_length}')
return Schema_PreviousMAAROption(
type=type,
length=34,
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
prefix_length=prefix_length,
maar=maar,
prefix=prefix,
)
[docs]
def _make_opt_serv_maar(self, type: 'Enum_Option', option: 'Optional[Data_ServingMAAROption]' = None, *,
address: 'bytes | str | int | IPv6Address' = '::',
**kwargs: 'Any') -> 'Schema_ServingMAAROption':
"""Make MH serving MAAR option.
Args:
type: Option type.
option: Option data model.
address: Serving MAAR's global address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
address = option.address
return Schema_ServingMAAROption(
type=type,
length=16,
address=address,
)
[docs]
def _make_opt_dlif_lla(self, type: 'Enum_Option', option: 'Optional[Data_DLIFLinkLocalAddressOption]' = None, *,
address: 'bytes | str | int | IPv6Address' = '::',
**kwargs: 'Any') -> 'Schema_DLIFLinkLocalAddressOption':
"""Make MH DLIF link-local address option.
Args:
type: Option type.
option: Option data model.
address: Distributed logical interface's link-local address.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
"""
if option is not None:
address = option.address
return Schema_DLIFLinkLocalAddressOption(
type=type,
length=16,
address=address,
)
[docs]
def _make_opt_dlif_lladdr(self, type: 'Enum_Option', option: 'Optional[Data_DLIFLinkLayerAddressOption]' = None, *,
lla: 'bytes' = b'',
reserved: 'bytes' = b'\x00\x00',
**kwargs: 'Any') -> 'Schema_DLIFLinkLayerAddressOption':
"""Make MH DLIF link-layer address option.
Args:
type: Option type.
option: Option data model.
lla: Distributed logical interface's link-layer address.
reserved: The two reserved octets as captured.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed option schema.
Raises:
ProtocolError: If the option is too long for its one-octet
``Option Length``. Also if ``reserved`` is not two octets.
"""
if option is not None:
reserved = getattr(option, 'reserved', b'\x00\x00')
lla = option.lla
return Schema_DLIFLinkLayerAddressOption(
type=type,
length=self._make_uint(2 + len(lla), bits=8, name='Option Length'),
reserved=self._make_octets(reserved, size=2, name='reserved octets'),
lla=lla,
)
[docs]
def _make_ext_none(self, type: 'Enum_CGAExtension', option: 'Optional[Data_UnknownExtension]' = None, *,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_UnknownExtension':
"""Make CGA extension.
Args:
type: Extension type.
option: Extension data model.
data: Extension data.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed extension schema.
"""
if option is not None:
data = option.data
return Schema_UnknownExtension(
type=type,
length=len(data),
data=data,
)
[docs]
def _make_ext_multiprefix(self, type: 'Enum_CGAExtension', option: 'Optional[Data_MultiPrefixExtension]' = None, *,
flag: 'bool' = False,
prefixes: 'Optional[list[int]]' = None,
reserved: 'int' = 0,
**kwargs: 'Any') -> 'Schema_MultiPrefixExtension':
"""Make CGA multi-prefix extension.
Args:
type: Extension type.
option: Extension data model.
flag: Public key flag.
prefixes: Prefixes.
reserved: Reserved bits, i.e. the 31 bits after the ``P`` flag.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed extension schema.
Raises:
ProtocolError: If ``reserved`` or an entry of ``prefixes`` does not fit its
field.
"""
if option is not None:
flag = option.flag
reserved = option.reserved
# NOTE: ``list()``, not a cast: the data model holds a :obj:`tuple`, which
# ``ListField`` refuses to pack.
prefixes = list(option.prefixes)
else:
prefixes = prefixes or []
for item in prefixes:
self._make_uint(item, bits=64, name='prefix')
return Schema_MultiPrefixExtension(
type=type,
# NOTE: A 4-octet flags word, then 8 octets per prefix, as each is a
# ``UInt64Field`` in ``MultiPrefixExtension.prefixes``.
length=4 + len(prefixes) * 8,
flags={
'P': int(flag),
'reserved': self._make_uint(reserved, bits=31, name='reserved bits'),
},
prefixes=prefixes,
)
[docs]
def _make_ext_exp(self, type: 'Enum_CGAExtension', option: 'Optional[Data_ExperimentalExtension]' = None, *,
data: 'bytes' = b'',
**kwargs: 'Any') -> 'Schema_ExperimentalExtension':
"""Make experimental CGA extension.
Args:
type: Extension type.
option: Extension data model.
data: Extension data, which :rfc:`4581#section-3` gives no structure.
**kwargs: Arbitrary keyword arguments.
Returns:
Constructed extension schema.
"""
if option is not None:
data = option.data
return Schema_ExperimentalExtension(
type=type,
length=len(data),
data=data,
)
@staticmethod
def _mh_message_length(header_len: 'int') -> 'int':
"""Compute the total on-the-wire MH header length for a given ``Header Len``.
Per :rfc:`6275#section-6.1.1`, ``Header Len`` is *"the length of the
Mobility Header, in units of 8 octets, excluding the first 8
octets"* -- i.e. the total header is ``8 + 8 * header_len`` octets,
or equivalently ``(header_len + 1) * 8``. Every ``_read_msg_*``
below reports this value as the parsed message's own ``.length``,
which :meth:`~pcapkit.protocols.internet.mh.protocol.MH.read` subtracts from the
outer packet length to find the next layer's length. It is the inverse
of :meth:`make`'s ``(len(data_val) + 6) // 8 - 1``. Do NOT drop the
``+ 1``: the units either side of it differ (octets vs. 8-octet
units), and dropping the offset silently reinterprets the field.
Args:
header_len: raw ``Header Len`` field value, as read off the wire.
Returns:
Total length, in octets, of the on-the-wire MH header.
"""
return (header_len + 1) * 8
@staticmethod
def _mh_option_length(schema_length: 'int') -> 'int':
"""Compute an MH option's whole-option length from its on-the-wire ``Option Length``.
Per :rfc:`6275#section-6.2`, an MH option's ``Option Length`` field
(what each ``Schema_*Option.length`` here holds) counts *"the length
of the option, in octets, excluding the Option Type and Option
Length fields"* -- so the whole option, which is what every
``_read_opt_*`` below reports back as the parsed option's own
``.length``, is two octets more. The exceptions are the
options whose ``Option Length`` is not that octet count: a ``Pad1``
option, which has no ``Option Length`` field at all (see the ``Note:``
on :meth:`_read_opt_pad` below), the BADF option, whose length leaves
out its SPI (see :meth:`_read_opt_badf`), and the multicast mobility
and acknowledgement options, whose length counts 32-bit words (see
:meth:`_read_opt_mcast`). Do NOT drop the ``+ 2``.
A CGA extension's type and length fields are two octets each
[:rfc:`4581#section-2`], so its readers use :meth:`_mh_extension_length`
instead; this helper would report each one two octets short.
Only the read side's stored lengths go through it; most ``_make_opt_*``
methods recompute ``Option Length`` from the value instead.
Args:
schema_length: raw ``Option Length`` field value, as read off the wire.
Returns:
Whole-option length, in octets, including the Option Type and
Option Length fields.
"""
return schema_length + 2
[docs]
def _read_mh_options(self, options_schema: 'list[Schema_Option]') -> 'Option':
"""Read MH options.
Structure of MH option [:rfc:`6275`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Type | Option Length | Option Data...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
options_schema: Parsed MH options.
Returns:
Parsed MH options data.
Raises:
ProtocolError: If an option read by a ``_read_opt_*`` method has fields
that do not take exactly the octets its ``Option Length`` declares
[:rfc:`6275#section-6.2.1`]. A parser registered as a callable
reports its own length, and is not checked. Or if the capture cut
an option short and its parser refuses the zeros it lacks, or its
maker does not write the octets captured back.
.. versionchanged:: 1.6.0
* Such an option is refused, rather than read from octets outside it
and rebuilt with the length its fields give (:issue:`2684`).
* An option the capture cut short is read, its missing octets as
zeros, if its maker writes the octets captured back
(:issue:`2694`).
"""
options = OrderedMultiDict() # type: Option
for schema in options_schema:
type = schema.type
name = self._lookup_registry(self.__option__, type)
if isinstance(name, str):
meth_name = f'_read_opt_{name}'
meth = cast('OptionParser',
getattr(self, meth_name, self._read_opt_none))
maker = cast('OptionConstructor',
getattr(self, f'_make_opt_{name}', self._make_opt_none))
else:
meth, maker = name
# NOTE: A cut-short option reads its missing octets as zeros; it stands if its parser
# takes them and its maker writes the captured ones back (#2694), else it is refused.
if '__short_read__' in schema.__dict__:
try:
with CaughtByCaller(BaseError):
data = meth(schema, options=options)
made = maker(type, data).pack()[:len(schema)]
except BaseError as error:
raise ProtocolError(f'{self.alias}: [OptNo {type}] option cut short: {error}') from error
if made != bytes(schema):
raise ProtocolError(f'{self.alias}: [OptNo {type}] option cut short does not '
'rebuild as captured')
else:
data = meth(schema, options=options)
# NOTE: Fields are read at their own widths, so a length they do not fill exactly reads
# octets not the option's, and rebuilds as another (#2684).
if isinstance(name, str) and len(schema) != data.length:
raise ProtocolError(f'{self.alias}: [OptNo {type}] invalid option length: {schema.length}')
# record option data
options.add(type, data)
return options
[docs]
def _read_fid_suboptions(
self, suboptions_schema: 'list[Schema_FlowIdentificationSuboption]',
) -> 'FlowIDSuboption':
"""Read MH flow identification sub-options.
Structure of MH flow identification sub-option [:rfc:`6089#section-4.2.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sub-Opt Type |Sub-Opt Length | Sub-Option Data...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
These are sub-options of one mobility option rather than mobility options
in their own right, so they are dispatched here rather than through
:attr:`self.__option__ <MH.__option__>`.
Note:
Dispatch is on the sub-option **type code** rather than on
``isinstance`` of the schema class: the code is on the wire and the
registry keys on it, so it is the cheaper discriminator. A class test
would also be sound, since every
:class:`~pcapkit.protocols.schema.Schema` subclass carries its
own :class:`abc.ABCMeta` cache (:issue:`439`). The same reasoning
applies to :meth:`_read_ani_suboptions`,
:meth:`_read_qos_attributes`, :meth:`_read_lcmp_suboptions` and their
constructor counterparts.
Args:
suboptions_schema: Parsed sub-option schemas.
Returns:
Parsed sub-option data.
Raises:
ProtocolError: If a sub-option's fields do not take exactly the octets
its ``Sub-Opt Length`` declares [:rfc:`6089#section-4.2.1`], or a
target care-of address is neither IPv4 nor IPv6
[:rfc:`7109#section-6.2.2`], as the binding identifier option's
is refused.
.. versionchanged:: 1.6.0
Such a sub-option is refused, as a mobility option is by
:meth:`_read_mh_options`, rather than read from octets outside it or
left unable to rebuild (:issue:`2684`).
"""
suboptions = OrderedMultiDict() # type: FlowIDSuboption
for schema in suboptions_schema:
code = schema.type
if code in (Enum_FlowIDSuboption.Pad, Enum_FlowIDSuboption.PadN):
pad = cast('Schema_PadFlowIdentificationSuboption', schema)
size = 1 if code == Enum_FlowIDSuboption.Pad else self._mh_option_length(pad.length)
data = Data_PadFlowIdentificationSuboption(
type=code,
length=size,
data=pad.data,
) # type: Data_FlowIdentificationSuboption
elif code == Enum_FlowIDSuboption.BID_Reference:
bid_ref = cast('Schema_BIDReferenceSuboption', schema)
data = Data_BIDReferenceSuboption(
type=code,
length=self._mh_option_length(bid_ref.length),
bid=tuple(bid_ref.bid),
)
elif code == Enum_FlowIDSuboption.Traffic_Selector:
selector = cast('Schema_TrafficSelectorSuboption', schema)
data = Data_TrafficSelectorSuboption(
type=code,
length=self._mh_option_length(selector.length),
ts_format=selector.ts_format,
reserved=selector.reserved,
selector=selector.selector,
)
elif code == Enum_FlowIDSuboption.Flow_Binding_Action:
action = cast('Schema_FlowBindingActionSuboption', schema)
data = Data_FlowBindingActionSuboption(
type=code,
length=self._mh_option_length(action.length),
reserved=action.reserved,
action=action.action,
)
elif code == Enum_FlowIDSuboption.Target_Care_of_Address:
target = cast('Schema_TargetCareofAddressSuboption', schema)
if target.length not in (6, 18):
raise ProtocolError(f'{self.alias}: [FID SubOpt {code}] invalid sub-option length: {target.length}')
data = Data_TargetCareofAddressSuboption(
type=code,
length=self._mh_option_length(target.length),
reserved=target.reserved,
address=target.address,
)
else:
unknown = cast('Schema_UnassignedFlowIdentificationSuboption', schema)
data = Data_UnassignedFlowIdentificationSuboption(
type=code,
length=self._mh_option_length(unknown.length),
data=unknown.data,
)
if len(schema) != data.length:
raise ProtocolError(f'{self.alias}: [FID SubOpt {code}] invalid sub-option length: {schema.length}')
suboptions.add(code, data)
return suboptions
[docs]
def _read_ani_suboptions(
self, suboptions_schema: 'list[Schema_ANISuboption]',
) -> 'ANISuboption':
"""Read MH access network identifier sub-options.
Structure of MH access network identifier sub-option
[:rfc:`6757#section-3.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ANI Type | ANI Length | Option Data ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The geo-location degrees of :rfc:`6757#section-3.1.2` are 24-bit
**two's-complement** fixed-point values with nine integer bits. A
:class:`~pcapkit.corekit.fields.strings.BitField` reads them unsigned, so
the sign is applied here by :meth:`_read_sint`, and both the decoded
degrees and the raw signed integers are recorded -- the latter so that
the exact wire value survives a round trip through the data model.
Note:
Dispatch is on the sub-option type code rather than on ``isinstance``
of the schema class, for the reason given in
:meth:`_read_fid_suboptions`.
Args:
suboptions_schema: Parsed sub-option schemas.
Returns:
Parsed sub-option data.
Raises:
ProtocolError: If a sub-option's fields do not take exactly the octets
its ``ANI Length`` declares [:rfc:`6757#section-3.1`].
.. versionchanged:: 1.6.0
Such a sub-option is refused, as a mobility option is by
:meth:`_read_mh_options` (:issue:`2684`).
"""
suboptions = OrderedMultiDict() # type: ANISuboption
for schema in suboptions_schema:
code = schema.type
if code == Enum_ANISuboption.Network_Identifier:
net = cast('Schema_ANINetworkIdentifierSuboption', schema)
data = Data_ANINetworkIdentifierSuboption(
type=code,
length=self._mh_option_length(net.length),
utf8=bool(net.flags['E']),
reserved=net.flags['reserved'],
net_name=net.net_name,
ap_name=net.ap_name,
) # type: Data_ANISuboption
elif code == Enum_ANISuboption.Geo_Location:
geo = cast('Schema_ANIGeoLocationSuboption', schema)
raw_lat = self._read_sint(geo.location['latitude'], 24)
raw_lon = self._read_sint(geo.location['longitude'], 24)
data = Data_ANIGeoLocationSuboption(
type=code,
length=self._mh_option_length(geo.length),
latitude=raw_lat / 2 ** 15,
longitude=raw_lon / 2 ** 15,
raw_latitude=raw_lat,
raw_longitude=raw_lon,
)
elif code == Enum_ANISuboption.Operator_Identifier:
operator = cast('Schema_ANIOperatorIdentifierSuboption', schema)
data = Data_ANIOperatorIdentifierSuboption(
type=code,
length=self._mh_option_length(operator.length),
op_id_type=operator.op_id_type,
identifier=operator.identifier,
)
elif code == Enum_ANISuboption.Civic_Location:
civic = cast('Schema_ANICivicLocationSuboption', schema)
data = Data_ANICivicLocationSuboption(
type=code,
length=self._mh_option_length(civic.length),
format=civic.format,
reserved=civic.reserved,
location=civic.location,
)
elif code == Enum_ANISuboption.MAG_Group_Identifier:
group = cast('Schema_ANIMAGGroupIdentifierSuboption', schema)
data = Data_ANIMAGGroupIdentifierSuboption(
type=code,
length=self._mh_option_length(group.length),
group_id=group.group_id,
)
elif code == Enum_ANISuboption.ANI_Update_Timer:
timer = cast('Schema_ANIUpdateTimerSuboption', schema)
data = Data_ANIUpdateTimerSuboption(
type=code,
length=self._mh_option_length(timer.length),
timer=datetime.timedelta(seconds=timer.timer * 4),
)
else:
unknown = cast('Schema_UnassignedANISuboption', schema)
data = Data_UnassignedANISuboption(
type=code,
length=self._mh_option_length(unknown.length),
data=unknown.data,
)
if len(schema) != data.length:
raise ProtocolError(f'{self.alias}: [ANI SubOpt {code}] invalid sub-option length: {schema.length}')
suboptions.add(code, data)
return suboptions
[docs]
def _read_qos_attributes(
self, attributes_schema: 'list[Schema_QoSAttribute]',
) -> 'QoSAttribute':
"""Read MH quality-of-service attributes.
Structure of MH quality-of-service attribute [:rfc:`7222#section-4.2`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Value ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
Every bit rate here is in **bits** per second [:rfc:`7222#section-4.2.1`],
not the kilobytes per second of the load information option's capacity
fields.
Dispatch is on the attribute type code rather than on ``isinstance`` of
the schema class, for the reason given in
:meth:`_read_fid_suboptions`.
Args:
attributes_schema: Parsed attribute schemas.
Returns:
Parsed attribute data.
Raises:
ProtocolError: If an attribute's fields do not take exactly the octets
its ``Length`` declares [:rfc:`7222#section-4.2`].
.. versionchanged:: 1.6.0
Such an attribute is refused, as a mobility option is by
:meth:`_read_mh_options` (:issue:`2684`).
"""
attributes = OrderedMultiDict() # type: QoSAttribute
for schema in attributes_schema:
code = schema.type
if code in (Enum_QoSAttribute.Per_Session_Agg_Max_DL_Bit_Rate,
Enum_QoSAttribute.Per_Session_Agg_Max_UL_Bit_Rate):
session = cast('Schema_PerSessionBitRateAttribute', schema)
data = Data_PerSessionBitRateAttribute(
type=code,
length=self._mh_option_length(session.length),
service=bool(session.flags['S']),
exclude=bool(session.flags['E']),
reserved=session.flags['reserved'],
rate=session.rate,
) # type: Data_QoSAttribute
elif code in (Enum_QoSAttribute.Per_MN_Agg_Max_DL_Bit_Rate,
Enum_QoSAttribute.Per_MN_Agg_Max_UL_Bit_Rate,
Enum_QoSAttribute.Aggregate_Max_DL_Bit_Rate,
Enum_QoSAttribute.Aggregate_Max_UL_Bit_Rate,
Enum_QoSAttribute.Guaranteed_DL_Bit_Rate,
Enum_QoSAttribute.Guaranteed_UL_Bit_Rate):
rate = cast('Schema_BitRateAttribute', schema)
data = Data_BitRateAttribute(
type=code,
length=self._mh_option_length(rate.length),
reserved=rate.reserved,
rate=rate.rate,
)
elif code == Enum_QoSAttribute.Allocation_Retention_Priority:
arp = cast('Schema_AllocationRetentionPriorityAttribute', schema)
data = Data_AllocationRetentionPriorityAttribute(
type=code,
length=self._mh_option_length(arp.length),
reserved=arp.reserved,
priority_level=arp.priority['PL'],
preemption_capability=arp.priority['PC'],
preemption_vulnerability=arp.priority['PV'],
)
elif code == Enum_QoSAttribute.QoS_Traffic_Selector:
selector = cast('Schema_QoSTrafficSelectorAttribute', schema)
data = Data_QoSTrafficSelectorAttribute(
type=code,
length=self._mh_option_length(selector.length),
reserved=selector.reserved,
ts_format=selector.ts_format,
selector=selector.selector,
)
elif code == Enum_QoSAttribute.QoS_Vendor_Specific_Attribute:
vendor = cast('Schema_QoSVendorSpecificAttribute', schema)
data = Data_QoSVendorSpecificAttribute(
type=code,
length=self._mh_option_length(vendor.length),
reserved=vendor.reserved,
vendor=vendor.vendor,
subtype=vendor.subtype,
data=vendor.data,
)
else:
unknown = cast('Schema_UnassignedQoSAttribute', schema)
data = Data_UnassignedQoSAttribute(
type=code,
length=self._mh_option_length(unknown.length),
data=unknown.data,
)
if len(schema) != data.length:
raise ProtocolError(f'{self.alias}: [QoS Attr {code}] invalid attribute length: {schema.length}')
attributes.add(code, data)
return attributes
[docs]
def _read_lcmp_suboptions(
self, suboptions_schema: 'list[Schema_LMAControlledMAGSuboption]',
) -> 'LMAControlledMAGSuboption':
"""Read MH LMA-controlled MAG parameters sub-options.
Structure of MH LMA-controlled MAG parameters sub-option
[:rfc:`8127#section-3.1`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| LCMP Type | LCMP Length | Sub-Option Data ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Note:
Only the re-registration start time is in units of 4 seconds; every
other interval in these two sub-options is in whole seconds
[:rfc:`8127#section-3.1.1`, :rfc:`8127#section-3.1.2`].
Dispatch is on the sub-option type code rather than on ``isinstance``
of the schema class, for the reason given in
:meth:`_read_fid_suboptions`.
Args:
suboptions_schema: Parsed sub-option schemas.
Returns:
Parsed sub-option data.
Raises:
ProtocolError: If a sub-option's fields do not take exactly the octets
its ``LCMP Length`` declares [:rfc:`8127#section-3.1`].
.. versionchanged:: 1.6.0
Such a sub-option is refused, as a mobility option is by
:meth:`_read_mh_options` (:issue:`2684`).
"""
suboptions = OrderedMultiDict() # type: LMAControlledMAGSuboption
for schema in suboptions_schema:
code = schema.type
if code == Enum_LMAControlledMAGSuboption.Binding_Re_registration_Control:
rereg = cast('Schema_BindingReregistrationControlSuboption', schema)
data = Data_BindingReregistrationControlSuboption(
type=code,
length=self._mh_option_length(rereg.length),
start_time=datetime.timedelta(seconds=rereg.start_time * 4),
initial_retransmission=datetime.timedelta(
seconds=rereg.initial_retransmission),
max_retransmission=datetime.timedelta(seconds=rereg.max_retransmission),
) # type: Data_LMAControlledMAGSuboption
elif code == Enum_LMAControlledMAGSuboption.Heartbeat_Control:
heartbeat = cast('Schema_HeartbeatControlSuboption', schema)
data = Data_HeartbeatControlSuboption(
type=code,
length=self._mh_option_length(heartbeat.length),
interval=datetime.timedelta(seconds=heartbeat.interval),
retransmission_delay=datetime.timedelta(
seconds=heartbeat.retransmission_delay),
max_retransmissions=heartbeat.max_retransmissions,
)
else:
unknown = cast('Schema_UnassignedLMAControlledMAGSuboption', schema)
data = Data_UnassignedLMAControlledMAGSuboption(
type=code,
length=self._mh_option_length(unknown.length),
data=unknown.data,
)
if len(schema) != data.length:
raise ProtocolError(f'{self.alias}: [LCMP SubOpt {code}] invalid sub-option length: {schema.length}')
suboptions.add(code, data)
return suboptions
@staticmethod
def _mh_extension_length(schema_length: 'int') -> 'int':
"""Compute a CGA extension's whole-structure length from its on-the-wire ``Extension Data Length``.
This is the CGA-extension counterpart of :meth:`_mh_option_length`, and
it deliberately adds ``4`` rather than ``2``. A CGA extension is **not**
an :rfc:`6275#section-6.2` mobility option: per :rfc:`4581#section-2`,
which defines the TLV format and formally updates :rfc:`3972`, its
``Extension Type`` is a *"16-bit identifier of the type of the Extension
Field"* and its ``Extension Data Length`` a *"16-bit unsigned integer.
Length of the Extension Data field of this option, in octets"*. So the
fixed header is two 2-octet fields, not two 1-octet ones, and the whole
structure is ``4`` octets more than the stored length -- a fact
:rfc:`5535#section-5` states from the other direction for the one
non-experimental assigned type, whose ``Ext Len`` is the *"[l]ength of
the Extension in octets, not including the first 4 octets"*.
Passing these lengths through :meth:`_mh_option_length` would report
every parsed CGA extension two octets short: an 8-octet extension with
an ``Extension Data Length`` of ``4`` would come back as ``6``. The write
side is unaffected, since :meth:`_make_cga_extensions` measures
``len(schema.pack())`` instead.
Args:
schema_length: raw ``Extension Data Length`` field value, as read off the wire.
Returns:
Whole-extension length, in octets, including the Extension Type and
Extension Data Length fields.
"""
return schema_length + 4
[docs]
def _read_cga_extensions(self, extensions_schema: 'list[Schema_CGAExtension]') -> 'Extension':
"""Read CGA extensions.
Structure of CGA extensions [:rfc:`4581`]:
.. code-block:: text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Extension Type | Extension Data Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
~ Extension Data ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Args:
extensions_schema: Parsed CGA extensions.
Returns:
Parsed CGA extensions data.
Raises:
ProtocolError: If an extension read by a ``_read_ext_*`` method has
fields that do not take exactly the octets its ``Extension Data
Length`` declares [:rfc:`4581#section-2`]; a parser registered as
a callable is not checked.
.. versionchanged:: 1.6.0
Such an extension is refused, as a mobility option is by
:meth:`_read_mh_options` (:issue:`2684`).
"""
extensions = OrderedMultiDict() # type: Extension
for schema in extensions_schema:
type = schema.type
name = self._lookup_registry(self.__extension__, type)
if isinstance(name, str):
meth_name = f'_read_ext_{name}'
meth = cast('ExtensionParser',
getattr(self, meth_name, self._read_ext_none))
else:
meth = name[0]
data = meth(schema, extensions=extensions)
if isinstance(name, str) and len(schema) != data.length:
raise ProtocolError(f'{self.alias}: [CGA Ext {type}] invalid extension length: {schema.length}')
# record extension data
extensions.add(type, data)
return extensions
def _make_pad_options(self, offset: 'int') -> 'tuple[list[Schema_PadOption], int]':
"""Make the padding options needed to align the header to 8 octets.
Args:
offset: Number of octets emitted so far, **counting the six octets
of the fixed header** (``Payload Proto``, ``Header Len``,
``MH Type``, ``Reserved`` and ``Checksum``) as well as the whole
message data.
Returns:
Tuple of the padding option schemas and the number of octets they
occupy.
Note:
The Mobility Header is a multiple of 8 octets and
:attr:`~pcapkit.protocols.internet.mh.schema.MH.length` counts those
units less one [:rfc:`6275#section-6.1.1`]. Its fixed part is
**six** octets, not the two of an IPv6 options header, so it is
``len(data) + 6`` that has to be aligned -- which means the message
data is well formed only when it is 2 octets past a multiple of 8,
a different modulus from the one the extension headers use.
A ``PadN`` option spends two octets on its own type and
``Option Length`` fields before any padding data, so occupying
``pad_len`` octets means an ``Option Length`` of ``pad_len - 2``.
One octet is padded with a ``Pad1`` option instead, that being the
only form which can occupy a single octet. Two octets are one
``PadN`` with an ``Option Length`` of ``0``, not two ``Pad1``:
:rfc:`6275#section-6.2.2` says a ``PadN`` should be used rather than
multiple ``Pad1`` options.
"""
pad_len = pad_length(offset, 8)
if pad_len == 0:
return [], 0
if pad_len == 1:
return [self._make_opt_pad(Enum_Option.Pad1, length=0)], pad_len # type: ignore[arg-type]
return [self._make_opt_pad(Enum_Option.PadN, length=pad_len - 2)], pad_len # type: ignore[arg-type]
[docs]
def _make_mh_options(self, options: 'Option | list[Schema_Option | tuple[Enum_Option | int, dict[str, Any]] | bytes]') -> 'list[Schema_Option | bytes]': # pylint: disable=line-too-long
"""Make options for MH.
Args:
options: MH options.
Returns:
Mobility options list.
Raises:
ProtocolError: If the type of an option, given in a ``(type,
kwargs)`` pair or as the key of parsed data, is a name, a
:obj:`bool` or a code its field cannot carry, or if an option
given as raw octets is empty (:issue:`2723`).
"""
if isinstance(options, list):
options_list = [] # type: list[Schema_Option | bytes]
for schema in options:
if isinstance(schema, bytes):
if not schema:
raise ProtocolError(f'{self.alias}: invalid option: {schema!r} holds no type octet')
code = Enum_Option.get(int.from_bytes(schema[0:1], 'big', signed=False))
data = schema # type: Schema_Option | bytes
elif isinstance(schema, Schema):
data = schema
else:
kind, args = cast('tuple[Enum_Option | int, dict[str, Any]]', schema)
code = self._make_index(kind, Enum_Option, bits=8, name='option type')
name = self._lookup_registry(self.__option__, code)
if isinstance(name, str):
meth_name = f'_make_opt_{name}'
meth = cast('OptionConstructor',
getattr(self, meth_name, self._make_opt_none))
else:
meth = name[1]
data = meth(code, **args)
options_list.append(data)
return options_list
options_list = []
for key, option in options.items(multi=True):
code = self._make_index(key, Enum_Option, bits=8, name='option type')
name = self._lookup_registry(self.__option__, code)
if isinstance(name, str):
meth_name = f'_make_opt_{name}'
meth = cast('OptionConstructor',
getattr(self, meth_name, self._make_opt_none))
else:
meth = name[1]
data = meth(code, option)
options_list.append(data)
return options_list
[docs]
def _make_fid_suboptions(
self,
suboptions: 'FlowIDSuboption | list[Schema_FlowIdentificationSuboption | tuple[Enum_FlowIDSuboption | int, dict[str, Any]] | bytes]', # pylint: disable=line-too-long
) -> 'list[Schema_FlowIdentificationSuboption | bytes]':
"""Make MH flow identification sub-options.
Args:
suboptions: Sub-options, as parsed data, schemas, ``(type, kwargs)``
pairs, or raw octets.
Returns:
Sub-option schema list.
Raises:
ProtocolError: If the type of a sub-option, given in a ``(type,
kwargs)`` pair or as the key of parsed data, is a name, a
:obj:`bool` or a code its field cannot carry, or if a
sub-option given as raw octets is empty (:issue:`2723`).
"""
if isinstance(suboptions, list):
entries = [] # type: list[Schema_FlowIdentificationSuboption | bytes]
for item in suboptions:
if isinstance(item, bytes) and not item:
raise ProtocolError(f'{self.alias}: invalid sub-option: {item!r} holds no type octet')
if isinstance(item, (bytes, Schema)):
entries.append(cast('Schema_FlowIdentificationSuboption | bytes', item))
else:
kind, args = cast('tuple[Enum_FlowIDSuboption | int, dict[str, Any]]', item)
code = self._make_index(kind, Enum_FlowIDSuboption, bits=8, name='sub-option type')
entries.append(self._make_fid_suboption(code, **args))
return entries
return [self._make_fid_suboption(
self._make_index(key, Enum_FlowIDSuboption, bits=8, name='sub-option type'), option)
for key, option in suboptions.items(multi=True)]
[docs]
def _make_fid_suboption(self, code: 'Enum_FlowIDSuboption',
option: 'Optional[Data_FlowIdentificationSuboption]' = None,
**kwargs: 'Any') -> 'Schema_FlowIdentificationSuboption':
"""Make one MH flow identification sub-option.
Args:
code: Sub-option type.
option: Sub-option data model.
**kwargs: Sub-option fields, when no data model is given.
Returns:
Constructed sub-option schema.
Raises:
ProtocolError: If an entry of a ``BID_Reference`` sub-option's ``bid`` does
not fit its field, a ``ts_format`` or ``action`` is a name, a
:obj:`bool` or a code its field cannot carry, or a sub-option's
``reserved`` is not as many octets as its field.
Note:
The data model parameter is named ``option`` rather than ``data``
to match :meth:`_make_opt_fid` and the rest of this module -- and
because ``data`` would **shadow a field**. The traffic selector and
unassigned sub-options both carry a field of their own called
``data``, so a caller's ``data=`` would bind to the model parameter
instead of landing in ``**kwargs``, and the fallback that reads
``data`` from ``**kwargs`` would never see it: the payload would be
silently dropped and the length written as though it were empty,
with no exception.
Dispatch is on ``code`` rather than on ``isinstance`` of the schema or
data class, for the reason given in :meth:`_read_fid_suboptions`.
"""
if code in (Enum_FlowIDSuboption.Pad, Enum_FlowIDSuboption.PadN):
if code == Enum_FlowIDSuboption.Pad:
pad_len = 0
elif option is not None:
pad_len = option.length - 2
else:
pad_len = cast('int', kwargs.get('length', 0))
if option is not None:
pad_data = getattr(option, 'data', b'')
else:
pad_data = cast('bytes', kwargs.get('data', b''))
return Schema_PadFlowIdentificationSuboption(type=code, length=pad_len, data=pad_data)
if code == Enum_FlowIDSuboption.BID_Reference:
if option is not None:
bid = list(cast('Data_BIDReferenceSuboption', option).bid)
else:
bid = cast('list[int]', kwargs.get('bid') or [])
for item in bid:
self._make_uint(item, bits=16, name='binding identifier')
return Schema_BIDReferenceSuboption(type=code, length=len(bid) * 2, bid=bid)
if code == Enum_FlowIDSuboption.Traffic_Selector:
if option is not None:
selector_opt = cast('Data_TrafficSelectorSuboption', option)
ts_format = selector_opt.ts_format # type: Enum_TrafficSelector | int
selector = selector_opt.selector
reserved = getattr(selector_opt, 'reserved', b'\x00')
else:
ts_format = self._make_index(
kwargs.get('ts_format', Enum_TrafficSelector.IPv6_Binary_Traffic_Selector),
Enum_TrafficSelector, bits=8, name='TS format')
selector = cast('bytes', kwargs.get('selector', b''))
reserved = cast('bytes', kwargs.get('reserved', b'\x00'))
return Schema_TrafficSelectorSuboption(
type=code, length=2 + len(selector),
ts_format=cast('Enum_TrafficSelector', ts_format),
reserved=self._make_octets(reserved, size=1, name='reserved octets'),
selector=selector)
if code == Enum_FlowIDSuboption.Flow_Binding_Action:
if option is not None:
action = cast('Data_FlowBindingActionSuboption',
option).action # type: Enum_FlowBindingAction | int
reserved = getattr(option, 'reserved', b'\x00')
else:
action = self._make_index(kwargs.get('action', Enum_FlowBindingAction.Add),
Enum_FlowBindingAction, bits=8, name='action')
reserved = cast('bytes', kwargs.get('reserved', b'\x00'))
return Schema_FlowBindingActionSuboption(
type=code, length=2, reserved=self._make_octets(reserved, size=1, name='reserved octets'),
action=cast('Enum_FlowBindingAction', action))
if code == Enum_FlowIDSuboption.Target_Care_of_Address:
if option is not None:
address = cast('Data_TargetCareofAddressSuboption', option).address # type: Any
reserved = getattr(option, 'reserved', b'\x00\x00')
else:
address = kwargs.get('address', '::')
reserved = cast('bytes', kwargs.get('reserved', b'\x00\x00'))
# NOTE: The sub-option length below is derived ahead of the schema, so a bare ``ip_address``
# would launder a ``bool`` past its guard: ``address=True`` packs as ``0506000000000001``.
addr = parse_ip_address(
address, f'{self.alias}: [OptNo {code}] invalid target care-of address')
return Schema_TargetCareofAddressSuboption(
type=code, length=6 if addr.version == 4 else 18,
reserved=self._make_octets(reserved, size=2, name='reserved octets'),
address=addr)
if option is not None:
payload = cast('Data_UnassignedFlowIdentificationSuboption', option).data
else:
payload = cast('bytes', kwargs.get('data', b''))
return Schema_UnassignedFlowIdentificationSuboption(
type=code, length=len(payload), data=payload)
[docs]
def _make_ani_suboptions(
self,
suboptions: 'ANISuboption | list[Schema_ANISuboption | tuple[Enum_ANISuboption | int, dict[str, Any]] | bytes]',
) -> 'list[Schema_ANISuboption | bytes]':
"""Make MH access network identifier sub-options.
Args:
suboptions: Sub-options, as parsed data, schemas, ``(type, kwargs)``
pairs, or raw octets.
Returns:
Sub-option schema list.
Raises:
ProtocolError: If the type of a sub-option, given in a ``(type,
kwargs)`` pair or as the key of parsed data, is a name, a
:obj:`bool` or a code its field cannot carry, or if a
sub-option given as raw octets is empty (:issue:`2723`).
"""
if isinstance(suboptions, list):
entries = [] # type: list[Schema_ANISuboption | bytes]
for item in suboptions:
if isinstance(item, bytes) and not item:
raise ProtocolError(f'{self.alias}: invalid sub-option: {item!r} holds no type octet')
if isinstance(item, (bytes, Schema)):
entries.append(cast('Schema_ANISuboption | bytes', item))
else:
kind, args = cast('tuple[Enum_ANISuboption | int, dict[str, Any]]', item)
code = self._make_index(kind, Enum_ANISuboption, bits=8, name='sub-option type')
entries.append(self._make_ani_suboption(code, **args))
return entries
return [self._make_ani_suboption(
self._make_index(key, Enum_ANISuboption, bits=8, name='sub-option type'), option)
for key, option in suboptions.items(multi=True)]
[docs]
def _make_ani_suboption(self, code: 'Enum_ANISuboption',
option: 'Optional[Data_ANISuboption]' = None,
**kwargs: 'Any') -> 'Schema_ANISuboption':
"""Make one MH access network identifier sub-option.
Args:
code: Sub-option type.
option: Sub-option data model.
**kwargs: Sub-option fields, when no data model is given.
Returns:
Constructed sub-option schema.
Raises:
ProtocolError: If a raw geo-location degree does not fit in 24
bits as two's complement, or a sub-option's ``reserved``,
``format``, ``group_id`` or ``timer`` does not fit its field, or
``op_id_type`` is a name, a :obj:`bool` or a code its field
cannot carry.
Note:
The geo-location degrees are re-encoded from the **raw** signed
integers rather than from the decoded floats, since a float cannot
always be converted back to the same 24-bit fixed-point value, and
:meth:`_make_sint` gives back the bits :meth:`_read_sint` read.
The data model parameter is named ``option`` rather than ``data`` so
that it cannot shadow a sub-option field of that name -- see
:meth:`_make_fid_suboption`. Dispatch is on ``code``
rather than on ``isinstance``, for the reason given in
:meth:`_read_fid_suboptions`.
"""
if code == Enum_ANISuboption.Network_Identifier:
if option is not None:
net = cast('Data_ANINetworkIdentifierSuboption', option)
utf8, net_name, ap_name = net.utf8, net.net_name, net.ap_name
reserved = net.reserved
else:
utf8 = cast('bool', kwargs.get('utf8', False))
reserved = cast('int', kwargs.get('reserved', 0))
net_name = cast('bytes', kwargs.get('net_name', b''))
ap_name = cast('bytes', kwargs.get('ap_name', b''))
return Schema_ANINetworkIdentifierSuboption(
type=code, length=3 + len(net_name) + len(ap_name),
flags={'E': int(utf8), 'reserved': self._make_uint(reserved, bits=7,
name='reserved bits')},
net_name_len=len(net_name), net_name=net_name,
ap_name_len=len(ap_name), ap_name=ap_name)
if code == Enum_ANISuboption.Geo_Location:
if option is not None:
geo = cast('Data_ANIGeoLocationSuboption', option)
raw_lat, raw_lon = geo.raw_latitude, geo.raw_longitude
else:
raw_lat = cast('int', kwargs.get('raw_latitude', 0))
raw_lon = cast('int', kwargs.get('raw_longitude', 0))
return Schema_ANIGeoLocationSuboption(
type=code, length=6, location={
'latitude': self._make_sint(raw_lat, bits=24, name='ANI geo-location latitude'),
'longitude': self._make_sint(raw_lon, bits=24, name='ANI geo-location longitude'),
})
if code == Enum_ANISuboption.Operator_Identifier:
if option is not None:
operator = cast('Data_ANIOperatorIdentifierSuboption', option)
op_id_type = operator.op_id_type # type: Enum_OperatorID | int
identifier = operator.identifier
else:
op_id_type = self._make_index(
kwargs.get('op_id_type', Enum_OperatorID.Realm_of_the_Operator),
Enum_OperatorID, bits=8, name='Op-ID type')
identifier = cast('bytes', kwargs.get('identifier', b''))
return Schema_ANIOperatorIdentifierSuboption(
type=code, length=1 + len(identifier),
op_id_type=cast('Enum_OperatorID', op_id_type), identifier=identifier)
if code == Enum_ANISuboption.Civic_Location:
if option is not None:
civic = cast('Data_ANICivicLocationSuboption', option)
fmt, location = civic.format, civic.location
octets = getattr(civic, 'reserved', b'\x00')
else:
fmt = cast('int', kwargs.get('format', 0))
location = cast('bytes', kwargs.get('location', b''))
octets = cast('bytes', kwargs.get('reserved', b'\x00'))
return Schema_ANICivicLocationSuboption(
type=code, length=2 + len(location),
format=self._make_uint(fmt, bits=8, name='civic location format'),
reserved=self._make_octets(octets, size=1, name='reserved octets'),
location=location)
if code == Enum_ANISuboption.MAG_Group_Identifier:
if option is not None:
group_id = cast('Data_ANIMAGGroupIdentifierSuboption', option).group_id
else:
group_id = cast('int', kwargs.get('group_id', 0))
return Schema_ANIMAGGroupIdentifierSuboption(
type=code, length=2, group_id=self._make_uint(group_id, bits=16,
name='MAG group identifier'))
if code == Enum_ANISuboption.ANI_Update_Timer:
if option is not None:
timer = math.ceil(cast('Data_ANIUpdateTimerSuboption',
option).timer.total_seconds() / 4)
else:
raw_timer = kwargs.get('timer', 0)
timer = raw_timer if isinstance(raw_timer, int) else math.ceil(
raw_timer.total_seconds() / 4)
return Schema_ANIUpdateTimerSuboption(
type=code, length=2, timer=self._make_uint(timer, bits=16, name='ANI update timer'))
if option is not None:
payload = cast('Data_UnassignedANISuboption', option).data
else:
payload = cast('bytes', kwargs.get('data', b''))
return Schema_UnassignedANISuboption(type=code, length=len(payload), data=payload)
[docs]
def _make_qos_attributes(
self,
attributes: 'QoSAttribute | list[Schema_QoSAttribute | tuple[Enum_QoSAttribute | int, dict[str, Any]] | bytes]',
) -> 'list[Schema_QoSAttribute | bytes]':
"""Make MH quality-of-service attributes.
Args:
attributes: Attributes, as parsed data, schemas, ``(type, kwargs)``
pairs, or raw octets.
Returns:
Attribute schema list.
Raises:
ProtocolError: If the type of an attribute, given in a ``(type,
kwargs)`` pair or as the key of parsed data, is a name, a
:obj:`bool` or a code its field cannot carry, or if an
attribute given as raw octets is empty (:issue:`2723`).
"""
if isinstance(attributes, list):
entries = [] # type: list[Schema_QoSAttribute | bytes]
for item in attributes:
if isinstance(item, bytes) and not item:
raise ProtocolError(f'{self.alias}: invalid attribute: {item!r} holds no type octet')
if isinstance(item, (bytes, Schema)):
entries.append(cast('Schema_QoSAttribute | bytes', item))
else:
kind, args = cast('tuple[Enum_QoSAttribute | int, dict[str, Any]]', item)
code = self._make_index(kind, Enum_QoSAttribute, bits=8, name='attribute type')
entries.append(self._make_qos_attribute(code, **args))
return entries
return [self._make_qos_attribute(
self._make_index(key, Enum_QoSAttribute, bits=8, name='attribute type'), option)
for key, option in attributes.items(multi=True)]
[docs]
def _make_qos_attribute(self, code: 'Enum_QoSAttribute',
option: 'Optional[Data_QoSAttribute]' = None,
**kwargs: 'Any') -> 'Schema_QoSAttribute':
"""Make one MH quality-of-service attribute.
Args:
code: Attribute type.
option: Attribute data model.
**kwargs: Attribute fields, when no data model is given.
Returns:
Constructed attribute schema.
Raises:
ProtocolError: If a rate, priority, vendor or reserved value does not fit
its field, or ``ts_format`` is a name, a :obj:`bool` or a code its
field cannot carry.
Note:
The data model parameter is named ``option`` rather than ``data``
deliberately. The vendor-specific attribute of
:rfc:`7222#section-4.2.11` has a field of its own called ``data``, so
a parameter of that name would capture a caller's ``data=`` and leave
the ``kwargs.get('data')`` fallback empty, silently dropping the vendor
payload and writing the length as though it were empty. ``vendor`` and
``subtype`` do not collide, so the loss would look like a partial
success rather than a bug.
Dispatch is on ``code`` rather than on ``isinstance``, for the reason
given in :meth:`_read_fid_suboptions`.
"""
if code in (Enum_QoSAttribute.Per_Session_Agg_Max_DL_Bit_Rate,
Enum_QoSAttribute.Per_Session_Agg_Max_UL_Bit_Rate):
if option is not None:
session = cast('Data_PerSessionBitRateAttribute', option)
service, exclude, rate = session.service, session.exclude, session.rate
reserved = session.reserved
else:
service = cast('bool', kwargs.get('service', False))
exclude = cast('bool', kwargs.get('exclude', False))
reserved = cast('int', kwargs.get('reserved', 0))
rate = cast('int', kwargs.get('rate', 0))
return Schema_PerSessionBitRateAttribute(
type=code, length=6,
flags={'S': int(service), 'E': int(exclude),
'reserved': self._make_uint(reserved, bits=14, name='reserved bits')},
rate=self._make_uint(rate, bits=32, name='bit rate'))
if code in (Enum_QoSAttribute.Per_MN_Agg_Max_DL_Bit_Rate,
Enum_QoSAttribute.Per_MN_Agg_Max_UL_Bit_Rate,
Enum_QoSAttribute.Aggregate_Max_DL_Bit_Rate,
Enum_QoSAttribute.Aggregate_Max_UL_Bit_Rate,
Enum_QoSAttribute.Guaranteed_DL_Bit_Rate,
Enum_QoSAttribute.Guaranteed_UL_Bit_Rate):
if option is not None:
rate = cast('Data_BitRateAttribute', option).rate
octets = getattr(option, 'reserved', b'\x00\x00')
else:
rate = cast('int', kwargs.get('rate', 0))
octets = cast('bytes', kwargs.get('reserved', b'\x00\x00'))
return Schema_BitRateAttribute(type=code, length=6,
reserved=self._make_octets(octets, size=2, name='reserved octets'),
rate=self._make_uint(rate, bits=32, name='bit rate'))
if code == Enum_QoSAttribute.Allocation_Retention_Priority:
if option is not None:
arp = cast('Data_AllocationRetentionPriorityAttribute', option)
level = arp.priority_level
capability = arp.preemption_capability
vulnerability = arp.preemption_vulnerability
octets = getattr(arp, 'reserved', b'\x00')
else:
level = cast('int', kwargs.get('priority_level', 1))
capability = cast('int', kwargs.get('preemption_capability', 0))
vulnerability = cast('int', kwargs.get('preemption_vulnerability', 0))
octets = cast('bytes', kwargs.get('reserved', b'\x00'))
return Schema_AllocationRetentionPriorityAttribute(
type=code, length=2, reserved=self._make_octets(octets, size=1, name='reserved octets'),
priority={
'PL': self._make_uint(level, bits=4, name='priority level'),
'PC': self._make_uint(capability, bits=2, name='preemption capability'),
'PV': self._make_uint(vulnerability, bits=2, name='preemption vulnerability'),
})
if code == Enum_QoSAttribute.QoS_Traffic_Selector:
if option is not None:
ts_attr = cast('Data_QoSTrafficSelectorAttribute', option)
ts_format = ts_attr.ts_format # type: Enum_TrafficSelector | int
selector = ts_attr.selector
octets = getattr(ts_attr, 'reserved', b'\x00')
else:
ts_format = self._make_index(
kwargs.get('ts_format', Enum_TrafficSelector.IPv6_Binary_Traffic_Selector),
Enum_TrafficSelector, bits=8, name='TS format')
selector = cast('bytes', kwargs.get('selector', b''))
octets = cast('bytes', kwargs.get('reserved', b'\x00'))
return Schema_QoSTrafficSelectorAttribute(
type=code, length=2 + len(selector),
reserved=self._make_octets(octets, size=1, name='reserved octets'),
ts_format=cast('Enum_TrafficSelector', ts_format), selector=selector)
if code == Enum_QoSAttribute.QoS_Vendor_Specific_Attribute:
if option is not None:
vendor_attr = cast('Data_QoSVendorSpecificAttribute', option)
vendor, subtype, payload = (vendor_attr.vendor, vendor_attr.subtype,
vendor_attr.data)
octets = getattr(vendor_attr, 'reserved', b'\x00\x00')
else:
vendor = cast('int', kwargs.get('vendor', 0))
subtype = cast('int', kwargs.get('subtype', 0))
payload = cast('bytes', kwargs.get('data', b''))
octets = cast('bytes', kwargs.get('reserved', b'\x00\x00'))
return Schema_QoSVendorSpecificAttribute(
type=code, length=7 + len(payload),
reserved=self._make_octets(octets, size=2, name='reserved octets'),
vendor=self._make_uint(vendor, bits=32, name='vendor ID'),
subtype=self._make_uint(subtype, bits=8, name='vendor subtype'), data=payload)
if option is not None:
payload = cast('Data_UnassignedQoSAttribute', option).data
else:
payload = cast('bytes', kwargs.get('data', b''))
return Schema_UnassignedQoSAttribute(type=code, length=len(payload), data=payload)
[docs]
def _make_lcmp_suboptions(
self,
suboptions: 'LMAControlledMAGSuboption | list[Schema_LMAControlledMAGSuboption | tuple[Enum_LMAControlledMAGSuboption | int, dict[str, Any]] | bytes]', # pylint: disable=line-too-long
) -> 'list[Schema_LMAControlledMAGSuboption | bytes]':
"""Make MH LMA-controlled MAG parameters sub-options.
Args:
suboptions: Sub-options, as parsed data, schemas, ``(type, kwargs)``
pairs, or raw octets.
Returns:
Sub-option schema list.
Raises:
ProtocolError: If the type of a sub-option, given in a ``(type,
kwargs)`` pair or as the key of parsed data, is a name, a
:obj:`bool` or a code its field cannot carry, or if a
sub-option given as raw octets is empty (:issue:`2723`).
"""
if isinstance(suboptions, list):
entries = [] # type: list[Schema_LMAControlledMAGSuboption | bytes]
for item in suboptions:
if isinstance(item, bytes) and not item:
raise ProtocolError(f'{self.alias}: invalid sub-option: {item!r} holds no type octet')
if isinstance(item, (bytes, Schema)):
entries.append(cast('Schema_LMAControlledMAGSuboption | bytes', item))
else:
kind, args = cast(
'tuple[Enum_LMAControlledMAGSuboption | int, dict[str, Any]]', item)
code = self._make_index(kind, Enum_LMAControlledMAGSuboption, bits=8,
name='sub-option type')
entries.append(self._make_lcmp_suboption(code, **args))
return entries
return [self._make_lcmp_suboption(
self._make_index(key, Enum_LMAControlledMAGSuboption, bits=8,
name='sub-option type'), option)
for key, option in suboptions.items(multi=True)]
[docs]
@staticmethod
def _seconds(value: 'int | timedelta', unit: 'int' = 1) -> 'int':
"""Return an interval in wire units.
Args:
value: Interval, already in wire units when an :obj:`int`, or as a
:class:`~datetime.timedelta`.
unit: Number of seconds one wire unit stands for.
Returns:
The interval in wire units.
"""
if isinstance(value, int):
return value
return math.ceil(value.total_seconds() / unit)
[docs]
def _make_lcmp_suboption(self, code: 'Enum_LMAControlledMAGSuboption',
option: 'Optional[Data_LMAControlledMAGSuboption]' = None,
**kwargs: 'Any') -> 'Schema_LMAControlledMAGSuboption':
"""Make one MH LMA-controlled MAG parameters sub-option.
Args:
code: Sub-option type.
option: Sub-option data model.
**kwargs: Sub-option fields, when no data model is given.
Returns:
Constructed sub-option schema.
Raises:
ProtocolError: If a timer or count does not fit its field.
Note:
The data model parameter is named ``option`` rather than ``data`` so
that it cannot shadow a sub-option field of that name -- see
:meth:`_make_fid_suboption`. Dispatch is on ``code``
rather than on ``isinstance``, for the reason given in
:meth:`_read_fid_suboptions`.
"""
if code == Enum_LMAControlledMAGSuboption.Binding_Re_registration_Control:
if option is not None:
rereg = cast('Data_BindingReregistrationControlSuboption', option)
start = math.ceil(rereg.start_time.total_seconds() / 4)
initial = math.ceil(rereg.initial_retransmission.total_seconds())
maximum = math.ceil(rereg.max_retransmission.total_seconds())
else:
start = self._seconds(kwargs.get('start_time', 0), 4)
initial = self._seconds(kwargs.get('initial_retransmission', 0))
maximum = self._seconds(kwargs.get('max_retransmission', 0))
return Schema_BindingReregistrationControlSuboption(
type=code, length=6,
start_time=self._make_uint(start, bits=16, name='re-registration start time'),
initial_retransmission=self._make_uint(initial, bits=16,
name='initial retransmission timer'),
max_retransmission=self._make_uint(maximum, bits=16,
name='maximum retransmission timer'))
if code == Enum_LMAControlledMAGSuboption.Heartbeat_Control:
if option is not None:
heartbeat = cast('Data_HeartbeatControlSuboption', option)
interval = math.ceil(heartbeat.interval.total_seconds())
delay = math.ceil(heartbeat.retransmission_delay.total_seconds())
count = heartbeat.max_retransmissions
else:
interval = self._seconds(kwargs.get('interval', 0))
delay = self._seconds(kwargs.get('retransmission_delay', 0))
count = cast('int', kwargs.get('max_retransmissions', 0))
return Schema_HeartbeatControlSuboption(
type=code, length=6,
interval=self._make_uint(interval, bits=16, name='heartbeat interval'),
retransmission_delay=self._make_uint(delay, bits=16, name='retransmission delay'),
max_retransmissions=self._make_uint(count, bits=16, name='maximum retransmissions'))
if option is not None:
payload = cast('Data_UnassignedLMAControlledMAGSuboption', option).data
else:
payload = cast('bytes', kwargs.get('data', b''))
return Schema_UnassignedLMAControlledMAGSuboption(
type=code, length=len(payload), data=payload)
[docs]
def _make_cga_extensions(self, extensions: 'Extension | list[Schema_CGAExtension | tuple[Enum_CGAExtension | int, dict[str, Any]] | bytes]') -> 'tuple[list[Schema_CGAExtension | bytes], int]': # pylint: disable=line-too-long
"""Make CGA extensions for MH.
Args:
extensions: CGA extensions.
Returns:
Tuple of extensions and total length of extensions.
Raises:
ProtocolError: If the type of an extension, given in a ``(type,
kwargs)`` pair or as the key of parsed data, is a name, a
:obj:`bool` or a code its field cannot carry, or if an
extension given as raw octets is empty (:issue:`2723`).
"""
total_length = 0
if isinstance(extensions, list):
extensions_list = [] # type: list[Schema_CGAExtension | bytes]
for schema in extensions:
if isinstance(schema, bytes):
if not schema:
raise ProtocolError(f'{self.alias}: invalid CGA extension: {schema!r} holds no type field')
code = Enum_CGAExtension.get(int.from_bytes(schema[0:2], 'big', signed=False))
data = schema # type: Schema_CGAExtension | bytes
data_len = len(data)
elif isinstance(schema, Schema):
data = schema
data_len = len(schema.pack())
else:
kind, args = cast('tuple[Enum_CGAExtension | int, dict[str, Any]]', schema)
code = self._make_index(kind, Enum_CGAExtension, bits=16,
name='extension type')
name = self._lookup_registry(self.__extension__, code)
if isinstance(name, str):
meth_name = f'_make_ext_{name}'
meth = cast('ExtensionConstructor',
getattr(self, meth_name, self._make_ext_none))
else:
meth = name[1]
data = meth(code, **args)
data_len = len(data.pack())
extensions_list.append(data)
total_length += data_len
return extensions_list, total_length
extensions_list = []
for key, extension in extensions.items(multi=True):
code = self._make_index(key, Enum_CGAExtension, bits=16, name='extension type')
name = self._lookup_registry(self.__extension__, code)
if isinstance(name, str):
meth_name = f'_make_ext_{name}'
meth = cast('ExtensionConstructor',
getattr(self, meth_name, self._make_ext_none))
else:
meth = name[1]
data = meth(code, extension)
data_len = len(data.pack())
extensions_list.append(data)
total_length += data_len
return extensions_list, total_length