From: Simon Glass <sjg@chromium.org> LUKS version 2 uses JSON as a means of communicating the key information. Add a simple library which can print JSON in a human-readable format. Note that it does not fully parse the JSON fragment. That may be considered later, if needed. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- MAINTAINERS | 3 + include/json.h | 23 +++++ lib/Kconfig | 7 ++ lib/Makefile | 1 + lib/json.c | 122 +++++++++++++++++++++++++++ test/lib/Makefile | 1 + test/lib/json.c | 211 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 368 insertions(+) create mode 100644 include/json.h create mode 100644 lib/json.c create mode 100644 test/lib/json.c diff --git a/MAINTAINERS b/MAINTAINERS index 9b00829db93..a0374df1c4d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1297,7 +1297,10 @@ F: cmd/luks.c F: doc/usage/cmd/luks.rst F: drivers/block/luks.c F: include/luks.h +F: include/json.h +F: lib/json.c F: test/boot/luks.c +F: test/lib/json.c MALI DISPLAY PROCESSORS M: Liviu Dudau <liviu.dudau@foss.arm.com> diff --git a/include/json.h b/include/json.h new file mode 100644 index 00000000000..4d925e9db36 --- /dev/null +++ b/include/json.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * JSON utilities + * + * Copyright (C) 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#ifndef __JSON_H__ +#define __JSON_H__ + +/** + * json_print_pretty() - Print JSON with indentation + * + * This function takes a JSON string and prints it with proper indentation, + * making it more human-readable. It handles nested objects and arrays. + * + * @json: JSON string to print (may be nul terminated before @len) + * @len: Length of JSON string + */ +void json_print_pretty(const char *json, int len); + +#endif /* __JSON_H__ */ diff --git a/lib/Kconfig b/lib/Kconfig index 9de4667731e..c8bf4b4b049 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -976,6 +976,13 @@ config GETOPT help This enables functions for parsing command-line options. +config JSON + bool "Enable JSON parsing and printing" + help + This enables JSON (JavaScript Object Notation) parsing and pretty- + printing functions. JSON is used for structured data representation, + such as LUKS2 metadata. + config OF_LIBFDT bool "Enable the FDT library" default y if OF_CONTROL diff --git a/lib/Makefile b/lib/Makefile index 43df5733ffd..71c9c0d1766 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -39,6 +39,7 @@ endif obj-y += crc8.o obj-$(CONFIG_ERRNO_STR) += errno_str.o +obj-$(CONFIG_JSON) += json.o obj-$(CONFIG_FIT) += fdtdec_common.o obj-$(CONFIG_TEST_FDTDEC) += fdtdec_test.o obj-$(CONFIG_GZIP_COMPRESSED) += gzip.o diff --git a/lib/json.c b/lib/json.c new file mode 100644 index 00000000000..8cce042d631 --- /dev/null +++ b/lib/json.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * JSON pretty-printer + * + * Copyright (C) 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#include <ctype.h> +#include <log.h> + +/** + * print_indent() - Print indentation spaces + * + * @indent: Indentation level (each level is 2 spaces) + */ +static void print_indent(int indent) +{ + for (int i = 0; i < indent * 2; i++) + putc(' '); +} + +void json_print_pretty(const char *json, int len) +{ + int indent = 0; + bool in_string = false; + bool escaped = false; + bool after_open = false; + int i; + + for (i = 0; i < len && json[i]; i++) { + char c = json[i]; + + /* Handle escape sequences */ + if (escaped) { + putc(c); + escaped = false; + continue; + } + + if (c == '\\') { + putc(c); + escaped = true; + continue; + } + + /* Track whether we're inside a string */ + if (c == '"') { + in_string = !in_string; + if (after_open) { + print_indent(indent); + after_open = false; + } + putc(c); + continue; + } + + /* Don't format inside strings */ + if (in_string) { + putc(c); + continue; + } + + /* Format structural characters */ + switch (c) { + case '{': + case '[': + if (after_open) { + print_indent(indent); + after_open = false; + } + putc(c); + putc('\n'); + indent++; + after_open = true; + break; + + case '}': + case ']': + if (!after_open) { + putc('\n'); + indent--; + print_indent(indent); + } else { + indent--; + } + putc(c); + after_open = false; + break; + + case ',': + putc(c); + putc('\n'); + print_indent(indent); + after_open = false; + break; + + case ':': + putc(c); + putc(' '); + after_open = false; + break; + + case ' ': + case '\t': + case '\n': + case '\r': + /* Skip whitespace outside strings */ + break; + + default: + if (after_open) { + print_indent(indent); + after_open = false; + } + putc(c); + break; + } + } + + putc('\n'); +} diff --git a/test/lib/Makefile b/test/lib/Makefile index 5c89421918b..1d94d6604d5 100644 --- a/test/lib/Makefile +++ b/test/lib/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_EFI_LOADER) += efi_device_path.o obj-$(CONFIG_EFI_SECURE_BOOT) += efi_image_region.o obj-$(CONFIG_EFI_LOG) += efi_log.o obj-y += hexdump.o +obj-$(CONFIG_JSON) += json.o obj-$(CONFIG_SANDBOX) += kconfig.o obj-y += lmb.o obj-$(CONFIG_HAVE_SETJMP) += longjmp.o diff --git a/test/lib/json.c b/test/lib/json.c new file mode 100644 index 00000000000..76afeb9b241 --- /dev/null +++ b/test/lib/json.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tests for JSON pretty-printer + * + * Copyright (C) 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#include <json.h> +#include <test/lib.h> +#include <test/test.h> +#include <test/ut.h> + +static int lib_test_json_simple_object(struct unit_test_state *uts) +{ + const char *json = "{\"name\":\"value\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"name\": \"value\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_simple_object, UTF_CONSOLE); + +static int lib_test_json_simple_array(struct unit_test_state *uts) +{ + const char *json = "[1,2,3]"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("["); + ut_assert_nextline(" 1,"); + ut_assert_nextline(" 2,"); + ut_assert_nextline(" 3"); + ut_assert_nextline("]"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_simple_array, UTF_CONSOLE); + +static int lib_test_json_nested_object(struct unit_test_state *uts) +{ + const char *json = "{\"outer\":{\"inner\":\"value\"}}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"outer\": {"); + ut_assert_nextline(" \"inner\": \"value\""); + ut_assert_nextline(" }"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_nested_object, UTF_CONSOLE); + +static int lib_test_json_nested_array(struct unit_test_state *uts) +{ + const char *json = "[[1,2],[3,4]]"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("["); + ut_assert_nextline(" ["); + ut_assert_nextline(" 1,"); + ut_assert_nextline(" 2"); + ut_assert_nextline(" ],"); + ut_assert_nextline(" ["); + ut_assert_nextline(" 3,"); + ut_assert_nextline(" 4"); + ut_assert_nextline(" ]"); + ut_assert_nextline("]"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_nested_array, UTF_CONSOLE); + +static int lib_test_json_mixed_nested(struct unit_test_state *uts) +{ + const char *json = "{\"array\":[1,{\"nested\":\"obj\"}]}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"array\": ["); + ut_assert_nextline(" 1,"); + ut_assert_nextline(" {"); + ut_assert_nextline(" \"nested\": \"obj\""); + ut_assert_nextline(" }"); + ut_assert_nextline(" ]"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_mixed_nested, UTF_CONSOLE); + +static int lib_test_json_string_with_colon(struct unit_test_state *uts) +{ + const char *json = "{\"url\":\"http://example.com\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"url\": \"http://example.com\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_string_with_colon, UTF_CONSOLE); + +static int lib_test_json_string_with_comma(struct unit_test_state *uts) +{ + const char *json = "{\"name\":\"last, first\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"name\": \"last, first\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_string_with_comma, UTF_CONSOLE); + +static int lib_test_json_string_with_braces(struct unit_test_state *uts) +{ + const char *json = "{\"text\":\"some {braces} here\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"text\": \"some {braces} here\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_string_with_braces, UTF_CONSOLE); + +static int lib_test_json_escaped_quote(struct unit_test_state *uts) +{ + const char *json = "{\"quote\":\"He said \\\"hello\\\"\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"quote\": \"He said \\\"hello\\\"\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_escaped_quote, UTF_CONSOLE); + +static int lib_test_json_multiple_fields(struct unit_test_state *uts) +{ + const char *json = "{\"name\":\"test\",\"age\":25,\"active\":true}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"name\": \"test\","); + ut_assert_nextline(" \"age\": 25,"); + ut_assert_nextline(" \"active\": true"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_multiple_fields, UTF_CONSOLE); + +static int lib_test_json_empty_object(struct unit_test_state *uts) +{ + const char *json = "{}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_empty_object, UTF_CONSOLE); + +static int lib_test_json_empty_array(struct unit_test_state *uts) +{ + const char *json = "[]"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("["); + ut_assert_nextline("]"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_empty_array, UTF_CONSOLE); + +static int lib_test_json_whitespace(struct unit_test_state *uts) +{ + const char *json = "{ \"name\" : \"value\" , \"num\" : 42 }"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"name\": \"value\","); + ut_assert_nextline(" \"num\": 42"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_whitespace, UTF_CONSOLE); -- 2.43.0