mirror of
https://gitlab.com/d3tn/ud3tn.git
synced 2026-08-18 13:09:11 +02:00
Previously the conversion to `enum bundle_block_type` could result in an unsigned integer overflow, triggering the assertion in rare cases. This change 1. adapts the assertion such that the check is performed using the correct data type and 2. introduces a range check for the bundle block type (only type codes 0-255 are defined). Closes: #227 Signed-off-by: Felix Walter <felix.walter@d3tn.com>
856 lines
22 KiB
C
856 lines
22 KiB
C
// SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
|
|
#include "bundle7/parser.h"
|
|
#include "bundle7/timestamp.h"
|
|
|
|
#include "ud3tn/common.h"
|
|
|
|
#include "compilersupport_p.h" // Private TinyCBOR header, used for endianness
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
/**
|
|
* Shortcut access to current bundle block
|
|
*/
|
|
#define BLOCK(state) ((*((state)->current_block_entry))->data)
|
|
|
|
|
|
/**
|
|
* Shortcut to transition into error state
|
|
*/
|
|
#define FAIL(state) ((state)->basedata->status = PARSER_STATUS_ERROR)
|
|
|
|
|
|
// -----------------------------
|
|
// Cyclic Redundancy Check (CRC)
|
|
// -----------------------------
|
|
|
|
static void crc_verify(struct bundle7_parser *state, uint32_t checksum,
|
|
uint32_t expected)
|
|
{
|
|
if (checksum != expected)
|
|
state->basedata->flags |= PARSER_FLAG_CRC_INVALID;
|
|
}
|
|
|
|
|
|
// --------------------
|
|
// Bundle start and end
|
|
// --------------------
|
|
|
|
static CborError bundle_start(struct bundle7_parser *, CborValue *);
|
|
static CborError bundle_end(struct bundle7_parser *, CborValue *);
|
|
|
|
|
|
// -------------
|
|
// Primary block
|
|
// -------------
|
|
|
|
static CborError primary_block_start(struct bundle7_parser *, CborValue *);
|
|
static CborError protocol_version(struct bundle7_parser *, CborValue *);
|
|
static CborError primary_proc_flags(struct bundle7_parser *, CborValue *);
|
|
static CborError crc_type_field(struct bundle7_parser *, CborValue *);
|
|
static CborError destination_eid(struct bundle7_parser *, CborValue *);
|
|
static CborError source_eid(struct bundle7_parser *, CborValue *);
|
|
static CborError report_to_eid(struct bundle7_parser *, CborValue *);
|
|
static CborError creation_timestamp(struct bundle7_parser *, CborValue *);
|
|
static CborError lifetime(struct bundle7_parser *, CborValue *);
|
|
static CborError fragment_offset(struct bundle7_parser *, CborValue *);
|
|
static CborError total_adu_length(struct bundle7_parser *, CborValue *);
|
|
static CborError primary_block_crc(struct bundle7_parser *, CborValue *);
|
|
|
|
|
|
// ----------------
|
|
// Extension Blocks
|
|
// ----------------
|
|
|
|
static CborError block_start(struct bundle7_parser *, CborValue *);
|
|
static CborError block_type(struct bundle7_parser *, CborValue *);
|
|
static CborError block_number(struct bundle7_parser *, CborValue *);
|
|
static CborError block_proc_flags(struct bundle7_parser *, CborValue *);
|
|
static CborError block_crc_type(struct bundle7_parser *, CborValue *);
|
|
static CborError block_crc(struct bundle7_parser *, CborValue *);
|
|
static CborError block_data(struct bundle7_parser *, CborValue *);
|
|
static CborError block_end(struct bundle7_parser *, CborValue *);
|
|
|
|
|
|
// --------------------
|
|
// Bundle start and end
|
|
// --------------------
|
|
|
|
CborError bundle_start(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
if (!cbor_value_is_array(it) || cbor_value_is_length_known(it))
|
|
return CborErrorIllegalType;
|
|
|
|
state->next = primary_block_start;
|
|
return cbor_value_enter_container(it, it);
|
|
}
|
|
|
|
|
|
CborError bundle_end(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
struct bundle *bundle;
|
|
|
|
if (*it->source.ptr != 0xff)
|
|
return CborErrorIllegalType;
|
|
it->source.ptr++;
|
|
|
|
// Transition into "Done" state
|
|
// NOTE that it is expected that a transition to "DONE" also occurs if
|
|
// the CRC is invalid. In this case, however, the "send" callback
|
|
// is not called. The caller may check for this case by examining the
|
|
// parser flags for `PARSER_FLAG_CRC_INVALID` when the `status` is
|
|
// `PARSER_STATUS_DONE`.
|
|
state->basedata->status = PARSER_STATUS_DONE;
|
|
|
|
// Clear bundle reference
|
|
bundle = state->bundle;
|
|
state->bundle = NULL;
|
|
|
|
// Call "send" callback if set and all CRCs passed, otherwise discard
|
|
// parsed bundle silently
|
|
if (state->send_callback == NULL ||
|
|
state->basedata->flags & PARSER_FLAG_CRC_INVALID)
|
|
bundle_free(bundle);
|
|
else
|
|
state->send_callback(bundle, state->send_param);
|
|
|
|
return CborNoError;
|
|
}
|
|
|
|
|
|
// -------------
|
|
// Primary Block
|
|
// -------------
|
|
|
|
CborError primary_block_start(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
if (!cbor_value_is_array(it) || !cbor_value_is_length_known(it))
|
|
return CborErrorIllegalType;
|
|
|
|
// Primary block CRC
|
|
state->flags |= BUNDLE_V7_PARSER_CRC_FEED;
|
|
|
|
crc_init(&state->crc16, CRC16_X25);
|
|
crc_init(&state->crc32, CRC32);
|
|
|
|
state->next = protocol_version;
|
|
return cbor_value_enter_container(it, it);
|
|
}
|
|
|
|
|
|
CborError protocol_version(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t version;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &version);
|
|
state->bundle->protocol_version = version;
|
|
|
|
state->next = primary_proc_flags;
|
|
return cbor_value_advance_fixed(it);
|
|
}
|
|
|
|
|
|
CborError primary_proc_flags(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t flags;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &flags);
|
|
|
|
state->bundle->proc_flags = (uint32_t)flags & BP_V7_FLAGS;
|
|
state->next = crc_type_field;
|
|
return cbor_value_advance_fixed(it);
|
|
}
|
|
|
|
|
|
CborError crc_type_field(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t type;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &type);
|
|
|
|
// Validate CRC type
|
|
if (type > BUNDLE_CRC_TYPE_32)
|
|
return CborErrorIllegalType;
|
|
|
|
state->bundle->crc_type = type;
|
|
|
|
// Deactivate CRC feeding if CRC is not enabled
|
|
if (state->bundle->crc_type == BUNDLE_CRC_TYPE_NONE)
|
|
state->flags &= ~BUNDLE_V7_PARSER_CRC_FEED;
|
|
|
|
state->next = destination_eid;
|
|
return cbor_value_advance_fixed(it);
|
|
}
|
|
|
|
|
|
CborError parse_eid(struct bundle7_parser *state, CborValue *it, char **eid,
|
|
CborError (*next)(struct bundle7_parser *, CborValue *))
|
|
{
|
|
CborError err = bundle7_eid_parse_cbor(it, eid);
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
state->next = next;
|
|
return CborNoError;
|
|
}
|
|
|
|
|
|
CborError destination_eid(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
return parse_eid(state, it, &state->bundle->destination, source_eid);
|
|
}
|
|
|
|
|
|
CborError source_eid(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
return parse_eid(state, it, &state->bundle->source, report_to_eid);
|
|
}
|
|
|
|
|
|
CborError report_to_eid(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
return parse_eid(state, it, &state->bundle->report_to,
|
|
creation_timestamp);
|
|
}
|
|
|
|
|
|
CborError creation_timestamp(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
CborError err;
|
|
|
|
err = bundle7_timestamp_parse(it,
|
|
&state->bundle->creation_timestamp_ms,
|
|
&state->bundle->sequence_number);
|
|
if (err)
|
|
return err;
|
|
|
|
state->next = lifetime;
|
|
return CborNoError;
|
|
}
|
|
|
|
|
|
CborError lifetime(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t value;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &value);
|
|
state->bundle->lifetime_ms = value;
|
|
|
|
const uint8_t *last_ptr = it->source.ptr;
|
|
CborError err = cbor_value_advance_fixed(it);
|
|
|
|
if (bundle_is_fragmented(state->bundle)) {
|
|
state->next = fragment_offset;
|
|
} else if (state->bundle->crc_type != BUNDLE_CRC_TYPE_NONE) {
|
|
state->next = primary_block_crc;
|
|
} else {
|
|
state->next = block_start;
|
|
|
|
// -1 Byte infinite array header
|
|
state->bundle->primary_block_length = state->bundle_size - 1 +
|
|
(it->source.ptr - last_ptr);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
CborError fragment_offset(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t offset;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &offset);
|
|
state->bundle->fragment_offset = offset;
|
|
|
|
state->next = total_adu_length;
|
|
return cbor_value_advance_fixed(it);
|
|
}
|
|
|
|
|
|
CborError total_adu_length(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t length;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &length);
|
|
state->bundle->total_adu_length = length;
|
|
|
|
const uint8_t *last_ptr = it->source.ptr;
|
|
CborError err = cbor_value_advance_fixed(it);
|
|
|
|
if (state->bundle->crc_type != BUNDLE_CRC_TYPE_NONE) {
|
|
state->next = primary_block_crc;
|
|
} else {
|
|
state->next = block_start;
|
|
|
|
// -1 Byte infinite array header
|
|
state->bundle->primary_block_length = state->bundle_size - 1 +
|
|
(it->source.ptr - last_ptr);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
CborError primary_block_crc(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
union crc crc;
|
|
size_t len = sizeof(crc);
|
|
CborError err;
|
|
|
|
if (!cbor_value_is_byte_string(it))
|
|
return CborErrorIllegalType;
|
|
|
|
err = cbor_value_copy_byte_string(it, crc.bytes, &len, it);
|
|
if (err)
|
|
return err;
|
|
|
|
// Deactivate CRC feeding for the CRC field itself
|
|
state->flags &= ~BUNDLE_V7_PARSER_CRC_FEED;
|
|
|
|
if (state->bundle->crc_type == BUNDLE_CRC_TYPE_16) {
|
|
// Ensure correct CRC 16 length
|
|
if (len != 2)
|
|
return CborErrorIllegalType;
|
|
|
|
// CRC field is populated with zero
|
|
state->crc16.feed(&state->crc16, 0x42); // CBOR 2-b array header
|
|
state->crc16.feed(&state->crc16, 0x00);
|
|
state->crc16.feed(&state->crc16, 0x00);
|
|
state->crc16.feed_eof(&state->crc16);
|
|
|
|
// Swap from network byte order to native order and clear all
|
|
// higher bits
|
|
state->bundle->crc.checksum = cbor_ntohs(crc.checksum & 0xffff);
|
|
|
|
crc_verify(state,
|
|
state->crc16.checksum,
|
|
state->bundle->crc.checksum);
|
|
|
|
// -1 Byte infinite array header + 3 Bytes CRC field
|
|
state->bundle->primary_block_length = state->bundle_size - 1 + 3;
|
|
} else {
|
|
// Ensure correct CRC 32 length
|
|
if (len != 4)
|
|
return CborErrorIllegalType;
|
|
|
|
// CRC field is populated with zero
|
|
state->crc32.feed(&state->crc32, 0x44); // CBOR 4-b array header
|
|
state->crc32.feed(&state->crc32, 0x00);
|
|
state->crc32.feed(&state->crc32, 0x00);
|
|
state->crc32.feed(&state->crc32, 0x00);
|
|
state->crc32.feed(&state->crc32, 0x00);
|
|
state->crc32.feed_eof(&state->crc32);
|
|
|
|
// Swap from network byte order to native order
|
|
state->bundle->crc.checksum = cbor_ntohl(crc.checksum);
|
|
|
|
crc_verify(state,
|
|
state->crc32.checksum,
|
|
state->bundle->crc.checksum);
|
|
|
|
// -1 Byte infinite array header + 5 Bytes CRC field
|
|
state->bundle->primary_block_length = state->bundle_size - 1 + 5;
|
|
}
|
|
|
|
state->next = block_start;
|
|
|
|
return CborNoError;
|
|
}
|
|
|
|
|
|
// ----------------
|
|
// Extension Blocks
|
|
// ----------------
|
|
|
|
CborError block_start(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
if (!cbor_value_is_array(it))
|
|
return CborErrorIllegalType;
|
|
|
|
// Reset CRC streams
|
|
crc_init(&state->crc16, CRC16_X25);
|
|
crc_init(&state->crc32, CRC32);
|
|
|
|
// Enable CRC feeding again
|
|
state->flags |= BUNDLE_V7_PARSER_CRC_FEED;
|
|
|
|
state->next = block_type;
|
|
return cbor_value_enter_container(it, it);
|
|
}
|
|
|
|
|
|
CborError block_type(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t type;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &type);
|
|
|
|
// Invalid block type
|
|
if (type > (uint64_t)BUNDLE_BLOCK_TYPE_MAX)
|
|
return CborErrorImproperValue;
|
|
|
|
if (*state->current_block_entry == NULL) {
|
|
// Create bundle block
|
|
struct bundle_block *block = bundle_block_create(type);
|
|
|
|
if (block == NULL)
|
|
return CborErrorOutOfMemory;
|
|
|
|
// Create bundle block list entry
|
|
*state->current_block_entry = bundle_block_entry_create(block);
|
|
|
|
if (*state->current_block_entry == NULL) {
|
|
bundle_block_free(block);
|
|
return CborErrorOutOfMemory;
|
|
}
|
|
} else {
|
|
// Was already created but the last operation failed with a
|
|
// too-short chunk (CborErrorUnexpectedEOF).
|
|
ASSERT(
|
|
(*state->current_block_entry)->data->type ==
|
|
(enum bundle_block_type)type
|
|
);
|
|
}
|
|
|
|
state->next = block_number;
|
|
return cbor_value_advance_fixed(it);
|
|
}
|
|
|
|
|
|
CborError block_number(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t number;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &number);
|
|
BLOCK(state)->number = number;
|
|
|
|
state->next = block_proc_flags;
|
|
return cbor_value_advance_fixed(it);
|
|
}
|
|
|
|
|
|
CborError block_proc_flags(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t flags;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &flags);
|
|
|
|
BLOCK(state)->flags = (((uint8_t)flags) & (
|
|
BUNDLE_BLOCK_FLAG_MUST_BE_REPLICATED |
|
|
BUNDLE_BLOCK_FLAG_DISCARD_IF_UNPROC |
|
|
BUNDLE_BLOCK_FLAG_REPORT_IF_UNPROC |
|
|
BUNDLE_BLOCK_FLAG_DELETE_BUNDLE_IF_UNPROC
|
|
));
|
|
|
|
state->next = block_crc_type;
|
|
return cbor_value_advance_fixed(it);
|
|
}
|
|
|
|
|
|
CborError block_crc_type(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
uint64_t crc_type;
|
|
|
|
if (!cbor_value_is_unsigned_integer(it))
|
|
return CborErrorIllegalType;
|
|
|
|
cbor_value_get_uint64(it, &crc_type);
|
|
BLOCK(state)->crc_type = crc_type;
|
|
|
|
state->next = block_data;
|
|
|
|
// Deactivate CRC feeding
|
|
if (crc_type == BUNDLE_CRC_TYPE_NONE)
|
|
state->flags &= ~BUNDLE_V7_PARSER_CRC_FEED;
|
|
|
|
return cbor_value_advance_fixed(it);
|
|
}
|
|
|
|
|
|
CborError block_crc(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
union crc crc;
|
|
size_t len = sizeof(crc);
|
|
CborError err;
|
|
|
|
if (!cbor_value_is_byte_string(it))
|
|
return CborErrorIllegalType;
|
|
|
|
// Deactivate CRC feeding for the CRC field itself
|
|
state->flags &= ~BUNDLE_V7_PARSER_CRC_FEED;
|
|
|
|
err = cbor_value_copy_byte_string(it, crc.bytes, &len, it);
|
|
if (err)
|
|
return err;
|
|
|
|
// Feed CRC with raw CBOR block data
|
|
if (BLOCK(state)->crc_type == BUNDLE_CRC_TYPE_16) {
|
|
// Ensure correct CRC 16 checksum length
|
|
if (len != 2)
|
|
return CborErrorIllegalType;
|
|
|
|
crc_feed_bytes(&state->crc16,
|
|
BLOCK(state)->data,
|
|
BLOCK(state)->length);
|
|
|
|
// CRC field is populated with zero
|
|
state->crc16.feed(&state->crc16, 0x42); // CBOR byte string(2)
|
|
state->crc16.feed(&state->crc16, 0x00);
|
|
state->crc16.feed(&state->crc16, 0x00);
|
|
state->crc16.feed_eof(&state->crc16);
|
|
|
|
// Swap from network byte order to native order and clear all
|
|
// higher bits
|
|
BLOCK(state)->crc.checksum = cbor_ntohs(crc.checksum & 0xffff);
|
|
|
|
crc_verify(state,
|
|
state->crc16.checksum,
|
|
BLOCK(state)->crc.checksum);
|
|
} else {
|
|
// Ensure correct CRC 32 checksum length
|
|
if (len != 4)
|
|
return CborErrorIllegalType;
|
|
|
|
crc_feed_bytes(&state->crc32,
|
|
BLOCK(state)->data,
|
|
BLOCK(state)->length);
|
|
|
|
// CRC field is populated with zero
|
|
state->crc32.feed(&state->crc32, 0x44); // CBOR byte string(4)
|
|
state->crc32.feed(&state->crc32, 0x00);
|
|
state->crc32.feed(&state->crc32, 0x00);
|
|
state->crc32.feed(&state->crc32, 0x00);
|
|
state->crc32.feed(&state->crc32, 0x00);
|
|
state->crc32.feed_eof(&state->crc32);
|
|
|
|
// Swap from network byte order to native order
|
|
BLOCK(state)->crc.checksum = cbor_ntohl(crc.checksum);
|
|
|
|
crc_verify(state,
|
|
state->crc32.checksum,
|
|
BLOCK(state)->crc.checksum);
|
|
}
|
|
block_end(state, it);
|
|
|
|
return CborNoError;
|
|
}
|
|
|
|
|
|
CborError block_data(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
size_t length;
|
|
CborError err;
|
|
|
|
if (!cbor_value_is_byte_string(it))
|
|
return CborErrorIllegalType;
|
|
|
|
err = cbor_value_get_string_length(it, &length);
|
|
// CBOR length may be unknown (which is not allowed by the BPbis spec)
|
|
if (err)
|
|
return err;
|
|
|
|
// Activate CRC feeding again
|
|
state->flags |= BUNDLE_V7_PARSER_CRC_FEED;
|
|
|
|
BLOCK(state)->length = length;
|
|
|
|
// Block-specific data
|
|
// -------------------
|
|
//
|
|
BLOCK(state)->data = malloc(length);
|
|
if (BLOCK(state)->data == NULL)
|
|
return CborErrorOutOfMemory;
|
|
|
|
// Enable "bulk read" mode
|
|
state->basedata->next_buffer = BLOCK(state)->data;
|
|
state->basedata->next_bytes = length;
|
|
state->basedata->flags |= PARSER_FLAG_BULK_READ;
|
|
state->bundle_size += length;
|
|
|
|
if (BLOCK(state)->crc_type != BUNDLE_CRC_TYPE_NONE)
|
|
state->next = block_crc;
|
|
else
|
|
state->next = block_end;
|
|
|
|
// The CBOR byte string length is represented in the same manner as a
|
|
// CBOR uint. We thus change the type to uint to allow cleanly
|
|
// advancing the iterator to just the start of the block payload.
|
|
it->type = CborIntegerType;
|
|
// NOTE: This may return an error depending on the following data
|
|
// which it tries to preparse. It is already ensured that the header
|
|
// and length can safely be read and advanced over by the result of
|
|
// cbor_value_get_string_length. As we have requested a BULK_READ,
|
|
// control will be handed over to the bulk_read handler and afterwards
|
|
// we re-initialize the parser.
|
|
cbor_value_advance_fixed(it);
|
|
return CborNoError;
|
|
}
|
|
|
|
|
|
/**
|
|
* Bulk read operation handler
|
|
*
|
|
* This function gets called with an null-pointer instead of an iterator after
|
|
* the "bulk read" operation was performed and the bulk buffer was filled.
|
|
*
|
|
* @return This function never fails and always returns CborNoError
|
|
*/
|
|
CborError block_end(struct bundle7_parser *state, CborValue *it)
|
|
{
|
|
(void)it;
|
|
|
|
// Update parsing callback directly because there is no transition for
|
|
// this callback as it gets called after the bulk read operation with
|
|
// a null pointer instead an CBOR iterator.
|
|
//
|
|
// Set the "next" callback to the same function to prevent undesirable
|
|
// transitions to an old callback of a previous stage
|
|
if (BLOCK(state)->type == BUNDLE_BLOCK_TYPE_PAYLOAD) {
|
|
state->bundle->payload_block = BLOCK(state);
|
|
state->parse = bundle_end;
|
|
state->next = bundle_end;
|
|
} else {
|
|
state->parse = block_start;
|
|
state->next = block_start;
|
|
}
|
|
|
|
// Iterate to next block entry element
|
|
state->current_block_entry = &(*state->current_block_entry)->next;
|
|
|
|
return CborNoError;
|
|
}
|
|
|
|
|
|
// -----------
|
|
// Parser Core
|
|
// -----------
|
|
|
|
struct parser *bundle7_parser_init(struct bundle7_parser *state,
|
|
void (*send_callback)(struct bundle *, void *), void *param)
|
|
{
|
|
state->basedata = malloc(sizeof(struct parser));
|
|
if (state->basedata == NULL)
|
|
return NULL;
|
|
|
|
state->bundle_quota = BUNDLE7_DEFAULT_BUNDLE_QUOTA;
|
|
state->send_callback = send_callback;
|
|
state->send_param = param;
|
|
state->bundle = NULL;
|
|
state->next = NULL; // force reset to do its job
|
|
|
|
// Set to error that the reset handler does not abort
|
|
state->basedata->status = PARSER_STATUS_ERROR;
|
|
|
|
if (bundle7_parser_reset(state) != UD3TN_OK) {
|
|
free(state->basedata);
|
|
return NULL;
|
|
}
|
|
|
|
return state->basedata;
|
|
}
|
|
|
|
|
|
enum ud3tn_result bundle7_parser_reset(struct bundle7_parser *state)
|
|
{
|
|
if (state->basedata->status == PARSER_STATUS_GOOD &&
|
|
state->next == bundle_start)
|
|
return UD3TN_OK;
|
|
|
|
state->basedata->status = PARSER_STATUS_GOOD;
|
|
state->basedata->flags = PARSER_FLAG_NONE;
|
|
|
|
state->parse = bundle_start;
|
|
state->flags = 0;
|
|
state->bundle_size = 0;
|
|
|
|
if (state->bundle != NULL)
|
|
bundle_reset(state->bundle);
|
|
else
|
|
state->bundle = bundle_init();
|
|
|
|
if (state->bundle == NULL)
|
|
return UD3TN_FAIL;
|
|
|
|
state->current_block_entry = &state->bundle->blocks;
|
|
|
|
return UD3TN_OK;
|
|
}
|
|
|
|
enum ud3tn_result bundle7_parser_deinit(struct bundle7_parser *state)
|
|
{
|
|
free(state->basedata);
|
|
if (state->bundle != NULL)
|
|
bundle_free(state->bundle);
|
|
|
|
return UD3TN_OK;
|
|
}
|
|
|
|
|
|
size_t bundle7_parser_read(struct bundle7_parser *state,
|
|
const uint8_t *buffer, size_t length)
|
|
{
|
|
CborParser parser;
|
|
CborValue it;
|
|
CborError err;
|
|
size_t parsed = 0;
|
|
bool initialize_parser = true;
|
|
|
|
// Special case:
|
|
// Bulk read operation was performed and this function gets called
|
|
// with an empty buffer to continue processing
|
|
if (buffer == NULL) {
|
|
// If no CRC is to be read, block_end needs to be called now.
|
|
// "block_end" never fails - therefore no error handling
|
|
if (state->parse == block_end) {
|
|
state->parse(state, NULL);
|
|
state->parse = state->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
while (parsed < length && state->basedata->status == PARSER_STATUS_GOOD) {
|
|
// For arbitrarily-long data such as block payload data, a
|
|
// "BULK_READ" operation is requested, normally to be handled
|
|
// by the RX task. Though, sometimes we can handle this on our
|
|
// own (if we have enough data in the current buffer). Thus, we
|
|
// first check whether, given the bytes in the buffer, we can
|
|
// perform the bulk read operation on our own.
|
|
// NOTE: At this point the CBOR parser may be in an invalid
|
|
// state because we have to interrupt it before it starts
|
|
// trying to read the bytes from the array by itself. This is
|
|
// not possible as the array might be too short and we want to
|
|
// be able to "stream" the data.
|
|
if (state->basedata->flags & PARSER_FLAG_BULK_READ) {
|
|
if (parsed + state->basedata->next_bytes > length) {
|
|
// Bulk read operation was requested but cannot
|
|
// be performed by us. -> RX task will do it.
|
|
break;
|
|
}
|
|
|
|
memcpy(
|
|
state->basedata->next_buffer,
|
|
buffer + parsed,
|
|
state->basedata->next_bytes
|
|
);
|
|
|
|
parsed += state->basedata->next_bytes;
|
|
|
|
// Disables bulk read mode again
|
|
state->basedata->flags &= ~PARSER_FLAG_BULK_READ;
|
|
|
|
// Process copied data and proceed to next block if
|
|
// there is not CRC to be read.
|
|
// This call to "block_end" is always successful.
|
|
if (state->parse == block_end) {
|
|
state->parse(state, NULL);
|
|
state->parse = state->next;
|
|
}
|
|
|
|
// Re-initialize after the "bulk read".
|
|
initialize_parser = true;
|
|
|
|
// Force checking the loop condition again.
|
|
continue;
|
|
}
|
|
|
|
if (initialize_parser) {
|
|
err = cbor_parser_init(
|
|
buffer + parsed,
|
|
length - parsed,
|
|
0, &parser, &it
|
|
);
|
|
|
|
// CBOR decoding error when preparsing first byte
|
|
if (err) {
|
|
// Insufficient data
|
|
if (err == CborErrorUnexpectedEOF)
|
|
break;
|
|
|
|
// We allow "unexpected" breaks
|
|
//
|
|
// The bundle is an indefinite array and its end
|
|
// is marked by the "break" symbol.
|
|
// Therefore, the buffer could contain a single
|
|
// "break". This happens because we cut the
|
|
// indefinite array by the chunked reading
|
|
// operation.
|
|
if (err != CborErrorUnexpectedBreak) {
|
|
FAIL(state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
initialize_parser = false;
|
|
}
|
|
|
|
// Parsing step
|
|
err = state->parse(state, &it);
|
|
|
|
// Parsing error
|
|
if (err) {
|
|
if (err != CborErrorUnexpectedEOF)
|
|
FAIL(state);
|
|
break;
|
|
}
|
|
|
|
// Transition to next stage
|
|
size_t new_parsed = (cbor_value_get_next_byte(&it) - buffer);
|
|
|
|
state->bundle_size += new_parsed - parsed;
|
|
|
|
// Bundle is larger than allowed
|
|
if (state->bundle_size > state->bundle_quota) {
|
|
FAIL(state);
|
|
break;
|
|
}
|
|
|
|
if (state->flags & BUNDLE_V7_PARSER_CRC_FEED) {
|
|
crc_feed_bytes(&state->crc16,
|
|
buffer + parsed, new_parsed - parsed);
|
|
crc_feed_bytes(&state->crc32,
|
|
buffer + parsed, new_parsed - parsed);
|
|
}
|
|
|
|
state->parse = state->next;
|
|
parsed = new_parsed;
|
|
|
|
// If we have reached the end of the current item/container,
|
|
// we re-initialize the parser as we might want to read more
|
|
// than that. The parser is not fully aware of the bundle
|
|
// CBOR structure because we interrupt the process due to
|
|
// the chunked and bulk read mechanics.
|
|
if (cbor_value_at_end(&it))
|
|
initialize_parser = true;
|
|
}
|
|
|
|
return parsed;
|
|
}
|