package de.waypointaudio.license import org.json.JSONArray import org.json.JSONObject /** * v2.5.20 — Kanonische JSON-Serialisierung, kompatibel zum Python-Tool * (`canonical_json_bytes`): * - Keys lexikografisch sortiert. * - Trennzeichen ohne Whitespace: "," / ":". * - `ensure_ascii=False` ⇒ Unicode-Strings werden nicht escapet, ausser den * minimal nötigen JSON-Escapes (`\"`, `\\`, Steuerzeichen < 0x20). * - `null` für JSON-Null; `JSONObject.NULL` und Kotlin-`null` gleich behandelt. * * Diese kanonische Form ist die Eingabe für die ECDSA-P256-SHA256-Signatur. */ internal object CanonicalJson { fun encode(value: Any?): String { val sb = StringBuilder() appendValue(sb, value) return sb.toString() } private fun appendValue(sb: StringBuilder, value: Any?) { when { value == null || value === JSONObject.NULL -> sb.append("null") value is Boolean -> sb.append(if (value) "true" else "false") value is Number -> appendNumber(sb, value) value is String -> appendString(sb, value) value is JSONObject -> appendObject(sb, value) value is JSONArray -> appendArray(sb, value) else -> appendString(sb, value.toString()) } } private fun appendObject(sb: StringBuilder, obj: JSONObject) { val keys = obj.keys().asSequence().toList().sorted() sb.append('{') for ((index, key) in keys.withIndex()) { if (index > 0) sb.append(',') appendString(sb, key) sb.append(':') appendValue(sb, obj.opt(key)) } sb.append('}') } private fun appendArray(sb: StringBuilder, arr: JSONArray) { sb.append('[') for (i in 0 until arr.length()) { if (i > 0) sb.append(',') appendValue(sb, arr.opt(i)) } sb.append(']') } private fun appendNumber(sb: StringBuilder, n: Number) { when (n) { is Int, is Long, is Short, is Byte -> sb.append(n.toString()) is Double -> { if (n.isNaN() || n.isInfinite()) { sb.append("null") } else if (n == n.toLong().toDouble()) { sb.append(n.toLong().toString()) } else { sb.append(n.toString()) } } is Float -> appendNumber(sb, n.toDouble()) else -> sb.append(n.toString()) } } private fun appendString(sb: StringBuilder, s: String) { sb.append('"') for (c in s) { when (c.code) { 0x22 -> sb.append("\\\"") 0x5C -> sb.append("\\\\") 0x08 -> sb.append("\\b") 0x0C -> sb.append("\\f") 0x0A -> sb.append("\\n") 0x0D -> sb.append("\\r") 0x09 -> sb.append("\\t") else -> { val code = c.code if (code < 0x20) { sb.append("\\u%04x".format(code)) } else { sb.append(c) } } } } sb.append('"') } }