#define _POSIX_C_SOURCE 200809L #include "dat.h" #include "sleep_verify.h" #include "sleep_feed.h" #include "sleep.h" #include #include #include #include #include #include static int changes_seen; static uint64_t last_change_version; static int directory_entries_seen; static int count_change(const sleep_metadata_entry_t *entry, uint64_t version, void *context) { (void)entry; (void)context; changes_seen++; last_change_version = version; return 0; } static int count_directory_entry(const sleep_metadata_entry_t *entry, const char *name, void *context) { (void)entry; (void)context; assert(!strcmp(name, "sample.bin")); directory_entries_seen++; return 0; } int main(void) { char directory[] = "/tmp/datgpt-archive-XXXXXX"; char sync_directory[] = "/tmp/datgpt-sync-XXXXXX"; char secrets_directory[] = "/tmp/datgpt-secrets-XXXXXX"; dat_archive_t archive, reopened; dat_archive_t sync_archive; sleep_metadata_entry_t entry; uint8_t *input, *output; uint8_t parsed_key[DAT_KEY_BYTES]; char url[DAT_URL_BYTES]; char hex[65]; char legacy_url[DAT_URL_BYTES]; char link_text[DAT_LINK_BYTES]; char probe_path[256]; char secret_path[1024], discovery_hex[65]; uint8_t discovery_key[32]; struct stat secret_stat; FILE *probe; dat_link_t link, parsed_link; dat_checkout_t checkout; dat_url_t parsed_url; dat_path_info_t path_info; int serve_listing; uint64_t old_version, new_version, sync_version; sleep_feed_t remote_feed; uint8_t remote_key[32] = {0x42}, remote_proof[] = {0x01, 0x02}; uint8_t remote_value[] = {0x61, 0x00, 0x62}, remote_value3[] = {0x63}; uint8_t *remote_read; static const uint8_t old_text[] = "old archive value\n"; static const uint8_t new_text[] = "new archive value\n"; size_t input_len = DAT_BLOCK_BYTES * 2 + 123; size_t output_len, i; assert(mkdtemp(directory) != NULL); assert(mkdtemp(secrets_directory) != NULL); memset(&remote_feed, 0, sizeof(remote_feed)); remote_feed.remote_only = 1; memcpy(remote_feed.keypair.pubkey, remote_key, sizeof(remote_key)); assert(sleep_feed_append_remote(&remote_feed, 0, remote_value, sizeof(remote_value), remote_proof, sizeof(remote_proof)) == 0); assert(sleep_feed_read(&remote_feed, 0, &remote_read, &output_len) == 0); assert(output_len == sizeof(remote_value) && !memcmp(remote_read, remote_value, output_len)); free(remote_read); assert(sleep_feed_append_remote(&remote_feed, 3, remote_value3, sizeof(remote_value3), remote_proof, sizeof(remote_proof)) == 0); assert(sleep_feed_length(&remote_feed) == 4); assert(sleep_feed_has(&remote_feed, 2) == 0); assert(sleep_feed_read(&remote_feed, 2, &remote_read, &output_len) != 0); assert(sleep_feed_read(&remote_feed, 3, &remote_read, &output_len) == 0); assert(output_len == sizeof(remote_value3) && remote_read[0] == remote_value3[0]); free(remote_read); assert(sleep_feed_append(&remote_feed, remote_value, sizeof(remote_value)) != 0); assert(sleep_feed_flush(&remote_feed) != 0); sleep_feed_close(&remote_feed); input = malloc(input_len); assert(input != NULL); for (i = 0; i < input_len; i++) input[i] = (uint8_t)((i * 31u + 7u) & 0xffu); if (dat_archive_create(&archive, directory) != 0) { perror("dat_archive_create"); return 1; } assert(dat_archive_save_secrets(&archive, secrets_directory) == 0); assert(sleep_compute_discovery_key(archive.metadata_key, discovery_key) == 0); assert(dat_key_to_hex(discovery_key, discovery_hex) == 0); snprintf(secret_path, sizeof(secret_path), "%s/%.2s/%s", secrets_directory, discovery_hex, discovery_hex + 2); assert(stat(secret_path, &secret_stat) == 0 && secret_stat.st_size == 64); assert(sleep_compute_discovery_key(archive.content_key, discovery_key) == 0); assert(dat_key_to_hex(discovery_key, discovery_hex) == 0); snprintf(secret_path, sizeof(secret_path), "%s/%.2s/%s", secrets_directory, discovery_hex, discovery_hex + 2); assert(stat(secret_path, &secret_stat) == 0 && secret_stat.st_size == 64); assert(dat_archive_open_writable(&reopened, directory, secrets_directory) == 0); dat_archive_close(&reopened); assert(dat_key_to_url(archive.metadata_key, url) == 0); assert(strlen(url) == 70); assert(strncmp(url, "dat://", 6) == 0); assert(dat_url_to_key(url, parsed_key) == 0); assert(memcmp(parsed_key, archive.metadata_key, DAT_KEY_BYTES) == 0); assert(dat_key_to_compact_url(archive.metadata_key, url) == 0); assert(strlen(url) < 70); assert(dat_url_to_key(url, parsed_key) == 0); assert(memcmp(parsed_key, archive.metadata_key, DAT_KEY_BYTES) == 0); assert(dat_key_to_hex(archive.metadata_key, hex) == 0); snprintf(legacy_url, sizeof(legacy_url), "dat://%s", hex); assert(dat_url_to_key(legacy_url, parsed_key) == 0); assert(memcmp(parsed_key, archive.metadata_key, DAT_KEY_BYTES) == 0); assert(dat_archive_append_directory(&archive, "/docs", 0755, 1) == 0); assert(dat_archive_append_file(&archive, "/docs/sample.bin", input, input_len, 0644, 2) == 0); assert(dat_archive_append_symlink(&archive, "/latest", "/docs/sample.bin", 3) == 0); assert(dat_archive_append_file(&archive, "/versioned.txt", old_text, sizeof(old_text) - 1, 0644, 4) == 0); old_version = dat_archive_version(&archive); assert(dat_archive_append_file(&archive, "/versioned.txt", new_text, sizeof(new_text) - 1, 0644, 5) == 0); new_version = dat_archive_version(&archive); assert(new_version == old_version + 1); assert(dat_checkout_init(&checkout, &archive, old_version) == 0); assert(dat_checkout_find(&checkout, "/versioned.txt", &entry) == 0); assert(dat_checkout_read(&checkout, &entry, &output, &output_len) == 0); assert(output_len == sizeof(old_text) - 1); assert(memcmp(output, old_text, output_len) == 0); free(output); assert(dat_archive_find(&archive, "/versioned.txt", &entry) == 0); assert(dat_archive_read_file(&archive, &entry, &output, &output_len) == 0); assert(output_len == sizeof(new_text) - 1); assert(memcmp(output, new_text, output_len) == 0); free(output); changes_seen = 0; last_change_version = 0; assert(dat_archive_changes(&archive, old_version, new_version, count_change, NULL) == 0); assert(changes_seen == 1 && last_change_version == new_version); memset(&link, 0, sizeof(link)); memcpy(link.key, archive.metadata_key, DAT_KEY_BYTES); link.has_version = 1; link.version = old_version; strcpy(link.path, "versioned.txt"); assert(dat_link_format(&link, link_text, sizeof(link_text)) == 0); assert(strstr(link_text, "+") != NULL); assert(strlen(link_text) >= 70); assert(dat_link_parse(link_text, &parsed_link) == 0); assert(parsed_link.has_version && parsed_link.version == old_version); assert(!strcmp(parsed_link.path, "versioned.txt")); assert(!memcmp(parsed_link.key, archive.metadata_key, DAT_KEY_BYTES)); assert(dat_link_format_compact(&link, link_text, sizeof(link_text)) == 0); assert(dat_link_parse(link_text, &parsed_link) == 0); assert(!memcmp(parsed_link.key, archive.metadata_key, DAT_KEY_BYTES)); assert(dat_url_parse("dat://protozoa.nz/path%20name?version=7&stat=1", &parsed_url) == 0); assert(!strcmp(parsed_url.host, "protozoa.nz")); assert(!strcmp(parsed_url.path, "/path name")); assert(parsed_url.has_version && parsed_url.version == 7 && parsed_url.wants_stat); assert(dat_url_parse("dat://protozoa.nz+8/dir//", &parsed_url) == 0); assert(parsed_url.has_version && parsed_url.version == 8 && parsed_url.force_directory_listing); assert(dat_url_parse("dat://protozoa.nz/dir/../page", &parsed_url) == 0); assert(!strcmp(parsed_url.path, "/page")); assert(dat_archive_stat_at(&archive, old_version, "/docs", &path_info) == 0); assert(path_info.exists && path_info.is_directory); directory_entries_seen = 0; assert(dat_archive_readdir_at(&archive, old_version, "/docs", count_directory_entry, NULL) == 0); assert(directory_entries_seen == 1); assert(dat_archive_resolve_at(&archive, old_version, "/docs/", 0, &path_info, &serve_listing) == 0); assert(serve_listing); assert(dat_archive_find(&archive, "/docs/sample.bin", &entry) == 0); assert(entry.block_count == 3); assert(entry.size == input_len); assert(dat_archive_read_file(&archive, &entry, &output, &output_len) == 0); assert(output_len == input_len); assert(memcmp(input, output, input_len) == 0); free(output); dat_archive_close(&archive); assert(dat_archive_open(&reopened, directory) == 0); assert(dat_archive_find(&reopened, "/docs/sample.bin", &entry) == 0); assert(dat_archive_read_file(&reopened, &entry, &output, &output_len) == 0); assert(output_len == input_len); assert(memcmp(input, output, input_len) == 0); free(output); free(input); assert(sleep_verify_dat(reopened.dat_path) == 0); assert(dat_archive_append_file(&reopened, "/page.html", (const uint8_t *)"

