mirror of
https://gitlab.com/d3tn/ud3tn.git
synced 2026-08-19 13:18:32 +02:00
The integer value `bundle_id` does not have a "length" - this seems like a C&P error. Signed-off-by: Felix Walter <felix.walter@d3tn.com>
190 lines
6.7 KiB
Python
190 lines
6.7 KiB
Python
# SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
|
|
"""μD3TN Application Agent Protocol (AAP) Python implementation.
|
|
|
|
Usage:
|
|
Use the AAPMessage class to build, serialize, and parse AAP messages.
|
|
For a description of the protocol, see
|
|
https://gitlab.com/d3tn/ud3tn/-/blob/master/doc/ud3tn_aap.md.
|
|
"""
|
|
|
|
import struct
|
|
import enum
|
|
|
|
|
|
class InsufficientAAPDataError(Exception):
|
|
"""The binary AAP message appears to be valid but is missing some bytes.
|
|
|
|
Args:
|
|
bytes_needed: The total amount of bytes required to perform the next
|
|
parsing step.
|
|
"""
|
|
|
|
def __init__(self, bytes_needed):
|
|
self.bytes_needed = bytes_needed
|
|
|
|
|
|
class AAPMessageType(enum.IntEnum):
|
|
"""AAP message type codes."""
|
|
ACK = 0x0
|
|
NACK = 0x1
|
|
REGISTER = 0x2
|
|
SENDBUNDLE = 0x3
|
|
RECVBUNDLE = 0x4
|
|
SENDCONFIRM = 0x5
|
|
CANCELBUNDLE = 0x6
|
|
WELCOME = 0x7
|
|
PING = 0x8
|
|
SENDBIBE = 0x9
|
|
RECVBIBE = 0xA
|
|
|
|
|
|
class AAPMessage:
|
|
"""An AAP message representation supporting parsing and serialization."""
|
|
def __init__(self, msg_type, eid=None, payload=None, bundle_id=None):
|
|
if (msg_type < AAPMessageType.ACK or
|
|
msg_type > AAPMessageType.RECVBIBE):
|
|
raise ValueError("Invalid message type code")
|
|
self.msg_type = msg_type
|
|
self.eid = eid
|
|
self.payload = payload
|
|
self.bundle_id = bundle_id
|
|
|
|
def serialize(self):
|
|
"""Serialize the AAP message to its on-wire representation."""
|
|
msg = [struct.pack("B", 0x10 | (self.msg_type & 0xF))]
|
|
|
|
# EID
|
|
if self.msg_type in (AAPMessageType.REGISTER,
|
|
AAPMessageType.SENDBUNDLE,
|
|
AAPMessageType.RECVBUNDLE,
|
|
AAPMessageType.SENDBIBE,
|
|
AAPMessageType.RECVBIBE,
|
|
AAPMessageType.WELCOME):
|
|
msg.append(struct.pack("!H", len(self.eid)))
|
|
msg.append(self.eid.encode("ascii"))
|
|
|
|
# Payload
|
|
if self.msg_type in (AAPMessageType.SENDBUNDLE,
|
|
AAPMessageType.RECVBUNDLE,
|
|
AAPMessageType.SENDBIBE,
|
|
AAPMessageType.RECVBIBE):
|
|
msg.append(struct.pack("!Q", len(self.payload)))
|
|
msg.append(self.payload)
|
|
|
|
# Bundle ID
|
|
if self.msg_type in (AAPMessageType.SENDCONFIRM,
|
|
AAPMessageType.CANCELBUNDLE):
|
|
msg.append(struct.pack("!Q", self.bundle_id))
|
|
|
|
return b"".join(msg)
|
|
|
|
def decode_bundle_id(self):
|
|
"""Decode the contained bundle ID to timestamp and sequence number.
|
|
|
|
If the used bundle ID format is with timestamp, this function returns
|
|
a tuple of the milliseconds elapsed since the DTN epoch and the
|
|
sequence number. If the format without timestamp is used, a tuple
|
|
containing only the sequence number is returned. If an invalid
|
|
bundle ID (e.g. by an old uD3TN version) is provided, a ValueError
|
|
is raised.
|
|
"""
|
|
# If the Bundle ID has a format including the timestamp or sequence
|
|
# number, we can decode these values from it.
|
|
if (self.bundle_id & (1 << 63)) == 0:
|
|
raise ValueError("reserved bit not set to 1 (old uD3TN version?)")
|
|
if (self.bundle_id & (1 << 62)) == 0:
|
|
# with-timestamp
|
|
return (
|
|
(self.bundle_id & 0x3FFFFFFFFFFF0000) >> 16,
|
|
(self.bundle_id & 0xFFFF),
|
|
)
|
|
else:
|
|
# without-timestamp
|
|
return (
|
|
self.bundle_id & 0x3FFFFFFFFFFFFFFF,
|
|
)
|
|
|
|
def __bytes__(self):
|
|
return self.serialize()
|
|
|
|
@staticmethod
|
|
def parse(data):
|
|
"""Parse the provided AAP on-wire representation.
|
|
|
|
An instance of AAPMessage is returned on success.
|
|
If there are not enough bytes provided, an InsufficientAAPDataError is
|
|
raised, allowing the caller to wait for more data.
|
|
If invalid data is provided, a ValueError is raised.
|
|
"""
|
|
if len(data) == 0:
|
|
raise InsufficientAAPDataError(1)
|
|
|
|
version = (data[0] >> 4) & 0xF
|
|
if version != 1:
|
|
raise ValueError("Invalid AAP version: " + str(version))
|
|
|
|
msg_type = data[0] & 0xF
|
|
|
|
eid = payload = bundle_id = None
|
|
index = 1
|
|
|
|
# EID
|
|
if msg_type in (AAPMessageType.REGISTER,
|
|
AAPMessageType.SENDBUNDLE,
|
|
AAPMessageType.RECVBUNDLE,
|
|
AAPMessageType.SENDBIBE,
|
|
AAPMessageType.RECVBIBE,
|
|
AAPMessageType.WELCOME):
|
|
if len(data) - index < 2:
|
|
raise InsufficientAAPDataError(index + 2)
|
|
eid_length, = struct.unpack("!H", data[index:(index + 2)])
|
|
index += 2
|
|
if len(data) - index < eid_length:
|
|
raise InsufficientAAPDataError(index + eid_length)
|
|
eid = data[index:(index + eid_length)].decode("ascii")
|
|
index += eid_length
|
|
|
|
# Payload
|
|
if msg_type in (AAPMessageType.SENDBUNDLE,
|
|
AAPMessageType.RECVBUNDLE,
|
|
AAPMessageType.SENDBIBE,
|
|
AAPMessageType.RECVBIBE):
|
|
if len(data) - index < 8:
|
|
raise InsufficientAAPDataError(index + 8)
|
|
payload_length, = struct.unpack("!Q", data[index:(index + 8)])
|
|
index += 8
|
|
if len(data) - index < payload_length:
|
|
raise InsufficientAAPDataError(index + payload_length)
|
|
payload = data[index:(index + payload_length)]
|
|
index += payload_length
|
|
|
|
# Bundle ID
|
|
if msg_type in (AAPMessageType.SENDCONFIRM,
|
|
AAPMessageType.CANCELBUNDLE):
|
|
if len(data) - index < 8:
|
|
raise InsufficientAAPDataError(index + 8)
|
|
bundle_id, = struct.unpack("!Q", data[index:(index + 8)])
|
|
index += 8
|
|
|
|
return AAPMessage(msg_type, eid, payload, bundle_id)
|
|
|
|
@staticmethod
|
|
def encode_bundle_id(timestamp_ms=None, seqnum=0):
|
|
"""Encode the provided values into the uD3TN bundle ID format.
|
|
|
|
Args:
|
|
timestamp_ms: A DTN time in milliseconds to be encoded into the
|
|
bundle ID. If None, the bundle ID format without timestamp
|
|
will be used.
|
|
seqnum: The sequence number to be encoded into the bundle ID.
|
|
"""
|
|
if timestamp_ms is not None:
|
|
# with-timestamp
|
|
return (
|
|
(2 << 62) |
|
|
((timestamp_ms & 0x00003FFFFFFFFFFF) << 16) |
|
|
(seqnum & 0xFFFF)
|
|
)
|
|
else:
|
|
# without-timestamp
|
|
return (3 << 62) | (seqnum & 0x3FFFFFFFFFFFFFFF)
|