json: Provide a way to convert JSON to FDT
JSON is a rather more free format than devicetree, so it is sometimes better to parse it into dtb format. This is widely used in U-Boot and we can use the ofnode interface to access it. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com>
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
#ifndef __JSON_H__
|
||||
#define __JSON_H__
|
||||
|
||||
struct abuf;
|
||||
|
||||
/**
|
||||
* json_print_pretty() - Print JSON with indentation
|
||||
*
|
||||
@@ -20,4 +22,36 @@
|
||||
*/
|
||||
void json_print_pretty(const char *json, int len);
|
||||
|
||||
/**
|
||||
* json_to_fdt() - Convert JSON to a Flattened Device Tree (DTB) blob
|
||||
*
|
||||
* Parse a JSON string and convert it to a dtb blob. JSON objects become nodes;
|
||||
* JSON properties become device tree properties. This is useful for converting
|
||||
* LUKS2 metadata to a format that can be queried using U-Boot's ofnode APIs.
|
||||
*
|
||||
* This function temporarily modifies the JSON string in-place, writing nul
|
||||
* terminators during parsing, then restores the original characters. The JSON
|
||||
* string is only modified during the function call and is restored before
|
||||
* returning.
|
||||
*
|
||||
* The resulting DTB contains copies of all data, so the JSON string can be
|
||||
* freed or modified after this function returns.
|
||||
*
|
||||
* Conversion rules:
|
||||
* - JSON objects → DT nodes
|
||||
* - JSON strings → string properties
|
||||
* - JSON numbers → u32 or u64 cell properties
|
||||
* - JSON arrays of numbers → cell array properties (max MAX_ARRAY_SIZE)
|
||||
* - JSON arrays of strings → stringlist properties (max MAX_ARRAY_SIZE)
|
||||
* - JSON booleans → u32 properties (0 or 1). This breaks the dtb convention of
|
||||
* simply using presence to indicate true, so that we can actually check
|
||||
* what was present in the JSON data
|
||||
* - JSON null → empty property
|
||||
*
|
||||
* @json: JSON string to parse (temporarily modified during call)
|
||||
* @buf: abuf to init and populate with the DTB (called must uninit)
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
int json_to_fdt(const char *json, struct abuf *buf);
|
||||
|
||||
#endif /* __JSON_H__ */
|
||||
|
||||
612
lib/json.c
612
lib/json.c
@@ -1,13 +1,67 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* JSON pretty-printer
|
||||
* JSON utilities including parser and devicetree converter
|
||||
*
|
||||
* Copyright (C) 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#include <abuf.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <log.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <malloc.h>
|
||||
|
||||
/* Maximum number of elements in a JSON array */
|
||||
#define MAX_ARRAY_SIZE 256
|
||||
|
||||
/* JSON token types */
|
||||
enum json_token_type {
|
||||
JSONT_EOF = 0,
|
||||
JSONT_LBRACE, /* { */
|
||||
JSONT_RBRACE, /* } */
|
||||
JSONT_LBRACKET, /* [ */
|
||||
JSONT_RBRACKET, /* ] */
|
||||
JSONT_COLON, /* : */
|
||||
JSONT_COMMA, /* , */
|
||||
JSONT_STRING, /* "string" */
|
||||
JSONT_NUMBER, /* 123 or 123.45 */
|
||||
JSONT_TRUE, /* true */
|
||||
JSONT_FALSE, /* false */
|
||||
JSONT_NULL, /* null */
|
||||
JSONT_ERROR
|
||||
};
|
||||
|
||||
/**
|
||||
* struct json_parser - JSON parser context
|
||||
* @json: Input JSON string
|
||||
* @pos: Current position in the input string
|
||||
* @tok: Current token type
|
||||
* @tok_start: Pointer to the start of the current token
|
||||
* @tok_end: Pointer to the end of the current token
|
||||
* @fdt: Flattened Device Tree buffer being constructed
|
||||
* @fdt_size: Size of the FDT buffer in bytes
|
||||
*/
|
||||
struct json_parser {
|
||||
const char *json;
|
||||
const char *pos;
|
||||
enum json_token_type tok;
|
||||
const char *tok_start;
|
||||
const char *tok_end;
|
||||
void *fdt;
|
||||
int fdt_size;
|
||||
};
|
||||
|
||||
/* Increment position in JSON string */
|
||||
#define INC() ctx->pos++
|
||||
|
||||
/* Set token type */
|
||||
#define SET(t) ctx->tok = (t)
|
||||
|
||||
/* Forward declarations for recursive parser functions */
|
||||
static int parse_object(struct json_parser *ctx, const char *node_name);
|
||||
static int parse_value(struct json_parser *ctx, const char *prop_name);
|
||||
|
||||
/**
|
||||
* print_indent() - Print indentation spaces
|
||||
@@ -120,3 +174,559 @@ void json_print_pretty(const char *json, int len)
|
||||
|
||||
putc('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* skip_whitespace() - Skip whitespace characters
|
||||
*
|
||||
* @ctx: Parser context
|
||||
*/
|
||||
static void skip_whitespace(struct json_parser *ctx)
|
||||
{
|
||||
while (*ctx->pos && isspace(*ctx->pos))
|
||||
INC();
|
||||
}
|
||||
|
||||
/**
|
||||
* next_token() - Get the next JSON token
|
||||
*
|
||||
* @ctx: Parser context
|
||||
* Return: token type
|
||||
*/
|
||||
static enum json_token_type next_token(struct json_parser *ctx)
|
||||
{
|
||||
skip_whitespace(ctx);
|
||||
|
||||
if (!*ctx->pos) {
|
||||
SET(JSONT_EOF);
|
||||
return JSONT_EOF;
|
||||
}
|
||||
|
||||
ctx->tok_start = ctx->pos;
|
||||
|
||||
switch (*ctx->pos) {
|
||||
case '{':
|
||||
INC();
|
||||
SET(JSONT_LBRACE);
|
||||
break;
|
||||
case '}':
|
||||
INC();
|
||||
SET(JSONT_RBRACE);
|
||||
break;
|
||||
case '[':
|
||||
INC();
|
||||
SET(JSONT_LBRACKET);
|
||||
break;
|
||||
case ']':
|
||||
INC();
|
||||
SET(JSONT_RBRACKET);
|
||||
break;
|
||||
case ':':
|
||||
INC();
|
||||
SET(JSONT_COLON);
|
||||
break;
|
||||
case ',':
|
||||
INC();
|
||||
SET(JSONT_COMMA);
|
||||
break;
|
||||
case '"': {
|
||||
/* Parse string */
|
||||
INC();
|
||||
ctx->tok_start = ctx->pos;
|
||||
while (*ctx->pos && *ctx->pos != '"') {
|
||||
if (*ctx->pos == '\\' && ctx->pos[1])
|
||||
INC();
|
||||
INC();
|
||||
}
|
||||
ctx->tok_end = ctx->pos;
|
||||
if (*ctx->pos == '"')
|
||||
INC();
|
||||
SET(JSONT_STRING);
|
||||
break;
|
||||
}
|
||||
case '-':
|
||||
case '0' ... '9': {
|
||||
/* Parse number */
|
||||
if (*ctx->pos == '-')
|
||||
INC();
|
||||
while (*ctx->pos && isdigit(*ctx->pos))
|
||||
INC();
|
||||
if (*ctx->pos == '.') {
|
||||
INC();
|
||||
while (*ctx->pos && isdigit(*ctx->pos))
|
||||
INC();
|
||||
}
|
||||
ctx->tok_end = ctx->pos;
|
||||
SET(JSONT_NUMBER);
|
||||
break;
|
||||
}
|
||||
case 't':
|
||||
if (!strncmp(ctx->pos, "true", 4)) {
|
||||
ctx->pos += 4;
|
||||
ctx->tok_end = ctx->pos;
|
||||
SET(JSONT_TRUE);
|
||||
} else {
|
||||
SET(JSONT_ERROR);
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (!strncmp(ctx->pos, "false", 5)) {
|
||||
ctx->pos += 5;
|
||||
ctx->tok_end = ctx->pos;
|
||||
SET(JSONT_FALSE);
|
||||
} else {
|
||||
SET(JSONT_ERROR);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
if (!strncmp(ctx->pos, "null", 4)) {
|
||||
ctx->pos += 4;
|
||||
ctx->tok_end = ctx->pos;
|
||||
SET(JSONT_NULL);
|
||||
} else {
|
||||
SET(JSONT_ERROR);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
SET(JSONT_ERROR);
|
||||
break;
|
||||
}
|
||||
|
||||
return ctx->tok;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_array() - Parse a JSON array
|
||||
*
|
||||
* @ctx: Parser context
|
||||
* @prop: Property name for this array
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int parse_array(struct json_parser *ctx, const char *prop)
|
||||
{
|
||||
u32 values[MAX_ARRAY_SIZE];
|
||||
int index = 0;
|
||||
int count = 0;
|
||||
int ret;
|
||||
|
||||
/* Expect [ */
|
||||
if (ctx->tok != JSONT_LBRACKET)
|
||||
return -EINVAL;
|
||||
|
||||
next_token(ctx);
|
||||
|
||||
/* Handle empty array */
|
||||
if (ctx->tok == JSONT_RBRACKET) {
|
||||
next_token(ctx);
|
||||
return fdt_property(ctx->fdt, prop, NULL, 0);
|
||||
}
|
||||
|
||||
/* Check if this is an array of objects */
|
||||
if (ctx->tok == JSONT_LBRACE) {
|
||||
/* Array of objects - create a subnode for each */
|
||||
while (ctx->tok != JSONT_RBRACKET) {
|
||||
char name[64];
|
||||
|
||||
snprintf(name, sizeof(name), "%s-%d", prop, index++);
|
||||
|
||||
ret = parse_object(ctx, name);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ctx->tok == JSONT_COMMA)
|
||||
next_token(ctx);
|
||||
else if (ctx->tok != JSONT_RBRACKET)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
next_token(ctx); /* Skip ] */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Array of primitives - collect into cell array */
|
||||
while (ctx->tok != JSONT_RBRACKET) {
|
||||
if (ctx->tok == JSONT_NUMBER) {
|
||||
char num_str[32];
|
||||
int len = min((int)(ctx->tok_end - ctx->tok_start),
|
||||
(int)sizeof(num_str) - 1);
|
||||
|
||||
if (count >= MAX_ARRAY_SIZE)
|
||||
return -E2BIG;
|
||||
|
||||
memcpy(num_str, ctx->tok_start, len);
|
||||
num_str[len] = '\0';
|
||||
values[count++] = simple_strtoul(num_str, NULL, 0);
|
||||
next_token(ctx);
|
||||
} else if (ctx->tok == JSONT_STRING) {
|
||||
/* String array - collect strings into a stringlist */
|
||||
char *strings[MAX_ARRAY_SIZE];
|
||||
char saved_chars[MAX_ARRAY_SIZE];
|
||||
int str_lens[MAX_ARRAY_SIZE];
|
||||
int count = 0;
|
||||
int total_len = 0;
|
||||
char *buf, *p;
|
||||
int i;
|
||||
|
||||
/* Collect all strings, temporarily nul-terminating */
|
||||
while (ctx->tok == JSONT_STRING) {
|
||||
if (count >= MAX_ARRAY_SIZE)
|
||||
return -E2BIG;
|
||||
|
||||
strings[count] = (char *)ctx->tok_start;
|
||||
saved_chars[count] = *ctx->tok_end;
|
||||
*(char *)ctx->tok_end = '\0';
|
||||
str_lens[count] = strlen(strings[count]) + 1;
|
||||
total_len += str_lens[count];
|
||||
count++;
|
||||
|
||||
next_token(ctx);
|
||||
|
||||
if (ctx->tok == JSONT_COMMA)
|
||||
next_token(ctx);
|
||||
else if (ctx->tok != JSONT_RBRACKET)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Build stringlist: concatenate all strings with nulls */
|
||||
buf = malloc(total_len);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
p = buf;
|
||||
for (i = 0; i < count; i++) {
|
||||
memcpy(p, strings[i], str_lens[i]);
|
||||
p += str_lens[i];
|
||||
}
|
||||
|
||||
/* Create FDT stringlist property */
|
||||
ret = fdt_property(ctx->fdt, prop, buf, total_len);
|
||||
free(buf);
|
||||
|
||||
/* Restore all the saved characters */
|
||||
for (i = 0; i < count; i++)
|
||||
strings[i][str_lens[i] - 1] = saved_chars[i];
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
next_token(ctx);
|
||||
return 0;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ctx->tok == JSONT_COMMA)
|
||||
next_token(ctx);
|
||||
else if (ctx->tok != JSONT_RBRACKET)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
next_token(ctx); /* Skip ] */
|
||||
|
||||
/* Write array as FDT property */
|
||||
if (count == 1) {
|
||||
/* Single element - use fdt_property_u32() */
|
||||
ret = fdt_property_u32(ctx->fdt, prop, values[0]);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (count > 1) {
|
||||
/* Multiple elements - convert to big-endian and write */
|
||||
u32 cells[MAX_ARRAY_SIZE];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
cells[i] = cpu_to_fdt32(values[i]);
|
||||
|
||||
ret = fdt_property(ctx->fdt, prop, cells, count * sizeof(u32));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_value() - Parse a JSON value
|
||||
*
|
||||
* @ctx: Parser context
|
||||
* @prop_name: Property name for this value
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int parse_value(struct json_parser *ctx, const char *prop_name)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (ctx->tok) {
|
||||
case JSONT_STRING: {
|
||||
char str[256];
|
||||
int len = min((int)(ctx->tok_end - ctx->tok_start),
|
||||
(int)sizeof(str) - 1);
|
||||
|
||||
memcpy(str, ctx->tok_start, len);
|
||||
str[len] = '\0';
|
||||
|
||||
ret = fdt_property_string(ctx->fdt, prop_name, str);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
next_token(ctx);
|
||||
break;
|
||||
}
|
||||
case JSONT_NUMBER: {
|
||||
char num_str[32];
|
||||
u64 val;
|
||||
int len = min((int)(ctx->tok_end - ctx->tok_start),
|
||||
(int)sizeof(num_str) - 1);
|
||||
|
||||
memcpy(num_str, ctx->tok_start, len);
|
||||
num_str[len] = '\0';
|
||||
|
||||
val = simple_strtoull(num_str, NULL, 0);
|
||||
|
||||
/* Use u32 if it fits, otherwise u64 */
|
||||
if (val <= 0xffffffff)
|
||||
ret = fdt_property_u32(ctx->fdt, prop_name, (u32)val);
|
||||
else
|
||||
ret = fdt_property_u64(ctx->fdt, prop_name, val);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
next_token(ctx);
|
||||
break;
|
||||
}
|
||||
case JSONT_TRUE:
|
||||
ret = fdt_property_u32(ctx->fdt, prop_name, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
next_token(ctx);
|
||||
break;
|
||||
case JSONT_FALSE:
|
||||
ret = fdt_property_u32(ctx->fdt, prop_name, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
next_token(ctx);
|
||||
break;
|
||||
case JSONT_NULL:
|
||||
ret = fdt_property(ctx->fdt, prop_name, NULL, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
next_token(ctx);
|
||||
break;
|
||||
case JSONT_LBRACE:
|
||||
/* Nested object */
|
||||
ret = parse_object(ctx, prop_name);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case JSONT_LBRACKET:
|
||||
/* Array */
|
||||
ret = parse_array(ctx, prop_name);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_object() - Parse a JSON object
|
||||
*
|
||||
* @ctx: Parser context
|
||||
* @node_name: Name for the device tree node (NULL for root)
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int parse_object(struct json_parser *ctx, const char *node_name)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Expect { */
|
||||
if (ctx->tok != JSONT_LBRACE)
|
||||
return -EINVAL;
|
||||
|
||||
/* Begin device tree node */
|
||||
if (node_name) {
|
||||
ret = fdt_begin_node(ctx->fdt, node_name);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
next_token(ctx);
|
||||
|
||||
/* Handle empty object */
|
||||
if (ctx->tok == JSONT_RBRACE) {
|
||||
next_token(ctx);
|
||||
if (node_name) {
|
||||
ret = fdt_end_node(ctx->fdt);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse key-value pairs */
|
||||
while (ctx->tok != JSONT_RBRACE) {
|
||||
char key[128];
|
||||
int len;
|
||||
|
||||
/* Expect string key */
|
||||
if (ctx->tok != JSONT_STRING)
|
||||
return -EINVAL;
|
||||
|
||||
len = min((int)(ctx->tok_end - ctx->tok_start),
|
||||
(int)sizeof(key) - 1);
|
||||
memcpy(key, ctx->tok_start, len);
|
||||
key[len] = '\0';
|
||||
|
||||
next_token(ctx);
|
||||
|
||||
/* Expect : */
|
||||
if (ctx->tok != JSONT_COLON)
|
||||
return -EINVAL;
|
||||
|
||||
next_token(ctx);
|
||||
|
||||
/* Parse value */
|
||||
ret = parse_value(ctx, key);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Expect , or } */
|
||||
if (ctx->tok == JSONT_COMMA)
|
||||
next_token(ctx);
|
||||
else if (ctx->tok != JSONT_RBRACE)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
next_token(ctx); /* Skip } */
|
||||
|
||||
/* End device tree node */
|
||||
if (node_name) {
|
||||
ret = fdt_end_node(ctx->fdt);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_json_root() - Parse JSON and create FDT
|
||||
*
|
||||
* @ctx: Parser context (must be initialized with FDT buffer)
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int parse_json_root(struct json_parser *ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Initialize FDT */
|
||||
ret = fdt_create(ctx->fdt, ctx->fdt_size);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = fdt_finish_reservemap(ctx->fdt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Begin root node */
|
||||
ret = fdt_begin_node(ctx->fdt, "");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Get first token */
|
||||
next_token(ctx);
|
||||
|
||||
/* Parse the JSON (expecting an object at the root) */
|
||||
if (ctx->tok == JSONT_LBRACE) {
|
||||
/* Parse the root's contents directly into the root node */
|
||||
next_token(ctx);
|
||||
|
||||
while (ctx->tok != JSONT_RBRACE) {
|
||||
char key[128];
|
||||
int len;
|
||||
|
||||
if (ctx->tok != JSONT_STRING)
|
||||
return -EINVAL;
|
||||
|
||||
len = min((int)(ctx->tok_end - ctx->tok_start),
|
||||
(int)sizeof(key) - 1);
|
||||
memcpy(key, ctx->tok_start, len);
|
||||
key[len] = '\0';
|
||||
|
||||
next_token(ctx);
|
||||
|
||||
if (ctx->tok != JSONT_COLON)
|
||||
return -EINVAL;
|
||||
|
||||
next_token(ctx);
|
||||
|
||||
ret = parse_value(ctx, key);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ctx->tok == JSONT_COMMA)
|
||||
next_token(ctx);
|
||||
else if (ctx->tok != JSONT_RBRACE)
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* End root node */
|
||||
ret = fdt_end_node(ctx->fdt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Finalize FDT */
|
||||
ret = fdt_finish(ctx->fdt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_to_fdt(const char *json, struct abuf *buf)
|
||||
{
|
||||
struct json_parser ctx;
|
||||
void *fdt_buf;
|
||||
int fdt_size;
|
||||
int ret;
|
||||
|
||||
if (!json || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
/* Estimate FDT size: JSON length * 2 should be plenty */
|
||||
fdt_size = max((int)strlen(json) * 2, 4096);
|
||||
|
||||
abuf_init(buf);
|
||||
if (!abuf_realloc(buf, fdt_size))
|
||||
return -ENOMEM;
|
||||
|
||||
fdt_buf = abuf_data(buf);
|
||||
|
||||
/* Init parser */
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ctx.json = json;
|
||||
ctx.pos = json;
|
||||
ctx.fdt = fdt_buf;
|
||||
ctx.fdt_size = fdt_size;
|
||||
|
||||
/* Parse JSON and create FDT */
|
||||
ret = parse_json_root(&ctx);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* Adjust abuf size to actual FDT size */
|
||||
abuf_realloc(buf, fdt_totalsize(fdt_buf));
|
||||
log_debug("json %ld buf %ld\n", strlen(json), buf->size);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
abuf_uninit(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
337
test/lib/json.c
337
test/lib/json.c
@@ -1,12 +1,16 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Tests for JSON pretty-printer
|
||||
* Tests for JSON utilities including parser and FDT converter
|
||||
*
|
||||
* Copyright (C) 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#include <dm/ofnode.h>
|
||||
#include <fdt_support.h>
|
||||
#include <image.h>
|
||||
#include <json.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <test/lib.h>
|
||||
#include <test/test.h>
|
||||
#include <test/ut.h>
|
||||
@@ -209,3 +213,334 @@ static int lib_test_json_whitespace(struct unit_test_state *uts)
|
||||
return 0;
|
||||
}
|
||||
LIB_TEST(lib_test_json_whitespace, UTF_CONSOLE);
|
||||
|
||||
/* JSON to FDT conversion tests */
|
||||
|
||||
static int lib_test_json_to_fdt_simple(struct unit_test_state *uts)
|
||||
{
|
||||
const char *json = "{\"name\":\"test\",\"value\":42}";
|
||||
struct abuf buf;
|
||||
void *fdt_buf;
|
||||
|
||||
ut_assertok(json_to_fdt(json, &buf));
|
||||
|
||||
fdt_buf = abuf_data(&buf);
|
||||
|
||||
/* Verify FDT is valid */
|
||||
ut_assertok(fdt_check_header(fdt_buf));
|
||||
|
||||
/* Check string property */
|
||||
ut_asserteq_str("test", fdt_getprop(fdt_buf, 0, "name", NULL));
|
||||
|
||||
/* Check integer property */
|
||||
ut_asserteq(42, fdtdec_get_int(fdt_buf, 0, "value", 0));
|
||||
|
||||
abuf_uninit(&buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
LIB_TEST(lib_test_json_to_fdt_simple, 0);
|
||||
|
||||
static int lib_test_json_to_fdt_nested(struct unit_test_state *uts)
|
||||
{
|
||||
const char *json = "{\"outer\":{\"inner\":\"value\"}}";
|
||||
struct abuf buf;
|
||||
void *fdt_buf;
|
||||
int node;
|
||||
|
||||
ut_assertok(json_to_fdt(json, &buf));
|
||||
|
||||
fdt_buf = abuf_data(&buf);
|
||||
|
||||
/* Verify FDT is valid */
|
||||
ut_assertok(fdt_check_header(fdt_buf));
|
||||
|
||||
/* Find nested node */
|
||||
node = fdt_path_offset(fdt_buf, "/outer");
|
||||
ut_assert(node >= 0);
|
||||
|
||||
/* Check property in nested node */
|
||||
ut_asserteq_str("value", fdt_getprop(fdt_buf, node, "inner", NULL));
|
||||
|
||||
abuf_uninit(&buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
LIB_TEST(lib_test_json_to_fdt_nested, 0);
|
||||
|
||||
static int lib_test_json_to_fdt_array(struct unit_test_state *uts)
|
||||
{
|
||||
const char *json = "{\"numbers\":[1,2,3]}";
|
||||
struct abuf buf;
|
||||
void *fdt_buf;
|
||||
u32 arr[8];
|
||||
int size;
|
||||
oftree tree;
|
||||
ofnode root;
|
||||
|
||||
ut_assertok(json_to_fdt(json, &buf));
|
||||
|
||||
fdt_buf = abuf_data(&buf);
|
||||
|
||||
/* Verify FDT is valid */
|
||||
ut_assertok(fdt_check_header(fdt_buf));
|
||||
|
||||
/* Create oftree from FDT */
|
||||
tree = oftree_from_fdt(fdt_buf);
|
||||
ut_assert(oftree_valid(tree));
|
||||
|
||||
root = oftree_root(tree);
|
||||
ut_assert(ofnode_valid(root));
|
||||
|
||||
/* Check array property */
|
||||
ut_assertnonnull(ofnode_get_property(root, "numbers", &size));
|
||||
size /= sizeof(u32);
|
||||
ut_asserteq(3, size);
|
||||
ut_assertok(ofnode_read_u32_array(root, "numbers", arr, size));
|
||||
ut_asserteq(1, arr[0]);
|
||||
ut_asserteq(2, arr[1]);
|
||||
ut_asserteq(3, arr[2]);
|
||||
|
||||
abuf_uninit(&buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
LIB_TEST(lib_test_json_to_fdt_array, 0);
|
||||
|
||||
static int lib_test_json_to_fdt_string_array(struct unit_test_state *uts)
|
||||
{
|
||||
char json[] = "{\"tags\":[\"first\",\"second\",\"third\"]}";
|
||||
struct abuf buf;
|
||||
void *fdt_buf;
|
||||
const char *str;
|
||||
ofnode root;
|
||||
oftree tree;
|
||||
|
||||
ut_assertok(json_to_fdt(json, &buf));
|
||||
|
||||
fdt_buf = abuf_data(&buf);
|
||||
|
||||
/* Verify FDT is valid */
|
||||
ut_assertok(fdt_check_header(fdt_buf));
|
||||
|
||||
/* Create oftree from FDT */
|
||||
tree = oftree_from_fdt(fdt_buf);
|
||||
ut_assert(oftree_valid(tree));
|
||||
|
||||
root = oftree_root(tree);
|
||||
ut_assert(ofnode_valid(root));
|
||||
|
||||
/* Check string array property */
|
||||
ut_assertok(ofnode_read_string_index(root, "tags", 0, &str));
|
||||
ut_asserteq_str("first", str);
|
||||
ut_assertok(ofnode_read_string_index(root, "tags", 1, &str));
|
||||
ut_asserteq_str("second", str);
|
||||
ut_assertok(ofnode_read_string_index(root, "tags", 2, &str));
|
||||
ut_asserteq_str("third", str);
|
||||
|
||||
abuf_uninit(&buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
LIB_TEST(lib_test_json_to_fdt_string_array, 0);
|
||||
|
||||
static int lib_test_json_to_fdt_bool(struct unit_test_state *uts)
|
||||
{
|
||||
const char *json = "{\"enabled\":true,\"disabled\":false}";
|
||||
struct abuf buf;
|
||||
void *fdt_buf;
|
||||
|
||||
ut_assertok(json_to_fdt(json, &buf));
|
||||
|
||||
fdt_buf = abuf_data(&buf);
|
||||
|
||||
/* Verify FDT is valid */
|
||||
ut_assertok(fdt_check_header(fdt_buf));
|
||||
|
||||
/* Check boolean properties */
|
||||
ut_asserteq(1, fdtdec_get_int(fdt_buf, 0, "enabled", 0));
|
||||
ut_asserteq(0, fdtdec_get_int(fdt_buf, 0, "disabled", 0));
|
||||
|
||||
abuf_uninit(&buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
LIB_TEST(lib_test_json_to_fdt_bool, 0);
|
||||
|
||||
/* Test with realistic LUKS2 JSON metadata using ofnode API */
|
||||
static int lib_test_json_to_fdt_luks2(struct unit_test_state *uts)
|
||||
{
|
||||
/* Simplified LUKS2 JSON metadata structure */
|
||||
const char *luks2_json =
|
||||
"{"
|
||||
" \"keyslots\": {"
|
||||
" \"0\": {"
|
||||
" \"type\": \"luks2\","
|
||||
" \"key_size\": 32,"
|
||||
" \"area\": {"
|
||||
" \"type\": \"raw\","
|
||||
" \"offset\": \"32768\","
|
||||
" \"size\": \"258048\""
|
||||
" },"
|
||||
" \"kdf\": {"
|
||||
" \"type\": \"pbkdf2\","
|
||||
" \"hash\": \"sha256\","
|
||||
" \"iterations\": 1000,"
|
||||
" \"salt\": \"aGVsbG93b3JsZA==\""
|
||||
" }"
|
||||
" },"
|
||||
" \"1\": {"
|
||||
" \"type\": \"luks2\","
|
||||
" \"key_size\": 32,"
|
||||
" \"area\": {"
|
||||
" \"type\": \"raw\","
|
||||
" \"offset\": \"290816\","
|
||||
" \"size\": \"258048\""
|
||||
" },"
|
||||
" \"kdf\": {"
|
||||
" \"type\": \"pbkdf2\","
|
||||
" \"hash\": \"sha256\","
|
||||
" \"iterations\": 2000,"
|
||||
" \"salt\": \"YW5vdGhlcnNhbHQ=\""
|
||||
" }"
|
||||
" }"
|
||||
" },"
|
||||
" \"segments\": {"
|
||||
" \"0\": {"
|
||||
" \"type\": \"crypt\","
|
||||
" \"offset\": \"16777216\","
|
||||
" \"size\": \"dynamic\","
|
||||
" \"iv_tweak\": \"0\","
|
||||
" \"encryption\": \"aes-cbc-essiv:sha256\","
|
||||
" \"sector_size\": 512"
|
||||
" }"
|
||||
" },"
|
||||
" \"digests\": {"
|
||||
" \"0\": {"
|
||||
" \"type\": \"pbkdf2\","
|
||||
" \"keyslots\": [0, 1],"
|
||||
" \"segments\": [0],"
|
||||
" \"hash\": \"sha256\","
|
||||
" \"iterations\": 1000,"
|
||||
" \"salt\": \"c2FsdHlzYWx0\""
|
||||
" }"
|
||||
" },"
|
||||
" \"config\": {"
|
||||
" \"json_size\": \"12288\","
|
||||
" \"keyslots_size\": \"3145728\""
|
||||
" }"
|
||||
"}";
|
||||
|
||||
ofnode segments, segment0, digests, digest0, config;
|
||||
ofnode root, keyslots, keyslot0, kdf;
|
||||
struct abuf buf;
|
||||
u32 arr[8];
|
||||
int size;
|
||||
void *fdt_buf;
|
||||
oftree tree;
|
||||
|
||||
ut_assertok(json_to_fdt(luks2_json, &buf));
|
||||
|
||||
/* Verify FDT is valid */
|
||||
fdt_buf = abuf_data(&buf);
|
||||
ut_assertok(fdt_check_header(fdt_buf));
|
||||
|
||||
/* Create oftree from FDT */
|
||||
tree = oftree_from_fdt(fdt_buf);
|
||||
ut_assert(oftree_valid(tree));
|
||||
|
||||
/* Get root node */
|
||||
root = oftree_root(tree);
|
||||
ut_assert(ofnode_valid(root));
|
||||
|
||||
/* Navigate to keyslots node */
|
||||
keyslots = ofnode_find_subnode(root, "keyslots");
|
||||
ut_assert(ofnode_valid(keyslots));
|
||||
|
||||
/* Navigate to keyslot 0 */
|
||||
keyslot0 = ofnode_find_subnode(keyslots, "0");
|
||||
ut_assert(ofnode_valid(keyslot0));
|
||||
|
||||
/* Check keyslot type */
|
||||
ut_asserteq_str("luks2", ofnode_read_string(keyslot0, "type"));
|
||||
|
||||
/* Check key_size */
|
||||
ut_asserteq(32, ofnode_read_u32_default(keyslot0, "key_size", 0));
|
||||
|
||||
/* Navigate to KDF node */
|
||||
kdf = ofnode_find_subnode(keyslot0, "kdf");
|
||||
ut_assert(ofnode_valid(kdf));
|
||||
|
||||
/* Check KDF type */
|
||||
ut_asserteq_str("pbkdf2", ofnode_read_string(kdf, "type"));
|
||||
|
||||
/* Check KDF hash */
|
||||
ut_asserteq_str("sha256", ofnode_read_string(kdf, "hash"));
|
||||
|
||||
/* Check iterations */
|
||||
ut_asserteq(1000, ofnode_read_u32_default(kdf, "iterations", 0));
|
||||
|
||||
/* Check salt (base64 string) */
|
||||
ut_asserteq_str("aGVsbG93b3JsZA==", ofnode_read_string(kdf, "salt"));
|
||||
|
||||
/* Navigate to segments node */
|
||||
segments = ofnode_find_subnode(root, "segments");
|
||||
ut_assert(ofnode_valid(segments));
|
||||
|
||||
/* Navigate to segment 0 */
|
||||
segment0 = ofnode_find_subnode(segments, "0");
|
||||
ut_assert(ofnode_valid(segment0));
|
||||
|
||||
/* Check segment type */
|
||||
ut_asserteq_str("crypt", ofnode_read_string(segment0, "type"));
|
||||
|
||||
/* Check encryption */
|
||||
ut_asserteq_str("aes-cbc-essiv:sha256", ofnode_read_string(segment0, "encryption"));
|
||||
|
||||
/* Check offset (stored as string in JSON) */
|
||||
ut_asserteq_str("16777216", ofnode_read_string(segment0, "offset"));
|
||||
|
||||
/* Check sector_size */
|
||||
ut_asserteq(512, ofnode_read_u32_default(segment0, "sector_size", 0));
|
||||
|
||||
/* Navigate to digests node */
|
||||
digests = ofnode_find_subnode(root, "digests");
|
||||
ut_assert(ofnode_valid(digests));
|
||||
|
||||
/* Navigate to digest 0 */
|
||||
digest0 = ofnode_find_subnode(digests, "0");
|
||||
ut_assert(ofnode_valid(digest0));
|
||||
|
||||
/* Check digest type */
|
||||
ut_asserteq_str("pbkdf2", ofnode_read_string(digest0, "type"));
|
||||
|
||||
/* Check keyslots array */
|
||||
ut_assertnonnull(ofnode_get_property(digest0, "keyslots", &size));
|
||||
size /= sizeof(u32);
|
||||
ut_asserteq(2, size);
|
||||
ut_assertok(ofnode_read_u32_array(digest0, "keyslots", arr, size));
|
||||
ut_asserteq(0, arr[0]);
|
||||
ut_asserteq(1, arr[1]);
|
||||
|
||||
/* Check segments array */
|
||||
ut_assertnonnull(ofnode_get_property(digest0, "segments", &size));
|
||||
ut_asserteq(4, size);
|
||||
size /= sizeof(u32);
|
||||
ut_assertok(ofnode_read_u32_array(digest0, "segments", arr, size));
|
||||
ut_asserteq(0, arr[0]);
|
||||
|
||||
/* Navigate to config node */
|
||||
config = ofnode_find_subnode(root, "config");
|
||||
ut_assert(ofnode_valid(config));
|
||||
|
||||
/* Check json_size (stored as string in JSON) */
|
||||
ut_asserteq_str("12288", ofnode_read_string(config, "json_size"));
|
||||
|
||||
/* Check keyslots_size (stored as string in JSON) */
|
||||
ut_asserteq_str("3145728", ofnode_read_string(config, "keyslots_size"));
|
||||
|
||||
abuf_uninit(&buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
LIB_TEST(lib_test_json_to_fdt_luks2, 0);
|
||||
|
||||
Reference in New Issue
Block a user