mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
The flip: FUNCTION/XFUNCTION/FUN::fun/delim_check and the module interfaces take `const UTF8 * const fargs[]`. Double-const is load-bearing: C++ qualification conversion needs const at both pointer levels, so builder-side `UTF8 *[]` arrays convert implicitly — the evaluator, the JIT marshaller, and every owner site need zero casts, and slot reassignment inside bodies becomes a compile error for free. The conversions: the flip landed first so the compiler enumerated every violation; this commit is that inventory worked to zero — ~250 sites across funceval, funceval2, functions, funmath, help, mail, session, powers, levels, predicates, conf, walkdb, stringutil, timeutil/ date_scan (regenerated, one-line diff), exp3, and mux_main, each classified per docs/campaign-2136-const-fargs.md's four recipes. New idioms (functions.h): trim_space_sep_n() — non-destructive trim for (pointer, length) consumers, so trim-then-scan sites need no copy at all; FargVec — the argv counterpart of FargCopy for CS_ARGV handlers. countwords() and DecodeListOfIntegers() rewritten non-destructive. The flip deleted more than it added: #2157's fun_munge list1 copy, the engine_com help-topic copy, fun_index's in-place NUL write, and five const_casts (process_sex x4, sha1_helper). const_cast budget: zero added. Trap recorded in the brief: an old-signature definition doesn't fail the build — it becomes a C++ overload, and the new-signature symbol stays undefined until dlopen(RTLD_NOW). delim_check, the conn_bridge bridges, the dbt_spike stub, and exp3::Call were all silently shadowed; muxscript was the only host that noticed, because netmux's own net.cpp resolved the flat-namespace lookup. After any signature flip, grep the old spelling. Verified: make test EXPECT_CONFIG="jit=yes" (35 passed / 0 failed) and make test-scenario, including the new tests/scenario/sidefx_fargs.py that live-probes the class-3 wrappers smoke never touches (pemit/ trigger/link/tel/wipe/destroy). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
204 lines
7.9 KiB
C++
204 lines
7.9 KiB
C++
/*! \file test_digest.cpp
|
|
* \brief Known-answer tests for the OS-backed digest entry points (#1963).
|
|
*
|
|
* mux_sha1_digest() feeds three surfaces whose output may never change:
|
|
* RFC 6455 Sec-WebSocket-Accept (websocket.cpp), $SHA1$/$P6H$ password
|
|
* verification (player.cpp), and sha1() softcode (funmath.cpp). These
|
|
* vectors pin all of them at the abstraction boundary, on BOTH backends —
|
|
* OpenSSL EVP (UNIX_DIGEST) and Windows CNG — so a backend swap that
|
|
* changes a single output byte fails here before any suite that merely
|
|
* exercises one caller.
|
|
*
|
|
* Every golden value below was generated with the openssl(1) CLI as an
|
|
* external oracle, not with either backend under test.
|
|
*
|
|
* Declares prototypes locally and links the built libmux, following the
|
|
* tests/libmux pattern.
|
|
*
|
|
* Build: make (Unix; needs a built mux/lib/libmux.so)
|
|
* Run: make test (TAP-ish output, nonzero exit on failure)
|
|
*
|
|
* Windows (manual, from this directory, after building the solution):
|
|
* cl /nologo /EHsc /std:c++17 test_digest.cpp /Fe:test_digest.exe ^
|
|
* /link ..\..\mux\bin_release\libmux.lib
|
|
* PATH=..\..\mux\bin_release;%PATH% test_digest.exe
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cstdint>
|
|
|
|
typedef unsigned char UTF8;
|
|
|
|
// sha1.h exports (declared locally; see file comment)
|
|
bool mux_sha1_digest(const UTF8 * const data[], const size_t lens[], int count,
|
|
uint8_t *out_digest, unsigned int *out_len);
|
|
bool mux_digest(const UTF8 *alg, const UTF8 *data[], const size_t lens[],
|
|
int count, uint8_t *out_digest, unsigned int *out_len);
|
|
|
|
static int g_tests = 0;
|
|
static int g_failures = 0;
|
|
|
|
static void hexify(const uint8_t *md, unsigned int len, char *out)
|
|
{
|
|
static const char digits[] = "0123456789abcdef";
|
|
for (unsigned int i = 0; i < len; ++i)
|
|
{
|
|
out[2*i] = digits[(md[i] >> 4) & 0x0F];
|
|
out[2*i+1] = digits[md[i] & 0x0F];
|
|
}
|
|
out[2*len] = '\0';
|
|
}
|
|
|
|
static void check(const char *label, bool ok_call, const uint8_t *md,
|
|
unsigned int len, const char *expected_hex)
|
|
{
|
|
++g_tests;
|
|
char hex[2*64 + 1] = "";
|
|
if (ok_call)
|
|
{
|
|
hexify(md, len, hex);
|
|
}
|
|
bool pass = ok_call && 0 == strcmp(hex, expected_hex);
|
|
printf("%s %d - %s\n", pass ? "ok" : "not ok", g_tests, label);
|
|
if (!pass)
|
|
{
|
|
printf("# expected %s\n# got %s%s\n",
|
|
expected_hex, hex, ok_call ? "" : " (call failed)");
|
|
++g_failures;
|
|
}
|
|
}
|
|
|
|
static void expect_unsupported(const char *label, const char *alg)
|
|
{
|
|
++g_tests;
|
|
uint8_t md[64];
|
|
unsigned int len = 0;
|
|
const UTF8 *parts[] = { reinterpret_cast<const UTF8 *>("abc") };
|
|
const size_t lens[] = { 3 };
|
|
bool ok = mux_digest(reinterpret_cast<const UTF8 *>(alg), parts, lens, 1, md, &len);
|
|
printf("%s %d - %s\n", !ok ? "ok" : "not ok", g_tests, label);
|
|
if (ok)
|
|
{
|
|
++g_failures;
|
|
}
|
|
}
|
|
|
|
static void sha1_one(const char *label, const char *input, const char *expected_hex)
|
|
{
|
|
uint8_t md[20];
|
|
unsigned int len = 0;
|
|
const UTF8 *parts[] = { reinterpret_cast<const UTF8 *>(input) };
|
|
const size_t lens[] = { strlen(input) };
|
|
bool ok = mux_sha1_digest(parts, lens, 1, md, &len);
|
|
check(label, ok && 20 == len, md, len, expected_hex);
|
|
}
|
|
|
|
static void digest_one(const char *label, const char *alg, const char *input,
|
|
const char *expected_hex)
|
|
{
|
|
uint8_t md[64];
|
|
unsigned int len = 0;
|
|
const UTF8 *parts[] = { reinterpret_cast<const UTF8 *>(input) };
|
|
const size_t lens[] = { strlen(input) };
|
|
bool ok = mux_digest(reinterpret_cast<const UTF8 *>(alg), parts, lens, 1, md, &len);
|
|
check(label, ok, md, len, expected_hex);
|
|
}
|
|
|
|
int main()
|
|
{
|
|
// ---- FIPS 180 vectors: pins sha1() softcode (funmath.cpp) ----
|
|
sha1_one("sha1 FIPS empty string",
|
|
"", "da39a3ee5e6b4b0d3255bfef95601890afd80709");
|
|
sha1_one("sha1 FIPS 'abc'",
|
|
"abc", "a9993e364706816aba3e25717850c26c9cd0d89d");
|
|
sha1_one("sha1 FIPS 56-char",
|
|
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
|
|
"84983e441c3bd26ebaae4aa1f95129e5e54670f1");
|
|
|
|
// ---- RFC 6455 worked example: pins Sec-WebSocket-Accept (websocket.cpp)
|
|
// base64 of this digest is the spec's "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=".
|
|
sha1_one("sha1 RFC 6455 key||GUID (websocket handshake)",
|
|
"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
|
|
"b37a4f2cc0624f1690f64606cf385945b2bec4ea");
|
|
|
|
// Same input as a two-part gather -- the call shape websocket.cpp and
|
|
// player.cpp actually use. Must equal the single-part digest.
|
|
{
|
|
uint8_t md[20];
|
|
unsigned int len = 0;
|
|
const UTF8 *parts[] = {
|
|
reinterpret_cast<const UTF8 *>("dGhlIHNhbXBsZSBub25jZQ=="),
|
|
reinterpret_cast<const UTF8 *>("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"),
|
|
};
|
|
const size_t lens[] = { 24, 36 };
|
|
bool ok = mux_sha1_digest(parts, lens, 2, md, &len);
|
|
check("sha1 two-part gather == one-part (RFC 6455 input)",
|
|
ok && 20 == len, md, len,
|
|
"b37a4f2cc0624f1690f64606cf385945b2bec4ea");
|
|
}
|
|
|
|
// ---- salt||password two-part gather: pins mux_crypt $SHA1$ (player.cpp:694)
|
|
{
|
|
uint8_t md[20];
|
|
unsigned int len = 0;
|
|
const UTF8 *parts[] = {
|
|
reinterpret_cast<const UTF8 *>("salt"),
|
|
reinterpret_cast<const UTF8 *>("password"),
|
|
};
|
|
const size_t lens[] = { 4, 8 };
|
|
bool ok = mux_sha1_digest(parts, lens, 2, md, &len);
|
|
check("sha1 salt||password gather ($SHA1$ shape)",
|
|
ok && 20 == len, md, len,
|
|
"59b3e8d637cf97edbe2384cf59cb7453dfe30789");
|
|
}
|
|
|
|
// ---- bare password digest: pins $P6H$$1:sha1: verification (player.cpp:527)
|
|
sha1_one("sha1 bare password ($P6H$ shape)",
|
|
"password", "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8");
|
|
|
|
// ---- mux_digest by name, both backends ----
|
|
digest_one("mux_digest sha1 matches mux_sha1_digest", "sha1",
|
|
"abc", "a9993e364706816aba3e25717850c26c9cd0d89d");
|
|
digest_one("mux_digest SHA1 (case-insensitive)", "SHA1",
|
|
"abc", "a9993e364706816aba3e25717850c26c9cd0d89d");
|
|
digest_one("mux_digest sha256 'abc'", "sha256",
|
|
"abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
|
digest_one("mux_digest sha384 'abc'", "sha384",
|
|
"abc", "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7");
|
|
digest_one("mux_digest sha512 'abc'", "sha512",
|
|
"abc", "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f");
|
|
digest_one("mux_digest md5 'abc'", "md5",
|
|
"abc", "900150983cd24fb0d6963f7d28e17f72");
|
|
|
|
#ifdef _WIN32
|
|
// Hyphenated aliases are guaranteed by the CNG name table; on OpenSSL
|
|
// they depend on version/provider (#1961), so only asserted here.
|
|
digest_one("mux_digest sha-1 alias (CNG table)", "sha-1",
|
|
"abc", "a9993e364706816aba3e25717850c26c9cd0d89d");
|
|
digest_one("mux_digest sha-256 alias (CNG table)", "sha-256",
|
|
"abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
|
#endif
|
|
|
|
// ---- rejection ----
|
|
expect_unsupported("mux_digest rejects unknown name", "sha3-999");
|
|
|
|
// ---- zero-part call: digest(<alg>) with no data == digest of "" ----
|
|
{
|
|
uint8_t md[20];
|
|
unsigned int len = 0;
|
|
bool ok = mux_sha1_digest(nullptr, nullptr, 0, md, &len);
|
|
check("sha1 zero-part call == empty-string digest",
|
|
ok && 20 == len, md, len,
|
|
"da39a3ee5e6b4b0d3255bfef95601890afd80709");
|
|
}
|
|
|
|
printf("1..%d\n", g_tests);
|
|
if (g_failures)
|
|
{
|
|
printf("# FAILED: %d of %d\n", g_failures, g_tests);
|
|
return 1;
|
|
}
|
|
printf("# all %d digest KATs passed\n", g_tests);
|
|
return 0;
|
|
}
|