page

", 13, 0644, 6) == 0); assert(dat_archive_resolve_at(&reopened, dat_archive_version(&reopened), "/page", 0, &path_info, &serve_listing) == 0); assert(!serve_listing && !strcmp(path_info.entry.path, "/page.html")); assert(!strcmp(dat_content_type(path_info.entry.path), "text/html")); dat_archive_close(&reopened); assert(mkdtemp(sync_directory) != NULL); assert(dat_archive_create(&sync_archive, sync_directory) == 0); assert(snprintf(probe_path, sizeof(probe_path), "%s/mtime-probe", sync_directory) > 0); probe = fopen(probe_path, "wb"); assert(probe != NULL); assert(fwrite("mtime", 1, 5, probe) == 5); assert(fclose(probe) == 0); assert(dat_archive_sync_path(&sync_archive, sync_directory, "") == 0); assert(dat_archive_find(&sync_archive, "mtime-probe", &entry) == 0); assert(entry.mtime > UINT64_C(1000000000000)); /* Hyperdrive stores epoch milliseconds. */ sync_version = dat_archive_version(&sync_archive); assert(dat_archive_sync_path(&sync_archive, sync_directory, "") == 0); assert(dat_archive_version(&sync_archive) == sync_version); /* unchanged paths do not append */ dat_archive_close(&sync_archive); return 0; }