/* * aksl_strhash_djb2 -- TODO.md section 1.6. * * The expected values are the canonical djb2 ones: h = 5381, then * h = h * 33 + byte for each of len bytes, truncated to 32 bits. They were * computed independently of this implementation. * * Most vectors here are 7-bit ASCII, where signed and unsigned char agree and * the test therefore says nothing either way about byte signedness. The high-bit * vector is the one that pins TODO.md 2.2.6 down. */ #include "aksl_capture.h" static int test_empty_string_is_the_djb2_seed(void) { uint32_t h = 0; AKSL_CHECK_OK(aksl_strhash_djb2("", 0, &h)); AKSL_CHECK(h == 5381); return 0; } /* len drives the loop, so a zero length ignores the contents entirely. */ static int test_zero_length_ignores_the_buffer(void) { char buf[] = "ignored"; uint32_t h = 0; AKSL_CHECK_OK(aksl_strhash_djb2(buf, 0, &h)); AKSL_CHECK(h == 5381); return 0; } static int test_known_answer_vectors(void) { char hello[] = "hello"; char libname[] = "libakstdlib"; uint32_t h = 0; AKSL_CHECK_OK(aksl_strhash_djb2(hello, 5, &h)); AKSL_CHECK(h == 261238937u); AKSL_CHECK_OK(aksl_strhash_djb2(libname, 11, &h)); AKSL_CHECK(h == 884285482u); return 0; } /* * TODO.md 2.2.6, pinned. The cursor is an unsigned char * now, so bytes at or * above 0x80 contribute their unsigned value. Iterating a plain char * on x86 or * ARM Linux made them negative, giving 5859874 here instead of 5868578 -- a hash * that disagreed with canonical djb2 and, worse, disagreed with itself across * platforms depending on whether char happened to be signed. * * Benign for the 7-bit identifiers akbasic hashes; quietly wrong for the first * caller to key a table on a filename or a UTF-8 string literal. */ static int test_high_bit_bytes_are_unsigned(void) { char buf[2] = { (char)0xff, (char)0xfe }; uint32_t h = 0; AKSL_CHECK_OK(aksl_strhash_djb2(buf, sizeof(buf), &h)); AKSL_CHECK(h == 5868578u); /* The sign-extended answer, spelled out so a regression names itself. */ AKSL_CHECK(h != 5859874u); return 0; } /* The NUL-terminated convenience form (TODO.md 3.6) agrees with the length one. */ static int test_str_form_matches_the_length_form(void) { const char *s = "libakstdlib"; uint32_t from_str = 0; uint32_t from_len = 0; AKSL_CHECK_OK(aksl_strhash_djb2_str(s, &from_str)); AKSL_CHECK_OK(aksl_strhash_djb2(s, strlen(s), &from_len)); AKSL_CHECK(from_str == from_len); AKSL_CHECK(from_str == 884285482u); /* The empty string is the seed, not an error. */ AKSL_CHECK_OK(aksl_strhash_djb2_str("", &from_str)); AKSL_CHECK(from_str == 5381); AKSL_CHECK_STATUS(aksl_strhash_djb2_str(NULL, &from_str), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strhash_djb2_str("x", NULL), AKERR_NULLPOINTER); return 0; } /* The function is length-driven, not NUL-driven: an embedded NUL is hashed. */ static int test_embedded_nul_is_hashed(void) { char buf[3] = { 'a', '\0', 'b' }; uint32_t whole = 0; uint32_t prefix = 0; AKSL_CHECK_OK(aksl_strhash_djb2(buf, sizeof(buf), &whole)); AKSL_CHECK(whole == 193482728u); /* Stopping at the NUL would give the one-byte hash instead. */ AKSL_CHECK_OK(aksl_strhash_djb2(buf, 1, &prefix)); AKSL_CHECK(prefix != whole); return 0; } static int test_hash_is_stable_across_calls(void) { char buf[] = "repeatable"; uint32_t first = 0; uint32_t second = 0; AKSL_CHECK_OK(aksl_strhash_djb2(buf, sizeof(buf) - 1, &first)); AKSL_CHECK_OK(aksl_strhash_djb2(buf, sizeof(buf) - 1, &second)); AKSL_CHECK(first == second); return 0; } static int test_rejects_null_arguments(void) { char buf[] = "x"; uint32_t h = 0; AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strhash_djb2(NULL, 1, &h), AKERR_NULLPOINTER, "str"); AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strhash_djb2(buf, 1, NULL), AKERR_NULLPOINTER, "hashval"); return 0; } int main(void) { int failures = 0; akerr_init(); AKSL_RUN(failures, test_empty_string_is_the_djb2_seed); AKSL_RUN(failures, test_zero_length_ignores_the_buffer); AKSL_RUN(failures, test_known_answer_vectors); AKSL_RUN(failures, test_high_bit_bytes_are_unsigned); AKSL_RUN(failures, test_str_form_matches_the_length_form); AKSL_RUN(failures, test_embedded_nul_is_hashed); AKSL_RUN(failures, test_hash_is_stable_across_calls); AKSL_RUN(failures, test_rejects_null_arguments); AKSL_REPORT(failures); }