From 4cf8b35097a131abcfc8e0d04d35294be13943ac Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Tue, 6 Jan 2026 03:27:12 -0600 Subject: initial commit --- .gitignore | 3 + lib/arena.c | 107 ++++++++++++++++++++ lib/include/arena.h | 28 ++++++ lib/include/log.h | 52 ++++++++++ lib/include/net.h | 8 ++ lib/include/sha1.h | 42 ++++++++ lib/log.c | 67 +++++++++++++ lib/macros.h | 6 ++ lib/manifest.c | 4 + lib/meson.build | 5 + lib/net.c | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/sha1.c | 237 +++++++++++++++++++++++++++++++++++++++++++++ main.c | 21 ++++ meson.build | 9 ++ 14 files changed, 863 insertions(+) create mode 100644 .gitignore create mode 100644 lib/arena.c create mode 100644 lib/include/arena.h create mode 100644 lib/include/log.h create mode 100644 lib/include/net.h create mode 100644 lib/include/sha1.h create mode 100644 lib/log.c create mode 100644 lib/macros.h create mode 100644 lib/manifest.c create mode 100644 lib/meson.build create mode 100644 lib/net.c create mode 100644 lib/sha1.c create mode 100644 main.c create mode 100644 meson.build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7daa81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.cache/ +builddir/ +compile_commands.json diff --git a/lib/arena.c b/lib/arena.c new file mode 100644 index 0000000..bea98ef --- /dev/null +++ b/lib/arena.c @@ -0,0 +1,107 @@ +#include "arena.h" +#include +#include +#include +#include + +#ifdef ARENA_ASAN_INSTRUMENT +#include + +#define ASAN_POISON(...) __asan_poison_memory_region(__VA_ARGS__) +#define ASAN_UNPOISON(...) __asan_unpoison_memory_region(__VA_ARGS__) +#else +#define ASAN_POISON(...) +#define ASAN_UNPOISON(...) +#endif + +struct vl__arena_tag { + size_t cap; /* number of bytes after the end of this struct we can use */ + size_t cur; +}; + +#define ARENA_ALIGN (size_t)((1 << 6) - 1) +#define ROUND_UP(_am) (((_am) + ARENA_ALIGN) & ~ARENA_ALIGN) + +static void *arena_addr(void *a, size_t len) +{ + return (void *)((uintptr_t)a + ROUND_UP(sizeof(vl_arena)) + len); +} + +/* returns NULL if an arena could not be allocated */ +vl_arena *vl_arena_new(size_t cap) +{ + cap = ROUND_UP(cap); + + vl_arena *arena = calloc(1, ROUND_UP(sizeof(vl_arena)) + cap); + if (!arena) return NULL; + + arena->cap = cap; + arena->cur = 0; + + ASAN_POISON(arena + 1, cap + (ROUND_UP(sizeof(vl_arena)) - sizeof(vl_arena))); + + return arena; +} + +/* aborts if the arena is overflowing */ +void *vl_arena_push(vl_arena *parena, size_t len) +{ + size_t rlen = ROUND_UP(len); + if (parena->cur + rlen > parena->cap) abort(); + + void *ret = arena_addr(parena, parena->cur); + parena->cur += rlen; + + ASAN_UNPOISON(ret, len); + return ret; +} + +/* resets the arena (but does not free it) */ +void vl_arena_reset(vl_arena *parena) +{ + parena->cur = 0; + ASAN_POISON(arena_addr(parena, 0), parena->cap); +} + +/* frees the arena */ +void vl_arena_free(vl_arena *parena) +{ + free(parena); +} + +char *vl_arena_strdup(vl_arena *parena, const char *str) +{ + size_t len = strlen(str); + void *out = vl_arena_push(parena, len + 1); + memcpy(out, str, len + 1); + return out; +} + +char *vl_arena_sprintf(vl_arena *parena, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + char *ret = vl_arena_vsprintf(parena, fmt, ap); + + va_end(ap); + return ret; +} + +char *vl_arena_vsprintf(vl_arena *parena, const char *fmt, va_list ap) +{ + va_list copy; + va_copy(copy, ap); + + int desire = vsnprintf(NULL, 0, fmt, copy); + va_end(copy); + + if (desire < 0) { + abort(); /* vsnprintf should never fail */ + } + + char *str = vl_arena_push(parena, (unsigned)desire + 1); + vsnprintf(str, (unsigned)desire + 1, fmt, ap); + + return str; +} diff --git a/lib/include/arena.h b/lib/include/arena.h new file mode 100644 index 0000000..fa1674b --- /dev/null +++ b/lib/include/arena.h @@ -0,0 +1,28 @@ +#ifndef VL_ARENA_H_INCLUDED +#define VL_ARENA_H_INCLUDED + +/* implements a basic arena allocator */ + +#include +#include + +typedef struct vl__arena_tag vl_arena; + +/* returns NULL if an arena could not be allocated */ +vl_arena *vl_arena_new(size_t cap); + +/* aborts if the arena is overflowing */ +void *vl_arena_push(vl_arena *parena, size_t len); + +/* resets the arena (but does not free it) */ +void vl_arena_reset(vl_arena *parena); + +/* frees the arena */ +void vl_arena_free(vl_arena *parena); + +char *vl_arena_strdup(vl_arena *parena, const char *str); + +char *vl_arena_sprintf(vl_arena *parena, const char *fmt, ...); +char *vl_arena_vsprintf(vl_arena *parena, const char *fmt, va_list ap); + +#endif /* include guard */ diff --git a/lib/include/log.h b/lib/include/log.h new file mode 100644 index 0000000..a8549e0 --- /dev/null +++ b/lib/include/log.h @@ -0,0 +1,52 @@ +#include +#include + +#define LOG_TRACE 0u +#define LOG_DEBUG 1u +#define LOG_INFO 2u +#define LOG_WARN 3u +#define LOG_ERROR 4u + +void vl_log_setlevel(unsigned level); + +void vl_logv(unsigned level, const char *fmt, va_list args); +void vl_log(unsigned level, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + +#ifndef LOG_MIN_LEVEL +#define LOG_MIN_LEVEL LOG_TRACE +#endif + +#if LOG_TRACE >= LOG_MIN_LEVEL +#define LOG_TRACE_ENABLED +#define vl_trace(...) vl_log(LOG_TRACE, __VA_ARGS__) +#define vl_tracev(...) vl_logv(LOG_TRACE, __VA_ARGS__) +#else +#define vl_trace(...) +#define vl_tracev(...) +#endif + +#if LOG_DEBUG >= LOG_MIN_LEVEL +#define LOG_DEBUG_ENABLED +#define vl_debug(...) vl_log(LOG_DEBUG, __VA_ARGS__) +#define vl_debugv(...) vl_logv(LOG_DEBUG, __VA_ARGS__) +#else +#define vl_debug(...) +#define vl_debugv(...) +#endif + +#if LOG_INFO >= LOG_MIN_LEVEL +#define LOG_INFO_ENABLED +#define vl_info(...) vl_log(LOG_INFO, __VA_ARGS__) +#define vl_infov(...) vl_logv(LOG_INFO, __VA_ARGS__) +#else +#define vl_info(...) +#define vl_infov(...) +#endif + +#define LOG_WARN_ENABLED +#define vl_warn(...) vl_log(LOG_WARN, __VA_ARGS__) +#define vl_warnv(...) vl_logv(LOG_WARN, __VA_ARGS__) + +#define LOG_ERROR_ENABLED +#define vl_error(...) vl_log(LOG_ERROR, __VA_ARGS__) +#define vl_errorv(...) vl_logv(LOG_ERROR, __VA_ARGS__) diff --git a/lib/include/net.h b/lib/include/net.h new file mode 100644 index 0000000..d5f1482 --- /dev/null +++ b/lib/include/net.h @@ -0,0 +1,8 @@ +#ifndef VL_NET_H_INCLUDED +#define VL_NET_H_INCLUDED + +#include "arena.h" + +int vl_net_ensure_cached(vl_arena *arena, const char *url, const char *target_path); + +#endif diff --git a/lib/include/sha1.h b/lib/include/sha1.h new file mode 100644 index 0000000..05cedd4 --- /dev/null +++ b/lib/include/sha1.h @@ -0,0 +1,42 @@ +#ifndef VL_SHA1_H_INCLUDED +#define VL_SHA1_H_INCLUDED + +#include +#include + +#define VL_SHA1_STATELEN_32 ( 5u) /* 160 bits / 32 (bits per int) = 5 ints */ +#define VL_SHA1_CHUNKLEN_BYTES (64u) /* 512 bits / 8 (bits per byte) = 64 bytes */ + +#define VL_SHA1_DIGEST_BYTES (20u) +#define VL_SHA1_DIGEST_HEX_STRLEN (VL_SHA1_DIGEST_BYTES << 1) + +typedef struct { + /* struct fields are internal (struct size is ABI doe) */ + size_t vl_nchunk; + uint64_t vl_total; + uint32_t vl_state[VL_SHA1_STATELEN_32]; + uint8_t vl_chunk[VL_SHA1_CHUNKLEN_BYTES]; +} vl_sha1_st; + +typedef uint8_t vl_sha1[VL_SHA1_DIGEST_BYTES]; + +/* initializes a SHA1 state struct */ +void vl_sha1_init(vl_sha1_st *st); + +/* updates a SHA1 state struct with new data (must have been initialized with vl_sha1_init first) */ +void vl_sha1_update(vl_sha1_st *st, const void *data, size_t sz); + +/* finalizes a SHA1 state struct, writing the final digest to the second argument. the state struct + * is now invalid, and vl_sha1_init must be used on it again before it can be reused. */ +void vl_sha1_finalize(vl_sha1_st *st, vl_sha1 out); + +/* shortcut for hashing a buffer */ +void vl_sha1_buffer(vl_sha1 odigest, const void *data, size_t sz); + +/* converts digest bytes to a hex string (does NOT place a NUL byte) */ +void vl_sha1_encode(const vl_sha1 dig, char hex[VL_SHA1_DIGEST_HEX_STRLEN]); + +/* converts a hex string to digest bytes */ +int vl_sha1_decode(vl_sha1 odigest, const char hex[VL_SHA1_DIGEST_HEX_STRLEN]); + +#endif /* include guard */ diff --git a/lib/log.c b/lib/log.c new file mode 100644 index 0000000..4924782 --- /dev/null +++ b/lib/log.c @@ -0,0 +1,67 @@ +#include +#include +#include "log.h" + +/* TODO: replace this with something thread-safe later. */ + +static unsigned log_level = LOG_INFO; + +void vl_log_setlevel(unsigned level) +{ + log_level = level; +} + +#define MAX_LOGLEN ((size_t)4096) +#define MAX_LOGTIMELEN ((size_t)64) /* december 999th of 10000 A.D. at 100 o'clock */ + +static const char *const log_names[] = { + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + NULL +}; + +static FILE *log_stream(unsigned level) +{ + switch (level) { + case LOG_TRACE: + case LOG_DEBUG: + case LOG_INFO: + return stdout; + default: + return stderr; + } +} + +void vl_logv(unsigned level, const char *fmt, va_list args) +{ + char buf[MAX_LOGLEN]; + char datebuf[MAX_LOGTIMELEN]; + time_t now; + struct tm now_tm; + + if (log_level > level) return; + if (level > LOG_ERROR) return; + + vsnprintf(buf, MAX_LOGLEN, fmt, args); + strcpy(buf + MAX_LOGLEN - 4, "..."); + + now = time(NULL); + localtime_r(&now, &now_tm); + + if (!strftime(datebuf, MAX_LOGTIMELEN, "%H:%M:%S", &now_tm)) { + strcpy(datebuf, "???"); + } + + fprintf(log_stream(level), "[%s] %s: %s\n", datebuf, log_names[level], buf); +} + +void vl_log(unsigned level, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vl_logv(level, fmt, args); + va_end(args); +} diff --git a/lib/macros.h b/lib/macros.h new file mode 100644 index 0000000..5c1ba5d --- /dev/null +++ b/lib/macros.h @@ -0,0 +1,6 @@ +#ifndef VL_MACROS_H_INCLUDED +#define VL_MACROS_H_INCLUDED + +#define VL_USER_AGENT "vacuum launcher/0.1.0 (olauncher)" + +#endif diff --git a/lib/manifest.c b/lib/manifest.c new file mode 100644 index 0000000..c4d9bda --- /dev/null +++ b/lib/manifest.c @@ -0,0 +1,4 @@ +#include +#include + + diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 0000000..d2fca24 --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,5 @@ +vaclaunch_lib_inc = include_directories('include') +vaclaunch_libs = both_libraries('vaclaunch', + files('manifest.c', 'log.c', 'net.c', 'sha1.c', 'arena.c'), + include_directories : vaclaunch_lib_inc, + dependencies : [ curl_dep, jansson_dep ]) diff --git a/lib/net.c b/lib/net.c new file mode 100644 index 0000000..7509ac0 --- /dev/null +++ b/lib/net.c @@ -0,0 +1,274 @@ +#include "log.h" +#include "arena.h" +#include "macros.h" +#include "sha1.h" +#include "net.h" + +#include /* for dirname/basename */ +#include +#include +#include +#include + +/* TODO: also support etag/if-not-match rather than just last-modified/if-not-modified */ + +/* memory usage is about 3 * strlen(path) -- what da heeeellll */ +static char *derive_meta_path(vl_arena *arena, const char *path) +{ + char *base = basename(vl_arena_strdup(arena, path)); + char *dname = dirname(vl_arena_strdup(arena, path)); + return vl_arena_sprintf(arena, "%s/.%s.meta", dname, base); +} + +#define NET_BUFSIZE (4096) + +static int hash_file(const char *fname, vl_sha1 ohash) +{ + FILE *file; + unsigned char data[NET_BUFSIZE]; + size_t nread; + int ret = -1; + vl_sha1_st hashst; + + file = fopen(fname, "rb"); + + if (!file) { + vl_debug("failed to hash file %s: %s", fname, strerror(errno)); + return -1; + } + + vl_sha1_init(&hashst); + while ((nread = fread(data, 1, NET_BUFSIZE, file)) > 0) { + vl_sha1_update(&hashst, data, nread); + } + + if (ferror(file)) { + vl_debug("failed to read file %s :(", fname); + goto cleanup; + } + + vl_sha1_finalize(&hashst, ohash); + + ret = 0; + +cleanup: + fclose(file); + return ret; +} + +static int check_cache_consistent(vl_arena *arena, const char *path, const char *meta_path, struct curl_slist **headers) +{ + json_error_t jerr; + int ret = -1; + json_t *j = json_load_file(meta_path, 0, &jerr); + char *header = NULL; + + vl_trace("checking meta file %s", meta_path); + + if (!j) { + vl_debug("failed to load json meta %s(%d:%d:%d): %s", jerr.source, jerr.line, jerr.column, jerr.position, jerr.text); + goto cleanup; + } + + const char *lm; + const char *sha1_hex; + size_t sha1_len; + + if (json_unpack_ex(j, &jerr, JSON_STRICT, "{s:s, s:s%}", "lm", &lm, "sha1", &sha1_hex, &sha1_len) < 0) { + vl_debug("failed to unpack json meta %s: %s", meta_path, jerr.text); + goto cleanup; + } + + if (sha1_len != VL_SHA1_DIGEST_HEX_STRLEN) { + vl_debug("failed to read json meta %s: invalid sha1 digest (is %zu chars long, expected %u)", meta_path, sha1_len, VL_SHA1_DIGEST_HEX_STRLEN); + goto cleanup; + } + + vl_sha1 hashgot, hashexp; + if (vl_sha1_decode(hashexp, sha1_hex) < 0) { + vl_debug("failed to read json meta %s: invalid sha1 digest (bad format)", meta_path); + goto cleanup; + } + + if (hash_file(path, hashgot) < 0) { + goto cleanup; + } + + if (memcmp(hashexp, hashgot, sizeof(vl_sha1)) != 0) { +#ifdef LOG_DEBUG_ENABLED + char hash_exp_hex[VL_SHA1_DIGEST_HEX_STRLEN + 1]; + char hash_got_hex[VL_SHA1_DIGEST_HEX_STRLEN + 1]; + + hash_exp_hex[VL_SHA1_DIGEST_HEX_STRLEN] = 0; + hash_got_hex[VL_SHA1_DIGEST_HEX_STRLEN] = 0; + + vl_sha1_encode(hashexp, hash_exp_hex); + vl_sha1_encode(hashgot, hash_got_hex); + + vl_debug("file %s tampered on disk (sha1 expect %s, got %s)", path, hash_exp_hex, hash_got_hex); +#endif + + goto cleanup; + } + + header = vl_arena_sprintf(arena, "If-Modified-Since: %s", lm); + *headers = curl_slist_append(*headers, header); + ret = 0; + +cleanup: + if (j) json_decref(j); + return ret; +} + +static void write_transfer_meta(CURL *easy, vl_sha1 ohash, const char *meta_path) +{ + json_t *metaj = NULL; + struct curl_header *hdr; + CURLHcode hcode; + char hash_hex[VL_SHA1_DIGEST_HEX_STRLEN + 1]; + + vl_trace("writing transfer meta to %s", meta_path); + + hash_hex[VL_SHA1_DIGEST_HEX_STRLEN] = '\0'; + + hcode = curl_easy_header(easy, "Last-Modified", 0, CURLH_HEADER, -1, &hdr); + if (hcode != CURLHE_OK) { + vl_debug("Not writing meta %s: curl_easy_header(Last-Modified): %u", meta_path, hcode); + goto cleanup; + } + + vl_sha1_encode(ohash, hash_hex); + metaj = json_pack("{s:s, s:s}", "lm", hdr->value, "sha1", hash_hex); + if (!metaj) { + vl_debug("Not writing meta %s: json_pack returned NULL (weird)", meta_path); + goto cleanup; + } + + if (json_dump_file(metaj, meta_path, JSON_COMPACT) < 0) { + vl_debug("Failed writing meta to %s: json_dump_file", meta_path); + goto cleanup; + } + +cleanup: + if (metaj) json_decref(metaj); +} + +struct write_ctx +{ + const char *opath; + FILE *ofile; + vl_sha1_st sha1_state; +}; + +static size_t handle_write(char *ptr, size_t sz, size_t nmemb, void *user) +{ + struct write_ctx *ctx = user; + if (sz * nmemb == 0) return 0; + + /* Note that the output file is opened lazily, because we might receive a 304 (Not Modified) + * response with an empty body (in such a case, this function could be called with nmemb == 0). + * fopen for writing truncates the file, so we can't open the file in the main function. + */ + if (!ctx->ofile) { + ctx->ofile = fopen(ctx->opath, "wb"); + if (!ctx->ofile) { + vl_warn("Failed to open output file: fopen(%s, wb): %s", ctx->opath, strerror(errno)); + return CURL_WRITEFUNC_ERROR; + } + } + + vl_sha1_update(&ctx->sha1_state, ptr, sz * nmemb); + return fwrite(ptr, sz, nmemb, ctx->ofile); +} + +int vl_net_ensure_cached(vl_arena *arena, const char *url, const char *target_path) +{ + char *meta_path = derive_meta_path(arena, target_path); + int ret = -1; + char errbuf[CURL_ERROR_SIZE]; + CURLcode ecode; + CURL *easy = NULL; + struct curl_slist *headers = NULL; + struct write_ctx wrctx = { .opath = target_path, .ofile = NULL }; + vl_sha1 ohash; + long response_code; + + vl_trace("Downloading cached file from %s to %s (%s)", url, target_path, meta_path); + + check_cache_consistent(arena, target_path, meta_path, &headers); + + easy = curl_easy_init(); + if (!easy) { + vl_warn("Failed to set up CURL handle to download %s!", url); + goto cleanup; + } + + errbuf[0] = '\0'; + + if ((ecode = curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, errbuf)) != CURLE_OK) { + vl_warn("curl_easy_setopt(CURLOPT_ERRORBUFFER) failed: %s", curl_easy_strerror(ecode)); + goto cleanup; + } + +#define CHECK(_ex) do { \ + if ((ecode = (_ex)) != CURLE_OK) { \ + vl_warn("%s failed: %s (%s)", #_ex, curl_easy_strerror(ecode), errbuf); \ + goto cleanup; \ + } \ +} while (0) + + CHECK(curl_easy_setopt(easy, CURLOPT_URL, url)); + CHECK(curl_easy_setopt(easy, CURLOPT_USERAGENT, VL_USER_AGENT)); + CHECK(curl_easy_setopt(easy, CURLOPT_HTTPGET, 1L)); + CHECK(curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_ALL)); + CHECK(curl_easy_setopt(easy, CURLOPT_TIMEOUT, 60)); + + if (headers) { + CHECK(curl_easy_setopt(easy, CURLOPT_HTTPHEADER, headers)); + } + + vl_sha1_init(&wrctx.sha1_state); + + CHECK(curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, &handle_write)); + CHECK(curl_easy_setopt(easy, CURLOPT_WRITEDATA, &wrctx)); + + CHECK(curl_easy_perform(easy)); + + CHECK(curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &response_code)); + +#undef CHECK + + if (response_code == 304) { + vl_info("Downloaded file %s not modified.", target_path); + ret = 0; + goto cleanup; + } else if (response_code / 100 == 2) { + vl_trace("download %s success: %ld", url, response_code); + } else { + vl_warn("Bad HTTP response code %ld downloading %s", response_code, url); + goto cleanup; + } + + if (wrctx.ofile) { + fclose(wrctx.ofile); + wrctx.ofile = NULL; + } + + vl_sha1_finalize(&wrctx.sha1_state, ohash); + write_transfer_meta(easy, ohash, meta_path); + + ret = 0; + +cleanup: + if (easy) curl_easy_cleanup(easy); + if (headers) curl_slist_free_all(headers); + if (wrctx.ofile) { + if (ret < 0 && remove(target_path) < 0) { + vl_debug("... failed to clean up after failed download: unlink(%s): %s", target_path, strerror(errno)); + } + + fclose(wrctx.ofile); + } + + return ret; +} diff --git a/lib/sha1.c b/lib/sha1.c new file mode 100644 index 0000000..ed456c8 --- /dev/null +++ b/lib/sha1.c @@ -0,0 +1,237 @@ +#include "sha1.h" +#include + +/* for byteswap functions (TODO: avoid GNU extensions) */ +#include + +/* code adapted from https://git.figboot.dev/l2su/tree/src/digest/sha1.c */ + +#define SHA1_H0 UINT32_C(0x67452301) +#define SHA1_H1 UINT32_C(0xEFCDAB89) +#define SHA1_H2 UINT32_C(0x98BADCFE) +#define SHA1_H3 UINT32_C(0x10325476) +#define SHA1_H4 UINT32_C(0xC3D2E1F0) + +#define SHA1_K0 UINT32_C(0x5A827999) +#define SHA1_K1 UINT32_C(0x6ED9EBA1) +#define SHA1_K2 UINT32_C(0x8F1BBCDC) +#define SHA1_K3 UINT32_C(0xCA62C1D6) + +void vl_sha1_init(vl_sha1_st *st) +{ + st->vl_state[0] = SHA1_H0; + st->vl_state[1] = SHA1_H1; + st->vl_state[2] = SHA1_H2; + st->vl_state[3] = SHA1_H3; + st->vl_state[4] = SHA1_H4; + + memset(st->vl_chunk, 0, VL_SHA1_CHUNKLEN_BYTES); + st->vl_nchunk = 0; + st->vl_total = 0; +} + +static uint32_t rol(uint32_t in, uint32_t v) +{ + return (in << v) | (in >> (32 - v)); +} + +static void sha1_upd(vl_sha1_st *st) +{ + uint32_t w[80]; + uint32_t a, b, c, d, e, f, k, temp; + unsigned i; + + memcpy(w, st->vl_chunk, VL_SHA1_CHUNKLEN_BYTES); + for (i = 0; i < 16; ++i) { + w[i] = htobe32(w[i]); + } + + for (i = 16; i < 80; ++i) { + w[i] = rol(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1); + } + + a = st->vl_state[0]; + b = st->vl_state[1]; + c = st->vl_state[2]; + d = st->vl_state[3]; + e = st->vl_state[4]; + +#define UP \ + temp = rol(a, 5) + f + e + k + w[i]; \ + e = d; \ + d = c; \ + c = rol(b, 30); \ + b = a; \ + a = temp; + +#define OP(_start, _end, _k, _e) \ + for (i = _start; i < _end; ++i) { \ + f = _e; \ + k = _k; \ + UP \ + } + + OP( 0, 20, SHA1_K0, (b & c) ^ ((~b) & d)) + OP(20, 40, SHA1_K1, b ^ c ^ d) + OP(40, 60, SHA1_K2, (b & c) ^ (b & d) ^ (c & d)) + OP(60, 80, SHA1_K3, b ^ c ^ d) + +#undef OP +#undef UP + + st->vl_state[0] += a; + st->vl_state[1] += b; + st->vl_state[2] += c; + st->vl_state[3] += d; + st->vl_state[4] += e; +} + +void vl_sha1_update(vl_sha1_st *st, const void *data, size_t sz) +{ + const uint8_t *dbytes = data; + size_t rem; + st->vl_total += sz; + + while (sz >= (rem = (VL_SHA1_CHUNKLEN_BYTES - st->vl_nchunk))) { + memcpy(st->vl_chunk + st->vl_nchunk, dbytes, rem); + sha1_upd(st); + st->vl_nchunk = 0; + + dbytes += rem; + sz -= rem; + } + + if (sz > 0) { + memcpy(st->vl_chunk + st->vl_nchunk, dbytes, sz); + st->vl_nchunk += sz; + } +} + +void vl_sha1_finalize(vl_sha1_st *st, vl_sha1 out) +{ + /* SHA1 specifies a 1 bit to follow the data */ + st->vl_chunk[st->vl_nchunk] = UINT8_C(0x80); + ++st->vl_nchunk; + if (st->vl_nchunk > (VL_SHA1_CHUNKLEN_BYTES - 8)) { + /* must pad the rest of the way */ + memset(st->vl_chunk + st->vl_nchunk, 0, VL_SHA1_CHUNKLEN_BYTES - st->vl_nchunk); + sha1_upd(st); + st->vl_nchunk = 0; + } + + size_t rem = (VL_SHA1_CHUNKLEN_BYTES - st->vl_nchunk) - 8; + uint64_t lenbe = htobe64(st->vl_total * 8); + memset(st->vl_chunk + st->vl_nchunk, 0, rem); + st->vl_nchunk += rem; + + memcpy(st->vl_chunk + st->vl_nchunk, (uint8_t *)&lenbe, 8); + sha1_upd(st); + + uint32_t pout_i[VL_SHA1_STATELEN_32]; + pout_i[0] = htobe32(st->vl_state[0]); + pout_i[1] = htobe32(st->vl_state[1]); + pout_i[2] = htobe32(st->vl_state[2]); + pout_i[3] = htobe32(st->vl_state[3]); + pout_i[4] = htobe32(st->vl_state[4]); + memcpy(out, pout_i, sizeof(vl_sha1)); +} + +/* shortcut for hashing a buffer */ +void vl_sha1_buffer(vl_sha1 odigest, const void *data, size_t sz) +{ + vl_sha1_st st; + vl_sha1_init(&st); + vl_sha1_update(&st, data, sz); + vl_sha1_finalize(&st, odigest); +} + +static const char nibs[] = "0123456789abcdef"; + +/* converts digest bytes to a hex string (does NOT place a NUL byte) */ +void vl_sha1_encode(const vl_sha1 dig, char hex[VL_SHA1_DIGEST_HEX_STRLEN]) +{ + for (unsigned i = 0; i < VL_SHA1_DIGEST_BYTES; ++i) { + hex[i * 2] = nibs[dig[i] >> 4]; /* unsigned shift, dig[i] is 8 bytes */ + hex[i * 2 + 1] = nibs[dig[i] & 0xf]; + } +} + +#define NIB_BAD (~0u) + +static unsigned decode_nib(char c) +{ + switch (c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return NIB_BAD; + } +} + +/* converts a hex string to digest bytes */ +int vl_sha1_decode(vl_sha1 odigest, const char hex[VL_SHA1_DIGEST_HEX_STRLEN]) +{ + for (unsigned i = 0; i < VL_SHA1_DIGEST_BYTES; ++i) { + unsigned nibhi = decode_nib(hex[i * 2]); + unsigned niblo = decode_nib(hex[i * 2 + 1]); + if ((nibhi | niblo) == NIB_BAD) return -1; + odigest[i] = (uint8_t)((nibhi << 4) | niblo); + } + + return 0; +} + +#ifdef SHA1_STANDALONE_TEST + +#include +#include +#include + +int main(void) +{ + unsigned char chunk[4096]; + char dig[VL_SHA1_DIGEST_HEX_STRLEN + 1]; + vl_sha1_st st; + + dig[VL_SHA1_DIGEST_HEX_STRLEN] = '\0'; + vl_sha1_init(&st); + + while (1) { + ssize_t nread = read(0, chunk, 4096); + if (nread < 0) { + fprintf(stderr, "read: %s\n", strerror(errno)); + return 1; + } else if (nread == 0) { + break; + } + + vl_sha1_update(&st, chunk, nread); + } + + vl_sha1 o; + vl_sha1_finalize(&st, o); + + vl_sha1_encode(o, dig); + printf("%s\n", dig); + + vl_sha1 o2; + vl_sha1_decode(o2, dig); + printf("cmp: %d\n", memcmp(o, o2, sizeof(vl_sha1))); + + return 0; +} + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..93fe7e2 --- /dev/null +++ b/main.c @@ -0,0 +1,21 @@ +#include /* for setlocale */ +#include +#include "log.h" +#include "arena.h" +#include "net.h" + +int main(void) +{ + setlocale(LC_ALL, ""); /* TODO: make sure I don't regret this */ + vl_log_setlevel(LOG_TRACE); + + vl_arena *arena = vl_arena_new(8192); + int ret = vl_net_ensure_cached(arena, "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", "manifest.json"); + vl_arena_reset(arena); + + ret = vl_net_ensure_cached(arena, "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json", "runtime-manifest.json"); + printf("%d\n", ret); + + vl_arena_free(arena); + return 0; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..1c09862 --- /dev/null +++ b/meson.build @@ -0,0 +1,9 @@ +project('vaclaunch', 'c') + +jansson_dep = dependency('jansson', required : true) +curl_dep = dependency('libcurl', required : true) +subdir('lib') + +executable('vl', files('main.c'), + link_with : [ vaclaunch_libs.get_static_lib() ], + include_directories : vaclaunch_lib_inc) -- cgit v1.2.3-70-g09d2