#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* The tests are included into the same translation unit as the program, so we can access the internal static function pointers and wrappers. We must select a valid mode (base64) for basenc (BASE_TYPE == 42). */ static void select_base64_mode(void) { #if BASE_TYPE == 42 /* Select base64 mode by wiring the function pointers. */ base_length = base64_length_wrapper; required_padding = base64_required_padding; isubase = isubase64; base_encode = base64_encode; base_decode_ctx_init = base64_decode_ctx_init_wrapper; base_decode_ctx = base64_decode_ctx_wrapper; base_decode_ctx_finalize = decode_ctx_finalize; has_padding = base64_ctx_has_padding; get_pending_length = base64_ctx_get_pending_length; #else /* For other BASE_TYPEs, these are compile-time bound, nothing to do. */ #endif } /* Helper to run do_decode in a child process, capturing output and exit code. */ static int run_do_decode_case(const char *input, size_t input_len, bool ignore_garbage, char **out_buf, size_t *out_len) { select_base64_mode(); /* Create a temporary input file. */ char tmpl[] = "/tmp/basenc_test_XXXXXX"; int fd = mkstemp(tmpl); if (fd < 0) { perror("mkstemp"); return -1; } ssize_t written = 0; while ((size_t)written < input_len) { ssize_t w = write(fd, input + written, input_len - written); if (w < 0) { if (errno == EINTR) continue; perror("write"); close(fd); unlink(tmpl); return -1; } written += w; } if (lseek(fd, 0, SEEK_SET) < 0) { perror("lseek"); close(fd); unlink(tmpl); return -1; } FILE *in_fp = fdopen(fd, "rb"); if (!in_fp) { perror("fdopen"); close(fd); unlink(tmpl); return -1; } /* Pipe for capturing output. */ int pipefd[2]; if (pipe(pipefd) != 0) { perror("pipe"); fclose(in_fp); unlink(tmpl); return -1; } fflush(stdout); fflush(stderr); pid_t pid = fork(); if (pid < 0) { perror("fork"); fclose(in_fp); close(pipefd[0]); close(pipefd[1]); unlink(tmpl); return -1; } if (pid == 0) { /* Child: call do_decode, writing output to pipe write-end. */ close(pipefd[0]); FILE *out_fp = fdopen(pipefd[1], "wb"); if (!out_fp) { _exit(127); } /* Call the target function; it will exit the process. */ do_decode(in_fp, tmpl, out_fp, ignore_garbage); /* Not reached */ _exit(126); } /* Parent: clean up and collect output. */ close(pipefd[1]); /* We can unlink the temp file now; child has the file handle open. */ unlink(tmpl); fclose(in_fp); /* Close parent's handle; child still has its own. */ /* Read all data from the pipe into a dynamically sized buffer. */ size_t cap = 1024; size_t used = 0; char *buf = (char *)malloc(cap); if (!buf) { close(pipefd[0]); /* Reap child to avoid zombie. */ int st; waitpid(pid, &st, 0); return -1; } for (;;) { if (used == cap) { size_t new_cap = cap * 2; char *new_buf = (char *)realloc(buf, new_cap); if (!new_buf) { free(buf); close(pipefd[0]); int st; waitpid(pid, &st, 0); return -1; } buf = new_buf; cap = new_cap; } ssize_t r = read(pipefd[0], buf + used, cap - used); if (r > 0) { used += (size_t)r; continue; } else if (r == 0) { break; /* EOF */ } else { if (errno == EINTR) continue; /* On read error, still proceed to collect exit status. */ break; } } close(pipefd[0]); int status = 0; if (waitpid(pid, &status, 0) < 0) { free(buf); return -1; } *out_buf = buf; *out_len = used; if (WIFEXITED(status)) return WEXITSTATUS(status); else return 128; /* Abnormal termination. */ } void setUp(void) { /* No global state to init beyond selecting base64 mode on each run. */ } void tearDown(void) { /* Nothing to cleanup here. */ } static void test_do_decode_base64_simple_no_padding(void) { const char *enc = "TWFu"; /* "Man" */ char *out = NULL; size_t out_len = 0; int ec = run_do_decode_case(enc, strlen(enc), false, &out, &out_len); TEST_ASSERT_EQUAL_INT(0, ec); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_size_t(3, out_len); TEST_ASSERT_EQUAL_MEMORY("Man", out, 3); free(out); } static void test_do_decode_base64_with_padding(void) { const char *enc = "TWE="; /* "Ma" */ char *out = NULL; size_t out_len = 0; int ec = run_do_decode_case(enc, strlen(enc), false, &out, &out_len); TEST_ASSERT_EQUAL_INT(0, ec); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_size_t(2, out_len); TEST_ASSERT_EQUAL_MEMORY("Ma", out, 2); free(out); } static void test_do_decode_base64_missing_padding_autopad(void) { const char *enc = "TWE"; /* "Ma" without '='; finalize should auto-pad */ char *out = NULL; size_t out_len = 0; int ec = run_do_decode_case(enc, strlen(enc), false, &out, &out_len); TEST_ASSERT_EQUAL_INT(0, ec); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_size_t(2, out_len); TEST_ASSERT_EQUAL_MEMORY("Ma", out, 2); free(out); } static void test_do_decode_base64_ignore_garbage(void) { /* Insert various garbage and newlines; with ignore_garbage it should decode. */ const char *enc = "T?W!E= \n"; /* Should reduce to "TWE=" -> "Ma" */ char *out = NULL; size_t out_len = 0; int ec = run_do_decode_case(enc, strlen(enc), true, &out, &out_len); TEST_ASSERT_EQUAL_INT(0, ec); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_size_t(2, out_len); TEST_ASSERT_EQUAL_MEMORY("Ma", out, 2); free(out); } static void test_do_decode_invalid_char_fails(void) { const char *enc = "TW?="; /* Invalid '?' should cause failure without ignore-garbage */ char *out = NULL; size_t out_len = 0; int ec = run_do_decode_case(enc, strlen(enc), false, &out, &out_len); TEST_ASSERT_NOT_EQUAL(0, ec); /* Output should be empty (no complete block decoded). */ TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_size_t(0, out_len); free(out); } static void test_do_decode_incomplete_length_error(void) { /* A single base64 character cannot be salvaged even with auto-padding. */ const char *enc = "T"; char *out = NULL; size_t out_len = 0; int ec = run_do_decode_case(enc, strlen(enc), false, &out, &out_len); TEST_ASSERT_NOT_EQUAL(0, ec); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_size_t(0, out_len); free(out); } static void test_do_decode_large_multiblock(void) { /* Build a large input exceeding a single DEC_BLOCKSIZE cycle. "TWFu" -> "Man". Repeat enough times. */ const int repeats = 2000; /* 4*2000 = 8000 encoded bytes, > BASE_LENGTH(4200) */ size_t enc_len = 4u * repeats; char *enc = (char *)malloc(enc_len); TEST_ASSERT_NOT_NULL(enc); for (int i = 0; i < repeats; i++) { memcpy(enc + 4u * i, "TWFu", 4); } char *out = NULL; size_t out_len = 0; int ec = run_do_decode_case(enc, enc_len, false, &out, &out_len); free(enc); TEST_ASSERT_EQUAL_INT(0, ec); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_size_t(3u * repeats, out_len); /* Spot check beginning, middle, and end segments for "Man". */ TEST_ASSERT_EQUAL_MEMORY("Man", out, 3); TEST_ASSERT_EQUAL_MEMORY("Man", out + (3u * (repeats/2)), 3); TEST_ASSERT_EQUAL_MEMORY("Man", out + out_len - 3, 3); free(out); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_do_decode_base64_simple_no_padding); RUN_TEST(test_do_decode_base64_with_padding); RUN_TEST(test_do_decode_base64_missing_padding_autopad); RUN_TEST(test_do_decode_base64_ignore_garbage); RUN_TEST(test_do_decode_invalid_char_fails); RUN_TEST(test_do_decode_incomplete_length_error); RUN_TEST(test_do_decode_large_multiblock); return UNITY_END(); }