/* test_rc4rand.c - test suite for rc4rand.c */ /* This file includes rc4rand.c directly so it can reach its static * functions. RC4RAND_TEST suppresses the demo main() in rc4rand.c. */ #define RC4RAND_TEST #include "rc4rand.c" #include static int tests_run; static int tests_failed; #define CHECK(cond, description) do { \ tests_run++; \ if (cond) { \ fprintf(stderr, "PASS: %s\n", (description)); \ } else { \ tests_failed++; \ fprintf(stderr, "FAIL: %s\n", (description)); \ } \ } while (0) /* fill buf with len keystream bytes from a state freshly seeded with key */ static void keystream(const void *key, size_t keylen, void *buf, size_t len) { struct rc4rand_state st; rc4rand_seed(&st, keylen, key); rc4rand(&st, len, buf); } /* Known-answer tests. This implementation is textbook RC4, so it must * match the published RC4 test vectors. These vectors are the real * portability guarantee: any platform that fails them is broken. */ static void test_kat(void) { unsigned char out[16]; /* key "Key" */ static const unsigned char kat_key[] = { 0xEB, 0x9F, 0x77, 0x81, 0xB7, 0x34, 0xCA, 0x72, 0xA7, 0x19 }; /* key "Wiki" */ static const unsigned char kat_wiki[] = { 0x60, 0x44, 0xDB, 0x6D, 0x41, 0xB7 }; /* key "Secret" */ static const unsigned char kat_secret[] = { 0x04, 0xD4, 0x6B, 0x05, 0x3C, 0xA8, 0x7B, 0x59 }; keystream("Key", 3, out, sizeof kat_key); CHECK(memcmp(out, kat_key, sizeof kat_key) == 0, "RC4 known-answer vector: key \"Key\""); keystream("Wiki", 4, out, sizeof kat_wiki); CHECK(memcmp(out, kat_wiki, sizeof kat_wiki) == 0, "RC4 known-answer vector: key \"Wiki\""); keystream("Secret", 6, out, sizeof kat_secret); CHECK(memcmp(out, kat_secret, sizeof kat_secret) == 0, "RC4 known-answer vector: key \"Secret\""); } /* the same seed must always yield the same sequence, and different * seeds must diverge. */ static void test_determinism(void) { unsigned char a[64], b[64], c[64]; keystream("map-seed", 8, a, sizeof a); keystream("map-seed", 8, b, sizeof b); CHECK(memcmp(a, b, sizeof a) == 0, "same seed reproduces sequence"); keystream("map-seee", 8, c, sizeof c); CHECK(memcmp(a, c, sizeof a) != 0, "different seed diverges"); } /* seeds are raw bytes of any length, including embedded NULs. */ static void test_seed_sizes(void) { unsigned char a[16], b[16]; static const unsigned char one[] = { 0x2a }; static const unsigned char withnul[] = { 'x', 0x00, 'y', 0x00, 'z' }; unsigned char big[300]; unsigned i; keystream(one, sizeof one, a, sizeof a); keystream(one, sizeof one, b, sizeof b); CHECK(memcmp(a, b, sizeof a) == 0, "1-byte seed is deterministic"); keystream(withnul, sizeof withnul, a, sizeof a); /* a seed truncated at the first NUL ("x") must differ */ keystream("x", 1, b, sizeof b); CHECK(memcmp(a, b, sizeof a) != 0, "embedded NUL bytes are part of seed"); for (i = 0; i < sizeof big; i++) big[i] = (unsigned char)i; keystream(big, sizeof big, a, sizeof a); keystream(big, sizeof big, b, sizeof b); CHECK(memcmp(a, b, sizeof a) == 0, "over-256-byte seed is deterministic"); } /* the little-endian accessors must assemble bytes the same way on every * platform: byte[0] is least significant. Verify against the raw stream. */ static void test_endian_accessors(void) { struct rc4rand_state s1, s2; unsigned char raw[8]; uint32_t v32, e32; uint64_t v64, e64; rc4rand_seed(&s1, 4, "endi"); rc4rand_seed(&s2, 4, "endi"); rc4rand(&s1, 4, raw); v32 = rc4rand32le(&s2); e32 = (uint32_t)raw[0] | ((uint32_t)raw[1] << 8) | ((uint32_t)raw[2] << 16) | ((uint32_t)raw[3] << 24); CHECK(v32 == e32, "rc4rand32le assembles little-endian"); rc4rand(&s1, 8, raw); v64 = rc4rand64le(&s2); e64 = (uint64_t)raw[0] | ((uint64_t)raw[1] << 8) | ((uint64_t)raw[2] << 16) | ((uint64_t)raw[3] << 24) | ((uint64_t)raw[4] << 32) | ((uint64_t)raw[5] << 40) | ((uint64_t)raw[6] << 48) | ((uint64_t)raw[7] << 56); CHECK(v64 == e64, "rc4rand64le assembles little-endian"); } /* the native-order accessors round-trip through the byte stream too; * exercising them also documents that the toy provides them. */ static void test_native_accessors(void) { struct rc4rand_state s1, s2; uint32_t a32, b32; uint64_t a64, b64; rc4rand_seed(&s1, 4, "nat0"); rc4rand_seed(&s2, 4, "nat0"); a32 = rc4rand32(&s1); b32 = rc4rand32(&s2); CHECK(a32 == b32, "rc4rand32 is deterministic"); rc4rand_seed(&s1, 4, "nat1"); rc4rand_seed(&s2, 4, "nat1"); a64 = rc4rand64(&s1); b64 = rc4rand64(&s2); CHECK(a64 == b64, "rc4rand64 is deterministic"); } /* rc4rand_below must stay in range, treat degenerate bounds sanely, and * distribute roughly uniformly (a fixed seed keeps this check stable). */ static void test_below(void) { struct rc4rand_state st; unsigned counts[10]; unsigned i, lo, hi, in_range; uint32_t v; CHECK(rc4rand_below(&st, 0) == 0, "rc4rand_below(0) returns 0"); CHECK(rc4rand_below(&st, 1) == 0, "rc4rand_below(1) returns 0"); rc4rand_seed(&st, 5, "range"); in_range = 1; for (i = 0; i < 100000; i++) { v = rc4rand_below(&st, 7); if (v >= 7) in_range = 0; } CHECK(in_range, "rc4rand_below(7) stays below 7"); /* large bound must not hang or overflow */ rc4rand_seed(&st, 3, "big"); in_range = 1; for (i = 0; i < 1000; i++) { v = rc4rand_below(&st, 0xfffffffbu); if (v >= 0xfffffffbu) in_range = 0; } CHECK(in_range, "rc4rand_below near-UINT32_MAX stays in range"); /* coarse uniformity: 100k draws into 10 buckets, expect ~10000 each */ rc4rand_seed(&st, 6, "unifrm"); for (i = 0; i < 10; i++) counts[i] = 0; for (i = 0; i < 100000; i++) counts[rc4rand_below(&st, 10)]++; lo = hi = counts[0]; for (i = 1; i < 10; i++) { if (counts[i] < lo) lo = counts[i]; if (counts[i] > hi) hi = counts[i]; } /* generous +/-10% band; deterministic seed so this cannot flake */ CHECK(lo >= 9000 && hi <= 11000, "rc4rand_below is roughly uniform"); } /* rc4rand_seed_str normalizes case and drops non-alphanumerics, so the * same map code typed different ways seeds identically. */ static void test_seed_str(void) { struct rc4rand_state a, b, c, d; unsigned char sa[16], sb[16], sc[16], sd[16]; rc4rand_seed_str(&a, "Volak-Tumi"); rc4rand_seed_str(&b, "volak tumi"); rc4rand_seed_str(&c, " V O L A K T U M I!!"); rc4rand_seed_str(&d, "dorn-esh"); rc4rand(&a, sizeof sa, sa); rc4rand(&b, sizeof sb, sb); rc4rand(&c, sizeof sc, sc); rc4rand(&d, sizeof sd, sd); CHECK(memcmp(sa, sb, sizeof sa) == 0 && memcmp(sb, sc, sizeof sb) == 0, "seed_str normalizes case, spaces, and punctuation"); CHECK(memcmp(sa, sd, sizeof sa) != 0, "seed_str distinguishes codes"); /* digits survive normalization: "swamp42" != "swamp" */ rc4rand_seed_str(&a, "swamp42"); rc4rand_seed_str(&b, "swamp"); rc4rand(&a, sizeof sa, sa); rc4rand(&b, sizeof sb, sb); CHECK(memcmp(sa, sb, sizeof sa) != 0, "seed_str keeps digits"); /* an all-punctuation string must not crash and must be deterministic */ rc4rand_seed_str(&a, "!!! ---"); rc4rand_seed_str(&b, "@@@"); rc4rand(&a, sizeof sa, sa); rc4rand(&b, sizeof sb, sb); CHECK(memcmp(sa, sb, sizeof sa) == 0, "seed_str empty-normalization falls back to a fixed seed"); } /* is a 3-letter chunk one of the curated pool syllables? */ static int is_pool_syllable(const char *s) { unsigned i; for (i = 0; i < RC4RAND_NSYL; i++) if (memcmp(s, rc4rand_syllables[i], 3) == 0) return 1; return 0; } /* every hyphen-separated chunk of a code must come from the curated pool. */ static int code_from_pool(const char *code) { for (;;) { if (!is_pool_syllable(code)) return 0; code += 3; if (*code == 0) return 1; if (*code != '-') return 0; code++; } } static void test_gen_code(void) { struct rc4rand_state a, b; char code[32], code2[32], tiny[4]; unsigned char sa[16], sb[16]; unsigned i, hyphens; /* determinism: same seed yields the same code */ rc4rand_seed(&a, 4, "code"); rc4rand_seed(&b, 4, "code"); CHECK(rc4rand_gen_code(&a, 4, code, sizeof code) == code, "gen_code returns its buffer on success"); rc4rand_gen_code(&b, 4, code2, sizeof code2); CHECK(strcmp(code, code2) == 0, "gen_code is deterministic"); /* 4 syllables -> "syl-syl-syl-syl" == 15 chars, 3 hyphens */ CHECK(strlen(code) == 15, "4-syllable code has expected length"); hyphens = 0; for (i = 0; code[i]; i++) if (code[i] == '-') hyphens++; CHECK(hyphens == 3, "4-syllable code has 3 separators"); CHECK(code_from_pool(code), "gen_code draws only from the curated pool"); /* a buffer that cannot hold the result must be rejected, not overrun */ CHECK(rc4rand_gen_code(&a, 4, tiny, sizeof tiny) == 0, "gen_code rejects an undersized buffer"); /* round-trip: a generated code re-seeds reproducibly via seed_str */ rc4rand_seed_str(&a, code); rc4rand_seed_str(&b, code); rc4rand(&a, sizeof sa, sa); rc4rand(&b, sizeof sb, sb); CHECK(memcmp(sa, sb, sizeof sa) == 0, "generated code round-trips as a seed"); } /* no generated code, once its separators are stripped, may contain a * banned substring. sweep many seeds and syllable counts to check. */ static void test_gen_code_clean(void) { static const char *const banned[] = { "uck", "hit", "unt", "iss", "igg" }; struct rc4rand_state st; char code[64], letters[64]; unsigned trial, syllables, b; int clean = 1; for (trial = 0; trial < 20000; trial++) { uint32_t s = trial; char *d = letters; const char *c; rc4rand_seed(&st, sizeof s, &s); syllables = 2 + (trial % 6); /* 2..7 syllables */ rc4rand_gen_code(&st, syllables, code, sizeof code); /* strip separators to the form a seed / a reader sees */ for (c = code; *c; c++) if (*c != '-') *d++ = *c; *d = 0; for (b = 0; b < sizeof banned / sizeof *banned; b++) if (strstr(letters, banned[b])) clean = 0; } CHECK(clean, "no generated code spells a banned substring"); } /* the pool's safety rests on an invariant: no syllable may itself be a * banned word, and none may end in a two-letter prefix that a seam could * complete into one ("is", "un", "ig", "uc"). Assert it directly so a * future edit to the pool that breaks the rule is caught here. */ static void test_pool_invariant(void) { static const char *const banned[] = { "uck", "hit", "unt", "iss", "igg" }; static const char *const bad_end[] = { "is", "un", "ig", "uc" }; unsigned i, k; int ok = 1; for (i = 0; i < RC4RAND_NSYL; i++) { const char *s = rc4rand_syllables[i]; if (strlen(s) != 3) ok = 0; for (k = 0; k < sizeof banned / sizeof *banned; k++) if (strcmp(s, banned[k]) == 0) ok = 0; for (k = 0; k < sizeof bad_end / sizeof *bad_end; k++) if (memcmp(s + 1, bad_end[k], 2) == 0) ok = 0; } CHECK(ok, "curated pool satisfies the safety invariant"); } int main(void) { test_kat(); test_determinism(); test_seed_sizes(); test_endian_accessors(); test_native_accessors(); test_below(); test_seed_str(); test_gen_code(); test_gen_code_clean(); test_pool_invariant(); fprintf(stderr, "\n%d tests, %d failed\n", tests_run, tests_failed); return tests_failed ? 1 : 0; }