From: Simon Glass <simon.glass@canonical.com> 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> --- include/json.h | 34 +++ lib/json.c | 612 +++++++++++++++++++++++++++++++++++++++++++++++- test/lib/json.c | 337 +++++++++++++++++++++++++- 3 files changed, 981 insertions(+), 2 deletions(-) diff --git a/include/json.h b/include/json.h index 4d925e9db36..65c9bc5dcae 100644 --- a/include/json.h +++ b/include/json.h @@ -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__ */ diff --git a/lib/json.c b/lib/json.c index 8cce042d631..3aad431d87b 100644 --- a/lib/json.c +++ b/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; +} diff --git a/test/lib/json.c b/test/lib/json.c index 76afeb9b241..0cddddcb2a3 100644 --- a/test/lib/json.c +++ b/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); -- 2.43.0