aboutsummaryrefslogtreecommitdiffstats
path: root/src/version.c
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2023-12-28 06:40:07 -0600
committerLibravatar bigfoot547 <[email protected]>2023-12-28 06:40:07 -0600
commitcfe01c9f8b7ac0d82e694323b60fc172e0c35a48 (patch)
tree6f02144b38fcb2a4c5116e412f9ed4b0cc711728 /src/version.c
parentadd cheap SHA1 implementation (diff)
[wip] version stuff
Diffstat (limited to 'src/version.c')
-rw-r--r--src/version.c327
1 files changed, 327 insertions, 0 deletions
diff --git a/src/version.c b/src/version.c
new file mode 100644
index 0000000..b442f85
--- /dev/null
+++ b/src/version.c
@@ -0,0 +1,327 @@
+#include "version.h"
+#include "digest/digest.h"
+#include "l2su.h"
+#include "macros.h"
+#include "endian.h"
+#include "command.h"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+#include <curl/header.h>
+#include <jansson.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <alloca.h>
+
+const char *const l2_version__messages[] = {
+ "Success",
+ "Malformed version manifest",
+ "Version manifest JSON error",
+ "Allocation failed",
+ "OS error",
+ "An unspecified error has occurred",
+ NULL
+};
+
+#define L2_VERSION_MANIFEST_ETAG_PATH "/.l2_vermanifest_etag"
+#define L2_VERSION_MANIFEST_PATH "version_manifest_v2.json"
+#define L2_VERSION_MANIFEST_EXP_TIME ((time_t)120)
+
+unsigned l2_version__download_manifest(json_t **out_json);
+unsigned l2_version__load_all_from_json(json_t *json);
+
+unsigned l2_version__add_remote(json_t *obj);
+
+unsigned l2_version_load_remote(void)
+{
+ json_t *vers;
+ return l2_version__download_manifest(&vers);
+}
+
+unsigned l2_version_load_local(void)
+{
+ return VERSION_SUCCESS;
+}
+
+char *l2_version__read_etag(time_t *lastmod)
+{
+ size_t dpathlen = strlen(l2_state.paths.data);
+ char *etagpath;
+
+ L2_ASTRCAT2(etagpath, l2_state.paths.data, dpathlen, L2_VERSION_MANIFEST_ETAG_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_ETAG_PATH));
+
+ FILE *etagfp = fopen(etagpath, "rb");
+ char *etag = NULL;
+ uint32_t buf;
+ uint64_t ts;
+
+ if (!etagfp) return NULL;
+ if (fread(&buf, sizeof(uint32_t), 1, etagfp) < sizeof(uint32_t)) goto cleanup;
+ if (fread(&ts, sizeof(uint64_t), 1, etagfp) < sizeof(uint64_t)) goto cleanup;
+
+ buf = l2_betoh32(buf);
+ ts = l2_betoh64(ts);
+
+ etag = calloc(buf + 1, 1);
+ if (!etag) goto cleanup;
+
+ if (fread(etag, 1, buf, etagfp) < buf) goto cleanup;
+ etag[buf] = '\0';
+
+ *lastmod = (time_t)ts;
+
+ fclose(etagfp);
+
+ return etag;
+
+cleanup:
+ free(etag);
+ fclose(etagfp);
+ return NULL;
+}
+
+/* this function does not report errors on failure because its operation is not essential */
+void l2_version__write_etag(time_t modtime, const char *etag)
+{
+ size_t dpathlen = strlen(l2_state.paths.data);
+ char *etagpath;
+
+ L2_ASTRCAT2(etagpath, l2_state.paths.data, dpathlen, L2_VERSION_MANIFEST_ETAG_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_ETAG_PATH));
+
+ uint32_t etaglen = l2_betoh32((uint32_t)strlen(etag));
+ uint64_t mt_write = l2_betoh64((uint64_t)modtime);
+
+ FILE *etagfp = fopen(etagpath, "wb");
+ if (!etagfp) return;
+
+ if (fwrite(&etaglen, sizeof(uint32_t), 1, etagfp) < sizeof(uint32_t)) goto cleanup;
+ if (fwrite(&mt_write, sizeof(uint64_t), 1, etagfp) < sizeof(uint64_t)) goto cleanup;
+ if (fwrite(etag, 1, etaglen, etagfp) < etaglen) goto cleanup;
+
+cleanup:
+ fclose(etagfp);
+}
+
+int l2_version__read_manifest_file(json_t **manifest)
+{
+ size_t dpathlen = strlen(l2_state.paths.data);
+ char *manpath;
+
+ L2_ASTRCAT2(manpath, l2_state.paths.data, dpathlen, L2_VERSION_MANIFEST_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_PATH));
+
+ json_error_t err;
+ json_t *man = json_load_file(manpath, JSON_REJECT_DUPLICATES, &err);
+ if (!man) {
+ CMD_WARN("Error loading manifest file: %s\n", err.text);
+ return -1;
+ }
+
+ *manifest = man;
+ return 0;
+}
+
+#if 0
+unsigned l2_version__download_manifest(json_t **out_versions)
+{
+ unsigned ret = VERSION_SUCCESS;
+ CURL *pc = NULL;
+ FILE *outfile = NULL;
+ void *verdata = NULL;
+ int en;
+
+ struct curl_slist *headers = NULL;
+
+ time_t lastmod;
+ char *etag = l2_version__read_etag(&lastmod);
+
+ if (etag) {
+ if (lastmod + L2_VERSION_MANIFEST_EXP_TIME > time(NULL)) {
+ /* no need to redownload */
+ *out_versions = NULL;
+ return VERSION_SUCCESS;
+ }
+ }
+
+ pc = curl_easy_init();
+ struct l2_dlbuf dlbuf;
+ if (!pc) return VERSION_EUNSPEC;
+
+ l2_launcher_download_init(&dlbuf);
+
+ if (etag) {
+ size_t etaglen = strlen(etag);
+ char *etagheader;
+
+#define H_IFNONEMATCH "If-None-Match: "
+ L2_ASTRCAT2(etagheader, H_IFNONEMATCH, L2_CSTRLEN(H_IFNONEMATCH), etag, etaglen);
+#undef H_IFNONEMATCH
+
+ headers = curl_slist_append(NULL, etagheader);
+ if (!headers) {
+ ret = VERSION_EUNSPEC;
+ goto cleanup;
+ }
+
+ curl_easy_setopt(pc, CURLOPT_HTTPHEADER, headers);
+ }
+
+ curl_easy_setopt(pc, CURLOPT_URL, L2_URL_META_VERSION_MANIFEST);
+ curl_easy_setopt(pc, CURLOPT_WRITEFUNCTION, l2_dlcb);
+ curl_easy_setopt(pc, CURLOPT_WRITEDATA, &dlbuf);
+
+ if (curl_easy_perform(pc) != CURLE_OK) {
+ /* TODO: set error string */
+ ret = VERSION_EUNSPEC;
+ goto cleanup;
+ }
+
+ long rescode = 0;
+ curl_easy_getinfo(pc, CURLINFO_RESPONSE_CODE, &rescode);
+ if (rescode == 200) {
+ char *mfpath;
+ size_t datalen;
+ verdata = l2_launcher_download_finalize(&dlbuf, &datalen);
+
+ L2_ASTRCAT2(mfpath, l2_state.paths.data, pathlen, L2_VERSION_MANIFEST_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_PATH));
+
+ outfile = fopen(mfpath, "w");
+ if (!outfile) {
+ ret = VERSION_ERRNO;
+ goto cleanup;
+ }
+
+ if (fwrite(verdata, 1, datalen, outfile) < datalen) {
+ ret = VERSION_EUNSPEC;
+ goto cleanup;
+ }
+
+ fclose(outfile);
+ outfile = NULL;
+ }
+
+ struct curl_header *ch;
+ if (curl_easy_header(pc, "ETag", 0, CURLH_HEADER | CURLH_TRAILER | CURLH_1XX, 0, &ch) == CURLHE_OK) {
+ l2_version__write_etag(etagpath, time(NULL), ch->value);
+ }
+
+ ret = VERSION_SUCCESS;
+
+cleanup:
+ en = errno;
+
+ if (pc) curl_easy_cleanup(pc);
+ if (headers) curl_slist_free_all(headers);
+ if (outfile) fclose(outfile);
+ free(verdata);
+ l2_launcher_download_cleanup(&dlbuf);
+
+ errno = en;
+ return ret;
+}
+#endif
+
+/* parses an instance of version_manifest_v2.json */
+unsigned l2_version__load_all_from_json(json_t *json)
+{
+ if (!json_is_object(json)) {
+ return VERSION_EFORMAT;
+ }
+
+ json_t *latest = json_object_get(json, "latest");
+ json_t *versions = json_object_get(json, "versions");
+
+ if (!json_is_object(latest) || !json_is_array(versions)) {
+ return VERSION_EFORMAT;
+ }
+
+ const char *latestrel = json_string_value(json_object_get(latest, "release"));
+ const char *latestsnap = json_string_value(json_object_get(latest, "snapshot"));
+ size_t index;
+ json_t *value;
+ unsigned res;
+
+ if (!latestrel || !latestsnap) {
+ return VERSION_EFORMAT;
+ }
+
+ json_array_foreach(versions, index, value) {
+ if ((res = l2_version__add_remote(value)) != VERSION_SUCCESS)
+ return res;
+ }
+
+ return VERSION_SUCCESS;
+}
+
+unsigned l2_version__add_remote(json_t *js)
+{
+ struct l2_version_remote *ver = NULL;
+ struct tm ts;
+ json_t *val;
+
+ unsigned res = VERSION_SUCCESS;
+
+ if (!json_is_object(js)) {
+ return VERSION_EFORMAT;
+ }
+
+ ver = calloc(1, sizeof(struct l2_version_remote));
+ if (!ver) return VERSION_EALLOC;
+
+ res = VERSION_EFORMAT;
+
+ val = json_object_get(js, "id");
+ if (!json_is_string(val)) goto cleanup;
+ ver->id = strdup(json_string_value(val));
+
+ val = json_object_get(js, "type");
+ if (!json_is_string(val)) goto cleanup;
+ ver->type = strdup(json_string_value(val));
+
+ val = json_object_get(js, "url");
+ if (!json_is_string(val)) goto cleanup;
+ ver->url = strdup(json_string_value(val));
+
+ val = json_object_get(js, "sha1");
+ if (!json_is_string(val)) goto cleanup;
+ if (l2_sha1_digest_from_hex(&ver->sha1, json_string_value(val)) < 0)
+ goto cleanup;
+
+ val = json_object_get(js, "complianceLevel");
+ if (!json_is_number(val)) goto cleanup;
+ ver->compliance_level = json_integer_value(val);
+
+ val = json_object_get(js, "time");
+ if (!json_is_string(val)) goto cleanup;
+ memset(&ts, 0, sizeof(struct tm));
+ if (!l2_launcher_parse_iso_time(json_string_value(val), &ts)) goto cleanup;
+ ver->update_time = mktime(&ts);
+
+ val = json_object_get(js, "releaseTime");
+ if (!json_is_string(val)) goto cleanup;
+ memset(&ts, 0, sizeof(struct tm));
+ if (!l2_launcher_parse_iso_time(json_string_value(val), &ts)) goto cleanup;
+ ver->release_time = mktime(&ts);
+
+ /* add the thing to the global list */
+ if (l2_state.ver_remote_tail) {
+ l2_state.ver_remote_tail->next = ver;
+ ver->prev = l2_state.ver_remote_tail;
+ } else {
+ l2_state.ver_remote_head = l2_state.ver_remote_tail = ver;
+ }
+
+ return VERSION_SUCCESS;
+
+cleanup:
+ if (ver) {
+ free(ver->id);
+ free(ver->type);
+ free(ver->url);
+ }
+
+ free(ver);
+
+ return res;
+}