ud3tn/components/bundle7/bundle7.c
Maximilian Nitsch 611b67fb0e refactor: cleanup includes
Removes unused includes detected by the clangd LSP.

Signed-off-by: Maximilian Nitsch <maximilian.nitsch@d3tn.com>
2026-05-20 16:16:35 +02:00

429 lines
13 KiB
C

// SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
#include "bundle7/bundle7.h"
#include "ud3tn/common.h"
#include "ud3tn/bundle.h"
#include "ud3tn/eid.h"
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#define MAX_VALIDATED_BLOCK_NUMBER UINT8_MAX
const uint32_t BUNDLE_V7_FLAG_MASK = BUNDLE_FLAG_IS_FRAGMENT
| BUNDLE_FLAG_ADMINISTRATIVE_RECORD
| BUNDLE_FLAG_MUST_NOT_BE_FRAGMENTED
| BUNDLE_FLAG_ACKNOWLEDGEMENT_REQUESTED
| BUNDLE_FLAG_REPORT_STATUS_TIME
| BUNDLE_FLAG_REPORT_RECEPTION
| BUNDLE_FLAG_REPORT_FORWARDING
| BUNDLE_FLAG_REPORT_DELIVERY
| BUNDLE_FLAG_REPORT_DELETION;
bool bundle7_is_valid(const struct bundle *bundle)
{
if (bundle->protocol_version != 7)
return false;
if (eid_is_error(bundle->source) || eid_is_error(bundle->destination) ||
eid_is_error(bundle->report_to) || eid_is_null(bundle->destination))
return false;
const bool is_admin_record = !!(bundle->proc_flags & BUNDLE_FLAG_ADMINISTRATIVE_RECORD);
const bool has_null_source = eid_is_null(bundle->source);
const bool has_primary_crc = bundle->crc_type != BUNDLE_CRC_TYPE_NONE;
/**
* From RFC 9171, 4.2.3:
* > If the bundle processing control flags indicate that the bundle's ADU is an
* > administrative record, then all status report request flag values MUST be zero.
*/
if (is_admin_record) {
if (bundle->proc_flags & BUNDLE_FLAG_REPORT_ANY)
return false;
}
/**
* From RFC 9171, 4.2.3:
* > If the bundle's source node is omitted (i.e., the source node ID is the ID of the null
* > endpoint, which has no members as discussed below; this option enables anonymous
* > bundle transmission), then the bundle is not uniquely identifiable and all Bundle
* > Protocol features that rely on bundle identity must therefore be disabled: the
* > "Bundle must not be fragmented" flag value MUST be 1, and all status report request
* > flag values MUST be zero.
*/
if (has_null_source) {
if (!(bundle->proc_flags & BUNDLE_FLAG_MUST_NOT_BE_FRAGMENTED))
return false;
if (bundle->proc_flags & BUNDLE_FLAG_REPORT_ANY)
return false;
}
struct bundle_block_list *cur = bundle->blocks;
bool has_payload_block = false;
bool has_previous_node_block = false;
bool has_bundle_age_block = false;
bool has_hop_count_block = false;
bool has_block_integrity_block = false;
bool any_block_requests_status_reporting = false;
bool taken_block_numbers[MAX_VALIDATED_BLOCK_NUMBER] = {false};
while (cur) {
if (cur->data->type == BUNDLE_BLOCK_TYPE_PAYLOAD) {
// From RFC 9171, 4.1: The last such block MUST be a payload block
if (cur->next)
return false;
// From RFC 9171, 4.1: the bundle MUST have exactly one payload block.
if (has_payload_block)
return false;
has_payload_block = true;
// From RFC 9171, 4.1: The block number of the payload block is always 1.
if (cur->data->number != 1)
return false;
} else if (cur->data->type == BUNDLE_BLOCK_TYPE_PREVIOUS_NODE) {
// From RFC 9171, 4.4.1: the bundle SHOULD contain one (1) occurrence of
// this type of block and MUST NOT contain more than one.
if (has_previous_node_block)
return false;
has_previous_node_block = true;
} else if (cur->data->type == BUNDLE_BLOCK_TYPE_BUNDLE_AGE) {
// From RFC 9171, 4.4.2: A bundle MUST NOT contain multiple occurrences
// of the Bundle Age Block
if (has_bundle_age_block)
return false;
has_bundle_age_block = true;
} else if (cur->data->type == BUNDLE_BLOCK_TYPE_HOP_COUNT) {
// From RFC 9171, 4.4.3: A bundle MAY contain one occurrence of this type
// of block but MUST NOT contain more than one.
if (has_hop_count_block)
return false;
has_hop_count_block = true;
} else if (cur->data->type == BUNDLE_BLOCK_TYPE_BLOCK_INTEGRITY) {
// TODO: Decode the security targets array to check whether the block
// actually targets the primary block. As the order in that CBOR array is
// arbitrary (RFC 9172, 3.6), this is non-trivial.
has_block_integrity_block = true;
}
// Status reporting is not possible in some cases (e.g. Null source)
if (HAS_FLAG(cur->data->flags, BUNDLE_BLOCK_FLAG_REPORT_IF_UNPROC))
any_block_requests_status_reporting = true;
// Reserved for primary block that is not part of the block list
if (cur->data->number == 0)
return false;
if (cur->data->number < MAX_VALIDATED_BLOCK_NUMBER) {
// Duplicate?
// From RFC 9171, 4.1: The block number uniquely identifies the block
// within the bundle
if (taken_block_numbers[cur->data->number - 1])
return false;
taken_block_numbers[cur->data->number - 1] = true;
}
cur = cur->next;
}
// From RFC 9171, 4.1: the bundle MUST have exactly one payload block.
if (!has_payload_block)
return false;
// From RFC 9171, 4.4.2: If the bundle's creation time is zero, then the bundle MUST
// contain exactly one (1) occurrence of this type of block
if (bundle->creation_timestamp_ms == 0 && !has_bundle_age_block)
return false;
// From RFC 9171, 4.3.1: A CRC SHALL be present in the primary block unless the bundle
// includes a BPSec Block Integrity Block [BPSEC] whose target is the primary block
if (!has_primary_crc && !has_block_integrity_block)
// Will be optimized away if BUNDLE7_ALLOW_NO_PRIMARY_CRC_NO_BIB is set
if (!BUNDLE7_ALLOW_NO_PRIMARY_CRC_NO_BIB)
return false;
/**
* From RFC 9171, 4.2.4:
* > For each bundle whose bundle processing control flags indicate that the bundle's
* > ADU is an administrative record, or whose source node ID is the null endpoint ID
* > as defined below, the value of the "Transmit status report if block can't be
* > processed" flag in every canonical block of the bundle MUST be zero.
*/
if (any_block_requests_status_reporting && (has_null_source || is_admin_record))
return false;
return true;
}
size_t bundle7_cbor_uint_sizeof(uint64_t num)
{
// Binary search
if (num <= UINT16_MAX) {
if (num <= UINT8_MAX)
// Embedded unsigned integer
if (num <= 23)
return 1;
// uint8
else
return 2;
// uint16
else
return 3;
// uint32
} else if (num <= UINT32_MAX) {
return 5;
// uint64
} else {
return 9;
}
}
size_t bundle7_eid_sizeof(const struct eid eid)
{
if (eid_is_error(eid))
return 0;
if (eid.scheme == EID_SCHEME_DTN) {
// dtn:none -> [1,0] -> 0x82 0x01 0x00
if (eid.dtn_eid == NULL)
return 3;
// dtn:
// The CBOR encoded SSP requires an header encoding the string
// length. Therefore we add the size of this header to the bound.
// The SSP is "string length - 4" bytes long.
const size_t dtn_length = strlen(eid.dtn_eid);
if (dtn_length < 4) // invalid; probably a bug
return 0;
return (
1 + // CBOR array header
1 + // bundle7_cbor_uint_sizeof(BUNDLE_V7_EID_SCHEMA_DTN)
bundle7_cbor_uint_sizeof(dtn_length - 4) +
(dtn_length - 4)
);
} else if (eid.scheme != EID_SCHEME_IPN) {
return 0;
}
// ipn
if (ipn_eid_allocator(eid.ipn_eid) == 0 || EID_IPN_FORCE_2_ELEMENT)
return (
1 + // CBOR array header
1 + // bundle7_cbor_uint_sizeof(BUNDLE_V7_EID_SCHEMA_IPN)
1 + // CBOR array header
bundle7_cbor_uint_sizeof(eid.ipn_eid.fully_qualified_node_number) +
bundle7_cbor_uint_sizeof(eid.ipn_eid.service_number)
);
else
return (
1 + // CBOR array header
1 + // bundle7_cbor_uint_sizeof(BUNDLE_V7_EID_SCHEMA_IPN)
1 + // CBOR array header
bundle7_cbor_uint_sizeof(ipn_eid_allocator(eid.ipn_eid)) +
bundle7_cbor_uint_sizeof(ipn_eid_node(eid.ipn_eid)) +
bundle7_cbor_uint_sizeof(eid.ipn_eid.service_number)
);
}
void bundle7_recalculate_primary_block_length(struct bundle *bundle)
{
// Primary Block
const size_t dst_eid_size = bundle7_eid_sizeof(bundle->destination);
const size_t src_eid_size = bundle7_eid_sizeof(bundle->source);
const size_t rpt_eid_size = bundle7_eid_sizeof(bundle->report_to);
// Bug: Bundles with invalid EID fields must not be processed!
ASSERT(dst_eid_size != 0);
ASSERT(src_eid_size != 0);
ASSERT(rpt_eid_size != 0);
size_t size = 1 // CBOR array header
+ bundle7_cbor_uint_sizeof(bundle->protocol_version)
+ bundle7_cbor_uint_sizeof(bundle->proc_flags)
+ bundle7_cbor_uint_sizeof(bundle->crc_type)
+ dst_eid_size
+ src_eid_size
+ rpt_eid_size
// Creation Timestamp
+ 1 // CBOR array header
+ bundle7_cbor_uint_sizeof(bundle->creation_timestamp_ms)
+ bundle7_cbor_uint_sizeof(bundle->sequence_number)
+ bundle7_cbor_uint_sizeof(bundle->lifetime_ms);
// Fragmented Bundle
if (bundle_is_fragmented(bundle)) {
size += bundle7_cbor_uint_sizeof(bundle->fragment_offset);
size += bundle7_cbor_uint_sizeof(bundle->total_adu_length);
}
if (bundle->crc_type == BUNDLE_CRC_TYPE_32)
size += 5;
else if (bundle->crc_type == BUNDLE_CRC_TYPE_16)
size += 3;
bundle->primary_block_length = size;
}
size_t bundle7_block_get_serialized_size(const struct bundle_block *block)
{
size_t size = 1 // CBOR array header
+ bundle7_cbor_uint_sizeof(block->type)
+ bundle7_cbor_uint_sizeof(block->number)
+ bundle7_cbor_uint_sizeof(block->flags)
+ bundle7_cbor_uint_sizeof(block->crc_type)
+ bundle7_cbor_uint_sizeof(block->length)
// Block-specific data
+ block->length;
// CRC field
if (block->crc_type == BUNDLE_CRC_TYPE_32)
size += 5;
else if (block->crc_type == BUNDLE_CRC_TYPE_16)
size += 3;
return size;
}
size_t bundle7_get_serialized_size(struct bundle *bundle)
{
size_t size = 0;
struct bundle_block_list *entry = bundle->blocks;
// Extension Blocks
while (entry != NULL) {
size += bundle7_block_get_serialized_size(entry->data);
entry = entry->next;
}
return 1 // CBOR indef-array start
+ bundle->primary_block_length
+ size
+ 1; // CBOR "stop"
}
size_t bundle7_get_serialized_size_without_payload(struct bundle *bundle)
{
size_t size = 0;
struct bundle_block_list *entry = bundle->blocks;
// Extension Blocks
while (entry->data->type != BUNDLE_BLOCK_TYPE_PAYLOAD) {
size += bundle7_block_get_serialized_size(entry->data);
entry = entry->next;
}
return 1 // CBOR indef-array start
+ bundle->primary_block_length
+ size
+ 1; // CBOR "stop"
}
size_t bundle7_get_first_fragment_min_size(struct bundle *bundle)
{
size_t size = 0;
struct bundle_block_list *entry = bundle->blocks;
while (entry != NULL) {
// Payload header
if (entry->data->type == BUNDLE_BLOCK_TYPE_PAYLOAD) {
size += 1 // CBOR array header
+ bundle7_cbor_uint_sizeof(entry->data->type)
+ bundle7_cbor_uint_sizeof(entry->data->number)
+ bundle7_cbor_uint_sizeof(entry->data->flags)
+ bundle7_cbor_uint_sizeof(entry->data->crc_type)
+ bundle7_cbor_uint_sizeof(entry->data->length);
// CRC field
if (entry->data->crc_type == BUNDLE_CRC_TYPE_32)
size += 5;
else if (entry->data->crc_type == BUNDLE_CRC_TYPE_16)
size += 3;
// Data length zero + empty byte string
size += 2;
}
// For the first fragment (fragment with the offset 0), all
// extension blocks must be replicated
else
size += bundle7_block_get_serialized_size(entry->data);
entry = entry->next;
}
// If bundle was not fragmented before, add the "Fragment offset" and
// "Total Application Data Unit Length" field sizes
if (!bundle_is_fragmented(bundle)) {
size += bundle7_cbor_uint_sizeof(bundle->payload_block->length);
// Conservative estimation for maximum "minimum bundle size",
// i.e., header length: the fragment offset could have a maximum
// size corresponding to the fragment offset. All other values
// in the bundle can only decrease.
size += bundle7_cbor_uint_sizeof(bundle->payload_block->length);
} else {
// Conservative estimation rgd. fragment offset
size += (
bundle7_cbor_uint_sizeof(bundle->total_adu_length) -
bundle7_cbor_uint_sizeof(bundle->fragment_offset)
);
}
return bundle->primary_block_length + size;
}
size_t bundle7_get_last_fragment_min_size(struct bundle *bundle)
{
size_t size = 0;
struct bundle_block_list *entry = bundle->blocks;
while (entry != NULL) {
if (entry->data->type == BUNDLE_BLOCK_TYPE_PAYLOAD) {
size += 1 // CBOR array header
+ bundle7_cbor_uint_sizeof(entry->data->type)
+ bundle7_cbor_uint_sizeof(entry->data->number)
+ bundle7_cbor_uint_sizeof(entry->data->flags)
+ bundle7_cbor_uint_sizeof(entry->data->crc_type)
+ bundle7_cbor_uint_sizeof(entry->data->length);
// CRC field
if (entry->data->crc_type == BUNDLE_CRC_TYPE_32)
size += 5;
else if (entry->data->crc_type == BUNDLE_CRC_TYPE_16)
size += 3;
// Data length zero + empty byte string
size += 2;
}
// Sum up all blocks that must be replicated in each fragment
else if (HAS_FLAG(entry->data->flags,
BUNDLE_BLOCK_FLAG_MUST_BE_REPLICATED))
size += bundle7_block_get_serialized_size(entry->data);
entry = entry->next;
}
// If bundle was not fragmented before, add the "Fragment offset" and
// "Total Application Data Unit Length" field sizes
if (!bundle_is_fragmented(bundle)) {
size += bundle7_cbor_uint_sizeof(bundle->payload_block->length);
// Conservative estimation for maximum "minimum bundle size",
// i.e., header length: the fragment offset could have a maximum
// size corresponding to the fragment offset. All other values
// in the bundle can only decrease.
size += bundle7_cbor_uint_sizeof(bundle->payload_block->length);
} else {
// Conservative estimation rgd. fragment offset
size += (
bundle7_cbor_uint_sizeof(bundle->total_adu_length) -
bundle7_cbor_uint_sizeof(bundle->fragment_offset)
);
}
return bundle->primary_block_length + size;
}