obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but that tree was gitignored, so a clone of this repository could not build the firmware it ships. It is now committed here as ordinary files in its own folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP bridge's signature verification (--trust) used on the bench. Nothing is fetched from or pushed to the colleague's repository; this repository and its remotes carry everything. The folder's own .gitignore keeps build output, downloaded components and private key material out, as it did there; the committed file set is identical to that repository's tracked files. The ESP32-C5 is still flashed from obu-firmware/, which only takes vanetza-idf from microbu-esp32c5/, so the two stay separate folders. FLASHING.md says how to take a newer version of the colleague's tree (copy it over the folder, rebuild, test, commit).
315 lines
7.3 KiB
C
315 lines
7.3 KiB
C
/*
|
|
* Copyright (c) 2004-2017 Lev Walkin <vlm@lionet.info>. All rights reserved.
|
|
* Redistribution and modifications are permitted subject to BSD license.
|
|
*/
|
|
#include "asn_application.h"
|
|
#include "asn_internal.h"
|
|
#include "jer_support.h" /* JER/JSON parsing support */
|
|
|
|
|
|
/*
|
|
* Decode the jer encoding of a given type.
|
|
*/
|
|
asn_dec_rval_t
|
|
jer_decode(const asn_codec_ctx_t *opt_codec_ctx,
|
|
const asn_TYPE_descriptor_t *td, void **struct_ptr,
|
|
const void *buffer, size_t size) {
|
|
asn_codec_ctx_t s_codec_ctx;
|
|
|
|
/*
|
|
* Stack checker requires that the codec context
|
|
* must be allocated on the stack.
|
|
*/
|
|
if(opt_codec_ctx) {
|
|
if(opt_codec_ctx->max_stack_size) {
|
|
s_codec_ctx = *opt_codec_ctx;
|
|
opt_codec_ctx = &s_codec_ctx;
|
|
}
|
|
} else {
|
|
/* If context is not given, be security-conscious anyway */
|
|
memset(&s_codec_ctx, 0, sizeof(s_codec_ctx));
|
|
s_codec_ctx.max_stack_size = ASN__DEFAULT_STACK_MAX;
|
|
opt_codec_ctx = &s_codec_ctx;
|
|
}
|
|
|
|
/*
|
|
* Invoke type-specific decoder.
|
|
*/
|
|
return td->op->jer_decoder(opt_codec_ctx, td, 0, struct_ptr, buffer, size);
|
|
}
|
|
|
|
|
|
|
|
struct jer__cb_arg {
|
|
pjson_chunk_type_e chunk_type;
|
|
size_t chunk_size;
|
|
const void *chunk_buf;
|
|
int callback_not_invoked;
|
|
};
|
|
|
|
static int
|
|
jer__token_cb(pjson_chunk_type_e type, const void *_chunk_data, size_t _chunk_size, void *key) {
|
|
struct jer__cb_arg *arg = (struct jer__cb_arg *)key;
|
|
arg->chunk_type = type;
|
|
arg->chunk_size = _chunk_size;
|
|
arg->chunk_buf = _chunk_data;
|
|
arg->callback_not_invoked = 0;
|
|
return -1; /* Terminate the JSON parsing */
|
|
}
|
|
|
|
/*
|
|
* Fetch the next token from the JER/JSON stream.
|
|
*/
|
|
ssize_t
|
|
jer_next_token(int *stateContext, const void *buffer, size_t size, pjer_chunk_type_e *ch_type) {
|
|
struct jer__cb_arg arg;
|
|
int new_stateContext = *stateContext;
|
|
ssize_t ret;
|
|
|
|
arg.callback_not_invoked = 1;
|
|
ret = pjson_parse(&new_stateContext, buffer, size, jer__token_cb, &arg);
|
|
if(ret < 0) return -1;
|
|
if(arg.callback_not_invoked) {
|
|
assert(ret == 0); /* No data was consumed */
|
|
*ch_type = PJER_WMORE;
|
|
return 0; /* Try again with more data */
|
|
} else {
|
|
assert(arg.chunk_size);
|
|
assert(arg.chunk_buf == buffer);
|
|
}
|
|
|
|
/*
|
|
* Translate the JSON chunk types into more convenient ones.
|
|
*/
|
|
switch(arg.chunk_type) {
|
|
case PJSON_TEXT:
|
|
*ch_type = PJER_TEXT;
|
|
break;
|
|
case PJSON_KEY:
|
|
*ch_type = PJER_WMORE;
|
|
break;
|
|
case PJSON_DLM:
|
|
*ch_type = PJER_DLM;
|
|
break;
|
|
case PJSON_KEY_END:
|
|
*ch_type = PJER_KEY;
|
|
break;
|
|
case PJSON_VALUE_END:
|
|
*ch_type = PJER_VALUE;
|
|
break;
|
|
default:
|
|
return 0; /* Want more */
|
|
}
|
|
|
|
*stateContext = new_stateContext;
|
|
return arg.chunk_size;
|
|
}
|
|
|
|
#define LCBRAC 0x7b /* '{' */
|
|
#define RCBRAC 0x7d /* '}' */
|
|
#define CQUOTE 0x22 /* '"' */
|
|
#define CCOMMA 0x2c /* ',' */
|
|
#define LSBRAC 0x5b /* '[' */
|
|
#define RSBRAC 0x5d /* ']' */
|
|
|
|
jer_check_sym_e
|
|
jer_check_sym(const void *buf_ptr, int size, const char *need_key) {
|
|
const char *buf = (const char *)buf_ptr;
|
|
const char *end;
|
|
|
|
if(!need_key) { /* expected data end */
|
|
switch(buf[size-1]) {
|
|
case LCBRAC:
|
|
return JCK_OSTART;
|
|
case RCBRAC:
|
|
return JCK_OEND;
|
|
case LSBRAC:
|
|
return JCK_ASTART;
|
|
case RSBRAC:
|
|
return JCK_AEND;
|
|
case CCOMMA:
|
|
return JCK_COMMA;
|
|
default:
|
|
return JCK_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
if(size < 2 ||
|
|
(buf[0] != CQUOTE || buf[size-1] != CQUOTE)) {
|
|
if(size >= 2)
|
|
ASN_DEBUG("Broken JSON key: \"%c...%c\"",
|
|
buf[0], buf[size - 1]);
|
|
return JCK_BROKEN;
|
|
}
|
|
|
|
buf++; /* advance past first quote */
|
|
size -= 2; /* strip quotes */
|
|
|
|
/*
|
|
* Determine the key name.
|
|
*/
|
|
for(end = buf + size; buf < end; buf++, need_key++) {
|
|
int b = *buf, n = *need_key;
|
|
if(b != n) {
|
|
if(n == 0) {
|
|
switch(b) {
|
|
case 0x09: case 0x0a: case 0x0c: case 0x0d:
|
|
case 0x20:
|
|
/* "abc def": accept whitespace */
|
|
return JCK_KEY;
|
|
}
|
|
}
|
|
return JCK_UNKNOWN;
|
|
}
|
|
if(b == 0)
|
|
return JCK_BROKEN; /* Embedded 0 in buf?! */
|
|
}
|
|
if(*need_key)
|
|
return JCK_UNKNOWN;
|
|
|
|
return JCK_KEY;
|
|
}
|
|
|
|
|
|
#undef ADVANCE
|
|
#define ADVANCE(num_bytes) do { \
|
|
size_t num = (num_bytes); \
|
|
buf_ptr = ((const char *)buf_ptr) + num; \
|
|
size -= num; \
|
|
consumed_myself += num; \
|
|
} while(0)
|
|
|
|
#undef RETURN
|
|
#define RETURN(_code) do { \
|
|
rval.code = _code; \
|
|
rval.consumed = consumed_myself; \
|
|
if(rval.code != RC_OK) \
|
|
ASN_DEBUG("Failed with %d", rval.code); \
|
|
return rval; \
|
|
} while(0)
|
|
|
|
#define JER_GOT_BODY(chunk_buf, chunk_size, size) do { \
|
|
ssize_t converted_size = body_receiver \
|
|
(struct_key, chunk_buf, chunk_size, \
|
|
(size_t)chunk_size <= size); \
|
|
if(converted_size == -1) RETURN(RC_FAIL); \
|
|
if(converted_size == 0 \
|
|
&& size == (size_t)chunk_size) \
|
|
RETURN(RC_WMORE); \
|
|
chunk_size = converted_size; \
|
|
} while(0)
|
|
#define JER_GOT_EMPTY() do { \
|
|
if(body_receiver(struct_key, 0, 0, size > 0) == -1) \
|
|
RETURN(RC_FAIL); \
|
|
} while(0)
|
|
|
|
/*
|
|
* Generalized function for decoding the primitive values.
|
|
*/
|
|
asn_dec_rval_t
|
|
jer_decode_general(const asn_codec_ctx_t *opt_codec_ctx,
|
|
asn_struct_ctx_t *ctx, /* Type decoder context */
|
|
void *struct_key,
|
|
const void *buf_ptr, size_t size,
|
|
int (*opt_unexpected_key_decoder)
|
|
(void *struct_key, const void *chunk_buf, size_t chunk_size),
|
|
ssize_t (*body_receiver)
|
|
(void *struct_key, const void *chunk_buf, size_t chunk_size,
|
|
int have_more)
|
|
) {
|
|
|
|
asn_dec_rval_t rval;
|
|
ssize_t consumed_myself = 0;
|
|
|
|
(void)opt_codec_ctx;
|
|
(void)opt_unexpected_key_decoder;
|
|
|
|
/*
|
|
* Phases of jer/JSON processing:
|
|
* Phase 0: Check that the opening key matches our expectations.
|
|
* Phase 1: Processing body and reacting on closing token.
|
|
*/
|
|
if(ctx->phase > 1) RETURN(RC_FAIL);
|
|
for(;;) {
|
|
pjer_chunk_type_e ch_type; /* jer chunk type */
|
|
ssize_t ch_size; /* Chunk size */
|
|
|
|
/*
|
|
* Get the next part of the JSON stream.
|
|
*/
|
|
ch_size = jer_next_token(&ctx->context, buf_ptr, size,
|
|
&ch_type);
|
|
if(ch_size == -1) {
|
|
RETURN(RC_FAIL);
|
|
} else {
|
|
switch(ch_type) {
|
|
case PJER_WMORE:
|
|
RETURN(RC_WMORE);
|
|
case PJER_TEXT:
|
|
ADVANCE(ch_size);
|
|
continue;
|
|
case PJER_VALUE:
|
|
JER_GOT_BODY(buf_ptr, ch_size, size);
|
|
ADVANCE(ch_size);
|
|
ADVANCE(jer_whitespace_span(buf_ptr, size)); /* skip whitespace */
|
|
ch_size = 1;
|
|
case PJER_KEY:
|
|
case PJER_DLM:
|
|
break; /* Check the rest down there */
|
|
}
|
|
}
|
|
|
|
ctx->phase = 2; /* Phase out */
|
|
RETURN(RC_OK);
|
|
|
|
break; /* Dark and mysterious things have just happened */
|
|
}
|
|
|
|
RETURN(RC_FAIL);
|
|
}
|
|
|
|
|
|
size_t
|
|
jer_whitespace_span(const void *chunk_buf, size_t chunk_size) {
|
|
const char *p = (const char *)chunk_buf;
|
|
const char *pend = (p == NULL)? NULL : p + chunk_size;
|
|
|
|
for(; p < pend; p++) {
|
|
switch(*p) {
|
|
/* X.693, #8.1.4
|
|
* HORISONTAL TAB (9)
|
|
* LINE FEED (10)
|
|
* CARRIAGE RETURN (13)
|
|
* SPACE (32)
|
|
*/
|
|
case 0x09: case 0x0a: case 0x0d: case 0x20:
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
return (p - (const char *)chunk_buf);
|
|
}
|
|
|
|
/*
|
|
* This is a vastly simplified, non-validating JSON tree skipper. [TODO]
|
|
*/
|
|
int
|
|
jer_skip_unknown(jer_check_sym_e scv, ber_tlv_len_t *depth) {
|
|
assert(*depth > 0);
|
|
switch(scv) {
|
|
case JCK_KEY:
|
|
++(*depth);
|
|
return 0;
|
|
case JCK_COMMA:
|
|
case JCK_OEND:
|
|
case JCK_UNKNOWN:
|
|
if(--(*depth) == 0)
|
|
return (scv == JCK_OEND) ? 2 : 1;
|
|
return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|