[Concept,18/24] luks: Create a very simple JSON library

Message ID 20251031065439.3251464-19-sjg@u-boot.org
State New
Headers
Series luks: Provide basic support for unlocking a LUKS1 partition |

Commit Message

Simon Glass Oct. 31, 2025, 6:54 a.m. UTC
  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
  

Patch

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);