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

api_interchange.inc

v0.1.0-build.85 Latest Commit ede38f5

Size
9.1 KB
Lines
134
Language
cpp
SHA-256
f029722579f5…
View on GitHub

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

1// Public C ABI: .ckey interchange (spec/CKey-Interchange-v1.md). Part of
2// ckey_codec: it needs only framing and the codeword decoder.
3
4namespace {
5
6// Record types. Bit 15 set = optional (a reader may skip it if unknown).
7constexpr uint16_t kRecordCodeword = 0x0001, kRecordStyle = 0x8002, kRecordLogo = 0x8003;
8constexpr size_t kInterchangeHeader = 12, kStyleRecordBytes = 64;
9
10void put16(std::vector<uint8_t> &o, uint16_t v) { o.push_back(static_cast<uint8_t>(v)); o.push_back(static_cast<uint8_t>(v >> 8)); }
11void put32(std::vector<uint8_t> &o, uint32_t v) { for (int s = 0; s < 32; s += 8) o.push_back(static_cast<uint8_t>(v >> s)); }
12void put_unit(std::vector<uint8_t> &o, double v) { put16(o, static_cast<uint16_t>(std::lround(std::clamp(v, 0.0, 1.0) * 65535))); }
13uint16_t get16(const uint8_t *p) { return static_cast<uint16_t>(p[0] | (p[1] << 8)); }
14uint32_t get32(const uint8_t *p) { return static_cast<uint32_t>(p[0]) | (static_cast<uint32_t>(p[1]) << 8) | (static_cast<uint32_t>(p[2]) << 16) | (static_cast<uint32_t>(p[3]) << 24); }
15
16// Fixed 64-byte style record: colours, shapes, effects (16-bit fixed point),
17// logo radius (1/1000 unit) and flags. Logos travel as their own record.
18std::vector<uint8_t> encode_style(const ckey_style &st) {
19 std::vector<uint8_t> o;
20 for (const ckey_rgb &c : {st.background, st.background_edge, st.ink, st.ink_shade, st.accents[0], st.accents[1], st.accents[2], st.accents[3]}) { o.push_back(c.r); o.push_back(c.g); o.push_back(c.b); }
21 o.push_back(static_cast<uint8_t>(st.accent_count)); o.push_back(static_cast<uint8_t>(st.accent_percent));
22 o.push_back(static_cast<uint8_t>(st.modules)); o.push_back(static_cast<uint8_t>(st.rings)); o.push_back(static_cast<uint8_t>(st.rays));
23 o.push_back(static_cast<uint8_t>(st.policy)); o.push_back(st.logo_plate ? 1 : 0);
24 put_unit(o, st.bevel); put_unit(o, st.shadow); put_unit(o, st.texture);
25 put32(o, static_cast<uint32_t>(std::lround(std::clamp(st.logo_radius, 0.0, 1e6) * 1000)));
26 while (o.size() < kStyleRecordBytes) o.push_back(0);
27 return o;
28}
29
30bool decode_style(const uint8_t *p, ckey_style &st) {
31 fill_default_style(st);
32 ckey_rgb *colours[8] = {&st.background, &st.background_edge, &st.ink, &st.ink_shade, &st.accents[0], &st.accents[1], &st.accents[2], &st.accents[3]};
33 for (int i = 0; i < 8; ++i) *colours[i] = {p[3 * i], p[3 * i + 1], p[3 * i + 2]};
34 const uint8_t *q = p + 24;
35 st.accent_count = q[0]; st.accent_percent = q[1];
36 st.modules = static_cast<ckey_module_shape>(q[2]); st.rings = static_cast<ckey_ring_style>(q[3]); st.rays = static_cast<ckey_ray_cap>(q[4]);
37 st.policy = q[5]; st.logo_plate = q[6];
38 st.bevel = get16(q + 7) / 65535.0; st.shadow = get16(q + 9) / 65535.0; st.texture = get16(q + 11) / 65535.0;
39 st.logo_radius = get32(q + 13) / 1000.0;
40 for (size_t i = 24 + 17; i < kStyleRecordBytes; ++i) if (p[i]) return false; // reserved bytes must be zero
41 return st.accent_count <= 4 && st.accent_percent <= 100 && q[2] <= CKEY_MODULE_DIAMOND && q[3] <= CKEY_RING_DOTS &&
42 q[4] <= CKEY_RAY_FLAT && q[5] <= CKEY_POLICY_OVERRIDE && q[6] <= 1 && st.logo_radius <= 14.5;
43}
44
45} // namespace
46
47extern "C" {
48
49ckey_status ckey_write_interchange(const ckey_scalable_symbol *s, const ckey_style *style, uint8_t *out, size_t cap, size_t *required) {
50 if (!s || !required || s->abi_version != CKEY_ABI_VERSION || s->version < 1 || s->version > CKEY_SCALABLE_VERSION_COUNT ||
51 s->ecc_level > 3 || s->layout >= static_cast<uint32_t>(kScalableFamilies)) return CKEY_INVALID_ARGUMENT;
52 const ScalableLayout &L = scalable_layout(static_cast<int>(s->version), static_cast<int>(s->layout));
53 if (s->codeword_length != L.codeword_bytes) return CKEY_INVALID_ARGUMENT;
54 std::vector<uint8_t> o = {'C', 'K', 'E', 'Y', static_cast<uint8_t>(CKEY_INTERCHANGE_VERSION), 0, 0, 0,
55 L.format, static_cast<uint8_t>(s->version), static_cast<uint8_t>(s->ecc_level), static_cast<uint8_t>(s->layout)};
56 put16(o, kRecordCodeword); put32(o, static_cast<uint32_t>(s->codeword_length));
57 o.insert(o.end(), s->codeword, s->codeword + s->codeword_length);
58 if (style) {
59 if (!structural_problem(L, *style).empty()) return CKEY_INVALID_ARGUMENT;
60 const auto rec = encode_style(*style);
61 put16(o, kRecordStyle); put32(o, static_cast<uint32_t>(rec.size())); o.insert(o.end(), rec.begin(), rec.end());
62 if (style->logo_href && *style->logo_href) {
63 const size_t n = std::strlen(style->logo_href);
64 if (n > 32768) return CKEY_INVALID_ARGUMENT;
65 put16(o, kRecordLogo); put32(o, static_cast<uint32_t>(n)); o.insert(o.end(), style->logo_href, style->logo_href + n);
66 }
67 }
68 put32(o, crc32c(o.data(), o.size()));
69 if (o.size() > CKEY_INTERCHANGE_MAX_BYTES) return CKEY_INVALID_ARGUMENT;
70 *required = o.size();
71 if (!out || cap < o.size()) return CKEY_INSUFFICIENT_BUFFER;
72 std::memcpy(out, o.data(), o.size());
73 return CKEY_OK;
74}
75
76ckey_status ckey_read_interchange(const uint8_t *data, size_t len, ckey_scalable_symbol *sym, ckey_style *out_style,
77 char *logo_href, size_t logo_cap, ckey_diagnostics *diag) {
78 const auto fail = [&](const char *why) { set_status(diag, CKEY_INVALID_ARGUMENT, why); return CKEY_INVALID_ARGUMENT; };
79 if (!data || !sym) return fail(".ckey reader needs data and a symbol");
80 if (len < kInterchangeHeader + 6 + 4 || len > CKEY_INTERCHANGE_MAX_BYTES) return fail(".ckey file is too short or too large");
81 if (std::memcmp(data, "CKEY", 4) != 0) return fail("not a .ckey file");
82 if (data[4] != CKEY_INTERCHANGE_VERSION) return fail("unsupported .ckey version");
83 if (data[5] || data[6] || data[7]) return fail(".ckey reserved header bytes must be zero");
84 if (crc32c(data, len - 4) != get32(data + len - 4)) return fail(".ckey CRC-32C mismatch");
85 const uint8_t format = data[8], version = data[9], level = data[10], layout = data[11];
86 if (version < 1 || version > CKEY_SCALABLE_VERSION_COUNT || level > 3 || layout >= kScalableFamilies) return fail(".ckey symbol header out of range");
87 const ScalableLayout &L = scalable_layout(version, layout);
88 if (format != L.format) return fail(".ckey format byte does not match its layout");
89 bool have_codeword = false, have_style = false;
90 ckey_scalable_symbol s{}; s.abi_version = CKEY_ABI_VERSION; s.version = version; s.ecc_level = level; s.layout = layout;
91 ckey_style st{}; std::string logo;
92 size_t at = kInterchangeHeader; const size_t end = len - 4;
93 while (at < end) {
94 if (end - at < 6) return fail(".ckey record header truncated");
95 const uint16_t type = get16(data + at); const uint32_t n = get32(data + at + 2); at += 6;
96 if (n > end - at) return fail(".ckey record overruns the file");
97 const uint8_t *v = data + at;
98 if (type == kRecordCodeword) {
99 if (have_codeword || n != L.codeword_bytes) return fail(".ckey codeword record is duplicated or has the wrong length");
100 std::memcpy(s.codeword, v, n); s.codeword_length = n; have_codeword = true;
101 } else if (type == kRecordStyle) {
102 if (have_style || n != kStyleRecordBytes || !decode_style(v, st)) return fail(".ckey style record is invalid");
103 have_style = true;
104 } else if (type == kRecordLogo) {
105 if (!have_style || !logo.empty() || n == 0 || n > 32768) return fail(".ckey logo record is invalid");
106 for (uint32_t i = 0; i < n; ++i) if (v[i] < 0x20 || v[i] == 0x7f) return fail(".ckey logo reference has control characters");
107 logo.assign(reinterpret_cast<const char *>(v), n);
108 } else if (!(type & 0x8000u)) return fail(".ckey has an unknown required record");
109 at += n;
110 }
111 if (!have_codeword) return fail(".ckey has no codeword");
112 // The codeword must decode: a .ckey never carries a symbol a reader would reject.
113 std::vector<uint8_t> payload(65535); size_t payload_len = 0; ckey_diagnostics d{};
114 if (ckey_decode_scalable(s.codeword, s.codeword_length, version, static_cast<ckey_scalable_layout>(layout), payload.data(), payload.size(), &payload_len, &d) != CKEY_OK)
115 return fail(".ckey codeword does not decode");
116 // Same check as the encoder: the stored codeword must be the canonical,
117 // error-free encoding (a corrected codeword would render differently).
118 ckey_scalable_symbol again{}; ckey_diagnostics e{};
119 if (ckey_encode_scalable(payload.data(), payload_len, version, static_cast<ckey_ecc_level>(level), static_cast<ckey_scalable_layout>(layout), &again, &e) != CKEY_OK ||
120 again.codeword_length != s.codeword_length || std::memcmp(again.codeword, s.codeword, s.codeword_length) != 0)
121 return fail(".ckey codeword is not a canonical encoding");
122 *sym = s;
123 if (out_style) {
124 if (have_style) {
125 st.logo_href = nullptr;
126 if (!logo.empty() && logo_href && logo_cap > logo.size()) { std::memcpy(logo_href, logo.c_str(), logo.size() + 1); st.logo_href = logo_href; }
127 *out_style = st;
128 } else fill_default_style(*out_style);
129 }
130 set_status(diag, CKEY_OK, have_style ? "read .ckey symbol and style" : "read .ckey symbol");
131 return CKEY_OK;
132}
133
134} // extern "C"