blob: 5e9591f414a62b9b9181b130c45d7689a75c231f [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "json.h"
struct token {
enum json_tokens type;
char *start;
char *end;
};
struct lexer {
void *(*state)(struct lexer *lexer);
char *start;
char *pos;
char *end;
struct token token;
};
struct json_obj {
struct lexer lexer;
};
struct json_obj_key_value {
const char *key;
size_t key_len;
struct token value;
};
static bool lexer_consume(struct lexer *lexer, struct token *token,
enum json_tokens empty_token)
{
if (lexer->token.type == empty_token) {
return false;
}
*token = lexer->token;
lexer->token.type = empty_token;
return true;
}
static bool lexer_next(struct lexer *lexer, struct token *token)
{
while (lexer->state) {
if (lexer_consume(lexer, token, JSON_TOK_NONE)) {
return true;
}
lexer->state = lexer->state(lexer);
}
return lexer_consume(lexer, token, JSON_TOK_EOF);
}
static void *lexer_json(struct lexer *lexer);
static void emit(struct lexer *lexer, enum json_tokens token)
{
lexer->token.type = token;
lexer->token.start = lexer->start;
lexer->token.end = lexer->pos;
lexer->start = lexer->pos;
}
static char next(struct lexer *lexer)
{
if (lexer->pos >= lexer->end) {
lexer->pos = lexer->end + 1;
return '\0';
}
return *lexer->pos++;
}
static void ignore(struct lexer *lexer)
{
lexer->start = lexer->pos;
}
static void backup(struct lexer *lexer)
{
lexer->pos--;
}
static char peek(struct lexer *lexer)
{
char chr = next(lexer);
backup(lexer);
return chr;
}
static void *lexer_string(struct lexer *lexer)
{
ignore(lexer);
while (true) {
char chr = next(lexer);
if (chr == '\0') {
emit(lexer, JSON_TOK_ERROR);
return NULL;
}
if (chr == '\\') {
switch (next(lexer)) {
case '"':
case '\\':
case '/':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
continue;
case 'u':
if (!isxdigit(next(lexer))) {
goto error;
}
if (!isxdigit(next(lexer))) {
goto error;
}
if (!isxdigit(next(lexer))) {
goto error;
}
if (!isxdigit(next(lexer))) {
goto error;
}
break;
default:
goto error;
}
}
if (chr == '"') {
backup(lexer);
emit(lexer, JSON_TOK_STRING);
next(lexer);
ignore(lexer);
return lexer_json;
}
}
error:
emit(lexer, JSON_TOK_ERROR);
return NULL;
}
static void *lexer_boolean(struct lexer *lexer)
{
backup(lexer);
switch (next(lexer)) {
case 't':
if (next(lexer) != 'r') {
goto error;
}
if (next(lexer) != 'u') {
goto error;
}
if (next(lexer) != 'e') {
goto error;
}
emit(lexer, JSON_TOK_TRUE);
return lexer_json;
case 'f':
if (next(lexer) != 'a') {
goto error;
}
if (next(lexer) != 'l') {
goto error;
}
if (next(lexer) != 's') {
goto error;
}
if (next(lexer) != 'e') {
goto error;
}
emit(lexer, JSON_TOK_FALSE);
return lexer_json;
}
error:
emit(lexer, JSON_TOK_ERROR);
return NULL;
}
static void *lexer_null(struct lexer *lexer)
{
if (next(lexer) != 'u') {
goto error;
}
if (next(lexer) != 'l') {
goto error;
}
if (next(lexer) != 'l') {
goto error;
}
emit(lexer, JSON_TOK_NULL);
return lexer_json;
error:
emit(lexer, JSON_TOK_ERROR);
return NULL;
}
static void *lexer_number(struct lexer *lexer)
{
while (true) {
char chr = next(lexer);
if (isdigit(chr) || chr == '.') {
continue;
}
backup(lexer);
emit(lexer, JSON_TOK_NUMBER);
return lexer_json;
}
}
static void *lexer_json(struct lexer *lexer)
{
while (true) {
char chr = next(lexer);
switch (chr) {
case '\0':
emit(lexer, JSON_TOK_EOF);
return NULL;
case '}':
case '{':
case ',':
case ':':
emit(lexer, (enum json_tokens)chr);
return lexer_json;
case '"':
return lexer_string;
case 'n':
return lexer_null;
case 't':
case 'f':
return lexer_boolean;
case '-':
if (isdigit(peek(lexer))) {
return lexer_number;
}
/* fallthrough */
default:
if (isspace(chr)) {
continue;
}
if (isdigit(chr)) {
return lexer_number;
}
emit(lexer, JSON_TOK_ERROR);
return NULL;
}
}
}
static void lexer_init(struct lexer *lexer, char *data, size_t len)
{
lexer->state = lexer_json;
lexer->start = data;
lexer->pos = data;
lexer->end = data + len;
lexer->token.type = JSON_TOK_NONE;
}
static int obj_init(struct json_obj *json, char *data, size_t len)
{
struct token token;
lexer_init(&json->lexer, data, len);
if (!lexer_next(&json->lexer, &token)) {
return -EINVAL;
}
if (token.type != JSON_TOK_OBJECT_START) {
return -EINVAL;
}
return 0;
}
static int obj_next(struct json_obj *json, struct json_obj_key_value *kv)
{
struct token token;
if (!lexer_next(&json->lexer, &token)) {
return -EINVAL;
}
/* Match end of object or next key */
switch (token.type) {
case JSON_TOK_OBJECT_END:
kv->key = NULL;
kv->key_len = 0;
kv->value = token;
return 0;
case JSON_TOK_COMMA:
if (!lexer_next(&json->lexer, &token)) {
return -EINVAL;
}
if (token.type != JSON_TOK_STRING) {
return -EINVAL;
}
/* fallthrough */
case JSON_TOK_STRING:
kv->key = token.start;
kv->key_len = (size_t)(token.end - token.start);
break;
default:
return -EINVAL;
}
/* Match : after key */
if (!lexer_next(&json->lexer, &token)) {
return -EINVAL;
}
if (token.type != JSON_TOK_COLON) {
return -EINVAL;
}
/* Match value */
if (!lexer_next(&json->lexer, &kv->value)) {
return -EINVAL;
}
switch (kv->value.type) {
case JSON_TOK_STRING:
case JSON_TOK_NUMBER:
case JSON_TOK_TRUE:
case JSON_TOK_FALSE:
case JSON_TOK_NULL:
return 0;
default:
return -EINVAL;
}
}
static int decode_num(const struct token *token, int32_t *num)
{
/* FIXME: strtod() is not available in newlib/minimal libc,
* so using strtol() here; this means no floating point
* numbers.
*/
char *endptr;
char prev_end;
prev_end = *token->end;
*token->end = '\0';
errno = 0;
*num = strtol(token->start, &endptr, 10);
*token->end = prev_end;
if (errno != 0) {
return -errno;
}
if (*endptr) {
return -EINVAL;
}
return 0;
}
static bool equivalent_types(enum json_tokens type1, enum json_tokens type2)
{
if (type1 == JSON_TOK_TRUE || type1 == JSON_TOK_FALSE) {
return type2 == JSON_TOK_TRUE || type2 == JSON_TOK_FALSE;
}
return type1 == type2;
}
int json_obj_parse(char *payload, size_t len,
const struct json_obj_descr *descr, size_t descr_len,
void *val)
{
struct json_obj obj;
struct json_obj_key_value kv;
int32_t decoded_fields = 0;
size_t i;
int ret;
assert(descr_len < (sizeof(decoded_fields) * CHAR_BIT - 1));
ret = obj_init(&obj, payload, len);
if (ret < 0) {
return ret;
}
while (!obj_next(&obj, &kv)) {
if (kv.value.type == JSON_TOK_OBJECT_END) {
if (decoded_fields == (1 << descr_len) - 1) {
return decoded_fields;
}
return -EINVAL;
}
for (i = 0; i < descr_len; i++) {
void *field = (char *)val + descr[i].offset;
/* Field has been decoded already, skip */
if (decoded_fields & (1 << i)) {
continue;
}
/* Check if it's the i-th field */
if (kv.key_len != descr[i].field_name_len) {
continue;
}
if (memcmp(kv.key, descr[i].field_name,
descr[i].field_name_len)) {
continue;
}
/* Is the value of the expected type? */
if (!equivalent_types(kv.value.type, descr[i].type)) {
return -EINVAL;
}
/* Store the decoded value */
switch (descr[i].type) {
case JSON_TOK_FALSE:
case JSON_TOK_TRUE: {
bool *value = field;
*value = descr[i].type == JSON_TOK_TRUE;
break;
}
case JSON_TOK_NUMBER: {
int32_t *num = field;
if (decode_num(&kv.value, num) < 0) {
return -EINVAL;
}
break;
}
case JSON_TOK_STRING: {
char **str = field;
*kv.value.end = '\0';
*str = kv.value.start;
break;
}
default:
return -EINVAL;
}
decoded_fields |= 1<<i;
}
}
return -EINVAL;
}
static const char escapable[] = "\"\\/\b\f\n\r\t";
static int json_escape_internal(char *str, size_t *len, size_t buf_size)
{
char tmp_buf[buf_size + 1];
char *cur, *out = tmp_buf, *escape;
for (cur = str; *cur; cur++) {
escape = memchr(escapable, *cur, sizeof(escapable) - 1);
if (escape) {
*out++ = '\\';
*out++ = "\"\\/bfnrt"[escape - escapable];
} else {
*out++ = *cur;
}
}
*out = '\0';
*len = out - tmp_buf;
memcpy(str, tmp_buf, *len);
return 0;
}
size_t json_calc_escaped_len(const char *str, size_t len)
{
size_t escaped_len = len;
size_t pos;
for (pos = 0; pos < len; pos++) {
if (memchr(escapable, str[pos], sizeof(escapable) - 1)) {
escaped_len++;
}
}
return escaped_len;
}
ssize_t json_escape(char *str, size_t *len, size_t buf_size)
{
size_t escaped_len;
escaped_len = json_calc_escaped_len(str, *len);
if (escaped_len == *len) {
/* If no escape is necessary, don't bother using up temporary
* stack space to copy the string.
*/
return 0;
}
if (escaped_len >= buf_size) {
return -ENOMEM;
}
return json_escape_internal(str, len, escaped_len);
}