ud3tn/test/unit/test_bundle7Serializer.c
Felix Walter 6a16e94808 bundle: Switch all flags and length fields to 64bit and do not filter
- 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>
2026-03-19 18:58:07 +01:00

687 lines
17 KiB
C

// SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
/**
* Unit Test for BPbis serializer
*
* Raw CBOR data is loaded from "test_bundle7Data.c".
*/
#include "bundle7/bundle7.h"
#include "bundle7/bundle_age.h"
#include "bundle7/eid.h"
#include "bundle7/hopcount.h"
#include "bundle7/previousnode.h"
#include "bundle7/serializer.h"
#include "platform/hal_io.h"
#include "ud3tn/bundle.h"
#include "ud3tn/result.h"
#include "testud3tn_unity.h"
#include "testud3tn_eid_helpers.h"
#include <stddef.h>
#include <stdlib.h> // malloc(), free()
#include <string.h> // memcpy()
TEST_GROUP(bundle7Serializer);
extern uint8_t cbor_simple_bundle[];
extern size_t len_simple_bundle;
static size_t output_bytes;
TEST_SETUP(bundle7Serializer)
{
output_bytes = 0;
}
TEST_TEAR_DOWN(bundle7Serializer)
{
}
static enum ud3tn_result write(void *cla_obj, const void *data,
const size_t len)
{
char buf[32];
TEST_ASSERT_TRUE_MESSAGE(
output_bytes + len <= len_simple_bundle,
"CBOR too long");
snprintf(buf, sizeof(buf), "Offset was: %zu", output_bytes);
if (len)
TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(
cbor_simple_bundle + output_bytes,
data, len, buf);
output_bytes += len;
return UD3TN_OK;
}
static struct bundle *create_simple_bundle(void)
{
struct bundle *bundle = bundle_init();
TEST_ASSERT_NOT_NULL(bundle);
bundle->protocol_version = 7;
bundle->proc_flags = BUNDLE_FLAG_MUST_NOT_BE_FRAGMENTED
| BUNDLE_FLAG_REPORT_DELIVERY;
bundle->crc_type = BUNDLE_CRC_TYPE_NONE;
bundle->destination = eid_from_string("dtn://GS2/");
bundle->source = eid_from_string("ipn:243.350");
bundle->report_to = eid_from_string("dtn:none");
bundle->creation_timestamp_ms = 658489863000; // 2020-11-12T09:51:03
bundle->sequence_number = 0;
bundle->lifetime_ms = 86400;
struct bundle_block_list *prev;
struct bundle_block_list *entry;
struct bundle_block *block;
// Previous node block
block = bundle_block_create(BUNDLE_BLOCK_TYPE_PREVIOUS_NODE);
entry = bundle_block_entry_create(block);
bundle->blocks = entry;
prev = entry;
// CBOR: [1, "GS4"]
uint8_t previous_node[6] = {
0x82, 0x01, 0x63, 0x47, 0x53, 0x34
};
block->number = 2;
block->crc_type = BUNDLE_CRC_TYPE_NONE;
block->length = sizeof(previous_node);
block->data = malloc(sizeof(previous_node));
TEST_ASSERT_NOT_NULL(block->data);
memcpy(block->data, previous_node, sizeof(previous_node));
// Hop count block
block = bundle_block_create(BUNDLE_BLOCK_TYPE_HOP_COUNT);
entry = bundle_block_entry_create(block);
prev->next = entry;
prev = entry;
// CBOR: [30, 0]
uint8_t hop_count[4] = { 0x82, 0x18, 0x1e, 0x00 };
block->number = 3;
block->crc_type = BUNDLE_CRC_TYPE_NONE;
block->length = sizeof(hop_count);
block->data = malloc(sizeof(hop_count));
TEST_ASSERT_NOT_NULL(block->data);
memcpy(block->data, hop_count, sizeof(hop_count));
// Bundle age block
block = bundle_block_create(BUNDLE_BLOCK_TYPE_BUNDLE_AGE);
entry = bundle_block_entry_create(block);
prev->next = entry;
prev = entry;
// CBOR: 0
uint8_t bundle_age[1] = { 0x00 };
block->number = 4;
block->crc_type = BUNDLE_CRC_TYPE_NONE;
block->length = sizeof(bundle_age);
block->data = malloc(sizeof(bundle_age));
TEST_ASSERT_NOT_NULL(block->data);
memcpy(block->data, bundle_age, sizeof(bundle_age));
// Payload
block = bundle_block_create(BUNDLE_BLOCK_TYPE_PAYLOAD);
entry = bundle_block_entry_create(block);
prev->next = entry;
uint8_t payload[12] = {
'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
};
TEST_ASSERT_EQUAL(1, block->number);
block->crc_type = BUNDLE_CRC_TYPE_NONE;
block->length = sizeof(payload);
block->data = malloc(sizeof(payload));
TEST_ASSERT_NOT_NULL(block->data);
memcpy(block->data, payload, sizeof(payload));
bundle->payload_block = block;
return bundle;
}
TEST(bundle7Serializer, simple_bundle)
{
struct bundle *bundle = create_simple_bundle();
TEST_ASSERT_EQUAL(UD3TN_OK, bundle7_serialize(bundle, write, NULL));
TEST_ASSERT_EQUAL(len_simple_bundle, output_bytes);
bundle_free(bundle);
}
static enum ud3tn_result write_fail(void *cla_obj, const void *data,
const size_t len)
{
output_bytes++; // Here we just use it as a counter.
return UD3TN_FAIL;
}
TEST(bundle7Serializer, write_fail)
{
struct bundle *bundle = create_simple_bundle();
TEST_ASSERT_EQUAL(UD3TN_FAIL,
bundle7_serialize(bundle, write_fail, NULL));
// Assert write_fail was only called once.
TEST_ASSERT_EQUAL(1, output_bytes);
bundle_free(bundle);
}
// -----------------------
// CRC 16 AX.25 Generation
// -----------------------
//
extern uint8_t cbor_crc16_primary_block[];
extern uint8_t cbor_crc16_payload_block[];
extern size_t len_crc16_primary_block;
extern size_t len_crc16_payload_block;
static enum ud3tn_result write_crc16_primary_block(
void *cla_obj, const void *data,
const size_t len)
{
char buf[32];
TEST_ASSERT_TRUE_MESSAGE(
output_bytes + len <= len_crc16_primary_block,
"CBOR too long");
snprintf(buf, sizeof(buf), "Offset was: %zu", output_bytes);
if (len)
TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(
cbor_crc16_primary_block + output_bytes,
data, len, buf);
output_bytes += len;
return UD3TN_OK;
}
static enum ud3tn_result write_crc16_payload_block(
void *cla_obj, const void *data,
const size_t len)
{
char buf[32];
TEST_ASSERT_TRUE_MESSAGE(
output_bytes + len <= len_crc16_payload_block,
"CBOR too long");
snprintf(buf, sizeof(buf), "Offset was: %zu", output_bytes);
if (len)
TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(
cbor_crc16_payload_block + output_bytes,
data, len, buf);
output_bytes += len;
return UD3TN_OK;
}
TEST(bundle7Serializer, crc16_generation)
{
// --------------------
// CRC-16 primary block
// --------------------
struct bundle *bundle = bundle_init();
TEST_ASSERT_NOT_NULL(bundle);
bundle->protocol_version = 7;
bundle->proc_flags = BUNDLE_FLAG_NONE;
bundle->crc_type = BUNDLE_CRC_TYPE_16;
bundle->destination = eid_from_string("dtn://GS2/");
bundle->source = eid_from_string("dtn:none");
bundle->report_to = eid_from_string("dtn:none");
bundle->creation_timestamp_ms = 0;
bundle->sequence_number = 0;
bundle->lifetime_ms = 86400;
struct bundle_block *block;
// Empty Payload
block = bundle_block_create(BUNDLE_BLOCK_TYPE_PAYLOAD);
bundle->blocks = bundle_block_entry_create(block);
block->number = 0;
block->crc_type = BUNDLE_CRC_TYPE_NONE;
block->length = 0;
block->data = NULL;
bundle->payload_block = block;
TEST_ASSERT_EQUAL(UD3TN_OK,
bundle7_serialize(bundle, write_crc16_primary_block, NULL));
TEST_ASSERT_EQUAL(len_crc16_primary_block, output_bytes);
// --------------------
// CRC-16 payload block
// --------------------
// Replace payload
free(block->data);
const uint8_t payload[] = {
'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
};
block->crc_type = BUNDLE_CRC_TYPE_16;
block->length = sizeof(payload);
block->data = malloc(sizeof(payload));
TEST_ASSERT_NOT_NULL(block->data);
memcpy(block->data, payload, sizeof(payload));
// Disable CRC for primary block
bundle->crc_type = BUNDLE_CRC_TYPE_NONE;
// Reset output counter
output_bytes = 0;
TEST_ASSERT_EQUAL(UD3TN_OK,
bundle7_serialize(bundle, write_crc16_payload_block, NULL));
TEST_ASSERT_EQUAL(len_crc16_payload_block, output_bytes);
bundle_free(bundle);
}
// --------------------------
// CRC 32 Ethernet Generation
// --------------------------
//
extern uint8_t cbor_crc32_primary_block[];
extern uint8_t cbor_crc32_payload_block[];
extern size_t len_crc32_primary_block;
extern size_t len_crc32_payload_block;
static enum ud3tn_result write_crc32_primary_block(
void *cla_obj, const void *data,
const size_t len)
{
char buf[32];
TEST_ASSERT_TRUE_MESSAGE(
output_bytes + len <= len_crc32_primary_block,
"CBOR too long");
snprintf(buf, sizeof(buf), "Offset was: %zu", output_bytes);
if (len)
TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(
cbor_crc32_primary_block + output_bytes,
data, len, buf);
output_bytes += len;
return UD3TN_OK;
}
static enum ud3tn_result write_crc32_payload_block(
void *cla_obj, const void *data,
const size_t len)
{
char buf[32];
TEST_ASSERT_TRUE_MESSAGE(
output_bytes + len <= len_crc32_payload_block,
"CBOR too long");
snprintf(buf, sizeof(buf), "Offset was: %zu", output_bytes);
if (len)
TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(
cbor_crc32_payload_block + output_bytes,
data, len, buf);
output_bytes += len;
return UD3TN_OK;
}
TEST(bundle7Serializer, crc32_generation)
{
// --------------------
// CRC-32 primary block
// --------------------
struct bundle *bundle = bundle_init();
TEST_ASSERT_NOT_NULL(bundle);
bundle->protocol_version = 7;
bundle->proc_flags = BUNDLE_FLAG_NONE;
bundle->crc_type = BUNDLE_CRC_TYPE_32;
bundle->destination = eid_from_string("dtn://GS2/");
bundle->source = eid_from_string("dtn:none");
bundle->report_to = eid_from_string("dtn:none");
bundle->creation_timestamp_ms = 0;
bundle->sequence_number = 0;
bundle->lifetime_ms = 86400;
struct bundle_block *block;
// Empty Payload
block = bundle_block_create(BUNDLE_BLOCK_TYPE_PAYLOAD);
bundle->blocks = bundle_block_entry_create(block);
block->number = 0;
block->crc_type = BUNDLE_CRC_TYPE_NONE;
block->length = 0;
block->data = NULL;
bundle->payload_block = block;
TEST_ASSERT_EQUAL(UD3TN_OK,
bundle7_serialize(bundle, write_crc32_primary_block, NULL));
TEST_ASSERT_EQUAL(len_crc32_primary_block, output_bytes);
// --------------------
// CRC-32 payload block
// --------------------
// Replace payload
free(block->data);
const uint8_t payload[] = {
'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
};
block->crc_type = BUNDLE_CRC_TYPE_32;
block->length = sizeof(payload);
block->data = malloc(sizeof(payload));
TEST_ASSERT_NOT_NULL(block->data);
memcpy(block->data, payload, sizeof(payload));
// Disable CRC for primary block
bundle->crc_type = BUNDLE_CRC_TYPE_NONE;
// Reset output counter
output_bytes = 0;
TEST_ASSERT_EQUAL(UD3TN_OK,
bundle7_serialize(bundle, write_crc32_payload_block, NULL));
TEST_ASSERT_EQUAL(len_crc32_payload_block, output_bytes);
bundle_free(bundle);
}
static uint8_t cbor_dtn_text[6] = { 0x82, 0x01, 0x63, 0x47, 0x53, 0x31 };
TEST(bundle7Serializer, dtn_text)
{
size_t length;
uint8_t *buffer;
// NOTE: The EID here is in fact invalid, but the serializer should work for this anyway.
buffer = bundle7_eid_serialize_alloc(EID_DTN("dtn:GS1"), &length);
TEST_ASSERT_NOT_NULL(buffer);
TEST_ASSERT_EQUAL(sizeof(cbor_dtn_text), length);
TEST_ASSERT_EQUAL_INT8_ARRAY(cbor_dtn_text, buffer,
sizeof(cbor_dtn_text));
free(buffer);
}
static const uint8_t dtn_none[] = { 0x82, 0x01, 0x00 };
static const uint8_t ipn_none[] = { 0x82, 0x02, 0x82, 0x00, 0x00 };
TEST(bundle7Serializer, dtn_none)
{
size_t length;
uint8_t *buffer;
buffer = bundle7_eid_serialize_alloc(EID_NULL_DTN, &length);
TEST_ASSERT_NOT_NULL(buffer);
TEST_ASSERT_EQUAL(sizeof(dtn_none), length);
TEST_ASSERT_EQUAL_INT8_ARRAY(dtn_none, buffer,
sizeof(dtn_none));
free(buffer);
buffer = bundle7_eid_serialize_alloc(EID_NULL_IPN, &length);
TEST_ASSERT_NOT_NULL(buffer);
TEST_ASSERT_EQUAL(sizeof(ipn_none), length);
TEST_ASSERT_EQUAL_INT8_ARRAY(ipn_none, buffer,
sizeof(ipn_none));
free(buffer);
}
// ipn:12.123
static const uint8_t dtn_ipn[] = { 0x82, 0x02, 0x82, 0x0c, 0x18, 0x7b };
static const char *const dtn_ipn_eid = "ipn:12.123";
// max. value for ipn: node with default allocator
static const uint8_t dtn_ipn2[] = {
0x82, 0x02, 0x82,
0x1a, 0xff, 0xff, 0xff, 0xff,
0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static const char *const dtn_ipn2_eid =
"ipn:4294967295.18446744073709551615";
// ipn:1.0.1
static const uint8_t dtn_ipn3[] = {
0x82, 0x02, 0x83,
0x01, 0x00, 0x01
};
static const char *const dtn_ipn3_eid =
"ipn:1.0.1";
// max. value for ipn: EID (3-element)
static const uint8_t dtn_ipn4[] = {
0x82, 0x02, 0x83,
0x1a, 0xff, 0xff, 0xff, 0xff,
0x1a, 0xff, 0xff, 0xff, 0xff,
0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static const char *const dtn_ipn4_eid =
"ipn:18446744073709551615.18446744073709551615";
static const char *const dtn_ipn_overflow1_eid =
"ipn:18446744073709551616.18446744073709551615";
static const char *const dtn_ipn_overflow2_eid =
"ipn:18446744073709551615.18446744073709551616";
static const char *const dtn_ipn_overflow3_eid =
"ipn:4294967296.4294967295.18446744073709551615";
static const char *const dtn_ipn_overflow4_eid =
"ipn:4294967295.4294967296.18446744073709551615";
TEST(bundle7Serializer, dtn_ipn)
{
size_t length;
uint8_t *buffer;
buffer = bundle7_eid_serialize_alloc(EID_IPN_S(dtn_ipn_eid), &length);
TEST_ASSERT_NOT_NULL(buffer);
TEST_ASSERT_EQUAL(sizeof(dtn_ipn), length);
TEST_ASSERT_EQUAL_UINT8_ARRAY(dtn_ipn, buffer,
sizeof(dtn_ipn));
TEST_ASSERT_EQUAL(length, bundle7_eid_sizeof(EID_IPN_S(dtn_ipn_eid)));
free(buffer);
buffer = bundle7_eid_serialize_alloc(
EID_IPN_S(dtn_ipn2_eid),
&length
);
TEST_ASSERT_NOT_NULL(buffer);
TEST_ASSERT_EQUAL(sizeof(dtn_ipn2), length);
TEST_ASSERT_EQUAL_UINT8_ARRAY(dtn_ipn2, buffer,
sizeof(dtn_ipn2));
TEST_ASSERT_EQUAL(length, bundle7_eid_sizeof(EID_IPN_S(dtn_ipn2_eid)));
free(buffer);
buffer = bundle7_eid_serialize_alloc(
EID_IPN_S(dtn_ipn3_eid),
&length
);
TEST_ASSERT_NOT_NULL(buffer);
TEST_ASSERT_EQUAL(sizeof(dtn_ipn3), length);
TEST_ASSERT_EQUAL_UINT8_ARRAY(dtn_ipn3, buffer,
sizeof(dtn_ipn3));
TEST_ASSERT_EQUAL(length, bundle7_eid_sizeof(EID_IPN_S(dtn_ipn3_eid)));
free(buffer);
buffer = bundle7_eid_serialize_alloc(
EID_IPN_S(dtn_ipn4_eid),
&length
);
TEST_ASSERT_NOT_NULL(buffer);
TEST_ASSERT_EQUAL(sizeof(dtn_ipn4), length);
TEST_ASSERT_EQUAL_UINT8_ARRAY(dtn_ipn4, buffer,
sizeof(dtn_ipn4));
TEST_ASSERT_EQUAL(length, bundle7_eid_sizeof(EID_IPN_S(dtn_ipn4_eid)));
free(buffer);
buffer = bundle7_eid_serialize_alloc(
EID_IPN_S(dtn_ipn_overflow1_eid),
&length
);
TEST_ASSERT_EQUAL(0, bundle7_eid_sizeof(EID_IPN_S(dtn_ipn_overflow1_eid)));
TEST_ASSERT_NULL(buffer);
buffer = bundle7_eid_serialize_alloc(
EID_IPN_S(dtn_ipn_overflow2_eid),
&length
);
TEST_ASSERT_EQUAL(0, bundle7_eid_sizeof(EID_IPN_S(dtn_ipn_overflow2_eid)));
TEST_ASSERT_NULL(buffer);
buffer = bundle7_eid_serialize_alloc(
EID_IPN_S(dtn_ipn_overflow3_eid),
&length
);
TEST_ASSERT_EQUAL(0, bundle7_eid_sizeof(EID_IPN_S(dtn_ipn_overflow3_eid)));
TEST_ASSERT_NULL(buffer);
buffer = bundle7_eid_serialize_alloc(
EID_IPN_S(dtn_ipn_overflow4_eid),
&length
);
TEST_ASSERT_EQUAL(0, bundle7_eid_sizeof(EID_IPN_S(dtn_ipn_overflow4_eid)));
TEST_ASSERT_NULL(buffer);
}
// [40, 10]
static const uint8_t cbor_hop_count[] = { 0x82, 0x18, 0x28, 0x0a };
TEST(bundle7Serializer, hop_count)
{
struct bundle_hop_count hop_count = {
.limit = 40,
.count = 10
};
uint8_t *buffer = malloc(BUNDLE7_HOP_COUNT_MAX_ENCODED_SIZE);
TEST_ASSERT_NOT_NULL(buffer)
size_t written = bundle7_hop_count_serialize(
&hop_count, buffer, BUNDLE7_HOP_COUNT_MAX_ENCODED_SIZE);
TEST_ASSERT_EQUAL(sizeof(cbor_hop_count), written);
TEST_ASSERT_EQUAL_INT8_ARRAY(cbor_hop_count, buffer,
sizeof(cbor_hop_count));
free(buffer);
}
// 42000
static const uint8_t cbor_bundle_age[] = { 0x19, 0xa4, 0x10 };
TEST(bundle7Serializer, bundle_age)
{
uint64_t bundle_age = 42000;
uint8_t *buffer = malloc(BUNDLE_AGE_MAX_ENCODED_SIZE);
TEST_ASSERT_NOT_NULL(buffer);
size_t written = bundle_age_serialize(bundle_age, buffer,
BUNDLE_AGE_MAX_ENCODED_SIZE);
TEST_ASSERT_EQUAL(sizeof(cbor_bundle_age), written);
TEST_ASSERT_EQUAL_INT8_ARRAY(cbor_bundle_age, buffer,
sizeof(cbor_bundle_age));
free(buffer);
}
static const char *text_previous_node = "ipn:1.0";
static const uint8_t cbor_previous_node[] = { 0x82, 0x02, 0x82, 0x01, 0x00 };
TEST(bundle7Serializer, previous_node)
{
uint8_t *const buffer = malloc(sizeof(cbor_previous_node));
// BLock serialization
TEST_ASSERT_NOT_NULL(buffer);
const size_t written = bundle7_previous_node_serialize(
EID_IPN_S(text_previous_node),
buffer,
sizeof(cbor_previous_node)
);
TEST_ASSERT_EQUAL(sizeof(cbor_previous_node), written);
TEST_ASSERT_EQUAL_INT8_ARRAY(cbor_previous_node, buffer,
sizeof(cbor_previous_node));
free(buffer);
// Block entry creation
struct bundle_block_list *const bbl_prev_node = bundle7_previous_node_create_entry(
EID_IPN_S(text_previous_node)
);
TEST_ASSERT_NOT_NULL(bbl_prev_node);
TEST_ASSERT_NOT_NULL(bbl_prev_node->data);
TEST_ASSERT_NULL(bbl_prev_node->next);
TEST_ASSERT_EQUAL_INT8_ARRAY(cbor_previous_node, bbl_prev_node->data->data,
sizeof(cbor_previous_node));
TEST_ASSERT_EQUAL_UINT(sizeof(cbor_previous_node), bbl_prev_node->data->length);
TEST_ASSERT_EQUAL(BUNDLE_BLOCK_TYPE_PREVIOUS_NODE, bbl_prev_node->data->type);
bundle_block_entry_free(bbl_prev_node);
}
TEST_GROUP_RUNNER(bundle7Serializer)
{
RUN_TEST_CASE(bundle7Serializer, dtn_text);
RUN_TEST_CASE(bundle7Serializer, dtn_none);
RUN_TEST_CASE(bundle7Serializer, dtn_ipn);
RUN_TEST_CASE(bundle7Serializer, hop_count);
RUN_TEST_CASE(bundle7Serializer, bundle_age);
RUN_TEST_CASE(bundle7Serializer, previous_node);
RUN_TEST_CASE(bundle7Serializer, simple_bundle);
RUN_TEST_CASE(bundle7Serializer, write_fail);
RUN_TEST_CASE(bundle7Serializer, crc16_generation);
RUN_TEST_CASE(bundle7Serializer, crc32_generation);
}