mirror of
https://gitlab.com/d3tn/ud3tn.git
synced 2026-08-13 12:33:27 +02:00
- The standard defines 64 bits for all flags. - Also see https://www.rfc-editor.org/rfc/rfc9171.html#section-4.2.3-6: "Bundle processing control flags that are unrecognized MUST be ignored, as future definitions of additional flags might not be integrated simultaneously into the Bundle Protocol implementations operating at all nodes." - Similar for blocks: https://www.rfc-editor.org/rfc/rfc9171.html#section-4.2.4-2 - The primary block length is calculated internally based on the bundle length -- to prevent overflow, we use 64 bit here as well. (We might introduce additional checks in the future, but the input being CBOR with the validations in the BPv7 parser should already work to only allow a "sane" primary block length. For BPv6 we enforce still parsing only 16 bits for it.) - Also define a bit mask for BPv6 flags (`BP_V6_FLAGS`) Closes: #247, #237 Signed-off-by: Felix Walter <felix.walter@d3tn.com>
433 lines
12 KiB
C
433 lines
12 KiB
C
// SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
|
|
#include "bundle6/bundle6.h"
|
|
#include "bundle6/sdnv.h"
|
|
|
|
#include "ud3tn/bundle.h"
|
|
#include "ud3tn/common.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
bool bundle6_is_valid(const struct bundle *bundle)
|
|
{
|
|
if (bundle->protocol_version != 6)
|
|
return false;
|
|
|
|
// We do not allow invalid values or an empty destination.
|
|
if (eid_is_error(bundle->source) || eid_is_error(bundle->destination) ||
|
|
eid_is_error(bundle->report_to) || eid_is_error(bundle->current_custodian) ||
|
|
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);
|
|
|
|
/**
|
|
* From RFC 5050, 4.2:
|
|
* > If the bundle processing control flags indicate that the bundle's
|
|
* > application data unit is an administrative record, then the custody
|
|
* > transfer requested flag must be zero and all status report request
|
|
* > flags must be zero.
|
|
*/
|
|
if (is_admin_record) {
|
|
if (bundle->proc_flags & BUNDLE_V6_FLAG_CUSTODY_TRANSFER_REQUESTED)
|
|
return false;
|
|
if (bundle->proc_flags & BUNDLE_FLAG_REPORT_ANY)
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* From RFC 5050, 4.2:
|
|
* > If the bundle's source endpoint ID is "dtn:none" (see
|
|
* > below), then the bundle is not uniquely identifiable and all bundle
|
|
* > protocol features that rely on bundle identity must therefore be
|
|
* > disabled: the bundle's custody transfer requested flag must be zero,
|
|
* > the "Bundle must not be fragmented" flag must be 1, and all status
|
|
* > report request flags must be zero.
|
|
*/
|
|
if (has_null_source) {
|
|
if (bundle->proc_flags & BUNDLE_V6_FLAG_CUSTODY_TRANSFER_REQUESTED)
|
|
return false;
|
|
if (!(bundle->proc_flags & BUNDLE_FLAG_MUST_NOT_BE_FRAGMENTED))
|
|
return false;
|
|
if (bundle->proc_flags & BUNDLE_FLAG_REPORT_ANY)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static size_t write_scheme(char *dst, const char *eid)
|
|
{
|
|
if (eid == NULL) {
|
|
memcpy(dst, "dtn", 4);
|
|
return 4;
|
|
}
|
|
|
|
const char *colon = strchr(eid, ':');
|
|
|
|
if (colon == NULL) {
|
|
memcpy(dst, "dtn", 4);
|
|
return 4;
|
|
}
|
|
|
|
const size_t scheme_len = colon - eid;
|
|
|
|
memcpy(dst, eid, scheme_len);
|
|
dst[scheme_len] = 0;
|
|
return scheme_len + 1;
|
|
}
|
|
|
|
static size_t write_ssp(char *dst, const char *eid)
|
|
{
|
|
if (eid == NULL) {
|
|
memcpy(dst, "none", 5);
|
|
return 5;
|
|
}
|
|
|
|
const char *colon = strchr(eid, ':');
|
|
|
|
if (colon == NULL) {
|
|
memcpy(dst, "none", 5);
|
|
return 5;
|
|
}
|
|
|
|
const size_t scheme_len = colon - eid;
|
|
const char *ssp = &eid[scheme_len + 1];
|
|
const size_t ssp_len = strlen(ssp);
|
|
|
|
memcpy(dst, ssp, ssp_len + 1);
|
|
return ssp_len + 1;
|
|
}
|
|
|
|
static size_t write_eid(char *dst, const struct bundle6_eid_info info)
|
|
{
|
|
const size_t scheme_len = write_scheme(dst, info.str_ptr);
|
|
|
|
return scheme_len + write_ssp(&dst[scheme_len], info.str_ptr);
|
|
}
|
|
|
|
void bundle6_serialize_dictionary(char *buf,
|
|
const struct bundle6_dict_descriptor *desc)
|
|
{
|
|
buf += write_eid(buf, desc->destination_eid_info);
|
|
buf += write_eid(buf, desc->source_eid_info);
|
|
buf += write_eid(buf, desc->report_to_eid_info);
|
|
buf += write_eid(buf, desc->custodian_eid_info);
|
|
|
|
for (size_t i = 0; i < desc->eid_reference_count; i++)
|
|
buf += write_eid(buf, desc->eid_references[i]);
|
|
}
|
|
|
|
|
|
// ----------------------
|
|
// Bundle and block sizes
|
|
// ----------------------
|
|
|
|
struct eid_len_info {
|
|
size_t scheme_len;
|
|
size_t ssp_len;
|
|
};
|
|
|
|
static struct eid_len_info analyze_eid(const char *eid)
|
|
{
|
|
// dtn:none
|
|
struct eid_len_info result = {
|
|
.scheme_len = 3,
|
|
.ssp_len = 4,
|
|
};
|
|
|
|
if (eid == NULL)
|
|
return result;
|
|
|
|
const char *colon = strchr(eid, ':');
|
|
|
|
if (colon == NULL)
|
|
return result;
|
|
|
|
result.scheme_len = colon - eid;
|
|
|
|
const char *ssp = &eid[result.scheme_len + 1];
|
|
|
|
result.ssp_len = strlen(ssp);
|
|
|
|
return result;
|
|
}
|
|
|
|
static struct bundle6_eid_info calculate_eid_info(
|
|
char *eid, size_t *dict_length_bytes)
|
|
{
|
|
struct bundle6_eid_info result;
|
|
// Note that bundle6_serialize_dictionary checks for the same conditions as analyze_eid and
|
|
// handles NULL EIDs gracefully (writing "dtn:none"). Thus, we do not need extended error
|
|
// handling for the result.
|
|
struct eid_len_info eid_info = analyze_eid(eid);
|
|
|
|
// For every EID, its length (including the colon) plus 1 byte
|
|
// is required: The colon is removed and two \0 chars are added.
|
|
result.str_ptr = eid;
|
|
// Behind the colon
|
|
result.ssp_ptr = (eid != NULL) ? (eid + eid_info.scheme_len + 1) : NULL;
|
|
// Start of scheme (index) is at current dict length value
|
|
result.dict_scheme_offset = *dict_length_bytes;
|
|
// SSP starts after zero-termination of scheme
|
|
result.dict_ssp_offset = *dict_length_bytes + eid_info.scheme_len + 1;
|
|
// Extend length as last operation (used above!)
|
|
*dict_length_bytes += eid_info.scheme_len + eid_info.ssp_len + 2;
|
|
|
|
return result;
|
|
}
|
|
|
|
// Calculate all offsets and lengths within the dict
|
|
struct bundle6_dict_descriptor *bundle6_calculate_dict(const struct bundle *const bundle)
|
|
{
|
|
size_t eid_reference_count;
|
|
struct bundle_block_list *cur_block;
|
|
struct eid_list *cur_eid;
|
|
|
|
// Calculate total amount of EID refs in extension blocks
|
|
eid_reference_count = 0;
|
|
cur_block = bundle->blocks;
|
|
while (cur_block) {
|
|
if (HAS_FLAG(cur_block->data->flags,
|
|
BUNDLE_V6_BLOCK_FLAG_HAS_EID_REF_FIELD)) {
|
|
cur_eid = cur_block->data->eid_refs;
|
|
while (cur_eid) {
|
|
eid_reference_count++;
|
|
cur_eid = cur_eid->next;
|
|
}
|
|
}
|
|
cur_block = cur_block->next;
|
|
}
|
|
|
|
// Allocate memory for desciptor
|
|
struct bundle6_dict_descriptor *const result = malloc(
|
|
sizeof(struct bundle6_dict_descriptor) +
|
|
sizeof(struct bundle6_eid_info) * eid_reference_count
|
|
);
|
|
|
|
if (result == NULL)
|
|
return NULL;
|
|
|
|
result->eid_reference_count = eid_reference_count;
|
|
result->dict_length_bytes = 0;
|
|
result->destination_eid_info = calculate_eid_info(
|
|
eid_to_string2(bundle->destination, true), &result->dict_length_bytes
|
|
);
|
|
result->source_eid_info = calculate_eid_info(
|
|
eid_to_string2(bundle->source, true), &result->dict_length_bytes
|
|
);
|
|
result->report_to_eid_info = calculate_eid_info(
|
|
eid_to_string2(bundle->report_to, true), &result->dict_length_bytes
|
|
);
|
|
result->custodian_eid_info = calculate_eid_info(
|
|
eid_to_string2(bundle->current_custodian, true), &result->dict_length_bytes
|
|
);
|
|
|
|
// Add list of EID references
|
|
int i = 0;
|
|
|
|
cur_block = bundle->blocks;
|
|
while (cur_block) {
|
|
cur_eid = cur_block->data->eid_refs;
|
|
while (cur_eid) {
|
|
result->eid_references[i++] = calculate_eid_info(
|
|
eid_to_string2(cur_eid->eid, true), &result->dict_length_bytes
|
|
);
|
|
cur_eid = cur_eid->next;
|
|
}
|
|
cur_block = cur_block->next;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void bundle6_free_dict_descriptor(struct bundle6_dict_descriptor *const ddesc)
|
|
{
|
|
if (ddesc == NULL)
|
|
return;
|
|
free(ddesc->destination_eid_info.str_ptr);
|
|
free(ddesc->source_eid_info.str_ptr);
|
|
free(ddesc->report_to_eid_info.str_ptr);
|
|
free(ddesc->custodian_eid_info.str_ptr);
|
|
for (size_t i = 0; i < ddesc->eid_reference_count; i++)
|
|
free(ddesc->eid_references[i].str_ptr);
|
|
free(ddesc);
|
|
}
|
|
|
|
static struct bundle6_dict_descriptor *bundle6_recalculate_header_length_internal(
|
|
struct bundle *bundle)
|
|
{
|
|
struct bundle6_dict_descriptor *ddesc = bundle6_calculate_dict(bundle);
|
|
|
|
if (ddesc == NULL)
|
|
return NULL;
|
|
// bundle->primary_block_length only contains the length of the block
|
|
// after the length field (see RFC 5050, Section 4.5.1).
|
|
bundle->primary_block_length =
|
|
+ sdnv_get_size_u32(
|
|
ddesc->destination_eid_info.dict_scheme_offset
|
|
)
|
|
+ sdnv_get_size_u32(
|
|
ddesc->destination_eid_info.dict_ssp_offset
|
|
)
|
|
+ sdnv_get_size_u32(
|
|
ddesc->source_eid_info.dict_scheme_offset
|
|
)
|
|
+ sdnv_get_size_u32(
|
|
ddesc->source_eid_info.dict_ssp_offset
|
|
)
|
|
+ sdnv_get_size_u32(
|
|
ddesc->report_to_eid_info.dict_scheme_offset
|
|
)
|
|
+ sdnv_get_size_u32(
|
|
ddesc->report_to_eid_info.dict_ssp_offset
|
|
)
|
|
+ sdnv_get_size_u32(
|
|
ddesc->custodian_eid_info.dict_scheme_offset
|
|
)
|
|
+ sdnv_get_size_u32(
|
|
ddesc->custodian_eid_info.dict_ssp_offset
|
|
)
|
|
+ sdnv_get_size_u64(bundle->creation_timestamp_ms / 1000) // ms->s
|
|
+ sdnv_get_size_u64(bundle->sequence_number)
|
|
+ sdnv_get_size_u64(bundle->lifetime_ms / 1000) // ms->s
|
|
+ sdnv_get_size_u32(ddesc->dict_length_bytes)
|
|
+ ddesc->dict_length_bytes
|
|
+ (HAS_FLAG(bundle->proc_flags, BUNDLE_FLAG_IS_FRAGMENT)
|
|
? (sdnv_get_size_u64(bundle->fragment_offset)
|
|
+ sdnv_get_size_u64(bundle->total_adu_length))
|
|
: 0);
|
|
|
|
return ddesc;
|
|
}
|
|
|
|
void bundle6_recalculate_header_length(struct bundle *bundle)
|
|
{
|
|
bundle6_free_dict_descriptor(bundle6_recalculate_header_length_internal(bundle));
|
|
}
|
|
|
|
static size_t get_serialized_size(struct bundle *bundle, bool exclude_payload,
|
|
bool first_fragment, bool last_fragment)
|
|
{
|
|
struct bundle6_dict_descriptor *ddesc =
|
|
bundle6_recalculate_header_length_internal(bundle);
|
|
|
|
// If this is NULL (e.g. because an allocation failed), the primary block length is also
|
|
// not set correctly and we cannot continue.
|
|
if (ddesc == NULL)
|
|
return 0;
|
|
|
|
size_t result = bundle->primary_block_length;
|
|
const struct bundle_block_list *cur = bundle->blocks;
|
|
bool payload_reached = false;
|
|
int eid_ref_offset = 0;
|
|
|
|
// bundle->primary_block_length only contains the length of the block
|
|
// after the length field (see RFC 5050, Section 4.5.1).
|
|
result += (
|
|
1 // version
|
|
+ sdnv_get_size_u64(bundle->proc_flags)
|
|
+ sdnv_get_size_u64(bundle->primary_block_length)
|
|
);
|
|
|
|
while (cur != NULL) {
|
|
const struct bundle_block *block = cur->data;
|
|
bool is_payload = block->type == BUNDLE_BLOCK_TYPE_PAYLOAD;
|
|
bool block_wanted = (
|
|
// Either the block is part of all fragments
|
|
HAS_FLAG(block->flags,
|
|
BUNDLE_BLOCK_FLAG_MUST_BE_REPLICATED) ||
|
|
// or we are before PL _and_ want the size of preceding
|
|
(first_fragment && !payload_reached) ||
|
|
// or we are after PL _and_ want the size of following.
|
|
(last_fragment && payload_reached)
|
|
);
|
|
|
|
if (is_payload)
|
|
payload_reached = true;
|
|
|
|
size_t eid_ref_size = 0;
|
|
|
|
// Has to be done for all blocks to count them for the index
|
|
if (HAS_FLAG(block->flags,
|
|
BUNDLE_V6_BLOCK_FLAG_HAS_EID_REF_FIELD)) {
|
|
const struct eid_list *cur_eid_ref =
|
|
block->eid_refs;
|
|
size_t eid_ref_count = 0;
|
|
|
|
while (cur_eid_ref) {
|
|
const struct bundle6_eid_info eid_info =
|
|
ddesc->eid_references[eid_ref_offset];
|
|
|
|
eid_ref_size += sdnv_get_size_u16(
|
|
eid_info.dict_scheme_offset
|
|
);
|
|
eid_ref_size += sdnv_get_size_u16(
|
|
eid_info.dict_ssp_offset
|
|
);
|
|
|
|
eid_ref_offset++;
|
|
eid_ref_count++;
|
|
cur_eid_ref = cur_eid_ref->next;
|
|
}
|
|
eid_ref_size += sdnv_get_size_u16(eid_ref_count);
|
|
}
|
|
|
|
if (block_wanted) {
|
|
size_t block_size = (
|
|
1
|
|
+ sdnv_get_size_u32(block->flags)
|
|
+ sdnv_get_size_u64(block->length)
|
|
+ eid_ref_size
|
|
);
|
|
|
|
if (!(is_payload && exclude_payload))
|
|
block_size += block->length;
|
|
|
|
result += block_size;
|
|
}
|
|
|
|
cur = cur->next;
|
|
}
|
|
|
|
// If fragment, add sizes of the additional headers
|
|
if (HAS_FLAG(bundle->proc_flags, BUNDLE_FLAG_IS_FRAGMENT) &&
|
|
bundle->payload_block != NULL) {
|
|
result += sdnv_get_size_u64(bundle->total_adu_length);
|
|
result += sdnv_get_size_u64(bundle->fragment_offset);
|
|
}
|
|
|
|
bundle6_free_dict_descriptor(ddesc);
|
|
return result;
|
|
}
|
|
|
|
|
|
size_t bundle6_get_serialized_size(struct bundle *bundle)
|
|
{
|
|
return get_serialized_size(bundle, false, true, true);
|
|
}
|
|
|
|
|
|
// -------------------
|
|
// Fragmentation sizes
|
|
// -------------------
|
|
|
|
size_t bundle6_get_first_fragment_min_size(struct bundle *bundle)
|
|
{
|
|
return get_serialized_size(bundle, true, true, false);
|
|
}
|
|
|
|
|
|
size_t bundle6_get_mid_fragment_min_size(struct bundle *bundle)
|
|
{
|
|
return get_serialized_size(bundle, true, false, false);
|
|
}
|
|
|
|
|
|
size_t bundle6_get_last_fragment_min_size(struct bundle *bundle)
|
|
{
|
|
return get_serialized_size(bundle, true, false, true);
|
|
}
|