// SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0 #include "bundle7/bundle7.h" #include "bundle7/eid.h" #include "ud3tn/bundle.h" #include "ud3tn/eid.h" #include "cbor.h" #include #include #include #include #include // -------------------------------- // Endpoint Identifier (EID) Parser // -------------------------------- static CborError eid_parse_dtn(CborValue *it, char **eid); static CborError eid_parse_ipn(CborValue *it, struct ipn_eid *eid); CborError bundle7_eid_parse_cbor(CborValue *it, struct eid *eid) { CborValue recursed; CborError err; size_t length; uint64_t schema; if (!cbor_value_is_array(it) || !cbor_value_is_length_known(it)) return CborErrorIllegalType; // Array must contain 2 items if (cbor_value_get_array_length(it, &length) != CborNoError) return CborErrorIllegalType; if (length != 2) return CborErrorIllegalType; // Enter EID array err = cbor_value_enter_container(it, &recursed); if (err) return err; // EID schema (first element) if (!cbor_value_is_unsigned_integer(&recursed)) return CborErrorIllegalType; cbor_value_get_uint64(&recursed, &schema); // Second item (schema specific) err = cbor_value_advance_fixed(&recursed); if (err) return err; char *dtn_eid; struct ipn_eid ipn_eid; // Call schema specific parsing functions switch (schema) { case BUNDLE_V7_EID_SCHEMA_DTN: err = eid_parse_dtn(&recursed, &dtn_eid); break; case BUNDLE_V7_EID_SCHEMA_IPN: err = eid_parse_ipn(&recursed, &ipn_eid); break; // unknown schema default: return CborErrorIllegalType; } // SSP parsing returned error if (err) return err; assert(cbor_value_at_end(&recursed)); // Leave EID array err = cbor_value_leave_container(it, &recursed); // Validation/post-processing if (schema == BUNDLE_V7_EID_SCHEMA_DTN) { if (err) { free(dtn_eid); return err; } else if (dtn_eid != NULL && !eidstr_dtn_is_valid(dtn_eid)) { free(dtn_eid); return CborErrorImproperValue; } eid->scheme = EID_SCHEME_DTN; eid->dtn_eid = dtn_eid; return CborNoError; } if (err) return err; // NOTE: We could consider normalizing ipn:0.0 or ipn:0.x to dtn:none here. // We do not do this (for now) to ensure that the primary block stays unchanged. // This explicitly includes the case where an ipn EID has a zero node number but nonzero // service number, which is considered equal to the Null Endpoint. eid->scheme = EID_SCHEME_IPN; eid->ipn_eid = ipn_eid; return CborNoError; } CborError eid_parse_dtn(CborValue *it, char **eid) { CborError err; size_t length; uint64_t num; // Special case: // // null-EID ("dtn:none") // if (cbor_value_is_unsigned_integer(it)) { cbor_value_get_uint64(it, &num); // The only allowed value here is zero if (num != 0) return CborErrorIllegalType; // Go to end of array cbor_value_advance_fixed(it); // Allocate output buffer *eid = NULL; return CborNoError; } // The SSP of the "dtn" schema is a CBOR text string if (!cbor_value_is_text_string(it)) return CborErrorIllegalType; // Determine SSP length. We do not check if the length is known because // this is already done in cbor_value_get_string_length(). If the // length is unknown a CborErrorUnknownLength will be returned err = cbor_value_get_string_length(it, &length); if (err) return err; // Length + "dtn:" w/o '\0' termination is too big if (length + 4 > EID_MAX_LEN) return CborErrorImproperValue; // Remaining bytes are not sufficient if ((size_t)(it->parser->source.end - it->source.ptr) < length) return CborErrorUnexpectedEOF; // The number of bytes written to the buffer will be written to the // length variable length += 1; // '\0' termination // Allocate output buffer for "dtn:" prefix + SSP *eid = malloc(4 + length); if (*eid == NULL) return CborErrorOutOfMemory; // Copy prefix memcpy(*eid, "dtn:", 4); // Copy SSP + '\0' and advance iterator to next element after SSP. err = cbor_value_copy_text_string(it, *eid + 4, &length, it); if (err) { free(*eid); // Ensure that the pointer is not used afterwards. // Otherwise a parser reset might try to double-free it. *eid = NULL; return err; } return CborNoError; } static CborError eid_parse_ipn(CborValue *it, struct ipn_eid *eid) { CborValue recursed; CborError err; uint64_t nodenum, servicenum; uint64_t allocatorid_tmp; size_t array_length; // Enter array if (!cbor_value_is_array(it) || !cbor_value_is_length_known(it)) return CborErrorIllegalType; // Get array length to determine EID format if (cbor_value_get_array_length(it, &array_length) != CborNoError) return CborErrorIllegalType; err = cbor_value_enter_container(it, &recursed); if (err) return err; if (array_length == 2) { // FQNN if (!cbor_value_is_unsigned_integer(&recursed)) return CborErrorIllegalType; cbor_value_get_uint64(&recursed, &nodenum); } else if (array_length == 3) { // Allocator ID if (!cbor_value_is_unsigned_integer(&recursed)) return CborErrorIllegalType; cbor_value_get_uint64(&recursed, &allocatorid_tmp); // Advance to node number err = cbor_value_advance_fixed(&recursed); if (err) return err; // Assigned node number if (!cbor_value_is_unsigned_integer(&recursed)) return CborErrorIllegalType; cbor_value_get_uint64(&recursed, &nodenum); if (allocatorid_tmp > UINT32_MAX || nodenum > UINT32_MAX) return CborErrorIllegalType; nodenum = ipn_eid_get_fqnn(allocatorid_tmp, nodenum); } else { // Invalid array length return CborErrorIllegalType; } // Advance to service number err = cbor_value_advance_fixed(&recursed); if (err) return err; // Service number // -------------- if (!cbor_value_is_unsigned_integer(&recursed)) return CborErrorIllegalType; cbor_value_get_uint64(&recursed, &servicenum); err = cbor_value_advance_fixed(&recursed); if (err) return err; // Expect end of array if (!cbor_value_at_end(&recursed)) return CborErrorIllegalType; // Leave array err = cbor_value_leave_container(it, &recursed); if (err) return err; eid->fully_qualified_node_number = nodenum; eid->service_number = servicenum; return CborNoError; } struct eid bundle7_eid_parse(const uint8_t *buffer, size_t length) { struct eid eid; CborValue it; CborParser parser; CborError err; err = cbor_parser_init(buffer, length, 0, &parser, &it); if (err != CborNoError) return eid_get_invalid(); err = bundle7_eid_parse_cbor(&it, &eid); if (err != CborNoError) return eid_get_invalid(); return eid; } // ------------------------------------ // Endpoint Identifier (EID) Serializer // ------------------------------------ //static const uint8_t _dtn_none[3] = { // 0x82, 0x01, 0x00 //}; CborError serialize_dtn_none(CborEncoder *encoder) { CborEncoder recursed; cbor_encoder_create_array(encoder, &recursed, 2); cbor_encode_uint(&recursed, BUNDLE_V7_EID_SCHEMA_DTN); cbor_encode_uint(&recursed, 0); cbor_encoder_close_container(encoder, &recursed); return CborNoError; } CborError serialize_dtn(const char *eid, CborEncoder *encoder) { CborEncoder recursed; size_t ssp_length = strlen(eid) - 4; // EID container cbor_encoder_create_array(encoder, &recursed, 2); // Schema cbor_encode_uint(&recursed, BUNDLE_V7_EID_SCHEMA_DTN); // SSP cbor_encode_text_string(&recursed, eid + 4, ssp_length); // EID container cbor_encoder_close_container(encoder, &recursed); return CborNoError; } CborError serialize_ipn(const struct ipn_eid eid, CborEncoder *encoder) { CborEncoder inner, ssp; // EID container cbor_encoder_create_array(encoder, &inner, 2); // Schema cbor_encode_uint(&inner, BUNDLE_V7_EID_SCHEMA_IPN); // SSP if (ipn_eid_allocator(eid) == 0 || EID_IPN_FORCE_2_ELEMENT) { cbor_encoder_create_array(&inner, &ssp, 2); cbor_encode_uint(&ssp, eid.fully_qualified_node_number); cbor_encode_uint(&ssp, eid.service_number); cbor_encoder_close_container(&inner, &ssp); } else { cbor_encoder_create_array(&inner, &ssp, 3); cbor_encode_uint(&ssp, ipn_eid_allocator(eid)); cbor_encode_uint(&ssp, ipn_eid_node(eid)); cbor_encode_uint(&ssp, eid.service_number); cbor_encoder_close_container(&inner, &ssp); } // EID container cbor_encoder_close_container(encoder, &inner); return CborNoError; } uint8_t *bundle7_eid_serialize_alloc(const struct eid eid, size_t *length) { const size_t buffer_size = bundle7_eid_sizeof(eid); if (buffer_size == 0) return NULL; uint8_t *const buffer = malloc(buffer_size); // We could not allocate enough memory if (buffer == NULL) return NULL; int written = bundle7_eid_serialize(eid, buffer, buffer_size); if (written <= 0) { free(buffer); return NULL; } // Shorten buffer to correct length uint8_t *compress = realloc(buffer, written); // Something went south during realloc, clear buffer and abort if (compress == NULL) { // False positive in some versions of cppcheck. It is correct // to free here as `realloc` failed and does not free. // cppcheck-suppress doubleFree free(buffer); return NULL; } *length = (size_t)written; return compress; } CborError bundle7_eid_serialize_cbor(const struct eid eid, CborEncoder *encoder) { // Special case: null-EID if (eid.scheme == EID_SCHEME_DTN && eid.dtn_eid == NULL) return serialize_dtn_none(encoder); // DTN (allows empty SSPs) else if (eid.scheme == EID_SCHEME_DTN) return serialize_dtn(eid.dtn_eid, encoder); // IPN else if (eid.scheme == EID_SCHEME_IPN) return serialize_ipn(eid.ipn_eid, encoder); // Unknown schema else return CborErrorIllegalType; } int bundle7_eid_serialize(const struct eid eid, uint8_t *buffer, size_t buffer_size) { CborEncoder encoder; CborError err; cbor_encoder_init(&encoder, buffer, buffer_size, 0); err = bundle7_eid_serialize_cbor(eid, &encoder); // A non-recoverable error occurred if (err != CborNoError && err != CborErrorOutOfMemory) return -1; // Out of memory (buffer too small) if (cbor_encoder_get_extra_bytes_needed(&encoder) > 0) return 0; return cbor_encoder_get_buffer_size(&encoder, buffer); }