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

ckey_conformance.cpp

v0.1.0-build.85 Latest Commit ede38f5

Size
11 KB
Lines
163
Language
cpp
SHA-256
b08306f30364…
View on GitHub

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

1// CKey conformance vectors (specification §6, acceptance criterion 1).
2//
3// ckey-conformance --write tests/vectors/conformance-v1.json
4// ckey-conformance --check tests/vectors/conformance-v1.json
5//
6// Each vector fixes an input (profile, version, level, layout, payload) and
7// the expected outputs: the codeword (hex, plus FNV-1a 64), the canonical
8// geometry (shape count and an FNV-1a 64 over every shape with coordinates
9// rounded to 1e-6 units), the .ckey file hash, and a behavioural check that
10// a binary raster at 8 px/module decodes back to the payload. Any SDK can
11// load the same JSON and compare its own results. Hashes are FNV-1a 64 over
12// the bytes described in tools/conformance/README.md.
13#include "ckey/ckey.h"
14
15#include <cmath>
16#include <cstdio>
17#include <cstring>
18#include <fstream>
19#include <iostream>
20#include <map>
21#include <memory>
22#include <sstream>
23#include <string>
24#include <vector>
25
26namespace {
27
28uint64_t fnv(const uint8_t *p, size_t n, uint64_t h = 0xcbf29ce484222325ull) { for (size_t i = 0; i < n; ++i) { h ^= p[i]; h *= 0x100000001b3ull; } return h; }
29std::string hex(const uint8_t *p, size_t n) { static const char *d = "0123456789abcdef"; std::string s; for (size_t i = 0; i < n; ++i) { s += d[p[i] >> 4]; s += d[p[i] & 15]; } return s; }
30std::string hex64(uint64_t v) { char b[17]; std::snprintf(b, sizeof(b), "%016llx", static_cast<unsigned long long>(v)); return b; }
31std::vector<uint8_t> unhex(const std::string &s) { std::vector<uint8_t> o; for (size_t i = 0; i + 1 < s.size(); i += 2) o.push_back(static_cast<uint8_t>(std::stoi(s.substr(i, 2), nullptr, 16))); return o; }
32
33struct Input { std::string id, profile; uint32_t version = 0, level = 1, layout = 0; std::vector<uint8_t> payload; };
34using Result = std::map<std::string, std::string>;
35
36// Geometry hash: for each shape, kind, layer, paint, round_cap as int32 LE,
37// then p[0..4] and width as int64 LE of round(value * 1e6).
38uint64_t geometry_hash(const std::vector<ckey_shape> &shapes) {
39 uint64_t h = 0xcbf29ce484222325ull;
40 for (const auto &s : shapes) {
41 const int32_t ints[4] = {static_cast<int32_t>(s.kind), static_cast<int32_t>(s.layer), s.paint, static_cast<int32_t>(s.round_cap)};
42 for (int32_t v : ints) { uint8_t b[4]; for (int i = 0; i < 4; ++i) b[i] = static_cast<uint8_t>(static_cast<uint32_t>(v) >> (8 * i)); h = fnv(b, 4, h); }
43 const double vals[6] = {s.p[0], s.p[1], s.p[2], s.p[3], s.p[4], s.width};
44 for (double v : vals) { const int64_t q = std::llround(v * 1e6); uint8_t b[8]; for (int i = 0; i < 8; ++i) b[i] = static_cast<uint8_t>(static_cast<uint64_t>(q) >> (8 * i)); h = fnv(b, 8, h); }
45 }
46 return h;
47}
48
49Result run(const Input &in) {
50 Result r; ckey_diagnostics d{};
51 if (in.profile == "0") {
52 auto s = std::make_unique<ckey_scalable_symbol>();
53 if (ckey_encode_scalable(in.payload.data(), in.payload.size(), in.version, static_cast<ckey_ecc_level>(in.level), static_cast<ckey_scalable_layout>(in.layout), s.get(), &d) != CKEY_OK) { r["error"] = d.message; return r; }
54 r["version"] = std::to_string(s->version);
55 r["codeword_fnv1a64"] = hex64(fnv(s->codeword, s->codeword_length));
56 if (s->codeword_length <= 160) r["codeword_hex"] = hex(s->codeword, s->codeword_length);
57 size_t count = 0; ckey_scalable_geometry(s.get(), nullptr, nullptr, 0, &count);
58 std::vector<ckey_shape> shapes(count); ckey_scalable_geometry(s.get(), nullptr, shapes.data(), shapes.size(), &count);
59 r["shape_count"] = std::to_string(count);
60 r["geometry_fnv1a64"] = hex64(geometry_hash(shapes));
61 size_t n = 0; ckey_write_interchange(s.get(), nullptr, nullptr, 0, &n);
62 std::vector<uint8_t> file(n); ckey_write_interchange(s.get(), nullptr, file.data(), n, &n);
63 r["ckey_file_fnv1a64"] = hex64(fnv(file.data(), n));
64 // Behavioural: binary raster at 8 px/module decodes to the payload.
65 const uint32_t modules = ckey_scalable_modules_across(s->version);
66 ckey_raster_options o; ckey_raster_options_default(&o);
67 o.size_px = static_cast<uint32_t>(std::ceil(31.0 * 8 * modules / 29.0)); o.policy = CKEY_RASTER_BINARY; o.format = CKEY_PIXELS_GRAY8;
68 std::vector<uint8_t> px(static_cast<size_t>(o.size_px) * o.size_px);
69 bool ok = ckey_render_scalable_raster(s.get(), nullptr, &o, px.data(), px.size(), &n) == CKEY_OK;
70 const double k = o.size_px / 31.0, q[8] = {k, k, 30 * k, k, 30 * k, 30 * k, k, 30 * k};
71 std::vector<uint8_t> out(65535); size_t len = 0; uint32_t v = 0, lay = 0;
72 ok = ok && ckey_decode_scalable_gray8(px.data(), o.size_px, o.size_px, o.size_px, q, out.data(), out.size(), &len, &v, &lay, &d) == CKEY_OK &&
73 len == in.payload.size() && std::equal(in.payload.begin(), in.payload.end(), out.begin()) && v == s->version && lay == in.layout;
74 r["raster_decodes"] = ok ? "true" : "false";
75 } else if (in.profile == "1") {
76 ckey_symbol s{}; if (ckey_encode_locator(in.payload.data(), in.payload.size(), &s, &d) != CKEY_OK) { r["error"] = d.message; return r; }
77 r["codeword_hex"] = hex(s.codeword, sizeof(s.codeword)); r["codeword_fnv1a64"] = hex64(fnv(s.codeword, sizeof(s.codeword)));
78 } else if (in.profile == "2") {
79 auto s = std::make_unique<ckey_text_symbol>(); if (ckey_encode_text(in.payload.data(), in.payload.size(), s.get(), &d) != CKEY_OK) { r["error"] = d.message; return r; }
80 r["codeword_fnv1a64"] = hex64(fnv(s->codeword, sizeof(s->codeword)));
81 } else if (in.profile == "3") {
82 auto s = std::make_unique<ckey_extended_symbol>(); if (ckey_encode_extended(in.payload.data(), in.payload.size(), s.get(), &d) != CKEY_OK) { r["error"] = d.message; return r; }
83 r["codeword_fnv1a64"] = hex64(fnv(s->codeword, sizeof(s->codeword)));
84 }
85 return r;
86}
87
88std::vector<Input> default_inputs() {
89 std::vector<Input> v;
90 const auto bytes = [](size_t n, uint32_t seed) { std::vector<uint8_t> p(n); for (auto &b : p) { seed = seed * 1664525u + 1013904223u; b = static_cast<uint8_t>(seed >> 24); } return p; };
91 const auto text = [](const std::string &s) { return std::vector<uint8_t>(s.begin(), s.end()); };
92 v.push_back({"p0-auto-url", "0", 0, 1, 0, text("https://openckey.org/tour")});
93 v.push_back({"p0-empty", "0", 1, 1, 0, {}});
94 v.push_back({"p0-v1-L", "0", 1, 0, 0, bytes(30, 1)});
95 for (uint32_t ver : {2u, 7u, 16u, 31u}) for (uint32_t level : {0u, 3u}) {
96 const size_t cap = ckey_scalable_capacity(ver, static_cast<ckey_ecc_level>(level), CKEY_LAYOUT_STANDARD);
97 v.push_back({"p0-v" + std::to_string(ver) + "-" + "LMQH"[level] + "-full", "0", ver, level, 0, bytes(cap, ver * 7 + level)});
98 }
99 for (uint32_t ver : {1u, 3u, 12u, 31u}) {
100 const size_t cap = ckey_scalable_capacity(ver, CKEY_ECC_M, CKEY_LAYOUT_PRINT);
101 v.push_back({"p01-v" + std::to_string(ver) + "-M", "0", ver, 1, 1, bytes(std::min<size_t>(cap, 100), ver * 13)});
102 }
103 v.push_back({"p0-vcard", "0", 0, 2, 0, text("BEGIN:VCARD\nVERSION:4.0\nFN:Ada Lovelace\nTEL:+15551234567\nEND:VCARD")});
104 v.push_back({"p1-locator", "1", 0, 0, 0, bytes(16, 99)});
105 v.push_back({"p2-text", "2", 0, 0, 0, text("CKey Profile 2 conformance vector")});
106 v.push_back({"p3-extended", "3", 0, 0, 0, bytes(600, 5)});
107 return v;
108}
109
110// --- minimal JSON for this file's own shape (objects, arrays, strings, numbers) ---
111struct J { enum T { Null, Num, Str, Arr, Obj } t = Null; double n = 0; std::string s; std::vector<J> a; std::vector<std::string> keys; std::vector<J> values; // object members (vector<J> allows the incomplete type)
112 const J *get(const std::string &k) const { for (size_t i = 0; i < keys.size(); ++i) if (keys[i] == k) return &values[i]; return nullptr; } };
113struct P { const std::string &s; size_t i = 0;
114 void ws() { while (i < s.size() && std::isspace(static_cast<unsigned char>(s[i]))) ++i; }
115 J parse() { ws(); J j; if (s[i] == '{') { ++i; j.t = J::Obj; ws(); if (s[i] == '}') { ++i; return j; }
116 for (;;) { ws(); J k = parse(); ws(); ++i; J v = parse(); j.keys.push_back(k.s); j.values.push_back(v); ws(); if (s[i++] == '}') break; } }
117 else if (s[i] == '[') { ++i; j.t = J::Arr; ws(); if (s[i] == ']') { ++i; return j; } for (;;) { j.a.push_back(parse()); ws(); if (s[i++] == ']') break; } }
118 else if (s[i] == '"') { ++i; j.t = J::Str; while (s[i] != '"') { if (s[i] == '\\') ++i; j.s += s[i++]; } ++i; }
119 else { j.t = J::Num; size_t e = i; while (e < s.size() && (std::isdigit(static_cast<unsigned char>(s[e])) || s[e] == '-' || s[e] == '.')) ++e; j.n = std::stod(s.substr(i, e - i)); i = e; }
120 return j; } };
121
122} // namespace
123
124int main(int argc, char **argv) {
125 if (argc != 3 || (std::string(argv[1]) != "--write" && std::string(argv[1]) != "--check")) { std::cerr << "usage: ckey-conformance --write|--check <file.json>\n"; return 2; }
126 const std::string path = argv[2];
127 if (std::string(argv[1]) == "--write") {
128 std::ofstream f(path);
129 f << "{\n \"suite\": \"ckey-conformance\",\n \"suite_version\": 1,\n \"generated_by\": \"" << ckey_get_software_version() << "\",\n"
130 << " \"hash\": \"FNV-1a 64; see tools/conformance/README.md\",\n \"vectors\": [\n";
131 const auto inputs = default_inputs();
132 for (size_t i = 0; i < inputs.size(); ++i) {
133 const auto &in = inputs[i]; const Result r = run(in);
134 f << " {\"id\": \"" << in.id << "\", \"profile\": \"" << in.profile << "\", \"version\": " << in.version << ", \"level\": " << in.level
135 << ", \"layout\": " << in.layout << ", \"payload_hex\": \"" << hex(in.payload.data(), in.payload.size()) << "\",\n \"expect\": {";
136 size_t k = 0; for (const auto &[key, value] : r) f << (k++ ? ", " : "") << '"' << key << "\": \"" << value << '"';
137 f << "}}" << (i + 1 < inputs.size() ? "," : "") << "\n";
138 }
139 f << " ]\n}\n";
140 std::cout << "wrote " << inputs.size() << " vectors to " << path << '\n';
141 return 0;
142 }
143 std::ifstream f(path); std::stringstream ss; ss << f.rdbuf(); const std::string text = ss.str();
144 if (text.empty()) { std::cerr << "cannot read " << path << '\n'; return 2; }
145 P parser{text}; const J root = parser.parse();
146 size_t passed = 0, failed = 0;
147 for (const J &v : root.get("vectors")->a) {
148 Input in; in.id = v.get("id")->s; in.profile = v.get("profile")->s;
149 in.version = static_cast<uint32_t>(v.get("version")->n); in.level = static_cast<uint32_t>(v.get("level")->n); in.layout = static_cast<uint32_t>(v.get("layout")->n);
150 in.payload = unhex(v.get("payload_hex")->s);
151 const Result got = run(in);
152 bool ok = true;
153 const J &expect = *v.get("expect");
154 for (size_t e = 0; e < expect.keys.size(); ++e) {
155 const std::string &key = expect.keys[e]; const J &want = expect.values[e];
156 const auto it = got.find(key);
157 if (it == got.end() || it->second != want.s) { std::cerr << in.id << ": " << key << " expected " << want.s << " got " << (it == got.end() ? "(missing)" : it->second) << '\n'; ok = false; }
158 }
159 (ok ? passed : failed)++;
160 }
161 std::cout << "conformance: " << passed << " passed, " << failed << " failed\n";
162 return failed ? 1 : 0;
163}