ud3tn/components/bundle7/serializer.c
Felix Walter e8a319a277 TX: Allow the bundle serializers to indicate failure if CLA fails
If the write method of the CLA fails, the serailizers will now return
early and this failure is handled properly by the TX task.

In the case of the BPv6 serializer, we add the necessary flow control to
the `write_bytes` macro, to prevent needing a conditional for every
write* or serialize* statement.

Signed-off-by: Felix Walter <felix.walter@d3tn.com>
2024-06-21 12:38:15 +02:00

288 lines
7.1 KiB
C

// SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
#include "bundle7/eid.h"
#include "bundle7/serializer.h"
#include "cbor.h"
#include "compilersupport_p.h" // Private TinyCBOR header, used for endianness
#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* CBOR output buffer size. This buffer will be allocated during the
* serialization process and released afterwards
*/
#define BUFFER_SIZE 128
static inline size_t primary_block_get_item_count(struct bundle *bundle)
{
size_t length = 8;
if (bundle->crc_type != BUNDLE_CRC_TYPE_NONE)
length++;
if (bundle_is_fragmented(bundle))
length += 2;
return length;
}
static inline size_t block_get_item_count(const struct bundle_block *block)
{
size_t length = 5;
if (block->crc_type != BUNDLE_CRC_TYPE_NONE)
length++;
return length;
}
static CborError write_crc(
struct CborEncoder *encoder,
enum bundle_crc_type crc_type, struct crc_stream *crc)
{
// CRC-32
if (crc_type == BUNDLE_CRC_TYPE_32) {
// Feed the "zero" CRC checksum
crc->feed(crc, 0x44);
crc->feed(crc, 0x00);
crc->feed(crc, 0x00);
crc->feed(crc, 0x00);
crc->feed(crc, 0x00);
crc->feed_eof(crc);
// Swap to network byte order
// cppcheck-suppress selfAssignment
crc->checksum = cbor_htonl(crc->checksum);
return cbor_encode_byte_string(encoder, crc->bytes, 4);
// CRC-16
} else {
// Feed the "zero" CRC checksum
crc->feed(crc, 0x42);
crc->feed(crc, 0x00);
crc->feed(crc, 0x00);
crc->feed_eof(crc);
// Swap to network byte order
// cppcheck-suppress selfAssignment
crc->checksum = cbor_htons(crc->checksum);
// We skip the first two (empty) bytes of the checksum here
return cbor_encode_byte_string(encoder, crc->bytes, 2);
}
}
/*
* Number of bytes required for CBOR-encoded max. value:
*
* Primary Block
* -------------
*
*
* Extension Block (without payload):
* ----------------------------------
*
* Array header: 1 Byte
* Block type code (255): 2 Byte
* Block number (255): 2 Byte
* Block processing control flags: 1 Byte
* CRC (32 bit): 5 Byte
* Block data length: 5 Byte
*/
static inline void init_crc(struct crc_stream *crc,
enum bundle_crc_type type)
{
if (type == BUNDLE_CRC_TYPE_32)
crc_init(crc, CRC32);
else if (type == BUNDLE_CRC_TYPE_16)
crc_init(crc, CRC16_X25);
}
static inline void feed_crc(struct crc_stream *crc,
enum bundle_crc_type type, uint8_t *buffer, size_t length)
{
if (type != BUNDLE_CRC_TYPE_NONE)
crc_feed_bytes(crc, buffer, length);
}
static uint32_t bundle7_filter_protocol_proc_flags(const struct bundle *bundle)
{
uint32_t flags = bundle->proc_flags & BP_V7_FLAGS;
return flags;
}
enum ud3tn_result bundle7_serialize(
struct bundle *bundle,
enum ud3tn_result (*write)(void *cla_obj, const void *, const size_t),
void *cla_obj)
{
// Assert that the bundle has correct version
if (bundle->protocol_version != 7)
return UD3TN_FAIL;
uint8_t *buffer;
CborEncoder encoder;
struct crc_stream crc;
int written;
buffer = malloc(BUFFER_SIZE);
if (buffer == NULL)
return UD3TN_FAIL;
// Bundle start (CBOR indefinite array)
buffer[0] = 0x9f;
// -------------
// Primary Block
// -------------
buffer[1] = 0x80 + primary_block_get_item_count(bundle);
init_crc(&crc, bundle->crc_type);
cbor_encoder_init(&encoder, buffer + 2, BUFFER_SIZE - 2, 0);
cbor_encode_uint(&encoder, bundle->protocol_version);
cbor_encode_uint(&encoder,
bundle7_filter_protocol_proc_flags(bundle));
cbor_encode_uint(&encoder, bundle->crc_type);
if (write(cla_obj, buffer,
cbor_encoder_get_buffer_size(&encoder, buffer)) != UD3TN_OK)
goto fail;
feed_crc(&crc, bundle->crc_type, buffer + 1,
cbor_encoder_get_buffer_size(&encoder, buffer + 1));
// Destination EID
written = bundle7_eid_serialize(bundle->destination,
buffer, BUFFER_SIZE);
if (written <= 0)
goto fail;
if (write(cla_obj, buffer, written) != UD3TN_OK)
goto fail;
feed_crc(&crc, bundle->crc_type, buffer, written);
// Source EID
written = bundle7_eid_serialize(bundle->source,
buffer, BUFFER_SIZE);
if (written <= 0)
goto fail;
if (write(cla_obj, buffer, written) != UD3TN_OK)
goto fail;
feed_crc(&crc, bundle->crc_type, buffer, written);
// Report-To EID
written = bundle7_eid_serialize(bundle->report_to,
buffer, BUFFER_SIZE);
if (written <= 0)
goto fail;
if (write(cla_obj, buffer, written) != UD3TN_OK)
goto fail;
feed_crc(&crc, bundle->crc_type, buffer, written);
// Creation Timestamp
buffer[0] = 0x82;
cbor_encoder_init(&encoder, buffer + 1, BUFFER_SIZE - 1, 0);
cbor_encode_uint(&encoder, bundle->creation_timestamp_ms);
cbor_encode_uint(&encoder, bundle->sequence_number);
cbor_encode_uint(&encoder, bundle->lifetime_ms);
if (bundle_is_fragmented(bundle)) {
cbor_encode_uint(&encoder, bundle->fragment_offset);
cbor_encode_uint(&encoder, bundle->total_adu_length);
}
// CRC checksum for primary block
if (bundle->crc_type != BUNDLE_CRC_TYPE_NONE) {
feed_crc(&crc, bundle->crc_type, buffer,
cbor_encoder_get_buffer_size(&encoder, buffer));
// Calculate and encode CRC checksum for primary block.
write_crc(&encoder, bundle->crc_type, &crc);
}
if (write(cla_obj, buffer,
cbor_encoder_get_buffer_size(&encoder, buffer)) != UD3TN_OK)
goto fail;
// ----------------
// Extension Blocks
// ----------------
struct bundle_block_list *cur_block = bundle->blocks;
while (cur_block != NULL) {
const struct bundle_block *block = cur_block->data;
init_crc(&crc, block->crc_type);
// CBOR array header with embedded number of items
buffer[0] = 0x80 + block_get_item_count(block);
cbor_encoder_init(&encoder, buffer + 1, BUFFER_SIZE - 1, 0);
cbor_encode_uint(&encoder, block->type);
cbor_encode_uint(&encoder, block->number);
cbor_encode_uint(&encoder,
bundle7_convert_to_protocol_block_flags(
block));
cbor_encode_uint(&encoder, block->crc_type);
const size_t bytes_before_length = cbor_encoder_get_buffer_size(
&encoder, buffer
);
// As the byte string length is represented in the same manner
// as a uint in CBOR, we can write it like that and afterwards
// change the type code to byte string.
cbor_encode_uint(&encoder, block->length);
buffer[bytes_before_length] |= 0x40; // uint -> bytestring
if (write(cla_obj, buffer,
cbor_encoder_get_buffer_size(&encoder,
buffer)) != UD3TN_OK)
goto fail;
feed_crc(&crc, block->crc_type, buffer,
cbor_encoder_get_buffer_size(&encoder, buffer));
if (write(cla_obj, block->data, block->length) != UD3TN_OK)
goto fail;
feed_crc(&crc, block->crc_type,
block->data, block->length);
if (block->crc_type != BUNDLE_CRC_TYPE_NONE) {
// Reset CBOR encoder
cbor_encoder_init(&encoder, buffer, BUFFER_SIZE, 0);
// Calculate and CRC checksum for extension block
write_crc(&encoder, block->crc_type, &crc);
if (write(cla_obj, buffer,
cbor_encoder_get_buffer_size(&encoder, buffer)
) != UD3TN_OK)
goto fail;
}
cur_block = cur_block->next;
}
// CBOR "break"
buffer[0] = 0xff;
if (write(cla_obj, buffer, 1) != UD3TN_OK)
goto fail;
free(buffer);
return UD3TN_OK;
fail:
free(buffer);
return UD3TN_FAIL;
}