Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

ckey_web.cpp

v0.1.0-build.85 Latest Commit ede38f5

Size
25 KB
Lines
550
Language
cpp
SHA-256
0e71c025c522…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1// CKey Web SDK bridge: a thin, malloc-friendly C surface over the public C ABI
2// (include/ckey/ckey.h, include/ckey/telemetry.h) for the JavaScript wrapper in
3// sdk/web/js/. It never reimplements encoding, decoding or rendering; it only
4// marshals arguments and orchestrates the reader pipeline that
5// ios/CKeyReader/CameraScanner.swift uses (find borders -> refine per field ->
6// decode, Profile 0 first).
7//
8// Conventions for JS callers:
9// * Every parameter and result is an int, uint32, double or pointer: no
10// 64-bit integers cross the boundary (times and batch ids are doubles,
11// which are exact up to 2^53), so no BigInt support is needed.
12// * Functions that produce variable-length output return a malloc'd buffer
13// (or 0 on failure) and set ckw_last_len(); the caller copies it out and
14// frees it with ckw_free().
15// * On failure ckw_last_error() holds a NUL-terminated message (from the
16// core's diagnostics where it gives one).
17
18#include "ckey/ckey.h"
19#ifndef CKW_NO_TELEMETRY
20#include "ckey/telemetry.h"
21#endif
22
23#include <cmath>
24#include <cstdint>
25#include <cstdio>
26#include <cstdlib>
27#include <cstring>
28#include <initializer_list>
29#include <new>
30#include <memory>
31#include <string>
32#include <vector>
33
34#ifdef __EMSCRIPTEN__
35#include <emscripten/emscripten.h>
36#define CKW_EXPORT extern "C" EMSCRIPTEN_KEEPALIVE
37#else
38#define CKW_EXPORT extern "C"
39#endif
40
41namespace {
42
43char g_error[300] = "";
44size_t g_last_len = 0;
45double g_last_batch_id = 0;
46
47void set_error(const char *msg) {
48 std::snprintf(g_error, sizeof g_error, "%s", msg ? msg : "");
49}
50
51void set_error_diag(const ckey_diagnostics &d, const char *fallback) {
52 if (d.message[0]) {
53 char tmp[sizeof d.message + 1];
54 std::memcpy(tmp, d.message, sizeof d.message);
55 tmp[sizeof d.message] = '\0';
56 set_error(tmp);
57 } else {
58 set_error(fallback);
59 }
60}
61
62// A style plus storage for its logo URL (ckey_style holds a borrowed pointer).
63struct WebStyle {
64 ckey_style style;
65 std::string logo_href;
66};
67
68uint8_t *dup_bytes(const void *src, size_t n) {
69 uint8_t *out = static_cast<uint8_t *>(std::malloc(n ? n : 1));
70 if (!out) { set_error("out of memory"); return nullptr; }
71 if (n) std::memcpy(out, src, n);
72 g_last_len = n;
73 return out;
74}
75
76uint64_t to_ms(double v) {
77 if (!(v > 0)) return 0; // also NaN
78 if (v >= 18446744073709549568.0) return UINT64_MAX;
79 return static_cast<uint64_t>(v);
80}
81
82const ckey_style *style_of(const WebStyle *ws, ckey_style &fallback) {
83 if (ws) return &ws->style;
84 ckey_style_default(&fallback);
85 return &fallback;
86}
87
88} // namespace
89
90// ---- Memory and errors ------------------------------------------------------
91
92CKW_EXPORT void *ckw_malloc(uint32_t n) { return std::malloc(n ? n : 1); }
93CKW_EXPORT void ckw_free(void *p) { std::free(p); }
94CKW_EXPORT const char *ckw_last_error(void) { return g_error; }
95CKW_EXPORT uint32_t ckw_last_len(void) { return static_cast<uint32_t>(g_last_len); }
96CKW_EXPORT const char *ckw_software_version(void) { return ckey_get_software_version(); }
97CKW_EXPORT uint32_t ckw_abi_version(void) { return ckey_get_abi_version(); }
98
99// ---- Safe Actions -------------------------------------------------------------
100
101// Parses (key == null) or verifies a Safe Action and returns a malloc'd JSON
102// object of its fields, or null with the reason in ckw_last_error(). The status
103// is in "status": 0 OK, 6 bad signature, 7 expired. Every text field is limited by
104// the core to a JSON-safe character set; params are hexadecimal.
105CKW_EXPORT char *ckw_safe_action(const uint8_t *payload, uint32_t len, const uint8_t *key, double now, uint32_t max_lifetime) {
106 auto sa = std::make_unique<ckey_safe_action>();
107 ckey_diagnostics diag{};
108 const ckey_status st = key
109 ? ckey_safe_action_verify(payload, len, key, static_cast<uint64_t>(now), max_lifetime, sa.get(), &diag)
110 : ckey_safe_action_parse(payload, len, sa.get(), &diag);
111 if (st != CKEY_OK) { set_error_diag(diag, "not a valid Safe Action"); return nullptr; }
112 static const char hexd[] = "0123456789abcdef";
113 std::string params;
114 for (size_t i = 0; i < sa->params_len; ++i) { params.push_back(hexd[sa->params[i] >> 4]); params.push_back(hexd[sa->params[i] & 15]); }
115 std::string json = std::string("{\"scope\":") + std::to_string(sa->scope) + ",\"issuer\":\"" + sa->issuer + "\",\"app\":\"" + sa->app +
116 "\",\"action\":\"" + sa->action + "\",\"key_id\":\"" + sa->key_id + "\",\"request_id\":\"" + sa->request_id +
117 "\",\"issued_at\":" + std::to_string(sa->issued_at) + ",\"expires_at\":" + std::to_string(sa->expires_at) +
118 ",\"params\":\"" + params + "\"}";
119 char *out = static_cast<char *>(std::malloc(json.size() + 1));
120 if (!out) { set_error("out of memory"); return nullptr; }
121 std::memcpy(out, json.c_str(), json.size() + 1);
122 g_last_len = json.size();
123 return out;
124}
125
126// ---- Symbols ----------------------------------------------------------------
127
128CKW_EXPORT uint32_t ckw_scalable_capacity(uint32_t version, uint32_t level, uint32_t layout) {
129 return static_cast<uint32_t>(ckey_scalable_capacity(version, static_cast<ckey_ecc_level>(level),
130 static_cast<ckey_scalable_layout>(layout)));
131}
132
133CKW_EXPORT uint32_t ckw_scalable_modules_across(uint32_t version) { return ckey_scalable_modules_across(version); }
134
135// Encodes a Profile 0 symbol. Returns a heap ckey_scalable_symbol (free with
136// ckw_symbol_free) or 0 with ckw_last_error() set.
137CKW_EXPORT ckey_scalable_symbol *ckw_encode(const uint8_t *payload, uint32_t len, uint32_t version,
138 uint32_t level, uint32_t layout) {
139 auto *sym = static_cast<ckey_scalable_symbol *>(std::calloc(1, sizeof(ckey_scalable_symbol)));
140 if (!sym) { set_error("out of memory"); return nullptr; }
141 ckey_diagnostics d{};
142 const ckey_status st = ckey_encode_scalable(payload, len, version, static_cast<ckey_ecc_level>(level),
143 static_cast<ckey_scalable_layout>(layout), sym, &d);
144 if (st != CKEY_OK) {
145 set_error_diag(d, "encode failed");
146 std::free(sym);
147 return nullptr;
148 }
149 return sym;
150}
151
152CKW_EXPORT void ckw_symbol_free(ckey_scalable_symbol *sym) { std::free(sym); }
153
154// out[0..4] = version, ecc_level, layout, payload_length, codeword_length.
155CKW_EXPORT void ckw_symbol_info(const ckey_scalable_symbol *sym, uint32_t *out) {
156 if (!sym || !out) return;
157 out[0] = sym->version;
158 out[1] = sym->ecc_level;
159 out[2] = sym->layout;
160 out[3] = sym->payload_length;
161 out[4] = sym->codeword_length;
162}
163
164// ---- Styles -----------------------------------------------------------------
165
166// Returns a style filled from a preset (0 plain, 1 concept), or 0.
167CKW_EXPORT WebStyle *ckw_style_new(uint32_t preset) {
168 auto *ws = new (std::nothrow) WebStyle();
169 if (!ws) { set_error("out of memory"); return nullptr; }
170 if (ckey_style_preset_fill(&ws->style, static_cast<ckey_style_preset>(preset)) != CKEY_OK) {
171 set_error("unknown style preset");
172 delete ws;
173 return nullptr;
174 }
175 return ws;
176}
177
178CKW_EXPORT void ckw_style_free(WebStyle *ws) { delete ws; }
179
180// Colour fields: 0 background, 1 background_edge, 2 ink, 3 ink_shade, 4..7 accents[0..3].
181CKW_EXPORT int ckw_style_set_rgb(WebStyle *ws, uint32_t field, uint32_t r, uint32_t g, uint32_t b) {
182 if (!ws || r > 255 || g > 255 || b > 255) return 0;
183 ckey_rgb c{static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b)};
184 ckey_style &s = ws->style;
185 switch (field) {
186 case 0: s.background = c; break;
187 case 1: s.background_edge = c; break;
188 case 2: s.ink = c; break;
189 case 3: s.ink_shade = c; break;
190 case 4: case 5: case 6: case 7: s.accents[field - 4] = c; break;
191 default: return 0;
192 }
193 return 1;
194}
195
196// Numeric fields: 0 accent_count, 1 accent_percent, 2 modules, 3 rings, 4 rays,
197// 5 bevel, 6 shadow, 7 texture, 8 logo_radius, 9 logo_plate, 10 policy.
198// Range checks beyond the type are the core style check's job.
199CKW_EXPORT int ckw_style_set_num(WebStyle *ws, uint32_t field, double v) {
200 if (!ws || !std::isfinite(v)) return 0;
201 ckey_style &s = ws->style;
202 const auto u = [&](uint32_t &dst) { if (v < 0 || v > 4294967295.0) return 0; dst = static_cast<uint32_t>(v); return 1; };
203 switch (field) {
204 case 0: return u(s.accent_count);
205 case 1: return u(s.accent_percent);
206 case 2: if (v < 0 || v > 3) return 0; s.modules = static_cast<ckey_module_shape>(static_cast<int>(v)); return 1;
207 case 3: if (v < 0 || v > 1) return 0; s.rings = static_cast<ckey_ring_style>(static_cast<int>(v)); return 1;
208 case 4: if (v < 0 || v > 1) return 0; s.rays = static_cast<ckey_ray_cap>(static_cast<int>(v)); return 1;
209 case 5: s.bevel = v; return 1;
210 case 6: s.shadow = v; return 1;
211 case 7: s.texture = v; return 1;
212 case 8: s.logo_radius = v; return 1;
213 case 9: return u(s.logo_plate);
214 case 10:
215#if CKEY_ABI_VERSION >= 4
216 return u(s.policy);
217#else
218 return v == 0 ? 1 : 0; // this core has only the strict behaviour
219#endif
220 default: return 0;
221 }
222}
223
224CKW_EXPORT int ckw_style_set_logo(WebStyle *ws, const char *href) {
225 if (!ws) return 0;
226 ws->logo_href = href ? href : "";
227 ws->style.logo_href = ws->logo_href.empty() ? nullptr : ws->logo_href.c_str();
228 return 1;
229}
230
231// CKEY_OK (0) or a status with ckw_last_error() explaining the refusal.
232CKW_EXPORT int ckw_style_check(const ckey_scalable_symbol *sym, const WebStyle *ws) {
233 ckey_style fallback;
234 ckey_diagnostics d{};
235 const ckey_status st = ckey_check_scalable_style(sym, style_of(ws, fallback), &d);
236 if (st != CKEY_OK) set_error_diag(d, "style check failed");
237 else set_error_diag(d, "");
238 return st;
239}
240
241// ---- Rendering --------------------------------------------------------------
242// Renderers apply the style's own policy (strict refuses, adaptive repairs,
243// override draws as given), so the bridge does not pre-check the style; when
244// a render is refused it asks the style check for the reason.
245
246namespace {
247void explain_refusal(const ckey_scalable_symbol *sym, const WebStyle *ws, const char *fallback) {
248 ckey_style fallback_style;
249 ckey_diagnostics d{};
250 if (ckey_check_scalable_style(sym, style_of(ws, fallback_style), &d) != CKEY_OK) set_error_diag(d, fallback);
251 else set_error(fallback);
252}
253} // namespace
254
255// Renders RGBA8 (format 0) or GRAY8 (format 1) pixels: size x size, rows top to
256// bottom. logo_rgba (optional, straight RGBA8, logo_w x logo_h) is drawn into
257// the style's logo circle. Returns malloc'd pixels or 0.
258CKW_EXPORT uint8_t *ckw_render_raster(const ckey_scalable_symbol *sym, const WebStyle *ws, uint32_t size,
259 uint32_t policy, uint32_t transparent, uint32_t supersample,
260 double min_feature_px, uint32_t materials, uint32_t format,
261 const uint8_t *logo_rgba, uint32_t logo_w, uint32_t logo_h) {
262 if (!sym) { set_error("missing symbol"); return nullptr; }
263 ckey_style fallback;
264 const ckey_style *style = style_of(ws, fallback);
265 ckey_raster_options o;
266 ckey_raster_options_default(&o);
267 o.size_px = size;
268 o.policy = policy;
269 o.format = format;
270 o.transparent = transparent;
271 o.supersample = supersample;
272 o.min_feature_px = min_feature_px;
273 o.materials = materials;
274 ckey_logo_image logo{};
275 if (logo_rgba && logo_w && logo_h) {
276 logo.rgba = logo_rgba;
277 logo.width = logo_w;
278 logo.height = logo_h;
279 logo.stride = 0;
280 o.logo = &logo;
281 }
282 size_t need = 0;
283 ckey_status st = ckey_render_scalable_raster(sym, style, &o, nullptr, 0, &need);
284 if (st != CKEY_INSUFFICIENT_BUFFER || need == 0) {
285 explain_refusal(sym, ws, "invalid raster options (size 16..16384, supersample 1..8, "
286 "min feature > 0; transparent needs RGBA)");
287 return nullptr;
288 }
289 auto *out = static_cast<uint8_t *>(std::malloc(need));
290 if (!out) { set_error("out of memory"); return nullptr; }
291 st = ckey_render_scalable_raster(sym, style, &o, out, need, &need);
292 if (st != CKEY_OK) { std::free(out); set_error("raster render failed"); return nullptr; }
293 g_last_len = need;
294 return out;
295}
296
297// Encodes RGBA8 pixels (stride = width * 4) as a PNG. dpi <= 0 omits pHYs.
298CKW_EXPORT uint8_t *ckw_encode_png_rgba(const uint8_t *rgba, uint32_t width, uint32_t height, double dpi) {
299 size_t need = 0;
300 const size_t stride = static_cast<size_t>(width) * 4;
301 ckey_status st = ckey_encode_png(rgba, width, height, stride, CKEY_PNG_RGBA8, dpi, nullptr, 0, &need);
302 if ((st != CKEY_INSUFFICIENT_BUFFER && st != CKEY_OK) || need == 0) { set_error("PNG encode failed"); return nullptr; }
303 auto *out = static_cast<uint8_t *>(std::malloc(need));
304 if (!out) { set_error("out of memory"); return nullptr; }
305 st = ckey_encode_png(rgba, width, height, stride, CKEY_PNG_RGBA8, dpi, out, need, &need);
306 if (st != CKEY_OK) { std::free(out); set_error("PNG encode failed"); return nullptr; }
307 g_last_len = need;
308 return out;
309}
310
311// Styled SVG; scale is pixels per canonical unit (width = 31 * scale).
312// Returns malloc'd UTF-8 (NUL-terminated; ckw_last_len excludes the NUL).
313CKW_EXPORT char *ckw_render_svg(const ckey_scalable_symbol *sym, const WebStyle *ws, uint32_t scale) {
314 if (!sym) { set_error("missing symbol"); return nullptr; }
315 ckey_style fallback;
316 const ckey_style *style = style_of(ws, fallback);
317 size_t need = 0;
318 ckey_status st = ckey_render_scalable_styled_svg(sym, style, scale, nullptr, 0, &need);
319 if (st != CKEY_INSUFFICIENT_BUFFER || need == 0) { explain_refusal(sym, ws, "SVG render failed (scale must be 1..256)"); return nullptr; }
320 auto *out = static_cast<char *>(std::malloc(need));
321 if (!out) { set_error("out of memory"); return nullptr; }
322 st = ckey_render_scalable_styled_svg(sym, style, scale, out, need, &need);
323 if (st != CKEY_OK) { std::free(out); set_error("SVG render failed"); return nullptr; }
324 g_last_len = std::strlen(out);
325 return out;
326}
327
328CKW_EXPORT uint8_t *ckw_render_pdf(const ckey_scalable_symbol *sym, const WebStyle *ws, double width_pt,
329 uint32_t transparent) {
330 if (!sym) { set_error("missing symbol"); return nullptr; }
331 ckey_style fallback;
332 const ckey_style *style = style_of(ws, fallback);
333 size_t need = 0;
334 ckey_status st = ckey_render_scalable_pdf(sym, style, width_pt, transparent, nullptr, 0, &need);
335 if (st != CKEY_INSUFFICIENT_BUFFER || need == 0) { explain_refusal(sym, ws, "PDF render failed (invalid width)"); return nullptr; }
336 auto *out = static_cast<uint8_t *>(std::malloc(need));
337 if (!out) { set_error("out of memory"); return nullptr; }
338 st = ckey_render_scalable_pdf(sym, style, width_pt, transparent, out, need, &need);
339 if (st != CKEY_OK) { std::free(out); set_error("PDF render failed"); return nullptr; }
340 g_last_len = need;
341 return out;
342}
343
344// ---- Scanning ---------------------------------------------------------------
345
346namespace {
347
348constexpr size_t kMaxBorders = 6;
349constexpr size_t kScalableOut = 65535; // P0 reaches 4,619 bytes at v31/M; same buffer as the iOS reader
350
351// out_info[0] = profile code (0 P0, 1 P0.1 print, 2 P1, 3 P2, 4 P3: the
352// telemetry profile codes), out_info[1] = P0 version (0 for P1..P3),
353// out_info[2] = P0 layout (0 standard, 1 print; 0 for P1..P3),
354// out_info[3] = number of border candidates found.
355uint8_t *decode_gray(const uint8_t *gray, size_t w, size_t h, size_t stride, uint32_t *out_info) {
356 if (!gray || !out_info || w == 0 || h == 0) { set_error("invalid frame"); return nullptr; }
357 double borders[kMaxBorders * 8];
358 const size_t n = ckey_find_borders_gray8(gray, w, h, stride, borders, kMaxBorders);
359 out_info[3] = static_cast<uint32_t>(n);
360 if (n == 0) { set_error("no CKey border found"); return nullptr; }
361 std::vector<uint8_t> out(kScalableOut);
362 ckey_diagnostics d{};
363 for (size_t i = 0; i < n; ++i) {
364 const double *border = borders + 8 * i;
365 double q[8];
366 size_t len = 0;
367 // Profile 0 (scalable family) first: the core aligns the frame,
368 // identifies layout and version from the keys, then decodes.
369 uint32_t version = 0, layout = 0;
370 if (ckey_refine_quad_gray8(gray, w, h, stride, border, CKEY_FIELD_SCALABLE, q) >= 0 &&
371 ckey_decode_scalable_gray8(gray, w, h, stride, q, out.data(), out.size(), &len, &version, &layout, &d) == CKEY_OK) {
372 out_info[0] = layout == CKEY_LAYOUT_PRINT ? 1u : 0u;
373 out_info[1] = version;
374 out_info[2] = layout;
375 return dup_bytes(out.data(), len);
376 }
377 // Legacy profiles, each refined against its own calibration field
378 // (never let one profile's fit pick another's transform).
379 if (ckey_refine_quad_gray8(gray, w, h, stride, border, CKEY_FIELD_LOCATOR, q) >= 0 &&
380 ckey_decode_gray8(gray, w, h, stride, q, out.data(), CKEY_LOCATOR_PAYLOAD_BYTES, &d) == CKEY_OK) {
381 out_info[0] = 2; out_info[1] = 0; out_info[2] = 0;
382 return dup_bytes(out.data(), CKEY_LOCATOR_PAYLOAD_BYTES);
383 }
384 if (ckey_refine_quad_gray8(gray, w, h, stride, border, CKEY_FIELD_TEXT, q) >= 0 &&
385 ckey_decode_text_gray8(gray, w, h, stride, q, out.data(), out.size(), &len, &d) == CKEY_OK) {
386 out_info[0] = 3; out_info[1] = 0; out_info[2] = 0;
387 return dup_bytes(out.data(), len);
388 }
389 if (ckey_refine_quad_gray8(gray, w, h, stride, border, CKEY_FIELD_EXTENDED, q) >= 0 &&
390 ckey_decode_extended_gray8(gray, w, h, stride, q, out.data(), out.size(), &len, &d) == CKEY_OK) {
391 out_info[0] = 4; out_info[1] = 0; out_info[2] = 0;
392 return dup_bytes(out.data(), len);
393 }
394 }
395 set_error_diag(d, "no CKey decoded");
396 return nullptr;
397}
398
399} // namespace
400
401// Decodes an 8-bit grayscale frame. Returns malloc'd payload bytes or 0.
402CKW_EXPORT uint8_t *ckw_decode_gray(const uint8_t *gray, uint32_t width, uint32_t height, uint32_t stride,
403 uint32_t *out_info) {
404 return decode_gray(gray, width, height, stride ? stride : width, out_info);
405}
406
407// Decodes an RGBA8 frame (as from ImageData). Pixels are composited over
408// white (so a transparent render reads as ink on paper) and converted to
409// Rec. 709 luma, then decoded as by ckw_decode_gray.
410CKW_EXPORT uint8_t *ckw_decode_rgba(const uint8_t *rgba, uint32_t width, uint32_t height, uint32_t stride,
411 uint32_t *out_info) {
412 if (!rgba || width == 0 || height == 0) { set_error("invalid frame"); return nullptr; }
413 const size_t row = stride ? stride : static_cast<size_t>(width) * 4;
414 std::vector<uint8_t> gray(static_cast<size_t>(width) * height);
415 for (size_t y = 0; y < height; ++y) {
416 const uint8_t *p = rgba + y * row;
417 uint8_t *g = gray.data() + y * width;
418 for (size_t x = 0; x < width; ++x, p += 4) {
419 // Rec. 709 luma in 8.8 fixed point: 0.2126, 0.7152, 0.0722.
420 const uint32_t luma = (54u * p[0] + 183u * p[1] + 19u * p[2] + 128u) >> 8;
421 const uint32_t a = p[3];
422 g[x] = static_cast<uint8_t>((luma * a + 255u * (255u - a) + 127u) / 255u);
423 }
424 }
425 return decode_gray(gray.data(), width, height, width, out_info);
426}
427
428// ---- Optical certification (ckey_certify; present from ABI 4) ---------------
429
430#ifdef CKEY_ADJUST_INK
431CKW_EXPORT uint32_t ckw_has_certify(void) { return 1; }
432
433// Certifies a style (0 = default) under its policy. colours: colour_count RGB
434// triplets (3 bytes each) the symbol may sit on; bd_rgba (optional) host
435// pixels behind the symbol. Model parameters < 0 keep the default (trials 0).
436// out (8 doubles): certified, policy, adjustments, trials, trials_passed,
437// min_contrast, feature_separation, logo_load. The certification message is
438// in ckw_last_error(). If out_style is non-zero, the style to render (the
439// adaptive repair) is written into it. Returns the core status.
440CKW_EXPORT int ckw_certify(const ckey_scalable_symbol *sym, const WebStyle *ws, uint32_t transparent,
441 const uint8_t *colours, uint32_t colour_count, const uint8_t *bd_rgba,
442 uint32_t bd_w, uint32_t bd_h, double px_per_module, double blur_px, double noise,
443 double glare, uint32_t trials, double *out, WebStyle *out_style) {
444 ckey_style fallback;
445 const ckey_style *style = style_of(ws, fallback);
446 std::vector<ckey_rgb> cs;
447 for (uint32_t i = 0; colours && i < colour_count; ++i) cs.push_back({colours[3 * i], colours[3 * i + 1], colours[3 * i + 2]});
448 ckey_backdrop bd{};
449 bd.colours = cs.empty() ? nullptr : cs.data();
450 bd.colour_count = static_cast<uint32_t>(cs.size());
451 if (bd_rgba && bd_w && bd_h) { bd.rgba = bd_rgba; bd.width = bd_w; bd.height = bd_h; bd.stride = 0; }
452 const bool has_backdrop = bd.colour_count || bd.rgba;
453 ckey_scan_model m;
454 ckey_scan_model_default(&m);
455 if (px_per_module >= 0) m.px_per_module = px_per_module;
456 if (blur_px >= 0) m.blur_px = blur_px;
457 if (noise >= 0) m.noise = noise;
458 if (glare >= 0) m.glare = glare;
459 if (trials) m.trials = trials;
460 ckey_style repaired;
461 std::memcpy(&repaired, style, sizeof repaired);
462 ckey_certification c{};
463 const ckey_status st = ckey_certify_scalable_style(sym, style, transparent, has_backdrop ? &bd : nullptr, &m,
464 &repaired, &c);
465 char msg[sizeof c.message + 1];
466 std::memcpy(msg, c.message, sizeof c.message);
467 msg[sizeof c.message] = '\0';
468 set_error(msg[0] ? msg : (st == CKEY_OK ? "" : "certification failed"));
469 if (out) {
470 out[0] = c.certified; out[1] = c.policy; out[2] = c.adjustments; out[3] = c.trials;
471 out[4] = c.trials_passed; out[5] = c.min_contrast; out[6] = c.feature_separation; out[7] = c.logo_load;
472 }
473 if (out_style && st == CKEY_OK) {
474 out_style->style = repaired;
475 // Keep the logo string owned by out_style.
476 out_style->logo_href = repaired.logo_href ? repaired.logo_href : "";
477 out_style->style.logo_href = out_style->logo_href.empty() ? nullptr : out_style->logo_href.c_str();
478 }
479 return st;
480}
481#else
482CKW_EXPORT uint32_t ckw_has_certify(void) { return 0; }
483#endif
484
485// ---- Telemetry --------------------------------------------------------------
486// Thin double-typed wrappers; the core does no networking and reads no clock.
487
488#ifndef CKW_NO_TELEMETRY
489CKW_EXPORT int ckw_tel_set_mode(int mode) { return ckey_telemetry_set_mode(mode); }
490CKW_EXPORT int ckw_tel_get_mode(void) { return ckey_telemetry_get_mode(); }
491CKW_EXPORT int ckw_tel_set_client(const char *sdk, const char *version, const char *platform) {
492 return ckey_telemetry_set_client(sdk, version, platform);
493}
494CKW_EXPORT void ckw_tel_session_begin(double now_ms) { ckey_telemetry_session_begin(to_ms(now_ms)); }
495CKW_EXPORT void ckw_tel_frame(double now_ms, double processing_ms) {
496 const uint64_t p = to_ms(processing_ms);
497 ckey_telemetry_frame(to_ms(now_ms), p > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(p));
498}
499CKW_EXPORT int ckw_tel_session_success(double now_ms, uint32_t profile_code) {
500 return ckey_telemetry_session_success(to_ms(now_ms), profile_code);
501}
502CKW_EXPORT void ckw_tel_session_end(double now_ms) { ckey_telemetry_session_end(to_ms(now_ms)); }
503CKW_EXPORT int ckw_tel_should_send(double now_ms) { return ckey_telemetry_should_send(to_ms(now_ms)); }
504
505// Takes a batch. Returns malloc'd JSON (NUL-terminated) and sets ckw_last_len()
506// and ckw_tel_last_batch_id(); returns 0 when there is nothing to send.
507CKW_EXPORT char *ckw_tel_take_batch(const char *utc_day) {
508 g_last_batch_id = 0;
509 g_last_len = 0;
510 char buf[CKEY_TELEMETRY_MAX_BODY_BYTES + 1];
511 size_t len = 0;
512 uint64_t id = 0;
513 const ckey_status st = ckey_telemetry_take_batch(utc_day, buf, sizeof buf, &len, &id);
514 if (st != CKEY_OK) { set_error("telemetry batch failed"); return nullptr; }
515 if (id == 0 || len == 0) return nullptr;
516 char *out = static_cast<char *>(std::malloc(len + 1));
517 if (!out) { set_error("out of memory"); return nullptr; }
518 std::memcpy(out, buf, len + 1);
519 g_last_len = len;
520 g_last_batch_id = static_cast<double>(id);
521 return out;
522}
523CKW_EXPORT double ckw_tel_last_batch_id(void) { return g_last_batch_id; }
524CKW_EXPORT int ckw_tel_batch_result(double batch_id, int http_status, double now_ms) {
525 return ckey_telemetry_batch_result(to_ms(batch_id), http_status, to_ms(now_ms));
526}
527
528// out (33 doubles): sessions started, succeeded, failed, frames, decoded[5],
529// time_to_decode {count, sum, buckets[10]}, frame_ms {count, sum, buckets[10]}.
530CKW_EXPORT int ckw_tel_snapshot(double *out) {
531 ckey_telemetry_counters c{};
532 const ckey_status st = ckey_telemetry_snapshot(&c);
533 if (st != CKEY_OK || !out) return st;
534 size_t k = 0;
535 out[k++] = static_cast<double>(c.sessions_started);
536 out[k++] = static_cast<double>(c.sessions_succeeded);
537 out[k++] = static_cast<double>(c.sessions_failed);
538 out[k++] = static_cast<double>(c.frames);
539 for (uint64_t v : c.decoded) out[k++] = static_cast<double>(v);
540 for (const ckey_telemetry_histogram *hgm : {&c.time_to_decode_ms, &c.frame_ms}) {
541 out[k++] = static_cast<double>(hgm->count);
542 out[k++] = static_cast<double>(hgm->sum);
543 for (uint64_t v : hgm->buckets) out[k++] = static_cast<double>(v);
544 }
545 return CKEY_OK;
546}
547
548// TEST ONLY: restores the telemetry defaults.
549CKW_EXPORT void ckw_tel_reset_for_testing(void) { ckey_telemetry_reset_for_testing(); }
550#endif