#include <oscore_native/platform.h>
#include <oscore/message.h>
#include <oscore_native/message.h>
enum option_behavior {
ONLY_E,
ONLY_E_IGNORE_OUTER,
PRIMARILY_U,
PRIMARILY_I,
HARDCODED,
};
static enum option_behavior get_option_behaviour(uint16_t option_number) {
switch (option_number) {
case 1: case 5: case 8: case 11: case 12: case 15: case 17: case 20: case 23: case 27: case 28: case 60: return ONLY_E;
case 4: case 14: case 258: case 252: return ONLY_E_IGNORE_OUTER;
case 3: case 7: case 39: return PRIMARILY_U;
case 6: case 9: case 35: case 21: return HARDCODED;
default:
return HARDCODED;
}
}
#define OPTNUM_MAX 0xffff
#define OPTPART_OFFSET_1BYTE 13
#define OPTPART_OFFSET_2BYTE 269
#define OPTPART_FLAG_1BYTE 13
#define OPTPART_FLAG_2BYTE 14
static uint8_t _optpart_length(uint16_t value) {
return (value >= OPTPART_OFFSET_1BYTE) + (value >= OPTPART_OFFSET_2BYTE);
}
static size_t _optparts_encode(uint8_t *buffer, uint16_t delta, uint16_t size) {
uint8_t deltalen = _optpart_length(delta);
uint8_t sizelen = _optpart_length(size);
uint8_t *startbuffer = buffer;
buffer ++;
switch (deltalen) {
case 0:
*startbuffer = delta << 4;
break;
case 1:
*startbuffer = OPTPART_FLAG_1BYTE << 4;
delta -= OPTPART_OFFSET_1BYTE;
*buffer++ = delta;
break;
case 2:
*startbuffer = OPTPART_FLAG_2BYTE << 4;
delta -= OPTPART_OFFSET_2BYTE;
*buffer++ = delta >> 8;
*buffer++ = delta;
break;
}
switch (sizelen) {
case 0:
*startbuffer |= size;
break;
case 1:
*startbuffer |= OPTPART_FLAG_1BYTE;
size -= OPTPART_OFFSET_1BYTE;
*buffer++ = size;
break;
case 2:
*startbuffer |= OPTPART_FLAG_2BYTE;
size -= OPTPART_OFFSET_2BYTE;
*buffer++ = size >> 8;
*buffer++ = size;
break;
}
return buffer - startbuffer;
}
oscore_msgerr_protected_t oscore_msg_protected_append_option_inner(
oscore_msg_protected_t *msg,
uint16_t option_number,
const uint8_t *value,
size_t value_len
)
{
if (option_number < msg->class_e.option_number) {
return OPTION_SEQUENCE;
}
if (msg->payload_offset != 0) {
return OPTION_SEQUENCE;
}
uint8_t *payload;
size_t payload_length;
oscore_msgerr_native_t err = oscore_msg_native_map_payload(msg->backend, &payload, &payload_length);
if (oscore_msgerr_native_is_error(err)) {
return NATIVE_ERROR;
}
uint16_t delta = option_number - msg->class_e.option_number;
size_t total_length = value_len + 1 + \
_optpart_length(delta) + \
_optpart_length(value_len);
if (
value_len > UINT16_MAX
||
total_length < value_len
||
total_length > payload_length - msg->class_e.cursor - 1
) {
return OPTION_SIZE;
}
size_t opthead = _optparts_encode(&payload[1 + msg->class_e.cursor], delta, value_len);
if (value_len) {
memcpy(&payload[1 + msg->class_e.cursor + opthead], value, value_len);
}
msg->class_e.cursor += opthead + value_len;
msg->class_e.option_number = option_number;
return OK;
}
OSCORE_NONNULL
oscore_msgerr_protected_t flush_autooptions_outer_until(oscore_msg_protected_t *msg, uint16_t optnum)
{
if (msg->flags & OSCORE_MSG_PROTECTED_FLAG_PENDING_OSCORE && optnum >= 9) {
msg->flags &= ~OSCORE_MSG_PROTECTED_FLAG_PENDING_OSCORE;
uint8_t optionbuffer[1 + PIV_BYTES + 1 + OSCORE_KEYIDCONTEXT_MAXLEN + OSCORE_KEYID_MAXLEN];
size_t optionlength = 0;
uint8_t n;
oscore_requestid_t *piv_source;
if (msg->request_id.is_first_use && !(msg->flags & OSCORE_MSG_PROTECTED_FLAG_REQUEST)) {
n = 0;
} else {
piv_source = msg->request_id.is_first_use ? &msg->request_id : &msg->partial_iv;
n = piv_source->used_bytes;
}
bool k = msg->flags & OSCORE_MSG_PROTECTED_FLAG_REQUEST;
bool h = oscore_context_emit_kidcontext(msg->secctx,
msg->flags & OSCORE_MSG_PROTECTED_FLAG_REQUEST);
optionbuffer[0] = n | (k << 3) | (h << 4);
optionlength = 1;
if (n != 0) {
memcpy(&optionbuffer[optionlength], &piv_source->bytes[PIV_BYTES - n], n);
optionlength += n;
}
if (h) {
const uint8_t *kidcontext;
size_t kidcontext_len;
oscore_context_get_kidcontext(msg->secctx, &kidcontext, &kidcontext_len);
optionbuffer[optionlength++] = kidcontext_len;
memcpy(&optionbuffer[optionlength], kidcontext, kidcontext_len);
optionlength += kidcontext_len;
}
if (k) {
const uint8_t *kid;
size_t kid_length;
oscore_context_get_kid(msg->secctx, OSCORE_ROLE_SENDER, &kid, &kid_length);
memcpy(&optionbuffer[optionlength], kid, kid_length);
optionlength += kid_length;
}
if (optionlength == 1 && optionbuffer[0] == 0) {
optionlength = 0;
}
oscore_msgerr_native_t err;
err = oscore_msg_native_append_option(msg->backend, 9, optionbuffer, optionlength);
if (oscore_msgerr_native_is_error(err))
return NATIVE_ERROR;
}
return OK;
}
OSCORE_NONNULL
oscore_msgerr_protected_t flush_autooptions_inner_until(oscore_msg_protected_t *msg, uint16_t optnum)
{
if (msg->flags & (OSCORE_MSG_PROTECTED_FLAG_PENDING_OBSERVE_0 | OSCORE_MSG_PROTECTED_FLAG_PENDING_OBSERVE_1)
&& optnum >= 9)
{
msg->flags &= ~(OSCORE_MSG_PROTECTED_FLAG_PENDING_OBSERVE_0 | OSCORE_MSG_PROTECTED_FLAG_PENDING_OBSERVE_1);
static uint8_t optionbuffer[] = {1};
size_t optionlength = (msg->flags & OSCORE_MSG_PROTECTED_FLAG_PENDING_OBSERVE_1) ? 1 : 0;
return oscore_msg_protected_append_option_inner(msg, 6 , optionbuffer, optionlength);
}
return OK;
}
static bool parse_option(
const uint8_t *option,
uint16_t *delta,
const uint8_t **value,
size_t *value_len
)
{
if (*option == 0xFF) {
return false; }
uint16_t d = *option >> 4u;
uint16_t l = *option & 0x0Fu;
option++;
if (d == 15 || l == 15) {
return false; }
if (d == 13) {
d += *option;
option += 1;
} else if (d == 14) {
d += (option[0] << 8u) + option[1] + 255;
option += 2;
}
if (l == 13) {
l += *option;
option += 1;
} else if (l == 14) {
l += (option[0] << 8u) + option[1] + 255;
option += 2;
}
*delta = d;
*value_len = l;
*value = option;
return true;
}
static void optiter_maybe_set_payload_length(
oscore_msg_protected_t *msg,
size_t length
)
{
if (msg->payload_offset == 0) {
msg->payload_offset = length;
} else {
assert(length == msg->payload_offset);
}
}
static void optiter_peek_inner_option(
oscore_msg_protected_t *msg,
oscore_msg_protected_optiter_t *iter
)
{
uint8_t *payload;
size_t payload_len;
oscore_msg_native_map_payload(msg->backend, &payload, &payload_len);
payload_len -= msg->tag_length;
const uint8_t *cursor_inner;
if (iter->inner_peeked_value == NULL) {
cursor_inner = payload + 1;
} else {
cursor_inner = iter->inner_peeked_value + iter->inner_peeked_value_len;
}
if (cursor_inner == payload + payload_len) {
iter->inner_peeked_value = NULL;
iter->inner_termination_reason = OK;
optiter_maybe_set_payload_length(msg, payload_len);
return;
}
uint16_t delta;
if (parse_option(
cursor_inner,
&delta,
&iter->inner_peeked_value,
&iter->inner_peeked_value_len
)) {
if (iter->inner_peeked_value_len > (iter->inner_peeked_value - payload) + payload_len) {
iter->inner_peeked_value = NULL;
iter->inner_termination_reason = INVALID_INNER_OPTION;
} else {
iter->inner_peeked_optionnumber += delta;
}
} else {
iter->inner_peeked_value = NULL;
if (*cursor_inner == 0xff) {
iter->inner_termination_reason = OK;
optiter_maybe_set_payload_length(msg, cursor_inner + 1 - payload);
} else {
iter->inner_termination_reason = INVALID_INNER_OPTION;
}
}
}
uint8_t oscore_msg_protected_get_code(oscore_msg_protected_t *msg)
{
uint8_t *payload;
size_t payload_len;
oscore_msg_native_map_payload(msg->backend, &payload, &payload_len);
assert(payload_len);
return payload[0];
}
void oscore_msg_protected_set_code(oscore_msg_protected_t *msg, uint8_t code)
{
uint8_t *payload;
size_t payload_len;
oscore_msg_native_map_payload(msg->backend, &payload, &payload_len);
assert(payload_len);
payload[0] = code;
}
oscore_msgerr_protected_t oscore_msg_protected_append_option(
oscore_msg_protected_t *msg,
uint16_t option_number,
const uint8_t *value,
size_t value_len
)
{
oscore_msgerr_protected_t flusherr;
enum option_behavior behavior = get_option_behaviour(option_number);
if (behavior == PRIMARILY_U || option_number == 6 ) {
flusherr = flush_autooptions_outer_until(msg, option_number);
if (flusherr != OK) {
return flusherr;
}
if (option_number == 6) {
if (value_len == 0 || !(msg->flags & OSCORE_MSG_PROTECTED_FLAG_REQUEST)) {
msg->flags |= OSCORE_MSG_PROTECTED_FLAG_PENDING_OBSERVE_0;
} else {
msg->flags |= OSCORE_MSG_PROTECTED_FLAG_PENDING_OBSERVE_1;
}
}
oscore_msgerr_native_t err = oscore_msg_native_append_option(
msg->backend,
option_number,
value,
value_len
);
return oscore_msgerr_native_is_error(err) ? NATIVE_ERROR : OK;
} else if (behavior == ONLY_E || behavior == ONLY_E_IGNORE_OUTER) {
flusherr = flush_autooptions_inner_until(msg, option_number);
if (flusherr != OK) {
return flusherr;
}
return oscore_msg_protected_append_option_inner(msg, option_number, value, value_len);
} else {
return NOTIMPLEMENTED_ERROR;
}
}
oscore_msgerr_protected_t oscore_msg_protected_update_option(
oscore_msg_protected_t *msg,
uint16_t option_number,
size_t option_occurrence,
const uint8_t *value,
size_t value_len
)
{
enum option_behavior behavior = get_option_behaviour(option_number);
if (behavior == PRIMARILY_U) {
oscore_msgerr_native_t err = oscore_msg_native_update_option(
msg->backend,
option_number,
option_occurrence,
value,
value_len
);
return oscore_msgerr_native_is_error(err) ? NATIVE_ERROR : OK;
} else if (behavior == ONLY_E || behavior == ONLY_E_IGNORE_OUTER) {
oscore_msg_protected_optiter_t iter = {
.inner_peeked_optionnumber = 0,
.inner_peeked_value = NULL
};
while (true) {
optiter_peek_inner_option(msg, &iter);
if (iter.inner_peeked_value == NULL) {
return INVALID_ARG_ERROR;
}
if (iter.inner_peeked_optionnumber != option_number) {
continue;
}
if (option_occurrence == 0) {
if (value_len != iter.inner_peeked_value_len) {
return INVALID_ARG_ERROR;
}
if (value_len != 0) {
memcpy((uint8_t *)iter.inner_peeked_value, value, value_len);
} else {
}
return OK;
}
option_occurrence--;
}
} else {
return NOTIMPLEMENTED_ERROR;
}
}
void oscore_msg_protected_optiter_init(
oscore_msg_protected_t *msg,
oscore_msg_protected_optiter_t *iter
)
{
iter->inner_peeked_optionnumber = 0;
iter->inner_peeked_value = NULL;
iter->backend_exhausted = false;
oscore_msg_native_optiter_init(msg->backend, &iter->backend);
optiter_peek_inner_option(msg, iter);
iter->backend_exhausted = !oscore_msg_native_optiter_next(
msg->backend,
&iter->backend,
&iter->backend_peeked_optionnumber,
&iter->backend_peeked_value,
&iter->backend_peeked_value_len);
}
static bool optiter_abort(
oscore_msg_protected_t *msg,
oscore_msg_protected_optiter_t *iter,
oscore_msgerr_protected_t reason
)
{
(void)msg;
iter->backend_exhausted = true;
iter->inner_peeked_value = NULL;
iter->inner_termination_reason = reason;
return false;
}
bool oscore_msg_protected_optiter_next(
oscore_msg_protected_t *msg,
oscore_msg_protected_optiter_t *iter,
uint16_t *option_number,
const uint8_t **value,
size_t *value_len
)
{
while (true) {
if (iter->inner_peeked_value == NULL && iter->backend_exhausted) {
return false;
}
bool next_is_inner;
if (iter->backend_exhausted) {
next_is_inner = true;
} else if (iter->inner_peeked_value == NULL) {
next_is_inner = false;
} else {
next_is_inner = iter->inner_peeked_optionnumber <
iter->backend_peeked_optionnumber;
}
if (next_is_inner) {
*option_number = iter->inner_peeked_optionnumber;
} else {
*option_number = iter->backend_peeked_optionnumber;
}
enum option_behavior class = get_option_behaviour(*option_number);
bool skip = false;
switch (class) {
case ONLY_E:
if (!next_is_inner) {
return optiter_abort(msg, iter, INVALID_OUTER_OPTION);
}
break;
case ONLY_E_IGNORE_OUTER:
if (!next_is_inner) {
skip = true;
};
break;
case PRIMARILY_U:
case PRIMARILY_I:
break;
case HARDCODED:
switch (*option_number) {
case 6: if (next_is_inner) {
} else {
skip = true;
}
break;
case 9: if (next_is_inner) {
return optiter_abort(msg, iter, INVALID_INNER_OPTION);
} else {
skip = true;
}
break;
case 35: return optiter_abort(msg, iter, INVALID_OUTER_OPTION);
default:
return optiter_abort(msg, iter, NOTIMPLEMENTED_ERROR);
}
break;
}
if (!skip) {
if (next_is_inner) {
*value = iter->inner_peeked_value;
*value_len = iter->inner_peeked_value_len;
} else {
*value = iter->backend_peeked_value;
*value_len = iter->backend_peeked_value_len;
}
}
if (next_is_inner) {
optiter_peek_inner_option(msg, iter);
} else {
iter->backend_exhausted =
!oscore_msg_native_optiter_next(msg->backend,
&iter->backend,
&iter->backend_peeked_optionnumber,
&iter->backend_peeked_value,
&iter->backend_peeked_value_len);
}
if (!skip) {
return true;
}
}
}
oscore_msgerr_protected_t oscore_msg_protected_optiter_finish(
oscore_msg_protected_t *msg,
oscore_msg_protected_optiter_t *iter
)
{
oscore_msgerr_native_t native_error;
native_error = oscore_msg_native_optiter_finish(msg->backend, &iter->backend);
if (oscore_msgerr_native_is_error(native_error)) {
return NATIVE_ERROR;
}
return iter->inner_peeked_value == NULL ? iter->inner_termination_reason : OK;
}
oscore_msgerr_protected_t oscore_msg_protected_map_payload(
oscore_msg_protected_t *msg,
uint8_t **payload,
size_t *payload_len
)
{
if (msg->flags & OSCORE_MSG_PROTECTED_FLAG_WRITABLE) {
oscore_msgerr_protected_t flusherr;
flusherr = flush_autooptions_outer_until(msg, OPTNUM_MAX);
if (flusherr != OK) {
return flusherr;
}
flusherr = flush_autooptions_inner_until(msg, OPTNUM_MAX);
if (flusherr != OK) {
return flusherr;
}
}
oscore_msgerr_native_t err = oscore_msg_native_map_payload(msg->backend, payload, payload_len);
if (oscore_msgerr_native_is_error(err)) {
return NATIVE_ERROR;
}
if (msg->flags & OSCORE_MSG_PROTECTED_FLAG_WRITABLE) {
size_t start_bytes = 1 + msg->class_e.cursor;
if (start_bytes + msg->tag_length > *payload_len) {
return MESSAGESIZE;
}
*payload_len -= start_bytes + msg->tag_length;
if (*payload_len > 0) {
(*payload)[start_bytes] = 0xff;
start_bytes += 1;
*payload_len -= 1;
}
*payload += start_bytes;
msg->payload_offset = start_bytes;
return OK;
}
if (msg->payload_offset == 0) {
oscore_msg_protected_optiter_t iter = {
.inner_peeked_optionnumber = 0,
.inner_peeked_value = NULL
};
do {
optiter_peek_inner_option(msg, &iter);
} while (iter.inner_peeked_value != NULL);
if (iter.inner_termination_reason != OK) {
return iter.inner_termination_reason;
}
}
assert(msg->payload_offset != 0);
size_t total_delta = msg->payload_offset + msg->tag_length;
assert(total_delta <= *payload_len);
*payload += msg->payload_offset;
*payload_len -= total_delta;
return OK;
}
oscore_msgerr_protected_t oscore_msg_protected_trim_payload(
oscore_msg_protected_t *msg,
size_t payload_len
)
{
if ((msg->flags & OSCORE_MSG_PROTECTED_FLAG_WRITABLE) == 0) {
return INVALID_ARG_ERROR;
}
if (payload_len == 0) {
oscore_msgerr_protected_t flusherr;
flusherr = flush_autooptions_outer_until(msg, OPTNUM_MAX);
if (flusherr != OK) {
return flusherr;
}
flusherr = flush_autooptions_inner_until(msg, OPTNUM_MAX);
if (flusherr != OK) {
return flusherr;
}
}
oscore_msgerr_native_t err = oscore_msg_native_trim_payload(msg->backend,
1 +
msg->class_e.cursor +
(payload_len > 0) +
payload_len +
msg->tag_length);
return oscore_msgerr_native_is_error(err) ? NATIVE_ERROR : OK;
}
bool oscore_msgerr_protected_is_error(oscore_msgerr_protected_t error)
{
return error != OK;
}