// 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 #include #include #include #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; }