Viewing is open to everyone. Downloading and starring need a GitHub sign-in.
Sign in with GitHub
HostHarness.java
v0.1.0-build.85
Latest
Commit ede38f5
Sign in with GitHub to download. Each download is recorded with your GitHub username.
| 1 | // Host-JVM harness for the CKey JNI bridge. Plain Java, no Android, no Kotlin |
| 2 | // runtime needed: it calls org.openckey.ckey.CKeyNative's static native |
| 3 | // methods directly, so it runs against either the Kotlin-compiled class or |
| 4 | // the Java mirror generated by gen_mirror.py. See run.sh. |
| 5 | |
| 6 | import java.nio.ByteBuffer; |
| 7 | import java.nio.charset.StandardCharsets; |
| 8 | import java.util.Arrays; |
| 9 | |
| 10 | import org.openckey.ckey.CKeyException; |
| 11 | import org.openckey.ckey.CKeyNative; |
| 12 | |
| 13 | public final class HostHarness { |
| 14 | static int checks = 0; |
| 15 | |
| 16 | static void check(boolean ok, String what) { |
| 17 | checks++; |
| 18 | if (!ok) throw new AssertionError("FAILED: " + what); |
| 19 | System.out.println("ok " + what); |
| 20 | } |
| 21 | |
| 22 | interface Body { void run() throws Exception; } |
| 23 | |
| 24 | static void throwsType(Class<? extends Throwable> type, Body body, String what) throws Exception { |
| 25 | try { |
| 26 | body.run(); |
| 27 | } catch (Throwable t) { |
| 28 | check(type.isInstance(t), what + " -> " + t); |
| 29 | return; |
| 30 | } |
| 31 | check(false, what + " (no exception)"); |
| 32 | } |
| 33 | |
| 34 | static int[] info5() { return new int[5]; } |
| 35 | |
| 36 | /** Straight RGBA8 -> Rec. 709 luma, composited over white, placed in a larger frame. */ |
| 37 | static byte[] frameFromRgba(byte[] rgba, int side, int frameW, int frameH, int stride, int x0, int y0) { |
| 38 | byte[] frame = new byte[stride * frameH]; |
| 39 | Arrays.fill(frame, (byte) 0xff); |
| 40 | for (int y = 0; y < side; y++) |
| 41 | for (int x = 0; x < side; x++) { |
| 42 | int p = (y * side + x) * 4; |
| 43 | int r = rgba[p] & 0xff, g = rgba[p + 1] & 0xff, b = rgba[p + 2] & 0xff, a = rgba[p + 3] & 0xff; |
| 44 | int luma = (2126 * r + 7152 * g + 722 * b + 5000) / 10000; |
| 45 | frame[(y0 + y) * stride + x0 + x] = (byte) ((luma * a + 255 * (255 - a) + 127) / 255); |
| 46 | } |
| 47 | return frame; |
| 48 | } |
| 49 | |
| 50 | public static void main(String[] args) throws Exception { |
| 51 | int features = CKeyNative.nativeFeatures(); |
| 52 | System.out.println("features=" + features + " version=" + CKeyNative.nativeSoftwareVersion() + " abi=" + CKeyNative.nativeAbiVersion()); |
| 53 | check((features & 7) == 7, "scan, render and telemetry compiled in"); |
| 54 | check(CKeyNative.nativeSoftwareVersion().matches("^0\\.[0-9]+\\.[0-9]+(-(build|dev)\\.[0-9]{1,6})?$"), "software version matches the telemetry schema"); |
| 55 | check(CKeyNative.nativeCapacity(1, 1, 0) > 0 && CKeyNative.nativeCapacity(99, 1, 0) == 0, "capacity"); |
| 56 | check(CKeyNative.nativeModulesAcross(1) == 29 && CKeyNative.nativeModulesAcross(31) == 232, "modules across v1/v31"); |
| 57 | |
| 58 | // ---- encode -> render RGBA -> gray -> decode (Profile 0 standard) |
| 59 | String text = "https://openckey.org/hello?from=android-jni"; |
| 60 | byte[] payload = text.getBytes(StandardCharsets.UTF_8); |
| 61 | byte[] symbol = CKeyNative.nativeEncode(payload, 0, 1, 0); |
| 62 | int[] info = CKeyNative.nativeSymbolInfo(symbol); |
| 63 | check(info[0] >= 1 && info[1] == 1 && info[2] == 0 && info[3] == payload.length, "symbol info " + Arrays.toString(info)); |
| 64 | |
| 65 | int side = 480; |
| 66 | byte[] rgba = CKeyNative.nativeRenderRaster(symbol, null, null, null, side, 0, false, false, false, 4, 1.0, true, null, 0, 0); |
| 67 | check(rgba.length == side * side * 4, "RGBA buffer size"); |
| 68 | int frameW = 760, frameH = 620, stride = 800, x0 = 140, y0 = 70; |
| 69 | byte[] frame = frameFromRgba(rgba, side, frameW, frameH, stride, x0, y0); |
| 70 | int[] dinfo = info5(); |
| 71 | double[] quad = new double[8]; |
| 72 | byte[] decoded = CKeyNative.nativeDecodeArray(frame, 0, frameW, frameH, stride, 6, dinfo, quad); |
| 73 | check(decoded != null && Arrays.equals(decoded, payload), "round trip via byte[] (P0 v" + dinfo[1] + ", candidates " + dinfo[3] + ")"); |
| 74 | check(dinfo[0] == 0 && dinfo[1] == info[0] && dinfo[2] == 0, "decode reports profile P0 and the encoded version"); |
| 75 | check(quad[0] > x0 - 20 && quad[0] < x0 + side + 20, "quad is in frame coordinates"); |
| 76 | |
| 77 | // Same frame through a direct ByteBuffer with a non-zero offset. |
| 78 | int pad = 37; |
| 79 | ByteBuffer direct = ByteBuffer.allocateDirect(pad + frame.length); |
| 80 | direct.position(pad); |
| 81 | direct.put(frame); |
| 82 | direct.position(0); |
| 83 | byte[] viaBuffer = CKeyNative.nativeDecodeBuffer(direct, pad, frameW, frameH, stride, 6, info5(), new double[8]); |
| 84 | check(viaBuffer != null && Arrays.equals(viaBuffer, payload), "round trip via direct ByteBuffer + offset"); |
| 85 | |
| 86 | // UTF-8 beyond the BMP, PRINT layout, level H, GRAY8 output, inverted polarity. |
| 87 | byte[] utf8 = "Grüße aus Köln 👋 — address:1 Main St".getBytes(StandardCharsets.UTF_8); |
| 88 | byte[] printSymbol = CKeyNative.nativeEncode(utf8, 0, 3, 1); |
| 89 | byte[] gray = CKeyNative.nativeRenderRaster(printSymbol, null, null, null, 400, 1, true, false, false, 4, 1.0, true, null, 0, 0); |
| 90 | check(gray.length == 400 * 400, "GRAY8 buffer size"); |
| 91 | byte[] inverted = new byte[gray.length]; |
| 92 | for (int i = 0; i < gray.length; i++) inverted[i] = (byte) (255 - (gray[i] & 0xff)); |
| 93 | int[] pinfo = info5(); |
| 94 | byte[] printDecoded = CKeyNative.nativeDecodeArray(gray, 0, 400, 400, 400, 6, pinfo, new double[8]); |
| 95 | check(printDecoded != null && Arrays.equals(printDecoded, utf8) && pinfo[0] == 1 && pinfo[2] == 1, "PRINT layout (P0.1) round trip, level H"); |
| 96 | byte[] invDecoded = CKeyNative.nativeDecodeArray(inverted, 0, 400, 400, 400, 6, info5(), new double[8]); |
| 97 | check(invDecoded != null && Arrays.equals(invDecoded, utf8), "inverted polarity round trip"); |
| 98 | |
| 99 | // Blank frame: no result, no exception. |
| 100 | byte[] blank = new byte[320 * 240]; |
| 101 | Arrays.fill(blank, (byte) 0x80); |
| 102 | check(CKeyNative.nativeDecodeArray(blank, 0, 320, 240, 320, 6, info5(), new double[8]) == null, "blank frame decodes to null"); |
| 103 | |
| 104 | // ---- transparent output and premultiplication |
| 105 | byte[] straight = CKeyNative.nativeRenderRaster(symbol, null, null, null, 128, 0, false, true, false, 4, 1.0, true, null, 0, 0); |
| 106 | byte[] premul = CKeyNative.nativeRenderRaster(symbol, null, null, null, 128, 0, false, true, true, 4, 1.0, true, null, 0, 0); |
| 107 | int zeroAlpha = 0, partial = 0, mismatches = 0; |
| 108 | for (int p = 0; p < straight.length; p += 4) { |
| 109 | int a = straight[p + 3] & 0xff; |
| 110 | if (a == 0) zeroAlpha++; |
| 111 | if (a > 0 && a < 255) partial++; |
| 112 | if ((premul[p + 3] & 0xff) != a) mismatches++; |
| 113 | for (int c = 0; c < 3; c++) { |
| 114 | int expect = ((straight[p + c] & 0xff) * a + 127) / 255; |
| 115 | if ((premul[p + c] & 0xff) != expect) mismatches++; |
| 116 | if ((premul[p + c] & 0xff) > (premul[p + 3] & 0xff)) mismatches++; // premultiplied invariant: c <= a |
| 117 | } |
| 118 | } |
| 119 | check(zeroAlpha > 0, "transparent output has alpha-0 pixels (" + zeroAlpha + ")"); |
| 120 | check(partial > 0, "coverage antialiasing yields partial alpha (" + partial + ")"); |
| 121 | check(mismatches == 0, "premultiplied RGBA = round(c * a / 255) with c <= a"); |
| 122 | |
| 123 | // ---- exports |
| 124 | byte[] png = CKeyNative.nativeRenderPng(symbol, null, null, null, 256, 0, true, 4, 1.0, true, null, 0, 0, 300.0); |
| 125 | check(png.length > 8 && (png[0] & 0xff) == 0x89 && png[1] == 'P' && png[2] == 'N' && png[3] == 'G', "PNG signature (" + png.length + " bytes)"); |
| 126 | String svg = CKeyNative.nativeRenderSvg(symbol, null, null, null, 8); |
| 127 | check(svg.contains("<svg") && svg.trim().endsWith("</svg>"), "SVG export (" + svg.length() + " chars)"); |
| 128 | byte[] pdf = CKeyNative.nativeRenderPdf(symbol, null, null, null, 144.0, false); |
| 129 | check(new String(pdf, 0, 5, StandardCharsets.US_ASCII).equals("%PDF-"), "PDF export (" + pdf.length + " bytes)"); |
| 130 | |
| 131 | // ---- styles |
| 132 | int[] ints = new int[15]; |
| 133 | double[] doubles = new double[4]; |
| 134 | CKeyNative.nativeStylePreset(1, ints, doubles); |
| 135 | check(ints[2] == 0xf6eee0 && ints[8] == 2 && doubles[0] == 0.6 && ints[14] == 0, "CONCEPT preset marshalled (ink, accents, bevel, policy strict)"); |
| 136 | String report = CKeyNative.nativeCheckStyle(symbol, ints, doubles, null); |
| 137 | check(report.startsWith("style OK"), "check style: " + report); |
| 138 | byte[] concept = CKeyNative.nativeRenderRaster(symbol, ints, doubles, null, side, 0, false, false, false, 4, 1.0, true, null, 0, 0); |
| 139 | byte[] conceptFrame = frameFromRgba(concept, side, frameW, frameH, stride, x0, y0); |
| 140 | byte[] conceptDecoded = CKeyNative.nativeDecodeArray(conceptFrame, 0, frameW, frameH, stride, 6, info5(), new double[8]); |
| 141 | check(conceptDecoded != null && Arrays.equals(conceptDecoded, payload), "CONCEPT style (light on dark) round trip"); |
| 142 | |
| 143 | int[] lowContrast = new int[15]; |
| 144 | double[] d0 = new double[4]; |
| 145 | CKeyNative.nativeStylePreset(0, lowContrast, d0); |
| 146 | lowContrast[2] = 0xf0f0f0; lowContrast[3] = 0xf0f0f0; // near-white ink on white |
| 147 | throwsType(CKeyException.class, () -> CKeyNative.nativeCheckStyle(symbol, lowContrast, d0, null), "low-contrast style refused with the core's diagnostic"); |
| 148 | try { |
| 149 | CKeyNative.nativeRenderRaster(symbol, lowContrast, d0, null, 128, 0, false, false, false, 4, 1.0, true, null, 0, 0); |
| 150 | } catch (CKeyException e) { |
| 151 | check(e.getStatus() == 1 && !e.getMessage().isEmpty(), "strict render refusal explains: " + e.getMessage()); |
| 152 | } |
| 153 | lowContrast[14] = 1; // ADAPTIVE: the renderer repairs the style |
| 154 | byte[] adaptive = CKeyNative.nativeRenderRaster(symbol, lowContrast, d0, null, side, 0, false, false, false, 4, 1.0, true, null, 0, 0); |
| 155 | byte[] adaptiveDecoded = CKeyNative.nativeDecodeArray(frameFromRgba(adaptive, side, frameW, frameH, stride, x0, y0), 0, frameW, frameH, stride, 6, info5(), new double[8]); |
| 156 | check(adaptiveDecoded != null && Arrays.equals(adaptiveDecoded, payload), "ADAPTIVE policy repairs the style and the result decodes"); |
| 157 | |
| 158 | // Logo pixels path (straight RGBA). |
| 159 | byte[] logo = new byte[8 * 8 * 4]; |
| 160 | for (int i = 0; i < logo.length; i += 4) { logo[i] = (byte) 200; logo[i + 3] = (byte) 255; } |
| 161 | int[] logoInts = new int[15]; |
| 162 | double[] logoDoubles = new double[4]; |
| 163 | CKeyNative.nativeStylePreset(0, logoInts, logoDoubles); |
| 164 | logoInts[13] = 1; |
| 165 | byte[] withLogo = CKeyNative.nativeRenderRaster(symbol, logoInts, logoDoubles, null, 256, 0, false, false, false, 4, 1.0, true, logo, 8, 8); |
| 166 | check(withLogo.length == 256 * 256 * 4, "raster with a logo bitmap"); |
| 167 | |
| 168 | // ---- certification (optional core module) |
| 169 | if ((features & 8) != 0) { |
| 170 | int[] outI = new int[15]; |
| 171 | double[] outD = new double[4]; |
| 172 | double[] cert = new double[9]; |
| 173 | String msg = CKeyNative.nativeCertify(symbol, null, null, null, false, null, null, 0, 0, null, outI, outD, cert); |
| 174 | check(cert[0] == 0 && cert[1] == 1, "default style certified: " + msg); |
| 175 | String tmsg = CKeyNative.nativeCertify(symbol, null, null, null, true, null, null, 0, 0, null, outI, outD, cert); |
| 176 | check(cert[1] == 0, "transparent without a backdrop is never certified: " + tmsg); |
| 177 | String bmsg = CKeyNative.nativeCertify(symbol, null, null, null, true, new int[] {0xffffff, 0xeeeeee}, null, 0, 0, |
| 178 | new double[] {8, 0.9, 0.03, 0.2, 2}, outI, outD, cert); |
| 179 | check(cert[1] == 1, "transparent over light backdrops certified: " + bmsg); |
| 180 | } else { |
| 181 | System.out.println("skip certification (ckey_certify.cpp not in this core)"); |
| 182 | } |
| 183 | |
| 184 | // ---- argument validation (Java exceptions, never crashes) |
| 185 | throwsType(IllegalArgumentException.class, () -> CKeyNative.nativeSymbolInfo(new byte[10]), "wrong-size symbol blob"); |
| 186 | byte[] corrupt = symbol.clone(); |
| 187 | corrupt[0] ^= 0x55; // abi_version |
| 188 | throwsType(IllegalArgumentException.class, () -> CKeyNative.nativeRenderSvg(corrupt, null, null, null, 4), "corrupt symbol header"); |
| 189 | throwsType(IllegalArgumentException.class, () -> CKeyNative.nativeDecodeArray(frame, 0, frameW, frameH, frameW - 1, 6, info5(), new double[8]), "stride < width"); |
| 190 | throwsType(IllegalArgumentException.class, () -> CKeyNative.nativeDecodeArray(frame, 100, frameW, frameH, stride, 6, info5(), new double[8]), "offset past the end"); |
| 191 | throwsType(IllegalArgumentException.class, () -> CKeyNative.nativeDecodeBuffer(ByteBuffer.allocate(100), 0, 10, 10, 10, 6, info5(), new double[8]), "heap buffer refused by native"); |
| 192 | throwsType(IllegalArgumentException.class, () -> CKeyNative.nativeRenderRaster(symbol, new int[3], new double[4], null, 64, 0, false, false, false, 4, 1, true, null, 0, 0), "style array shape"); |
| 193 | throwsType(NullPointerException.class, () -> CKeyNative.nativeEncode(null, 0, 1, 0), "null payload"); |
| 194 | throwsType(CKeyException.class, () -> CKeyNative.nativeEncode(new byte[20000], 0, 1, 0), "oversized payload"); |
| 195 | throwsType(CKeyException.class, () -> CKeyNative.nativeEncode(new byte[100], 1, 3, 0), "payload over a fixed version's capacity"); |
| 196 | |
| 197 | // ---- telemetry |
| 198 | CKeyNative.nativeTelemetryResetForTesting(); |
| 199 | check(CKeyNative.nativeTelemetryGetMode() == 1, "telemetry default mode 1"); |
| 200 | check(CKeyNative.nativeTelemetrySetMode(3) == 1 && CKeyNative.nativeTelemetryGetMode() == 1, "mode 3 refused, mode unchanged"); |
| 201 | check(CKeyNative.nativeTelemetrySetClient("android", CKeyNative.nativeSoftwareVersion(), "android") == 0, "set_client android"); |
| 202 | check(CKeyNative.nativeTelemetrySetClient("kotlin", "1", "android") == 1, "invalid client refused"); |
| 203 | String endpoint = CKeyNative.nativeTelemetryEndpoint(); |
| 204 | check(endpoint.equals("https://openckey.org/api/telemetry/v1/batch"), "endpoint " + endpoint); |
| 205 | long t = 1000; |
| 206 | CKeyNative.nativeTelemetrySessionBegin(t); |
| 207 | CKeyNative.nativeTelemetryFrame(t + 10, 7); |
| 208 | CKeyNative.nativeTelemetryFrame(t + 50, 30); |
| 209 | check(CKeyNative.nativeTelemetrySessionSuccess(t + 60, 0) == 0, "session success P0"); |
| 210 | check(CKeyNative.nativeTelemetrySessionSuccess(t + 70, 9) == 1, "invalid profile code refused"); |
| 211 | CKeyNative.nativeTelemetrySessionEnd(t + 80); |
| 212 | long[] snap = new long[33]; |
| 213 | check(CKeyNative.nativeTelemetrySnapshot(snap) == 0 && snap[0] == 1 && snap[1] == 1 && snap[3] == 2 && snap[4] == 1, "snapshot " + Arrays.toString(Arrays.copyOf(snap, 9))); |
| 214 | check(snap[9] == 1 && snap[10] == 60 && snap[11 + 2] == 1, "time-to-decode histogram (60 ms in bucket 2)"); |
| 215 | check(!CKeyNative.nativeTelemetryShouldSend(t + 100), "no batch before the delta is 60 s old"); |
| 216 | check(CKeyNative.nativeTelemetryShouldSend(t + 61_000), "batch due after 60 s"); |
| 217 | long[] id = new long[1]; |
| 218 | String batch = CKeyNative.nativeTelemetryTakeBatch("2026-11-05", id); |
| 219 | check(batch != null && id[0] != 0 && batch.contains("\"sdk\":\"android\"") && batch.contains("\"platform\":\"android\"") && batch.contains("\"day\":\"2026-11-05\""), "batch JSON: " + batch); |
| 220 | check(CKeyNative.nativeTelemetryTakeBatch("2026-11-05", new long[1]) == null, "no second batch while one is in flight"); |
| 221 | throwsType(CKeyException.class, () -> CKeyNative.nativeTelemetryTakeBatch("2026-13-45", new long[1]), "invalid day refused"); |
| 222 | check(CKeyNative.nativeTelemetryBatchResult(id[0], 204, t + 62_000) == 0, "batch result 204"); |
| 223 | check(CKeyNative.nativeTelemetryBatchResult(id[0], 204, t + 62_000) == 1, "stale batch id refused"); |
| 224 | String state = CKeyNative.nativeTelemetryExportState(); |
| 225 | check(state.startsWith("{\"state\":1"), "export state"); |
| 226 | check(CKeyNative.nativeTelemetrySetMode(0) == 0 && CKeyNative.nativeTelemetrySnapshot(snap) == 0 && snap[0] == 0, "mode 0 purges counters"); |
| 227 | check(CKeyNative.nativeTelemetryImportState(state) == 1, "import refused in mode 0"); |
| 228 | CKeyNative.nativeTelemetrySetMode(2); |
| 229 | check(CKeyNative.nativeTelemetryImportState(state) == 0, "import accepted in mode 2"); |
| 230 | CKeyNative.nativeTelemetrySessionBegin(t); |
| 231 | check(!CKeyNative.nativeTelemetryShouldSend(t + 10_000_000), "mode 2 never sends"); |
| 232 | CKeyNative.nativeTelemetryResetForTesting(); |
| 233 | |
| 234 | safeActions(); |
| 235 | System.out.println("HostHarness: " + checks + " checks passed"); |
| 236 | } |
| 237 | |
| 238 | static String field(String block, String name) { |
| 239 | java.util.regex.Matcher m = java.util.regex.Pattern.compile("\"" + name + "\":\\s*(\"[^\"]*\"|[0-9]+)").matcher(block); |
| 240 | if (!m.find()) throw new AssertionError("vector without " + name); |
| 241 | String v = m.group(1); |
| 242 | return v.startsWith("\"") ? v.substring(1, v.length() - 1) : v; |
| 243 | } |
| 244 | |
| 245 | /** tests/vectors/safe-actions-v1.json through nativeSafeAction (the bridge, not the policy). */ |
| 246 | static void safeActions() throws Exception { |
| 247 | String path = System.getProperty("org.openckey.ckey.vectors"); |
| 248 | if (path == null) { System.out.println("skip safe action vectors (no org.openckey.ckey.vectors)"); return; } |
| 249 | String json = new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(path)), StandardCharsets.UTF_8); |
| 250 | String[] blocks = json.split("\\{\\s*\"name\":"); |
| 251 | int n = 0; |
| 252 | for (int i = 1; i < blocks.length; i++) { |
| 253 | String b = blocks[i]; |
| 254 | String name = b.substring(b.indexOf('"') + 1, b.indexOf('"', b.indexOf('"') + 1)); |
| 255 | byte[] payload = field(b, "payload").getBytes(StandardCharsets.UTF_8); |
| 256 | String hex = field(b, "public_key"); |
| 257 | byte[] key = new byte[32]; |
| 258 | for (int k = 0; k < 32; k++) key[k] = (byte) Integer.parseInt(hex.substring(2 * k, 2 * k + 2), 16); |
| 259 | int[] status = new int[1]; |
| 260 | String out = CKeyNative.nativeSafeAction(payload, key, Long.parseLong(field(b, "now")), Integer.parseInt(field(b, "max_lifetime")), status); |
| 261 | String expect = field(b, "expect"); |
| 262 | boolean ok = switch (expect) { |
| 263 | case "ok" -> status[0] == 0 && out.split("\n", -1).length == 9 && out.contains("\n" + field(b, "request_id") + "\n"); |
| 264 | case "integrity" -> status[0] == 6; |
| 265 | case "expired" -> status[0] == 7; |
| 266 | case "invalid" -> status[0] == 1; |
| 267 | case "invalid_or_integrity" -> status[0] == 1 || status[0] == 6; |
| 268 | default -> false; |
| 269 | }; |
| 270 | if (!ok) throw new AssertionError("FAILED: safe action vector \"" + name + "\": expected " + expect + ", got " + status[0] + " " + out); |
| 271 | n++; |
| 272 | } |
| 273 | check(n >= 20, "safe action vectors: " + n + " verdicts match"); |
| 274 | int[] status = new int[1]; |
| 275 | String first = field(blocks[1], "payload"); |
| 276 | String parsed = CKeyNative.nativeSafeAction(first.getBytes(StandardCharsets.UTF_8), null, 0, 3600, status); |
| 277 | check(status[0] == 0 && parsed.startsWith("0\ncitee".replace("citee", "citeory.com")), "parse only (no key)"); |
| 278 | CKeyNative.nativeSafeAction("ckey-action:1:AAAA".getBytes(StandardCharsets.UTF_8), null, 0, 3600, status); |
| 279 | check(status[0] == 1, "malformed envelope -> invalid, no exception"); |
| 280 | throwsType(IllegalArgumentException.class, () -> CKeyNative.nativeSafeAction(new byte[1], new byte[31], 0, 600, new int[1]), "short public key"); |
| 281 | } |
| 282 | } |