ud3tn/components/bundle6/reports.c
Felix Walter 973f35d132 Implement status report support in AAP 2.0
This enables AAP 2.0 clients to receive status reports with a new ADU
flag (as we already deliver BIBE bundles). Moreover, it allows clients
to set a report-to EID and sets all of the status report flags on newly
created bundles in case a report-to EID is provided.

Closes: #241

Signed-off-by: Felix Walter <felix.walter@d3tn.com>
2025-06-05 17:13:17 +02:00

374 lines
10 KiB
C

// SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
#include "bundle6/create.h"
#include "bundle6/reports.h"
#include "bundle6/sdnv.h"
#include "ud3tn/common.h"
#include "ud3tn/parser.h"
#include "ud3tn/report_manager.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
// -------------------------------
// Administrative Record Generator
// -------------------------------
static struct bundle *encapsulate_record(
const struct bundle * const bundle,
const char *source_eid, const char *dest_eid,
uint8_t *payload, const int payload_len, const uint64_t timestamp_ms)
{
// Lifetime
const uint64_t exp_time_ms = bundle_get_expiration_time_ms(bundle);
if (exp_time_ms <= timestamp_ms) {
// NOTE: payload is freed by the create function
return NULL;
}
struct bundle *result = bundle6_create_local(
payload, payload_len,
source_eid, dest_eid, NULL,
timestamp_ms, 1,
exp_time_ms - timestamp_ms, BUNDLE_FLAG_ADMINISTRATIVE_RECORD);
if (result == NULL) {
// NOTE: payload is freed by the create function
return NULL;
}
result->ret_constraints = BUNDLE_RET_CONSTRAINT_FLAG_OWN;
return result;
}
#define LENGTH_MAX_SIZE 4
#define DTN_TIME_MAX_SIZE 9
#define SEQ_NUM_MAX_SIZE 4
#define EID_LENGTH_MAX_SIZE 2
#define EID_DEFAULT_LENGTH 40
#define ADMINISTRATIVE_HEADER_SIZE 1
#define ADMINISTRATVE_RECORD_MAX_SIZE \
(ADMINISTRATIVE_HEADER_SIZE \
+ 2 + (LENGTH_MAX_SIZE * 2) + (DTN_TIME_MAX_SIZE * 2) \
+ SEQ_NUM_MAX_SIZE + EID_LENGTH_MAX_SIZE + EID_DEFAULT_LENGTH)
static struct bundle *generate_record(
const struct bundle * const bundle, const char *source_eid,
const char *dest_eid, const bool is_custody_signal,
const uint8_t prefix_1, const uint8_t prefix_2,
const uint64_t timestamp_ms)
{
uint8_t *buffer = (uint8_t *)malloc(ADMINISTRATVE_RECORD_MAX_SIZE);
uint8_t *cur = buffer;
bool fragment;
uint16_t eid_length, cur_length;
struct bundle *ret;
if (buffer == NULL)
return NULL;
/* Write record type, flags, prefixes (status flags, reason, ...) */
fragment = bundle_is_fragmented(bundle);
(*cur++) = (is_custody_signal ? 0x20 : 0x10) | (fragment ? 0x01 : 0x00);
(*cur++) = prefix_1;
if (is_custody_signal)
(*cur++) = prefix_2;
/* Write fragment info if present */
if (fragment) {
cur += sdnv_write_u64(cur, bundle->fragment_offset);
cur += sdnv_write_u64(cur, bundle->payload_block->length);
}
/* Add a "DTN time": 1) TS, 2) Nanoseconds since start of cur. second */
cur += sdnv_write_u64(cur, (timestamp_ms / 1000));
cur += sdnv_write_u32(cur, (timestamp_ms % 1000) * 1000); /* "ns" */
/* Copy bundle data */
cur += sdnv_write_u64(cur, bundle->creation_timestamp_ms / 1000);
cur += sdnv_write_u64(cur, bundle->sequence_number);
/* Bundle source EID (length + data) */
eid_length = strlen(bundle->source);
cur += sdnv_write_u32(cur, eid_length);
cur_length = cur - buffer;
buffer = realloc(buffer, cur_length + eid_length);
cur = buffer + cur_length;
memcpy(cur, bundle->source, eid_length);
cur += eid_length;
/* Build the bundle around our generated payload */
ret = encapsulate_record(
bundle,
source_eid,
dest_eid,
buffer,
cur - buffer,
timestamp_ms
);
if (ret == NULL)
free(buffer);
return ret;
}
struct bundle *bundle6_generate_status_report(
const struct bundle * const bundle,
const struct bundle_status_report *report,
const char *local_eid,
const uint64_t timestamp_s)
{
return generate_record(
bundle,
local_eid,
bundle->report_to,
true,
(uint8_t)(report->status),
(uint8_t)(report->reason),
timestamp_s
);
}
struct bundle *bundle6_generate_custody_signal(
const struct bundle * const bundle,
const struct bundle_custody_signal *signal,
const char *local_eid,
const uint64_t timestamp_ms)
{
return generate_record(
bundle,
local_eid,
bundle->current_custodian,
false,
((uint8_t)(signal->reason) & 0x7F)
| ((signal->type == BUNDLE_CS_TYPE_ACCEPTANCE)
? 0x80 : 0x00),
0,
timestamp_ms
);
}
// ---------------------------------------
// Administrative Record Parser (RFC 5050)
// ---------------------------------------
struct record_parser {
enum parser_status status;
enum record_parser_stage {
RP_EXPECT_TYPE,
RP_EXPECT_REPORT_FLAGS,
RP_EXPECT_REPORT_REASON,
RP_EXPECT_CUSTODY_STATUS,
RP_EXPECT_FRAGMENT_OFFSET,
RP_EXPECT_FRAGMENT_LENGTH,
RP_EXPECT_RECORD_DTN_SECONDS,
RP_EXPECT_RECORD_DTN_NANOSECONDS,
RP_EXPECT_BUNDLE_CREATION_TIMESTAMP,
RP_EXPECT_BUNDLE_CREATION_SEQUENCE,
RP_EXPECT_BUNDLE_SOURCE_LENGTH,
RP_EXPECT_BUNDLE_SOURCE_EID
} stage;
struct sdnv_state sdnv_state;
struct bundle_administrative_record *record;
uint16_t current_index, bytes_remaining;
};
static enum ud3tn_result record_parser_init(struct record_parser *parser)
{
parser->status = PARSER_STATUS_GOOD;
parser->stage = RP_EXPECT_TYPE;
parser->record = malloc(sizeof(struct bundle_administrative_record));
if (parser->record == NULL)
return UD3TN_FAIL;
parser->record->status_report = NULL;
parser->record->custody_signal = NULL;
parser->record->fragment_offset = 0;
parser->record->fragment_length = 0;
parser->record->event_timestamp = 0;
parser->record->event_nanoseconds = 0;
parser->record->bundle_creation_timestamp_ms = 0;
parser->record->bundle_sequence_number = 0;
parser->record->bundle_source_eid_length = 0;
parser->record->bundle_source_eid = NULL;
return UD3TN_OK;
}
static void record_parser_next(
struct record_parser *parser, enum record_parser_stage next)
{
parser->stage = next;
sdnv_reset(&parser->sdnv_state);
}
static int record_parser_wait_for_sdnv(
struct record_parser *parser, enum record_parser_stage next)
{
switch (parser->sdnv_state.status) {
case SDNV_IN_PROGRESS:
break;
case SDNV_DONE:
record_parser_next(parser, next);
return 1;
case SDNV_ERROR:
parser->status = PARSER_STATUS_ERROR;
break;
}
return 0;
}
static void record_parser_read_byte(struct record_parser *parser, uint8_t byte)
{
switch (parser->stage) {
case RP_EXPECT_TYPE:
parser->record->type = (byte >> 4) & 0x0F;
parser->record->flags = byte & 0x0F;
if (parser->record->type == BUNDLE_AR_STATUS_REPORT) {
parser->record->status_report =
(struct bundle_status_report *)malloc(
sizeof(struct bundle_status_report));
record_parser_next(
parser, RP_EXPECT_REPORT_FLAGS);
} else if (parser->record->type
== BUNDLE_AR_CUSTODY_SIGNAL
) {
parser->record->custody_signal =
(struct bundle_custody_signal *)malloc(
sizeof(struct bundle_custody_signal));
record_parser_next(
parser, RP_EXPECT_CUSTODY_STATUS);
} else {
/* Can't parse other types */
parser->status = PARSER_STATUS_ERROR;
}
break;
case RP_EXPECT_REPORT_FLAGS:
parser->record->status_report->status = byte;
record_parser_next(parser, RP_EXPECT_REPORT_REASON);
break;
case RP_EXPECT_REPORT_REASON:
parser->record->status_report->reason = byte;
if (HAS_FLAG(parser->record->flags,
BUNDLE_AR_FLAG_FRAGMENT)
) {
record_parser_next(parser,
RP_EXPECT_FRAGMENT_OFFSET);
} else {
record_parser_next(parser,
RP_EXPECT_RECORD_DTN_SECONDS);
}
break;
case RP_EXPECT_CUSTODY_STATUS:
parser->record->custody_signal->type = (byte >> 7)
? BUNDLE_CS_TYPE_ACCEPTANCE
: BUNDLE_CS_TYPE_REFUSAL;
parser->record->custody_signal->reason = byte & 0x7F;
if (HAS_FLAG(parser->record->flags,
BUNDLE_AR_FLAG_FRAGMENT)
) {
record_parser_next(parser,
RP_EXPECT_FRAGMENT_OFFSET);
} else {
record_parser_next(parser,
RP_EXPECT_RECORD_DTN_SECONDS);
}
break;
case RP_EXPECT_FRAGMENT_OFFSET:
sdnv_read_u64(&parser->sdnv_state,
&parser->record->fragment_offset, byte);
record_parser_wait_for_sdnv(
parser, RP_EXPECT_FRAGMENT_LENGTH);
break;
case RP_EXPECT_FRAGMENT_LENGTH:
sdnv_read_u64(&parser->sdnv_state,
&parser->record->fragment_length, byte);
record_parser_wait_for_sdnv(
parser, RP_EXPECT_RECORD_DTN_SECONDS);
break;
case RP_EXPECT_RECORD_DTN_SECONDS:
sdnv_read_u64(&parser->sdnv_state,
&parser->record->event_timestamp, byte);
record_parser_wait_for_sdnv(
parser, RP_EXPECT_RECORD_DTN_NANOSECONDS);
break;
case RP_EXPECT_RECORD_DTN_NANOSECONDS:
sdnv_read_u32(&parser->sdnv_state,
&parser->record->event_nanoseconds, byte);
record_parser_wait_for_sdnv(
parser, RP_EXPECT_BUNDLE_CREATION_TIMESTAMP);
break;
case RP_EXPECT_BUNDLE_CREATION_TIMESTAMP:
sdnv_read_u64(&parser->sdnv_state,
&parser->record->bundle_creation_timestamp_ms,
byte);
// Parse the current byte and apply time conversion to ms if it
// is the last byte
if (record_parser_wait_for_sdnv(
parser, RP_EXPECT_BUNDLE_CREATION_SEQUENCE)
)
parser->record->bundle_creation_timestamp_ms *= 1000;
break;
case RP_EXPECT_BUNDLE_CREATION_SEQUENCE:
sdnv_read_u64(&parser->sdnv_state,
&parser->record->bundle_sequence_number,
byte);
record_parser_wait_for_sdnv(
parser, RP_EXPECT_BUNDLE_SOURCE_LENGTH);
break;
case RP_EXPECT_BUNDLE_SOURCE_LENGTH:
sdnv_read_u16(&parser->sdnv_state,
&parser->record->bundle_source_eid_length,
byte);
if (record_parser_wait_for_sdnv(
parser, RP_EXPECT_BUNDLE_SOURCE_EID)
) {
parser->record->bundle_source_eid =
malloc(parser->record
->bundle_source_eid_length + 1);
if (!parser->record->bundle_source_eid)
parser->status = PARSER_STATUS_ERROR;
parser->current_index = 0;
parser->bytes_remaining = parser->record
->bundle_source_eid_length;
}
break;
case RP_EXPECT_BUNDLE_SOURCE_EID:
parser->bytes_remaining--;
parser->record->bundle_source_eid
[parser->current_index++] = (char)byte;
if (parser->bytes_remaining == 0) {
parser->record->bundle_source_eid
[parser->current_index] = '\0';
parser->status = PARSER_STATUS_DONE;
}
break;
default:
parser->status = PARSER_STATUS_ERROR;
break;
}
}
struct bundle_administrative_record *bundle6_parse_administrative_record(
const uint8_t *const data, const size_t length)
{
struct record_parser parser;
uint32_t i = 0;
const uint8_t *cur_byte;
if (data == NULL || record_parser_init(&parser) != UD3TN_OK)
return NULL;
cur_byte = data;
parser.record->start_of_record_ptr = data + 1;
while (parser.status == PARSER_STATUS_GOOD && i < length) {
record_parser_read_byte(&parser, *cur_byte);
i++;
cur_byte++;
}
if (parser.status == PARSER_STATUS_DONE)
return parser.record;
free_administrative_record(parser.record);
return NULL;
}