commit 40037995c712bf6989e25d68354f45bf22d1173d
Author: Jack Sangdahl <jack@pngu.org>
Date: Mon, 29 Sep 2025 09:24:52 -0600
challenge 1: convert hex to base64
Diffstat:
7 files changed, 199 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+.cache
+build
+compile_commands.json
diff --git a/Makefile b/Makefile
@@ -0,0 +1,33 @@
+CFLAGS = -Wall -Wextra -Werror -Wno-unused -pedantic -g -Ilib
+LDFLAGS =
+
+SRC_DIR = src
+LIB_SRC = $(wildcard lib/*.c)
+LIB_OBJ = $(LIB_SRC:%.c=build/%.o)
+LIB = build/lib/libcryptoutils.a
+
+SOURCES = $(wildcard $(SRC_DIR)/*.c)
+TARGETS = $(SOURCES:%.c=build/%)
+
+all: $(TARGETS)
+
+build/%: build/%.o $(LIB)
+ @mkdir -p $(dir $@)
+ $(CC) $(LDFLAGS) $^ -o $@
+
+build/%.o: %.c
+ @mkdir -p $(dir $@)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+$(LIB): $(LIB_OBJ)
+ @mkdir -p $(dir $@)
+ ar rcs $@ $^
+
+build/%.o: %.c
+ @mkdir -p $(dir $@)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+clean:
+ rm -rf build
+
+.PHONY: all clean
diff --git a/lib/base64.c b/lib/base64.c
@@ -0,0 +1,63 @@
+#include "base64.h"
+
+static const unsigned char base64_table[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static int mod_table[] = {0, 2, 1};
+
+int base64_encode(const char *base64, size_t base64_len, char **res) {
+ size_t res_len = 4 * ((base64_len + 2) / 3);
+ int padding = mod_table[base64_len % 3];
+ *res = (char *)malloc((res_len + 1) * sizeof(char));
+ for (size_t i = 0, j = 0; i < base64_len;) {
+ int octet_a = i < base64_len ? base64[i++] : 0;
+ int octet_b = i < base64_len ? base64[i++] : 0;
+ int octet_c = i < base64_len ? base64[i++] : 0;
+ int triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
+ (*res)[j++] = base64_table[(triple >> 3 * 6) & 0x3F];
+ (*res)[j++] = base64_table[(triple >> 2 * 6) & 0x3F];
+ (*res)[j++] = base64_table[(triple >> 1 * 6) & 0x3F];
+ (*res)[j++] = base64_table[(triple >> 0 * 6) & 0x3F];
+ }
+ for (int i = 0; i < padding; i++)
+ (*res)[res_len - 1 - i] = '=';
+ return res_len;
+}
+
+int base64_decode(const char *base64, size_t base64_len, char **res) {
+ unsigned char dtable[256], in[4], block[4], tmp;
+ char *pos;
+ memset(dtable, 0x80, sizeof(dtable));
+ for (size_t i = 0; i < sizeof(base64_table); i++)
+ dtable[base64_table[i]] = i;
+ dtable['='] = 0;
+ size_t cnt = 0;
+ for (size_t i = 0; i < base64_len; i++)
+ if (dtable[(int)base64[i]] != 0x80)
+ cnt++;
+ if (cnt % 4)
+ return -1;
+ size_t res_len = cnt / 4 * 3;
+ pos = *res = (char *)malloc(cnt * sizeof(char));
+ cnt = 0;
+ for (size_t i = 0; i < base64_len; i++) {
+ tmp = dtable[(int)base64[i]];
+ if (tmp == 0x80)
+ continue;
+ in[cnt] = base64[i];
+ block[cnt] = tmp;
+ cnt++;
+ if (cnt == 4) {
+ *pos++ = (block[0] << 2) | (block[1] >> 4);
+ *pos++ = (block[1] << 4) | (block[2] >> 2);
+ *pos++ = (block[2] << 6) | block[3];
+ cnt = 0;
+ }
+ }
+ if (pos > *res) {
+ if (in[2] == '=')
+ res_len -= 2;
+ else if (in[3] == '=')
+ res_len--;
+ }
+ return res_len;
+}
diff --git a/lib/base64.h b/lib/base64.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <stdlib.h>
+#include <string.h>
+
+int base64_encode(const char *base64, size_t base64_len, char **res);
+int base64_decode(const char *base64, size_t base64_len, char **res);
diff --git a/lib/hex.c b/lib/hex.c
@@ -0,0 +1,51 @@
+#include "hex.h"
+
+int hex_to_int(char c) {
+ int first = c / 16 - 3;
+ int second = c % 16;
+ int res = first * 10 + second;
+ if (res > 9)
+ res--;
+ if (res > 29)
+ res -= 20;
+ return res;
+}
+
+int hex_to_ascii(const char *hex, char **res) {
+ size_t hex_len = strlen(hex), res_len = hex_len / 2, i;
+ *res = (char *)malloc((res_len + 1) * sizeof(char));
+ char buf = 0;
+ for (i = 0; i < hex_len; i++)
+ if (i % 2) {
+ int high = hex_to_int(buf) * 16;
+ int low = hex_to_int(hex[i]);
+ (*res)[i / 2] = high + low;
+ } else
+ buf = hex[i];
+ (*res)[i] = 0;
+ return res_len;
+}
+
+char hex_digit(unsigned int n) { return n < 10 ? n + '0' : (n - 10) + 'a'; }
+
+int ascii_to_hex(const char *ascii, char **res) {
+ size_t ascii_len = strlen((char *)ascii), res_len = ascii_len * 2, i;
+ *res = (char *)malloc((res_len + 1) * sizeof(char));
+ for (i = 0; i < ascii_len; i++) {
+ (*res)[i * 2] = hex_digit(ascii[i] / 0x10);
+ (*res)[i * 2 + 1] = hex_digit(ascii[i] % 0x10);
+ }
+ (*res)[i * 2] = 0;
+ return res_len;
+}
+
+int to_hex(const unsigned char *data, size_t data_len, char **res) {
+ size_t res_len = data_len * 2, i;
+ *res = (char *)malloc((res_len + 1) * sizeof(char));
+ for (i = 0; i < data_len; i++) {
+ (*res)[i * 2] = hex_digit(data[i] / 0x10);
+ (*res)[i * 2 + 1] = hex_digit(data[i] % 0x10);
+ }
+ (*res)[i * 2] = 0;
+ return res_len;
+}
diff --git a/lib/hex.h b/lib/hex.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <stdlib.h>
+#include <string.h>
+
+int hex_to_int(char c);
+int hex_to_ascii(const char *hex, char **res);
+char hex_digit(unsigned int n);
+int ascii_to_hex(const char *ascii, char **res);
+int to_hex(const unsigned char *data, size_t data_len, char **res);
diff --git a/src/01_hex_to_base64.c b/src/01_hex_to_base64.c
@@ -0,0 +1,32 @@
+// Convert hex to base64
+#include "base64.h"
+#include "hex.h"
+#include <assert.h>
+#include <stdio.h>
+
+/*
+ * the string:
+ * 49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d
+ * should produce:
+ * SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t
+ * so go ahead and make that happen. you'll need to use this code for the rest
+ * of the exercises.
+ *
+ */
+
+int main(void) {
+ static char *input = "49276d206b696c6c696e6720796f757220627261696e206c696b652"
+ "06120706f69736f6e6f7573206d757368726f6f6d";
+ static char *output =
+ "SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t";
+ char *ascii = NULL, *base64 = NULL;
+ size_t str_len = hex_to_ascii(input, &ascii);
+ base64_encode(ascii, str_len, &base64);
+ printf("Input: %s\n", input);
+ printf("Output: %s\n", base64);
+ printf("Expected: %s\n", output);
+ assert(!strcmp(base64, output));
+ free(ascii);
+ free(base64);
+ return 0;
+}