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

interchange_tests.cpp

v0.1.0-build.85 Latest Commit ede38f5

Size
6.4 KB
Lines
91
Language
cpp
SHA-256
7c313669657d…
View on GitHub

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

1// .ckey interchange (spec/CKey-Interchange-v1.md): round trips, strict
2// rejection, and a mutation fuzzer. The reader lives in ckey_codec; the
3// test links ckey_render only for the concept preset.
4#include "ckey/ckey.h"
5#include "check.h"
6
7#include <cstdio>
8#include <cstring>
9#include <string>
10#include <vector>
11
12namespace {
13std::vector<uint8_t> write(const ckey_scalable_symbol &s, const ckey_style *st) {
14 size_t n = 0; CKEY_CHECK(ckey_write_interchange(&s, st, nullptr, 0, &n) == CKEY_INSUFFICIENT_BUFFER);
15 std::vector<uint8_t> out(n); CKEY_CHECK(ckey_write_interchange(&s, st, out.data(), out.size(), &n) == CKEY_OK);
16 return out;
17}
18uint32_t crc32c(const uint8_t *p, size_t n) {
19 uint32_t c = 0xffffffffu;
20 for (size_t i = 0; i < n; ++i) { c ^= p[i]; for (int k = 0; k < 8; ++k) c = (c >> 1) ^ (0x82f63b78u & (0u - (c & 1u))); }
21 return ~c;
22}
23void reseal(std::vector<uint8_t> &f) { const uint32_t c = crc32c(f.data(), f.size() - 4); for (int i = 0; i < 4; ++i) f[f.size() - 4 + i] = static_cast<uint8_t>(c >> (8 * i)); }
24} // namespace
25
26int main() {
27 const char *text = "https://openckey.org/tour";
28 for (auto layout : {CKEY_LAYOUT_STANDARD, CKEY_LAYOUT_PRINT}) for (uint32_t v : {6u, 12u, 31u}) {
29 ckey_scalable_symbol s{}; ckey_diagnostics d{};
30 CKEY_CHECK(ckey_encode_scalable(reinterpret_cast<const uint8_t *>(text), std::strlen(text), v, CKEY_ECC_Q, layout, &s, &d) == CKEY_OK);
31 const auto file = write(s, nullptr);
32 CKEY_CHECK(file.size() == 12 + 6 + s.codeword_length + 4 && std::memcmp(file.data(), "CKEY", 4) == 0);
33 ckey_scalable_symbol back{}; ckey_style st{};
34 CKEY_CHECK(ckey_read_interchange(file.data(), file.size(), &back, &st, nullptr, 0, &d) == CKEY_OK);
35 CKEY_CHECK(back.version == v && back.layout == static_cast<uint32_t>(layout) && back.ecc_level == CKEY_ECC_Q && back.codeword_length == s.codeword_length);
36 CKEY_CHECK(std::memcmp(back.codeword, s.codeword, s.codeword_length) == 0 && st.ink.r == 0 && st.background.r == 255);
37 }
38 // Style and logo round trip.
39 ckey_scalable_symbol s{}; ckey_diagnostics d{};
40 CKEY_CHECK(ckey_encode_scalable(reinterpret_cast<const uint8_t *>(text), std::strlen(text), 6, CKEY_ECC_H, CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
41 ckey_style art{}; ckey_style_preset_fill(&art, CKEY_PRESET_CONCEPT);
42 art.modules = CKEY_MODULE_DIAMOND; art.policy = CKEY_POLICY_ADAPTIVE; art.logo_href = "https://openckey.org/logo.svg"; art.logo_plate = 1; art.logo_radius = 3.25;
43 const auto file = write(s, &art);
44 ckey_scalable_symbol back{}; ckey_style st{}; char logo[128];
45 CKEY_CHECK(ckey_read_interchange(file.data(), file.size(), &back, &st, logo, sizeof(logo), &d) == CKEY_OK);
46 CKEY_CHECK(st.ink.r == art.ink.r && st.accent_count == 2 && st.modules == CKEY_MODULE_DIAMOND && st.policy == CKEY_POLICY_ADAPTIVE);
47 CKEY_CHECK(std::abs(st.bevel - 0.6) < 1e-4 && std::abs(st.logo_radius - 3.25) < 1e-3 && st.logo_href && std::string(st.logo_href) == art.logo_href);
48 CKEY_CHECK(ckey_read_interchange(file.data(), file.size(), &back, &st, nullptr, 0, &d) == CKEY_OK && st.logo_href == nullptr); // no buffer: logo dropped
49
50 // Strict rejection.
51 auto bad = file; bad[0] = 'X'; CKEY_CHECK(ckey_read_interchange(bad.data(), bad.size(), &back, &st, nullptr, 0, &d) == CKEY_INVALID_ARGUMENT);
52 bad = file; bad[30] ^= 1; CKEY_CHECK(ckey_read_interchange(bad.data(), bad.size(), &back, &st, nullptr, 0, &d) == CKEY_INVALID_ARGUMENT); // CRC
53 std::printf("corrupted byte: %s\n", d.message);
54 bad = file; bad[30] ^= 1; reseal(bad); // valid CRC, but a non-canonical codeword
55 CKEY_CHECK(ckey_read_interchange(bad.data(), bad.size(), &back, &st, nullptr, 0, &d) == CKEY_INVALID_ARGUMENT);
56 std::printf("altered codeword with a valid CRC: %s\n", d.message);
57 bad = file; bad[4] = 2; reseal(bad); CKEY_CHECK(ckey_read_interchange(bad.data(), bad.size(), &back, &st, nullptr, 0, &d) == CKEY_INVALID_ARGUMENT);
58 bad = file; bad[8] = 14; reseal(bad); CKEY_CHECK(ckey_read_interchange(bad.data(), bad.size(), &back, &st, nullptr, 0, &d) == CKEY_INVALID_ARGUMENT);
59 { // unknown required record rejected; unknown optional record skipped
60 auto plain = write(s, nullptr); plain.resize(plain.size() - 4);
61 auto opt = plain; opt.insert(opt.end(), {0x34, 0x92, 2, 0, 0, 0, 7, 7}); opt.resize(opt.size() + 4); reseal(opt);
62 CKEY_CHECK(ckey_read_interchange(opt.data(), opt.size(), &back, &st, nullptr, 0, &d) == CKEY_OK);
63 auto req = plain; req.insert(req.end(), {0x34, 0x12, 2, 0, 0, 0, 7, 7}); req.resize(req.size() + 4); reseal(req);
64 CKEY_CHECK(ckey_read_interchange(req.data(), req.size(), &back, &st, nullptr, 0, &d) == CKEY_INVALID_ARGUMENT);
65 auto huge = plain; huge.insert(huge.end(), {0x34, 0x92, 0xff, 0xff, 0xff, 0x7f}); huge.resize(huge.size() + 4); reseal(huge);
66 CKEY_CHECK(ckey_read_interchange(huge.data(), huge.size(), &back, &st, nullptr, 0, &d) == CKEY_INVALID_ARGUMENT);
67 }
68 std::vector<uint8_t> tiny(10); CKEY_CHECK(ckey_read_interchange(tiny.data(), tiny.size(), &back, &st, nullptr, 0, &d) == CKEY_INVALID_ARGUMENT);
69 std::vector<uint8_t> big(CKEY_INTERCHANGE_MAX_BYTES + 1); CKEY_CHECK(ckey_read_interchange(big.data(), big.size(), &back, &st, nullptr, 0, &d) == CKEY_INVALID_ARGUMENT);
70
71 // Mutation fuzzing: random byte edits (resealed half the time so they
72 // reach the record parser), truncations and extensions never crash and
73 // are accepted only when the result is a valid, canonical file.
74 uint32_t seed = 12345; size_t accepted = 0;
75 const auto rnd = [&] { seed = seed * 1664525u + 1013904223u; return seed >> 8; };
76 for (int i = 0; i < 20000; ++i) {
77 auto f = file;
78 const int edits = 1 + static_cast<int>(rnd() % 4);
79 for (int e = 0; e < edits; ++e) {
80 const uint32_t op = rnd() % 3;
81 if (op == 0 && !f.empty()) f[rnd() % f.size()] = static_cast<uint8_t>(rnd());
82 else if (op == 1 && f.size() > 1) f.resize(rnd() % f.size());
83 else f.insert(f.begin() + static_cast<long>(rnd() % (f.size() + 1)), static_cast<uint8_t>(rnd()));
84 }
85 if (f.size() >= 4 && (i & 1)) reseal(f);
86 if (ckey_read_interchange(f.data(), f.size(), &back, &st, logo, sizeof(logo), &d) == CKEY_OK) ++accepted;
87 }
88 std::printf("fuzz: 20000 mutated files, %zu accepted (each a valid canonical file)\n", accepted);
89 std::printf("interchange tests passed\n");
90 return 0;
91}