ud3tn/components/spp/spp_timecodes.c
Felix Walter 13b2d78cec SPP: Prevent wrap-around of uint at end of loop
Not strictly necessary, but will prevent the value going below zero and
wrapping around after the loop finished.

Found by Clang's UB sanitizer.

Signed-off-by: Felix Walter <felix.walter@d3tn.com>
2023-05-10 14:32:47 +02:00

425 lines
10 KiB
C

// SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
#include "spp/spp_timecodes.h"
#include <string.h>
#define CCSDS_EPOCH_TO_DTN_EPOCH (1325376000ULL)
#define UNSEGMENTED_BASE_UNIT_LONGP_THRESHOLD (4)
#define UNSEGMENTED_FRACTIONAL_LONGP_THRESHOLD (3)
static int spp_tc_parser_start_from_config(struct spp_tc_parser_t *parser)
{
// FIXME: set the state for the correct format
parser->state.unsegmented.read_base_unit = false;
parser->state.unsegmented.base_unit_count = 0;
parser->state.unsegmented.base_unit_remaining =
parser->format.unsegmented.base_unit_octets;
parser->state.unsegmented.fractional_remaining =
parser->format.unsegmented.fractional_octets;
parser->state.unsegmented.fractional_count = 0;
parser->state.unsegmented.fractional_ptr = 0;
return SPP_TC_PARSER_GOOD;
}
static int spp_tc_pfield_parse_first(
struct spp_tc_parser_preamble_state_t *state,
struct spp_tc_config_t *dest,
const uint8_t byte)
{
const bool has_second = (byte & 0x80) != 0;
enum spp_tc_type detected_type = (byte & 0x70) >> 4;
state->has_second_octet = has_second;
state->detected_type = detected_type;
switch (state->detected_type) {
case SPP_TC_UNSEGMENTED_CCSDS_EPOCH:
case SPP_TC_UNSEGMENTED_CUSTOM_EPOCH:
{
// FIXME: handle custom epoch
dest->unsegmented.base_unit_octets = ((byte & 0x0c) >> 2) + 1;
dest->unsegmented.fractional_octets = (byte & 0x03);
break;
}
default:
return SPP_TC_PARSER_ERROR;
}
return SPP_TC_PARSER_GOOD;
}
static int spp_tc_pfield_parse_second(
struct spp_tc_parser_preamble_state_t *state,
struct spp_tc_config_t *dest,
const uint8_t byte)
{
const bool has_more = (byte & 0x80) != 0;
state->has_second_octet = has_more;
switch (state->detected_type) {
case SPP_TC_UNSEGMENTED_CCSDS_EPOCH:
case SPP_TC_UNSEGMENTED_CUSTOM_EPOCH:
{
/* yes, it is += and not a proper extension to the MSBs
*/
dest->unsegmented.base_unit_octets += ((byte & 0x60) >> 5);
dest->unsegmented.fractional_octets += ((byte & 0x1c) >> 2);
break;
}
default:
return SPP_TC_PARSER_ERROR;
}
return SPP_TC_PARSER_GOOD;
}
static int spp_tc_pfield_feed(struct spp_tc_parser_t *parser,
const uint8_t byte)
{
int result;
if (!parser->state.preamble.has_second_octet) {
// parse first octet
result = spp_tc_pfield_parse_first(
&parser->state.preamble,
&parser->format,
byte);
} else {
// parse subsequent octets
result = spp_tc_pfield_parse_second(
&parser->state.preamble,
&parser->format,
byte);
}
if (result != SPP_TC_PARSER_GOOD) {
/* forward error condition */
return result;
}
if (!parser->state.preamble.has_second_octet) {
parser->format.type = parser->state.preamble.detected_type;
spp_tc_parser_start_from_config(parser);
}
return result;
}
struct spp_tc_context_t *spp_timecode_create_none(void)
{
return NULL;
}
int spp_tc_configure_from_preamble(struct spp_tc_context_t *ctx,
const uint8_t *preamble,
const size_t preamble_len)
{
if (preamble_len < 1) {
/* preamble must be at least one byte */
return -1;
}
struct spp_tc_parser_preamble_state_t state;
int status = spp_tc_pfield_parse_first(&state, &ctx->defaults,
preamble[0]);
if (status != SPP_TC_PARSER_GOOD) {
/* fail early */
return -1;
}
for (size_t i = 1; i < preamble_len; ++i) {
if (!state.has_second_octet) {
/* no more octets -> break */
// TODO: should we report this as error?
break;
}
status = spp_tc_pfield_parse_second(&state, &ctx->defaults,
preamble[i]);
if (status != SPP_TC_PARSER_GOOD) {
/* forward failure */
return -1;
}
}
ctx->defaults.type = state.detected_type;
return 0;
}
void spp_tc_parser_init(const struct spp_tc_context_t *ctx,
struct spp_tc_parser_t *parser)
{
parser->ctx = ctx;
if (ctx->with_p_field) {
parser->format.type = SPP_TC_UNKNOWN;
parser->state.preamble.has_second_octet = false;
parser->status = SPP_TC_PARSER_GOOD;
} else {
// load config
memcpy(&parser->format, &ctx->defaults,
sizeof(struct spp_tc_config_t));
parser->status = spp_tc_parser_start_from_config(parser);
}
}
static void spp_tc_unsegmented_finalize(struct spp_tc_parser_t *parser)
{
// TODO: convert fractional_buf to actual fixed-point count value
}
static int spp_tc_unsegmented_advance(struct spp_tc_parser_t *parser)
{
if (!parser->state.unsegmented.read_base_unit) {
if (!parser->state.unsegmented.base_unit_remaining) {
/* base unit read completely, go on with fractional */
parser->state.unsegmented.read_base_unit = true;
}
}
// not using else since the above branch can change the value!
if (parser->state.unsegmented.read_base_unit) {
if (!parser->state.unsegmented.fractional_remaining) {
/* compute fractional value */
spp_tc_unsegmented_finalize(parser);
return SPP_TC_PARSER_DONE;
}
}
return SPP_TC_PARSER_GOOD;
}
static int _spp_tc_parser_feed(struct spp_tc_parser_t *parser,
const uint8_t byte)
{
switch (parser->format.type) {
case SPP_TC_UNKNOWN:
{
return spp_tc_pfield_feed(parser, byte);
}
case SPP_TC_UNSEGMENTED_CCSDS_EPOCH:
{
if (!parser->state.unsegmented.read_base_unit) {
// parsing base unit
parser->state.unsegmented.base_unit_remaining -= 1;
parser->state.unsegmented.base_unit_count <<= 8;
parser->state.unsegmented.base_unit_count |= byte;
return spp_tc_unsegmented_advance(parser);
}
// parsing fractional part
// not fully implemented yet (see _finalize)
const int index = parser->state.unsegmented.fractional_ptr++;
parser->state.unsegmented.fractional_buf[index] = byte;
parser->state.unsegmented.fractional_remaining -= 1;
return spp_tc_unsegmented_advance(parser);
}
default:
return SPP_TC_PARSER_ERROR;
}
}
int spp_tc_parser_feed(struct spp_tc_parser_t *parser,
const uint8_t byte)
{
int result = _spp_tc_parser_feed(parser, byte);
parser->status = result;
return result;
}
uint64_t spp_tc_get_dtn_timestamp(const struct spp_tc_parser_t *parser)
{
if (parser->status != SPP_TC_PARSER_DONE) {
/* not done yet -> return error value */
return UINT64_MAX;
}
switch (parser->format.type) {
case SPP_TC_UNSEGMENTED_CCSDS_EPOCH:
{
if (parser->state.unsegmented.base_unit_count <
CCSDS_EPOCH_TO_DTN_EPOCH) {
/* value outside DTN timestamp range,
* return error value
*/
return UINT64_MAX;
}
return parser->state.unsegmented.base_unit_count -
CCSDS_EPOCH_TO_DTN_EPOCH;
}
default:
return UINT64_MAX;
}
}
size_t spp_tc_get_size(const struct spp_tc_context_t *ctx)
{
size_t sz = 0;
switch (ctx->defaults.type) {
case SPP_TC_UNSEGMENTED_CCSDS_EPOCH:
case SPP_TC_UNSEGMENTED_CUSTOM_EPOCH:
{
const struct spp_tc_unsegmented_config_t *const format =
&ctx->defaults.unsegmented;
sz += format->base_unit_octets;
sz += format->fractional_octets;
if (ctx->with_p_field) {
const bool base_unit_wide =
format->base_unit_octets >
UNSEGMENTED_BASE_UNIT_LONGP_THRESHOLD;
const bool fractional_wide =
format->fractional_octets >
UNSEGMENTED_FRACTIONAL_LONGP_THRESHOLD;
const bool need_second_byte =
base_unit_wide || fractional_wide;
sz += need_second_byte ? 2 : 1;
}
break;
}
default:
return 0;
}
return sz;
}
static int spp_tc_serialize_preamble(const struct spp_tc_config_t *format,
uint8_t **out)
{
switch (format->type) {
case SPP_TC_UNSEGMENTED_CCSDS_EPOCH:
case SPP_TC_UNSEGMENTED_CUSTOM_EPOCH:
{
const bool base_unit_wide =
format->unsegmented.base_unit_octets
> UNSEGMENTED_BASE_UNIT_LONGP_THRESHOLD;
const bool fractional_wide =
format->unsegmented.fractional_octets
> UNSEGMENTED_FRACTIONAL_LONGP_THRESHOLD;
const bool need_second_byte =
base_unit_wide || fractional_wide;
uint8_t byte = format->type << 4;
if (need_second_byte) {
/* set flag for follow-up byte */
byte |= 0x80;
}
if (base_unit_wide) {
/* set maximum value */
byte |= 0x0c;
} else {
byte |= (format->unsegmented.base_unit_octets-1) << 2;
}
if (fractional_wide) {
/* set maximum value */
byte |= 0x03;
} else {
byte |= format->unsegmented.fractional_octets;
}
*(*out)++ = byte;
if (!need_second_byte) {
/* no more data to write, exit with success */
return 0;
}
byte = 0;
if (base_unit_wide) {
/* fill in the remainder of the value */
byte |= (format->unsegmented.base_unit_octets -
UNSEGMENTED_BASE_UNIT_LONGP_THRESHOLD) << 5;
}
if (fractional_wide) {
/* fill in the remainder of the value */
byte |= (format->unsegmented.fractional_octets -
UNSEGMENTED_FRACTIONAL_LONGP_THRESHOLD) << 2;
}
*(*out)++ = byte;
return 0;
}
default:
return -1;
}
}
static int serialize_unsegmented(
const struct spp_tc_unsegmented_config_t *config,
const uint64_t seconds,
const uint64_t fractional,
uint8_t **out)
{
/* we scan the `seconds` value from most-significant supported octet to
* the least-siginifcant octet. we do that by shifting it to the right
* so that the needed octet is the least-significant one (this allows
* us to mask it out and assign it directly to the uint8_t buffer).
* for this, we start with a shift which gets us the most-significant
* octet which can be serialized with the current settings and then
* decrease the shift value by 8 (one octet) on each iteration.
*/
int shift = (config->base_unit_octets - 1) * 8;
for (uint8_t i = 0; i < config->base_unit_octets; ++i) {
*(*out)++ = (seconds >> shift) & 0xff;
shift -= 8;
}
/* we do the same as we did for the `seconds`, but this time for the
* fractional part. we start at the highest octet and work down to
* less significant bits.
*/
shift = 64 - 8; /* bit width of fractional minus one octet */
for (uint8_t i = 0; i < config->fractional_octets; ++i) {
*(*out)++ = (fractional >> shift) & 0xff;
shift -= 8;
}
return 0;
}
int spp_tc_serialize(const struct spp_tc_context_t *ctx,
const uint64_t dtn_timestamp,
const uint32_t dtn_counter,
uint8_t **out)
{
if (ctx->with_p_field) {
if (spp_tc_serialize_preamble(&ctx->defaults, out) != 0) {
/* forward error condition */
return -1;
}
}
switch (ctx->defaults.type) {
case SPP_TC_UNSEGMENTED_CCSDS_EPOCH:
{
const uint64_t seconds =
dtn_timestamp + CCSDS_EPOCH_TO_DTN_EPOCH;
const uint64_t fractional =
(uint64_t)dtn_counter << 32;
return serialize_unsegmented(&ctx->defaults.unsegmented,
seconds,
fractional,
out);
}
default:
return -1;
}
return 0;
}