]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Index each cache node separately
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 20 Jan 2025 17:37:58 +0000 (11:37 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Tue, 21 Jan 2025 22:40:39 +0000 (16:40 -0600)
Background:

- Fort shouldn't lose the cache index when a signal interrupts it.
- Writing the index during the signal handler is not possible,
  because of the async-signal-safe requirement.
- Writing the index outside of the signal handler is seemingly not
  viable, because of the infelicities between the signal and
  multithreading APIs in C.

I haven't completely discarded the "dropping multithreading" option,
but since it seems disproportionate, I've been rethinking the index.

This commit scatters the index across several files, to minimize lost
information during a stopping signal. This will exacerbate the inode
problem, but that's temporary.

Reverts e0880f8eac3d35e576a2c1a2cb588424ab4b3e1b.

16 files changed:
src/cache.c
src/file.c
src/file.h
src/json_util.c
src/json_util.h
src/log.h
src/object/certificate.c
src/object/manifest.c
src/rrdp.c
src/rrdp.h
src/types/str.c
src/types/str.h
test/Makefile.am
test/cache_test.c
test/rrdp_test.c
test/rrdp_update_test.c

index f6b8158382bc5cb661b16bbd759a5bcb466a9f17..e0b6b4a3ee8834200372a5e35196b67f0d80a05f 100644 (file)
@@ -23,6 +23,7 @@
 #include "task.h"
 #include "types/array.h"
 #include "types/path.h"
+#include "types/str.h"
 #include "types/url.h"
 #include "types/uthash.h"
 
@@ -38,6 +39,8 @@ enum node_state {
        DLS_FRESH,
 };
 
+struct cache_table;
+
 /*
  * This is a delicate structure; pay attention.
  *
@@ -54,12 +57,18 @@ enum node_state {
  * this must be done through careful coding and review.
  */
 struct cache_node {
+       /*
+        * Hack: The "url" is a cache identifier, not an actual URL.
+        * If this is an rsync node, it equals `caRepository`.
+        * If this is an RRDP node, it's `rpkiNotify\tcaRepository`.
+        * This allows easy hash table indexing.
+        */
        struct cache_mapping map;
 
        enum node_state state;
        /* Result code of recent dl attempt (DLS_FRESH only) */
        int dlerr;
-       time_t attempt_ts;      /* Refresh: Dl attempt. Fallback: Commit */
+       time_t attempt_ts;      /* Refresh: Dl attempt. Fallback: Unused */
        time_t success_ts;      /* Refresh: Dl success. Fallback: Commit */
 
        struct mft_meta mft;    /* RPP fallbacks only */
@@ -125,7 +134,7 @@ static STAILQ_HEAD(cache_commits, cache_commit) commits = STAILQ_HEAD_INITIALIZE
 static pthread_mutex_t commits_lock = PTHREAD_MUTEX_INITIALIZER;
 
 #define LOCKFILE ".lock"
-#define INDEX_FILE "index.json"
+#define METAFILE "meta.json"
 #define TAGNAME_VERSION "fort-version"
 
 #ifdef UNIT_TESTING
@@ -139,7 +148,8 @@ delete_node(struct cache_table *tbl, struct cache_node *node, void *arg)
        __delete_node_cb(node);
 #endif
 
-       HASH_DEL(tbl->nodes, node);
+       if (tbl)
+               HASH_DEL(tbl->nodes, node);
 
        map_cleanup(&node->map);
        rrdp_state_free(node->rrdp);
@@ -206,6 +216,48 @@ strip_rsync_module(char const *url)
        return NULL;
 }
 
+static json_t *
+node2json(struct cache_node *node)
+{
+       char *tab;
+       json_t *json;
+
+       json = json_obj_new();
+       if (json == NULL)
+               return NULL;
+
+       tab = strchr(node->map.url, '\t');
+       if (tab == NULL) {
+               if (json_add_str(json, "url", node->map.url))
+                       goto fail;
+       } else {
+               if (json_add_strn(json, "notification", node->map.url, tab - node->map.url))
+                       goto fail;
+               if (json_add_str(json, "url", tab + 1))
+                       goto fail;
+       }
+       if (json_add_str(json, "path", node->map.path))
+               goto fail;
+       if (node->dlerr && json_add_int(json, "error", node->dlerr))
+               goto fail;
+       if (node->attempt_ts && json_add_ts(json, "attempt", node->attempt_ts))
+               goto fail;
+       if (node->success_ts && json_add_ts(json, "success", node->success_ts))
+               goto fail;
+       if (node->mft.num.size && json_add_bigint(json, "mftNum", &node->mft.num))
+               goto fail;
+       if (node->mft.update && json_add_ts(json, "mftUpdate", node->mft.update))
+               goto fail;
+       if (node->rrdp)
+               if (json_object_add(json, "rrdp", rrdp_state2json(node->rrdp)))
+                       goto fail;
+
+       return json;
+
+fail:  json_decref(json);
+       return NULL;
+}
+
 static int dl_rsync(struct cache_node *);
 static int dl_http(struct cache_node *);
 static int dl_rrdp(struct cache_node *);
@@ -216,7 +268,7 @@ init_table(struct cache_table *tbl, char *name, bool enabled, dl_cb dl)
        memset(tbl, 0, sizeof(*tbl));
        tbl->name = name;
        tbl->enabled = enabled;
-       cseq_init(&tbl->seq, name, false);
+       cseq_init(&tbl->seq, name, 0, false);
        tbl->download = dl;
        panic_on_fail(pthread_mutex_init(&tbl->lock, NULL),
            "pthread_mutex_init");
@@ -373,145 +425,264 @@ cache_setup(void)
        return 0;
 }
 
+static char *
+ctx2id(char const *rpkiNotify, char const *caRepository)
+{
+       char *result;
+       size_t nlen;
+
+       if (rpkiNotify == NULL && caRepository == NULL)
+               return NULL;
+       if (rpkiNotify == NULL)
+               return pstrdup(caRepository);
+       if (caRepository == NULL)
+               return pstrdup(rpkiNotify);
+
+       nlen = strlen(rpkiNotify);
+       result = pmalloc(nlen + strlen(caRepository) + 2);
+       strcpy(result, rpkiNotify);
+       result[nlen] = '\t';
+       strcpy(result + nlen + 1, caRepository);
+
+       return result;
+}
+
 static struct cache_node *
 json2node(json_t *json)
 {
        struct cache_node *node;
-       char const *str;
+       char const *notification;
+       char const *url;
+       char const *path;
        json_t *rrdp;
        int error;
 
        node = pzalloc(sizeof(struct cache_node));
 
-       if (json_get_str(json, "url", &str))
-               goto fail;
-       node->map.url = pstrdup(str);
-       if (json_get_str(json, "path", &str))
-               goto fail;
-       node->map.path = pstrdup(str);
-       error = json_get_ts(json, "attempt", &node->attempt_ts);
-       if (error != 0 && error != ENOENT)
-               goto fail;
-       error = json_get_ts(json, "success", &node->success_ts);
-       if (error != 0 && error != ENOENT)
-               goto fail;
-       error = json_get_bigint(json, "mftNum", &node->mft.num);
-       if (error < 0)
-               goto fail;
-       error = json_get_ts(json, "mftUpdate", &node->mft.update);
-       if (error < 0)
-               goto fail;
-       error = json_get_object(json, "rrdp", &rrdp);
-       if (error < 0)
-               goto fail;
-       if (error == 0 && rrdp_json2state(rrdp, &node->rrdp))
-               goto fail;
+       error = json_get_str(json, "notification", &notification);
+       switch (error) {
+       case 0:
+               break;
+       case ENOENT:
+               notification = NULL;
+               break;
+       default:
+               pr_op_debug("notification: %s", strerror(error));
+               goto fail1;
+       }
 
-       return node;
+       error = json_get_str(json, "url", &url);
+       switch (error) {
+       case 0:
+               break;
+       case ENOENT:
+               url = NULL;
+               break;
+       default:
+               pr_op_debug("url: %s", strerror(error));
+               goto fail1;
+       }
 
-fail:  map_cleanup(&node->map);
-       return NULL;
-}
+       node->map.url = ctx2id(notification, url);
+       if (node->map.url == NULL) {
+               pr_op_debug("Tag is missing both notification and url.");
+               goto fail1;
+       }
 
-static void
-json2tbl(json_t *root, struct cache_table *tbl)
-{
-       json_t *array, *child;
-       int index;
-       struct cache_node *node;
-       size_t urlen;
+       error = json_get_str(json, "path", &path);
+       if (error) {
+               pr_op_debug("path: %s", strerror(error));
+               goto fail2;
+       }
+       node->map.path = pstrdup(path);
 
-       if (json_get_object(root, tbl->name, &root))
-               return;
+       error = json_get_ts(json, "attempt", &node->attempt_ts);
+       if (error != 0 && error != ENOENT) {
+               pr_op_debug("attempt: %s", strerror(error));
+               goto fail2;
+       }
 
-       if (json_get_seq(root, "seq", &tbl->seq)) {
-               // XXX this is grouds to reset the cache...
-               pr_op_warn("Unable to load the 'seq' child for the %s table.",
-                   tbl->name);
-               return;
+       error = json_get_ts(json, "success", &node->success_ts);
+       if (error != 0 && error != ENOENT) {
+               pr_op_debug("success: %s", strerror(error));
+               goto fail2;
        }
-       if (json_get_array(root, "nodes", &array)) {
-               pr_op_warn("Unable to load the 'nodes' child for the %s table.",
-                   tbl->name);
-               return;
+
+       error = json_get_bigint(json, "mftNum", &node->mft.num);
+       if (error < 0) {
+               pr_op_debug("mftNum: %s", strerror(error));
+               goto fail2;
        }
 
-       json_array_foreach(array, index, child) {
-               node = json2node(child);
-               if (node == NULL)
-                       continue;
-               urlen = strlen(node->map.url);
-               // XXX worry about dupes
-               HASH_ADD_KEYPTR(hh, tbl->nodes, node->map.url, urlen, node);
+       error = json_get_ts(json, "mftUpdate", &node->mft.update);
+       if (error < 0) {
+               pr_op_debug("mftUpdate: %s", strerror(error));
+               goto fail3;
        }
+
+       error = json_get_object(json, "rrdp", &rrdp);
+       if (error < 0) {
+               pr_op_debug("rrdp: %s", strerror(error));
+               goto fail3;
+       }
+       if (error == 0 && rrdp_json2state(rrdp, node->map.path, &node->rrdp))
+               goto fail3;
+
+       return node;
+
+fail3: INTEGER_cleanup(&node->mft.num);
+fail2: map_cleanup(&node->map);
+fail1: free(node);
+       return NULL;
 }
 
 static int
-load_index_file(void)
+check_root_metafile(void)
 {
-       json_t *root;
        json_error_t jerr;
+       json_t *root;
        char const *file_version;
        int error;
 
-       pr_op_debug("Loading " INDEX_FILE "...");
+       pr_op_debug("Loading " METAFILE "...");
 
-       root = json_load_file(INDEX_FILE, 0, &jerr);
+       root = json_load_file(METAFILE, 0, &jerr);
        if (root == NULL) {
-               if (json_error_code(&jerr) == json_error_cannot_open_file)
-                       pr_op_debug(INDEX_FILE " does not exist.");
-               else
+               if (json_error_code(&jerr) == json_error_cannot_open_file) {
+                       pr_op_debug(METAFILE " does not exist.");
+                       return ENOENT;
+               } else {
                        pr_op_err("Json parsing failure at %s (%d:%d): %s",
-                           INDEX_FILE, jerr.line, jerr.column, jerr.text);
-               goto fail;
+                           METAFILE, jerr.line, jerr.column, jerr.text);
+                       return EINVAL;
+               }
        }
+
        if (json_typeof(root) != JSON_OBJECT) {
-               pr_op_err("The root tag of " INDEX_FILE " is not an object.");
+               pr_op_err("The root tag of " METAFILE " is not an object.");
                goto fail;
        }
 
        error = json_get_str(root, TAGNAME_VERSION, &file_version);
        if (error) {
                if (error > 0)
-                       pr_op_err(INDEX_FILE " is missing the '"
+                       pr_op_err(METAFILE " is missing the '"
                            TAGNAME_VERSION "' tag.");
                goto fail;
        }
-       if (strcmp(file_version, PACKAGE_VERSION) != 0)
+       if (strcmp(file_version, PACKAGE_VERSION) != 0) {
+               pr_op_err("The cache was written by Fort %s; "
+                   "I need to clear it.", file_version);
                goto fail;
-
-       json2tbl(root, &cache.rsync);
-       json2tbl(root, &cache.https);
-       json2tbl(root, &cache.rrdp);
-       json2tbl(root, &cache.fallback);
+       }
 
        json_decref(root);
-       pr_op_debug(INDEX_FILE " loaded.");
-
-       /*
-        * There are many ways in which a mismatching cache index can cause
-        * erratic behavior that's hard to detect. Since the index is written at
-        * the end of the validation cycle, crashing at any point between a
-        * cache refresh and the index write results in a misindexed cache.
-        *
-        * Deleting the index right after loading it seems to be a simple and
-        * reliable way to force Fort to reset the cache after a crash.
-        * (Recovering with an empty cache is safer than with a misindexed one.)
-        */
-       error = file_rm_f(INDEX_FILE);
-       if (error)
-               pr_op_warn("Unable to delete " INDEX_FILE ": %s. "
-                   "This means Fort might not recover properly after a crash. "
-                   "If Fort crashes for some reason, "
-                   "please clear the cache manually before restarting.",
-                   strerror(error));
-
+       pr_op_debug(METAFILE " loaded.");
        return 0;
 
 fail:  json_decref(root);
        return EINVAL;
 }
 
+static void
+collect_meta(struct cache_table *tbl, struct dirent *dir)
+{
+       char filename[64];
+       int wrt;
+       json_error_t jerr;
+       json_t *root;
+       struct cache_node *node;
+       size_t n;
+
+       if (S_ISDOTS(dir))
+               return;
+
+       wrt = snprintf(filename, 64, "%s/%s.json", tbl->name, dir->d_name);
+       if (wrt >= 64)
+               pr_crit("collect_meta: %d %s %s", wrt, tbl->name, dir->d_name);
+
+       pr_clutter("%s: Loading...", filename);
+
+       root = json_load_file(filename, 0, &jerr);
+       if (root == NULL) {
+               if (json_error_code(&jerr) == json_error_cannot_open_file)
+                       pr_op_warn("%s: File does not exist.", filename);
+               else
+                       pr_op_warn("%s: Json parsing failure at (%d:%d): %s",
+                           filename, jerr.line, jerr.column, jerr.text);
+               return;
+       }
+
+       if (json_typeof(root) != JSON_OBJECT) {
+               pr_op_warn("%s: Root tag is not an object.", filename);
+               goto end;
+       }
+
+       node = json2node(root);
+       if (node != NULL) {
+               n = strlen(node->map.url);
+               // XXX worry about dupes
+               HASH_ADD_KEYPTR(hh, tbl->nodes, node->map.url, n, node);
+       }
+
+       pr_clutter("%s: Loaded.", filename);
+end:   json_decref(root);
+}
+
+static void
+collect_metas(struct cache_table *tbl)
+{
+       DIR *dir;
+       struct dirent *file;
+       unsigned long id, max_id;
+       int error;
+
+       dir = opendir(tbl->name);
+       if (dir == NULL) {
+               error = errno;
+               if (error != ENOENT)
+                       pr_op_warn("Cannot open %s: %s",
+                           tbl->name, strerror(error));
+               return;
+       }
+
+       max_id = 0;
+       FOREACH_DIR_FILE(dir, file) {
+               if (hex2ulong(file->d_name, &id) != 0)
+                       continue;
+               if (id > max_id)
+                       max_id = id;
+               collect_meta(tbl, file);
+       }
+       error = errno;
+       if (error)
+               pr_op_warn("Could not finish traversing %s: %s",
+                   tbl->name, strerror(error));
+
+       closedir(dir);
+
+       tbl->seq.prefix = tbl->name;
+       tbl->seq.next_id = max_id + 1;
+       tbl->seq.pathlen = strlen(tbl->name);
+       tbl->seq.free_prefix = false;
+}
+
+static int
+load_index(void)
+{
+       int error;
+
+       error = check_root_metafile();
+       if (error)
+               return error;
+
+       collect_metas(&cache.rsync);
+       collect_metas(&cache.https);
+       collect_metas(&cache.rrdp);
+       collect_metas(&cache.fallback);
+       return 0;
+}
+
 int
 cache_prepare(void)
 {
@@ -521,7 +692,7 @@ cache_prepare(void)
        if (error)
                return error;
 
-       if (load_index_file() != 0) {
+       if (load_index() != 0) {
                error = reset_cache_dir();
                if (error)
                        goto fail;
@@ -551,107 +722,6 @@ fail:     flush_nodes();
        return error;
 }
 
-static json_t *
-node2json(struct cache_node *node)
-{
-       json_t *json;
-
-       json = json_obj_new();
-       if (json == NULL)
-               return NULL;
-
-       if (json_add_str(json, "url", node->map.url))
-               goto fail;
-       if (json_add_str(json, "path", node->map.path))
-               goto fail;
-       if (node->attempt_ts && json_add_ts(json, "attempt", node->attempt_ts))
-               goto fail;
-       if (node->success_ts && json_add_ts(json, "success", node->success_ts))
-               goto fail;
-       if (node->mft.num.size && json_add_bigint(json, "mftNum", &node->mft.num))
-               goto fail;
-       if (node->mft.update && json_add_ts(json, "mftUpdate", node->mft.update))
-               goto fail;
-       if (node->rrdp)
-               if (json_object_add(json, "rrdp", rrdp_state2json(node->rrdp)))
-                       goto fail;
-
-       return json;
-
-fail:  json_decref(json);
-       return NULL;
-}
-
-static json_t *
-tbl2json(struct cache_table *tbl)
-{
-       struct json_t *json, *nodes;
-       struct cache_node *node, *tmp;
-
-       json = json_obj_new();
-       if (!json)
-               return NULL;
-
-       if (json_add_seq(json, "seq", &tbl->seq))
-               goto fail;
-
-       nodes = json_array_new();
-       if (!nodes)
-               goto fail;
-       if (json_object_add(json, "nodes", nodes))
-               goto fail;
-
-       HASH_ITER(hh, tbl->nodes, node, tmp)
-               if (json_array_add(nodes, node2json(node)))
-                       goto fail;
-
-       return json;
-
-fail:  json_decref(json);
-       return NULL;
-}
-
-static json_t *
-build_index_file(void)
-{
-       json_t *json;
-
-       json = json_obj_new();
-       if (json == NULL)
-               return NULL;
-
-       if (json_object_add(json, TAGNAME_VERSION, json_str_new(PACKAGE_VERSION)))
-               goto fail;
-       if (json_object_add(json, "rsync", tbl2json(&cache.rsync)))
-               goto fail;
-       if (json_object_add(json, "https", tbl2json(&cache.https)))
-               goto fail;
-       if (json_object_add(json, "rrdp", tbl2json(&cache.rrdp)))
-               goto fail;
-       if (json_object_add(json, "fallback", tbl2json(&cache.fallback)))
-               goto fail;
-
-       return json;
-
-fail:  json_decref(json);
-       return NULL;
-}
-
-static void
-write_index_file(void)
-{
-       struct json_t *json;
-
-       json = build_index_file();
-       if (json == NULL)
-               return;
-
-       if (json_dump_file(json, INDEX_FILE, JSON_INDENT(2)))
-               pr_op_err("Unable to write " INDEX_FILE "; unknown cause.");
-
-       json_decref(json);
-}
-
 static int
 dl_rsync(struct cache_node *module)
 {
@@ -671,8 +741,8 @@ dl_rrdp(struct cache_node *notif)
        bool changed;
        int error;
 
-       error = rrdp_update(&notif->map, notif->success_ts,
-           &changed, &notif->rrdp);
+       error = rrdp_update(&notif->map, notif->success_ts, &changed,
+           &notif->rrdp);
        if (error)
                return error;
 
@@ -730,6 +800,44 @@ provide_node(struct cache_table *tbl, char const *url)
        return node;
 }
 
+static void
+rm_metadata(struct cache_node *node)
+{
+       char *filename;
+       int error;
+
+       filename = str_concat(node->map.path, ".json");
+       pr_op_debug("rm %s", filename);
+       if (unlink(filename) < 0) {
+               error = errno;
+               if (error == ENOENT)
+                       pr_op_debug("%s already doesn't exist.", filename);
+               else
+                       pr_op_warn("Cannot rm %s: %s", filename, strerror(errno));
+       }
+
+       free(filename);
+}
+
+static void
+write_metadata(struct cache_node *node)
+{
+       char *filename;
+       json_t *json;
+
+       json = node2json(node);
+       if (!json)
+               return;
+       filename = str_concat(node->map.path, ".json");
+
+       pr_op_debug("echo \"$json\" > %s", filename);
+       if (json_dump_file(json, filename, JSON_INDENT(2)))
+               pr_op_err("Unable to write %s; unknown cause.", filename);
+
+       free(filename);
+       json_decref(json);
+}
+
 /*
  * @uri is either a caRepository or a rpkiNotify
  * By contract, only sets @result on return 0.
@@ -770,7 +878,9 @@ do_refresh(struct cache_table *tbl, char const *uri, struct cache_node **result)
                mutex_unlock(&tbl->lock);
 
                node->attempt_ts = time_fatal();
+               rm_metadata(node);
                node->dlerr = tbl->download(node);
+               write_metadata(node);
                downloaded = true;
 
                mutex_lock(&tbl->lock);
@@ -1071,15 +1181,19 @@ cachent_print(struct cache_node *node)
        printf("\t%s (%s): ", node->map.url, node->map.path);
        switch (node->state) {
        case DLS_OUTDATED:
-               printf("stale");
+               printf("stale ");
                break;
        case DLS_ONGOING:
-               printf("downloading");
+               printf("downloading ");
                break;
        case DLS_FRESH:
-               printf("fresh (errcode %d)", node->dlerr);
+               printf("fresh (errcode %d) ", node->dlerr);
                break;
        }
+
+       printf("attempt:%lx success:%lx ", node->attempt_ts, node->success_ts);
+       printf("mftUpdate:%lx ", node->mft.update);
+       rrdp_print(node->rrdp);
        printf("\n");
 }
 
@@ -1088,7 +1202,7 @@ table_print(struct cache_table *tbl)
 {
        struct cache_node *node, *tmp;
 
-       printf("    %s enabled:%d seq:%s/%lu\n",
+       printf("%s enabled:%d seq:%s/%lx\n",
            tbl->name, tbl->enabled,
            tbl->seq.prefix, tbl->seq.next_id);
        HASH_ITER(hh, tbl->nodes, node, tmp)
@@ -1203,6 +1317,7 @@ commit_fallbacks(time_t now)
        struct cache_commit *commit;
        struct cache_node *fb;
        array_index i;
+       int error;
 
        while (!STAILQ_EMPTY(&commits)) {
                commit = STAILQ_FIRST(&commits);
@@ -1222,9 +1337,19 @@ commit_fallbacks(time_t now)
                                fb = provide_node(&cache.fallback,
                                    commit->caRepository);
                        }
-
-                       if (file_mkdir(fb->map.path, true) != 0)
-                               goto skip;
+                       fb->success_ts = now;
+
+                       pr_op_debug("mkdir -f %s", fb->map.path);
+                       if (mkdir(fb->map.path, CACHE_FILEMODE) < 0) {
+                               error = errno;
+                               if (error != EEXIST) {
+                                       pr_op_err("Cannot create '%s': %s",
+                                           fb->map.path, strerror(error));
+                                       goto skip;
+                               }
+
+                               rm_metadata(fb); /* error == EEXIST */
+                       }
 
                        commit_rpp(commit, fb);
                        discard_trash(commit, fb);
@@ -1235,14 +1360,16 @@ commit_fallbacks(time_t now)
                        pr_op_debug("Creating fallback for %s", map->url);
 
                        fb = provide_node(&cache.fallback, map->url);
+                       fb->success_ts = now;
                        if (is_fallback(map->path))
                                goto freshen;
 
                        file_ln(map->path, fb->map.path);
                }
 
+               write_metadata(fb);
+
 freshen:       fb->state = DLS_FRESH;
-               fb->attempt_ts = fb->success_ts = now;
 skip:          free(commit->rpkiNotify);
                free(commit->caRepository);
                for (i = 0; i < commit->nfiles; i++) {
@@ -1265,6 +1392,7 @@ remove_abandoned(struct cache_table *table, struct cache_node *node, void *arg)
 
        now = *((time_t *)arg);
        if (difftime(node->attempt_ts + cfg_cache_threshold(), now) < 0) {
+               rm_metadata(node);
                file_rm_rf(node->map.path);
                delete_node(table, node, NULL);
        }
@@ -1323,7 +1451,7 @@ void
 cache_commit(void)
 {
        cleanup_cache();
-       write_index_file();
+       file_write_txt(METAFILE, "{ \"fort-version\": \"" PACKAGE_VERSION "\" }");
        unlock_cache();
        flush_nodes();
 }
index 81387a64b61c9422b4552aa968c67e5ba8e189ee..50df56f05b4bcdbe77a552323bf9e7d1f9a7de36 100644 (file)
@@ -70,6 +70,7 @@ write_file(char const *path, void const *bytes, size_t n)
        if (error)
                return error;
 
+       errno = 0;
        if (fwrite(bytes, 1, n, out) != n) {
                error = errno;
                if (!error) /* Linux's man page does not mention errno */
@@ -210,6 +211,7 @@ file_rm_rf(char const *path)
        /* TODO (performance) optimize that 32 */
        if (nftw(path, rm, 32, FTW_DEPTH | FTW_PHYS) < 0) {
                error = errno;
+               // XXX This msg is sometimes annoying; maybe defer it
                pr_op_warn("Cannot remove %s: %s", path, strerror(error));
                return error ? error : -1;
        }
@@ -246,10 +248,11 @@ file_ln(char const *oldpath, char const *newpath)
 }
 
 void
-cseq_init(struct cache_sequence *seq, char *prefix, bool free_prefix)
+cseq_init(struct cache_sequence *seq, char *prefix, unsigned long id,
+    bool free_prefix)
 {
        seq->prefix = prefix;
-       seq->next_id = 0;
+       seq->next_id = id;
        seq->pathlen = strlen(prefix) + 4;
        seq->free_prefix = free_prefix;
 }
index a615d48914fb4d387cc7f2206c8717f3beb64647..b7be2e1e9201c6c8a7913e6a12c033d8b8078506 100644 (file)
@@ -43,10 +43,10 @@ struct cache_sequence {
        char *prefix;
        unsigned long next_id;
        size_t pathlen;
-       bool free_prefix;
+       bool free_prefix; // XXX seems to be always false
 };
 
-void cseq_init(struct cache_sequence *, char *, bool);
+void cseq_init(struct cache_sequence *, char *, unsigned long, bool);
 void cseq_cleanup(struct cache_sequence *);
 char *cseq_next(struct cache_sequence *);
 
index 696a85400afac504e5428dc7f24058f39b79ac6e..1466063203d0118afd61c443bbb45b84bd0bfb76 100644 (file)
@@ -173,35 +173,6 @@ json_get_object(json_t *parent, char const *name, json_t **obj)
        return 0;
 }
 
-int
-json_get_seq(json_t *parent, char const *name, struct cache_sequence *seq)
-{
-       json_t *child;
-       char const *pfx;
-       int error;
-
-       error = json_get_object(parent, name, &child);
-       if (error)
-               return error;
-
-       error = json_get_str(child, "pfx", &pfx);
-       if (error < 0)
-               return error;
-       if (error > 0)
-               return pr_op_err(
-                   "The '%s' JSON object is missing mandatory child 'pfx'.",
-                   name
-               );
-       error = json_get_ulong(child, "next", &seq->next_id);
-       if (error < 0)
-               return error;
-
-       seq->prefix = pstrdup(pfx);
-       seq->pathlen = strlen(pfx) + 4;
-       seq->free_prefix = true;
-       return 0;
-}
-
 /*
  * Any unknown members should be treated as errors, RFC8416 3.1:
  * "JSON members that are not defined here MUST NOT be used in SLURM
@@ -266,6 +237,18 @@ json_add_str(json_t *parent, char const *name, char const *value)
        return 0;
 }
 
+int
+json_add_strn(json_t *parent, char const *name, char const *value, size_t len)
+{
+       if (json_object_set_new(parent, name, json_stringn(value, len)))
+               return pr_op_err(
+                   "Cannot convert %s '%.*s' to json; unknown cause.",
+                   name, (int)len, value
+               );
+
+       return 0;
+}
+
 int
 json_add_ts(json_t *parent, char const *name, time_t value)
 {
@@ -288,33 +271,6 @@ json_add_ts(json_t *parent, char const *name, time_t value)
        return 0;
 }
 
-int
-json_add_seq(json_t *parent, char const *name,
-    struct cache_sequence const *value)
-{
-       json_t *seq;
-
-       seq = json_obj_new();
-       if (seq == NULL)
-               return -EINVAL;
-
-       if (json_add_ulong(seq, "next", value->next_id))
-               goto fail;
-       if (json_add_str(seq, "pfx", value->prefix))
-               goto fail;
-
-       if (json_object_set_new(parent, name, seq))
-               return pr_op_err(
-                   "Cannot convert sequence [%s, %lu] to json; unknown cause.",
-                   value->prefix, value->next_id
-               );
-
-       return 0;
-
-fail:  json_decref(seq);
-       return -EINVAL;
-}
-
 #define OOM_PFX " Likely out of memory (but there is no contract)."
 
 json_t *
index 7d0261b6f7f8a4bd2ece040727cfd1023ef4a234..e8f20f9a4e93a68463ac0fea1d670d0a79e0627d 100644 (file)
@@ -35,7 +35,6 @@ int json_get_ts(json_t *, char const *, time_t *);
 int json_get_str(json_t *, char const *, char const **);
 int json_get_array(json_t *, char const *, json_t **);
 int json_get_object(json_t *, char const *, json_t **);
-int json_get_seq(json_t *, char const *, struct cache_sequence *);
 
 bool json_valid_members_count(json_t *, size_t);
 
@@ -43,8 +42,8 @@ int json_add_int(json_t *, char const *, int);
 int json_add_ulong(json_t *, char const *, unsigned long);
 int json_add_bigint(json_t *, char const *, INTEGER_t *);
 int json_add_str(json_t *, char const *, char const *);
+int json_add_strn(json_t *, char const *, char const *, size_t);
 int json_add_ts(json_t *, char const *, time_t);
-int json_add_seq(json_t *, char const *, struct cache_sequence const *);
 
 json_t *json_obj_new(void);
 json_t *json_array_new(void);
index a4203481cdac81cacaba88085c45dd66073ea992..1544a1878865aff872ebf6c5d92666dfa243a99d 100644 (file)
--- a/src/log.h
+++ b/src/log.h
@@ -67,7 +67,7 @@ bool pr_val_enabled(unsigned int level);
 bool pr_op_enabled(unsigned int level);
 
 #define pr_clutter_enabled() false
-#define pr_clutter(...)
+#define pr_clutter(...) /* pr_op_debug(__VA_ARGS__) */
 
 /* == Operation logs == */
 
index a783eb5cd8859eacbe64d6c208aaaf384d6d3a96..12a8a9682d0e39e00023a684076d08c922ccaf49 100644 (file)
@@ -1895,7 +1895,7 @@ int
 certificate_traverse(struct rpki_certificate *ca)
 {
        struct cache_cage *cage;
-       char const *mft;
+       char const *mft_path;
        array_index i;
        struct cache_mapping *map;
        char const *ext;
@@ -1922,8 +1922,8 @@ certificate_traverse(struct rpki_certificate *ca)
                    "I'm going to have to skip it.", ca->sias.caRepository);
        }
 
-retry: mft = cage_map_file(cage, ca->sias.rpkiManifest);
-       if (!mft) {
+retry: mft_path = cage_map_file(cage, ca->sias.rpkiManifest);
+       if (!mft_path) {
                if (cage_disable_refresh(cage))
                        goto retry;
                error = pr_val_err("caRepository '%s' is missing a manifest.",
@@ -1931,7 +1931,7 @@ retry:    mft = cage_map_file(cage, ca->sias.rpkiManifest);
                goto end;
        }
 
-       error = manifest_traverse(ca->sias.rpkiManifest, mft, cage, ca);
+       error = manifest_traverse(ca->sias.rpkiManifest, mft_path, cage, ca);
        if (error) {
                if (cage_disable_refresh(cage))
                        goto retry;
index 09ad6834b813d6ba22c75ea376b10f45673f4ecf..7cf95c3eff0ddc10579dac3d7461796c760f4710 100644 (file)
@@ -208,21 +208,24 @@ validate_manifest(struct Manifest *mft, struct cache_cage *cage,
 }
 
 static void
-shuffle_mft_files(struct Manifest *mft)
+shuffle_mft_files(struct rpp *rpp)
 {
-       int i, j;
+       size_t i, j;
        unsigned int seed, rnd;
-       struct FileAndHash *tmpfah;
+       struct cache_mapping tmp;
+
+       if (rpp->nfiles < 2)
+               return;
 
        seed = time(NULL) ^ getpid();
 
        /* Fisher-Yates shuffle with modulo bias */
-       for (i = 0; i < mft->fileList.list.count - 1; i++) {
+       for (i = 0; i < rpp->nfiles - 1; i++) {
                rnd = rand_r(&seed);
-               j = i + rnd % (mft->fileList.list.count - i);
-               tmpfah = mft->fileList.list.array[j];
-               mft->fileList.list.array[j] = mft->fileList.list.array[i];
-               mft->fileList.list.array[i] = tmpfah;
+               j = i + rnd % (rpp->nfiles - i);
+               tmp = rpp->files[j];
+               rpp->files[j] = rpp->files[i];
+               rpp->files[i] = tmp;
        }
 }
 
@@ -291,7 +294,8 @@ check_file_and_hash(struct FileAndHash *fah, char const *path)
  */
 
 static int
-build_rpp(char const *mft_url, struct Manifest *mft, struct cache_cage *cage,
+collect_files(char const *mft_url, char const *mft_path,
+    struct Manifest *mft, struct cache_cage *cage,
     struct rpki_certificate *parent)
 {
        struct rpp *rpp;
@@ -302,11 +306,12 @@ build_rpp(char const *mft_url, struct Manifest *mft, struct cache_cage *cage,
        char const *path;
        int error;
 
-       shuffle_mft_files(mft);
+       if (mft->fileList.list.count == 0)
+               return pr_val_err("Manifest's file list is empty.");
 
        rpp = &parent->rpp;
        rpp_url = url_parent(mft_url); // XXX
-       rpp->nfiles = mft->fileList.list.count;
+       rpp->nfiles = mft->fileList.list.count + 1;     /* plus manifest */
        rpp->files = pzalloc(rpp->nfiles * sizeof(*rpp->files));
 
        for (i = 0; i < mft->fileList.list.count; i++) {
@@ -338,38 +343,64 @@ build_rpp(char const *mft_url, struct Manifest *mft, struct cache_cage *cage,
                error = check_file_and_hash(src, dst->path);
                if (error)
                        goto revert;
+       }
 
-               if (strcmp(((char const *)src->file.buf) + src->file.size - 4, ".crl") == 0) {
-                       if (rpp->crl.map != NULL) {
-                               error = pr_val_err(
-                                   "Manifest has more than one CRL.");
-                               goto revert;
-                       }
-                       rpp->crl.map = dst;
+       /* Manifest */
+       dst = &rpp->files[mft->fileList.list.count];
+       dst->url = pstrdup(mft_url);
+       dst->path = pstrdup(mft_path);
+
+       return 0;
+
+revert:        rpp_cleanup(rpp);
+       free(rpp_url);
+       return error;
+}
+
+static int
+load_crl(struct rpki_certificate *parent)
+{
+       struct rpp *rpp;
+       array_index f;
+
+       rpp = &parent->rpp;
+
+       for (f = 0; f < rpp->nfiles; f++)
+               if (str_ends_with(rpp->files[f].url, ".crl")) {
+                       if (rpp->crl.map != NULL)
+                               return pr_val_err("Manifest has more than one CRL.");
+                       rpp->crl.map = &rpp->files[f];
                }
-       }
 
        /* rfc6486#section-7 */
-       if (rpp->crl.map == NULL) {
-               error = pr_val_err("Manifest lacks a CRL.");
-               goto revert;
-       }
+       if (rpp->crl.map == NULL)
+               return pr_val_err("Manifest lacks a CRL.");
 
-       error = crl_load(rpp->crl.map, parent->x509, &rpp->crl.obj);
+       return crl_load(rpp->crl.map, parent->x509, &rpp->crl.obj);
+}
+
+static int
+build_rpp(char const *mft_url, char const *mft_path, struct Manifest *mft,
+    struct cache_cage *cage, struct rpki_certificate *parent)
+{
+       int error;
+
+       error = collect_files(mft_url, mft_path, mft, cage, parent);
        if (error)
-               goto revert;
+               return error;
 
-       free(rpp_url);
-       return 0;
+       shuffle_mft_files(&parent->rpp);
+
+       error = load_crl(parent);
+       if (error)
+               rpp_cleanup(&parent->rpp);
 
-revert:        rpp_cleanup(rpp);
-       free(rpp_url);
        return error;
 }
 
 int
-manifest_traverse(char const *url, char const *path, struct cache_cage *cage,
-    struct rpki_certificate *parent)
+manifest_traverse(char const *mft_url, char const *mft_path,
+    struct cache_cage *cage, struct rpki_certificate *parent)
 {
        static OID oid = OID_MANIFEST;
        struct oid_arcs arcs = OID2ARCS("manifest", oid);
@@ -379,10 +410,10 @@ manifest_traverse(char const *url, char const *path, struct cache_cage *cage,
        int error;
 
        /* Prepare */
-       fnstack_push(url); // XXX
+       fnstack_push(mft_url);
 
        /* Decode */
-       error = signed_object_decode(&sobj, path);
+       error = signed_object_decode(&sobj, mft_path);
        if (error)
                goto end1;
        error = decode_manifest(&sobj, &mft);
@@ -390,7 +421,7 @@ manifest_traverse(char const *url, char const *path, struct cache_cage *cage,
                goto end2;
 
        /* Initialize @summary */
-       error = build_rpp(url, mft, cage, parent);
+       error = build_rpp(mft_url, mft_path, mft, cage, parent);
        if (error)
                goto end3;
 
@@ -404,7 +435,7 @@ manifest_traverse(char const *url, char const *path, struct cache_cage *cage,
        error = validate_manifest(mft, cage, &parent->rpp.mft);
        if (error)
                goto end5;
-       error = refs_validate_ee(&ee.sias, parent->rpp.crl.map->url, url);
+       error = refs_validate_ee(&ee.sias, parent->rpp.crl.map->url, mft_url);
 
 end5:  rpki_certificate_cleanup(&ee);
        if (error)
index d5588ca2596795aae38e94921981e52cf71a4702..613eec00275a526cfe2f95904b1ac585ff0da629 100644 (file)
@@ -18,6 +18,7 @@
 #include "thread_var.h"
 #include "types/arraylist.h"
 #include "types/path.h"
+#include "types/str.h"
 #include "types/url.h"
 #include "types/uthash.h"
 
@@ -150,6 +151,21 @@ state_find_file(struct rrdp_state const *state, char const *url, size_t len)
        return file;
 }
 
+static struct cache_file *
+cache_file_add(struct rrdp_state *state, char *url, char *path)
+{
+       struct cache_file *file;
+       size_t urlen;
+
+       file = pzalloc(sizeof(struct cache_file));
+       file->map.url = url;
+       file->map.path = path;
+       urlen = strlen(url);
+       HASH_ADD_KEYPTR(hh, state->files, file->map.url, urlen, file);
+
+       return file;
+}
+
 static void
 metadata_cleanup(struct file_metadata *meta)
 {
@@ -525,7 +541,7 @@ handle_publish(xmlTextReaderPtr reader, struct parser_args *args)
 {
        struct publish tag = { 0 };
        struct cache_file *file;
-       size_t len;
+       char *path;
        int error;
 
        error = parse_publish(reader, &tag);
@@ -534,8 +550,7 @@ handle_publish(xmlTextReaderPtr reader, struct parser_args *args)
 
        pr_clutter("Publish %s", logv_filename(tag.meta.uri));
 
-       len = strlen(tag.meta.uri);
-       file = state_find_file(args->state, tag.meta.uri, len);
+       file = state_find_file(args->state, tag.meta.uri, strlen(tag.meta.uri));
 
        /* rfc8181#section-2.2 */
        if (file) {
@@ -575,17 +590,12 @@ handle_publish(xmlTextReaderPtr reader, struct parser_args *args)
                        goto end;
                }
 
-               file = pzalloc(sizeof(struct cache_file));
-               file->map.url = pstrdup(tag.meta.uri);
-               file->map.path = cseq_next(&args->state->seq);
-               if (!file->map.path) {
-                       free(file->map.url);
-                       free(file);
+               path = cseq_next(&args->state->seq);
+               if (!path) {
                        error = -EINVAL;
                        goto end;
                }
-
-               HASH_ADD_KEYPTR(hh, args->state->files, file->map.url, len, file);
+               file = cache_file_add(args->state, pstrdup(tag.meta.uri), path);
        }
 
        error = file_write_bin(file->map.path, tag.content, tag.content_len);
@@ -1173,8 +1183,8 @@ dl_notif(struct cache_mapping const *map,  time_t mtim, bool *changed,
  * snapshot, and explodes them into @notif->path.
  */
 int
-rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed,
-    struct rrdp_state **state)
+rrdp_update(struct cache_mapping const *notif, time_t mtim,
+    bool *changed, struct rrdp_state **state)
 {
        struct rrdp_state *old;
        struct update_notification new;
@@ -1190,7 +1200,8 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed,
        if (!(*changed))
                goto end;
 
-       pr_val_debug("New session/serial: %s/%s", new.session.session_id,
+       pr_val_debug("New session/serial: %s/%s",
+           new.session.session_id,
            new.session.serial.str);
 
        if ((*state) == NULL) {
@@ -1198,7 +1209,7 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed,
 
                old = pzalloc(sizeof(struct rrdp_state));
                /* session postponed! */
-               cseq_init(&old->seq, pstrdup(notif->path), true);
+               cseq_init(&old->seq, notif->path, 0, false);
                STAILQ_INIT(&old->delta_hashes);
 
                error = file_mkdir(notif->path, false);
@@ -1222,6 +1233,7 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed,
        serial_cmp = BN_cmp(old->session.serial.num, new.session.serial.num);
        if (serial_cmp < 0) {
                pr_val_debug("The Notification's serial changed.");
+
                error = validate_session_desync(old, &new);
                if (error)
                        goto snapshot_fallback;
@@ -1232,8 +1244,8 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed,
                if (!error)
                        goto end;
                /*
-                * The files are exploded and usable, but @cached is not
-                * updatable. So drop and create it anew.
+                * The files are exploded and usable, but @old is not updatable.
+                * So drop and create it anew.
                 * We might lose some delta hashes, but it's better than
                 * re-snapshotting the next time the notification changes.
                 * Not sure if it matters. This looks so unlikely, it's
@@ -1243,12 +1255,12 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed,
 
        } else if (serial_cmp > 0) {
                pr_val_debug("Cached serial is higher than notification serial.");
-               goto snapshot_fallback;
+               goto end;
 
        } else {
                pr_val_debug("The Notification changed, but the session ID and serial didn't, and no session desync was detected.");
                *changed = false;
-               goto clean_notif;
+               goto end;
        }
 
 snapshot_fallback:
@@ -1287,7 +1299,7 @@ rrdp_create_fallback(char *cage, struct rrdp_state **_state, char const *url)
        state = *_state;
        if (state == NULL) {
                *_state = state = pzalloc(sizeof(struct rrdp_state));
-               cseq_init(&state->seq, cage, false);
+               cseq_init(&state->seq, cage, 0, false);
        }
 
        file = pzalloc(sizeof(struct cache_file));
@@ -1383,8 +1395,6 @@ rrdp_state2json(struct rrdp_state *state)
        if (state->files)
                if (json_object_add(json, "files", files2json(state)))
                        goto fail;
-       if (json_add_seq(json, "seq", &state->seq))
-               goto fail;
        if (!STAILQ_EMPTY(&state->delta_hashes))
                if (json_object_add(json, TAGNAME_DELTAS, dh2json(state)))
                        goto fail;
@@ -1433,6 +1443,63 @@ json2serial(json_t *parent, struct rrdp_serial *serial)
        return 0;
 }
 
+static int
+json2files(json_t *jparent, char *parent, struct rrdp_state *state)
+{
+       json_t *jfiles;
+       char const *jkey;
+       json_t *jvalue;
+       size_t parent_len;
+       char const *path;
+       unsigned long id, max_id;
+       int error;
+
+       error = json_get_object(jparent, "files", &jfiles);
+       if (error < 0) {
+               pr_op_debug("files: %s", strerror(error));
+               return error;
+       }
+       if (error > 0)
+               return 0;
+
+       parent_len = strlen(parent);
+       max_id = 0;
+
+       json_object_foreach(jfiles, jkey, jvalue) {
+               if (!json_is_string(jvalue)) {
+                       pr_op_warn("RRDP file URL '%s' is not a string.", jkey);
+                       continue;
+               }
+
+               // XXX sanitize more
+
+               path = json_string_value(jvalue);
+               if (strncmp(path, parent, parent_len || path[parent_len] != '/') != 0) {
+                       pr_op_warn("RRDP path '%s' is not child of '%s'.",
+                           path, parent);
+                       continue;
+               }
+
+               error = hex2ulong(path + parent_len + 1, &id);
+               if (error) {
+                       pr_op_warn("RRDP file '%s' is not a hexadecimal number.", path);
+                       continue;
+               }
+               if (id > max_id)
+                       max_id = id;
+
+               cache_file_add(state, pstrdup(jkey), pstrdup(path));
+       }
+
+       if (HASH_COUNT(state->files) == 0) {
+               pr_op_warn("RRDP cage does not index any files.");
+               return EINVAL;
+       }
+
+       cseq_init(&state->seq, parent, max_id + 1, false);
+       return 0;
+}
+
 static int
 json2dh(json_t *json, struct rrdp_hash **dh)
 {
@@ -1501,8 +1568,9 @@ json2dhs(json_t *json, struct rrdp_state *state)
        return 0;
 }
 
+/* @path is expected to outlive the state. */
 int
-rrdp_json2state(json_t *json, struct rrdp_state **result)
+rrdp_json2state(json_t *json, char *path, struct rrdp_state **result)
 {
        struct rrdp_state *state;
        int error;
@@ -1510,30 +1578,30 @@ rrdp_json2state(json_t *json, struct rrdp_state **result)
        state = pzalloc(sizeof(struct rrdp_state));
 
        error = json2session(json, &state->session.session_id);
-       if (error)
-               goto revert_notif;
+       if (error < 0) {
+               pr_op_debug("session: %s", strerror(error));
+               goto fail;
+       }
        error = json2serial(json, &state->session.serial);
-       if (error)
-               goto revert_session;
-       error = json_get_seq(json, "seq", &state->seq);
-       if (error)
-               goto revert_serial;
+       if (error < 0) {
+               pr_op_debug("serial: %s", strerror(error));
+               goto fail;
+       }
+       error = json2files(json, path, state);
+       if (error) {
+               pr_op_debug("files: %s", strerror(error));
+               goto fail;
+       }
        error = json2dhs(json, state);
-       if (error)
-               goto revert_seq;
+       if (error) {
+               pr_op_debug("delta hashes: %s", strerror(error));
+               goto fail;
+       }
 
        *result = state;
        return 0;
 
-revert_seq:
-       cseq_cleanup(&state->seq);
-revert_serial:
-       BN_free(state->session.serial.num);
-       free(state->session.serial.str);
-revert_session:
-       free(state->session.session_id);
-revert_notif:
-       free(state);
+fail:  rrdp_state_free(state);
        return error;
 }
 
@@ -1555,3 +1623,28 @@ rrdp_state_free(struct rrdp_state *state)
        clear_delta_hashes(state);
        free(state);
 }
+
+void
+rrdp_print(struct rrdp_state *rs)
+{
+       struct cache_file *file, *tmp;
+       struct rrdp_hash *hash;
+       unsigned int i;
+
+       if (rs == NULL)
+               return;
+
+       printf("session:%s/%s\n", rs->session.session_id, rs->session.serial.str);
+       HASH_ITER(hh, rs->files, file, tmp)
+               printf("\t\tfile: %s\n", /* file->map.url, */ file->map.path);
+       printf("\t\tseq:%s/%lx\n", rs->seq.prefix, rs->seq.next_id);
+
+       STAILQ_FOREACH(hash, &rs->delta_hashes, hook) {
+               printf("\t\thash: ");
+               for (i = 0; i < RRDP_HASH_LEN; i++)
+                       printf("%c%c",
+                           hash_b2c(hash->bytes[i] >> 4),
+                           hash_b2c(hash->bytes[i]));
+               printf("\n");
+       }
+}
index 311b336b79b80961e8f960a70cb72c2dbf54e06b..f290ad22ccabf8772d7c8c879ddf809195c1a645 100644 (file)
@@ -17,8 +17,10 @@ char const *rrdp_file(struct rrdp_state const *, char const *);
 char const *rrdp_create_fallback(char *, struct rrdp_state **, char const *);
 
 json_t *rrdp_state2json(struct rrdp_state *);
-int rrdp_json2state(json_t *, struct rrdp_state **);
+int rrdp_json2state(json_t *, char *, struct rrdp_state **);
 
 void rrdp_state_free(struct rrdp_state *);
 
+void rrdp_print(struct rrdp_state *);
+
 #endif /* SRC_RRDP_H_ */
index 3c74edda2207c74302559da5fe3d36227fdbc79c..cbc9a17486dad7783d3a7f02bbd242cffe2bd801 100644 (file)
@@ -1,10 +1,44 @@
 #include "types/str.h"
 
+#include <errno.h>
 #include <openssl/bio.h>
 
 #include "log.h"
 #include "types/path.h"
 
+/* Allocates the result; will need free(). Never returns NULL. */
+char *
+str_concat(char const *s1, char const *s2)
+{
+       size_t n;
+       char *result;
+       int written;
+
+       n = strlen(s1) + strlen(s2) + 1;
+       result = pmalloc(n);
+
+       written = snprintf(result, n, "%s%s", s1, s2);
+       if (written != n - 1)
+               pr_crit("str_concat: %zu %d %s %s", n, written, s1, s2);
+
+       return result;
+}
+
+int
+hex2ulong(char const *hex, unsigned long *ulong)
+{
+       char *endptr;
+
+       errno = 0;
+       *ulong = strtoul(hex, &endptr, 16);
+       if (errno)
+               return errno;
+       if (endptr[0] != 0)
+               return -1;
+
+       return 0;
+}
+
 /**
  * Does not assume that @string is NULL-terminated.
  */
index 0489a103bd8ecc5eeb2a05ace4b9632e8aeb3f34..7f6ebc46f30d32daef83eee7892d20cac26e8792 100644 (file)
@@ -7,6 +7,10 @@
 
 #include "types/arraylist.h"
 
+char *str_concat(char const *, char const *);
+
+int hex2ulong(char const *, unsigned long *);
+
 int ia5s2string(ASN1_IA5STRING *, char **);
 int BN2string(BIGNUM *, char **);
 
index be654b4d164cffcce73dfa8a3a3feacf76a2803f..059570415a7bbce5db1113ce9c573e2c258739c4 100644 (file)
@@ -30,8 +30,8 @@ check_PROGRAMS =              address.test
 address_test_SOURCES =         types/address_test.c
 address_test_LDADD =           ${CHECK_LIBS}
 
-check_PROGRAMS += asn1_int.test
-asn1_int_test_SOURCES = asn1/asn1c/INTEGER_t_test.c
+check_PROGRAMS +=              asn1_int.test
+asn1_int_test_SOURCES =                asn1/asn1c/INTEGER_t_test.c
 asn1_int_test_LDADD =          ${CHECK_LIBS}
 
 check_PROGRAMS +=              base64.test
@@ -82,6 +82,7 @@ check_PROGRAMS +=             rrdp_update.test
 rrdp_update_test_SOURCES =     rrdp_update_test.c
 rrdp_update_test_LDADD =       ${CHECK_LIBS}
 rrdp_update_test_LDADD +=      ${XML2_LIBS}
+rrdp_update_test_LDADD +=      ${JANSSON_LIBS}
 
 check_PROGRAMS +=              rsync.test
 rsync_test_SOURCES =           rsync_test.c
index db5f4f0331ac4b97dc45f3bc707409c9f3aee097..d63b5980d04b2ec212a933656201b47428b557d6 100644 (file)
 #include "rrdp_util.h"
 #include "relax_ng.c"
 #include "rrdp.c"
+#include "asn1/asn1c/asn_codecs_prim.c"
+#include "asn1/asn1c/INTEGER.c"
 #include "types/map.c"
 #include "types/path.c"
+#include "types/str.c"
 #include "types/url.c"
 
 /* Mocks */
@@ -51,9 +54,20 @@ rsync_download(char const *url, char const *path)
 
 MOCK_VOID(__delete_node_cb, struct cache_node const *node)
 MOCK_VOID(task_wakeup_busy, void)
-__MOCK_ABORT(asn_INTEGER2str, char *, NULL, INTEGER_t const *bi)
-MOCK_VOID(INTEGER_move, INTEGER_t *to, INTEGER_t *from)
-MOCK_VOID(INTEGER_cleanup, INTEGER_t *i)
+static asn_dec_rval_t dummy = { 0 };
+__MOCK_ABORT(ber_check_tags, asn_dec_rval_t, dummy, const asn_codec_ctx_t *ctx,
+    const asn_TYPE_descriptor_t *td, asn_struct_ctx_t *opt_ctx,
+    const void *ptr, size_t size, int tag_mode, int last_tag_form,
+    ber_tlv_len_t *last_length, int *opt_tlv_form)
+__MOCK_ABORT(der_write_tags, ssize_t, 0, const asn_TYPE_descriptor_t *sd,
+    size_t struct_length, int tag_mode, int last_tag_form, ber_tlv_tag_t tag,
+    asn_app_consume_bytes_f *cb, void *app_key)
+__MOCK_ABORT(asn__format_to_callback, ssize_t, 0,
+    int (*cb)(const void *, size_t, void *key),
+    void *key, const char *fmt, ...)
+MOCK_ABORT_INT(asn_generic_no_constraint,
+    const asn_TYPE_descriptor_t *type_descriptor, const void *struct_ptr,
+    asn_app_constraint_failed_f *cb, void *key)
 
 /* Helpers */
 
@@ -62,8 +76,14 @@ setup_test(void)
 {
        dl_error = 0;
        init_tables();
-       ck_assert_int_eq(0, system("rm -rf rsync/ https/ rrdp/ fallback/ tmp/"));
-       ck_assert_int_eq(0, system("mkdir rsync/ https/ rrdp/ fallback/ tmp/"));
+
+       // XXX consider changing this function to `rm -rf <path>/*`
+       ck_assert_int_eq(0, file_rm_rf("."));
+       ck_assert_int_eq(0, mkdir("rsync", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("https", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("rrdp", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("fallback", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("tmp", CACHE_FILEMODE));
 }
 
 static struct cache_cage *
@@ -446,7 +466,10 @@ START_TEST(test_rsync_cleanup)
 
        setup_test();
 
-       ck_assert_int_eq(0, system("mkdir rsync/0 rsync/1 rsync/2 rsync/3"));
+       ck_assert_int_eq(0, mkdir("rsync/0", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("rsync/1", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("rsync/2", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("rsync/3", CACHE_FILEMODE));
 
        /* RPP0: Will remain constant */
        ck_assert_int_eq(0, file_write_txt("rsync/0/0", "A"));
@@ -477,9 +500,9 @@ START_TEST(test_rsync_cleanup)
        queue_commit(NULL, "rsync://domain/mod/rpp3", "rsync/3/0", "rsync/3/2");
        cleanup_cache();
        ck_filesystem("fallback",
-           /* RPP0 */ "fallback/0/0", "A", "fallback/0/1", "B",
-           /* RPP2 */ "fallback/1/0", "E", "fallback/1/1", "F",
-           /* RPP3 */ "fallback/2/0", "G", "fallback/2/1", "I",
+           /* RPP0 */ "fallback/0/0", "A", "fallback/0/1", "B", "fallback/0.json", "{",
+           /* RPP2 */ "fallback/1/0", "E", "fallback/1/1", "F", "fallback/1.json", "{",
+           /* RPP3 */ "fallback/2/0", "G", "fallback/2/1", "I", "fallback/2.json", "{",
            NULL);
 
        new_iteration(false);
@@ -492,9 +515,9 @@ START_TEST(test_rsync_cleanup)
        cleanup_cache();
 
        ck_filesystem("fallback",
-           /* RPP0 */ "fallback/0/0", "A", "fallback/0/1", "B",
-           /* RPP3 */ "fallback/2/0", "G", "fallback/2/2", "H",
-           /* RPP1 */ "fallback/3/0", "C", "fallback/3/1", "D",
+           /* RPP0 */ "fallback/0/0", "A", "fallback/0/1", "B", "fallback/0.json", "{",
+           /* RPP3 */ "fallback/2/0", "G", "fallback/2/2", "H", "fallback/2.json", "{",
+           /* RPP1 */ "fallback/3/0", "C", "fallback/3/1", "D", "fallback/3.json", "{",
            NULL);
 
        new_iteration(false);
@@ -593,7 +616,10 @@ START_TEST(test_https_cleanup)
        map.path = "https/52";
        cache_commit_file(&map);
        cleanup_cache();
-       ck_filesystem("fallback", "fallback/0", "A", "fallback/1", "C", NULL);
+       ck_filesystem("fallback",
+           "fallback/0", "A", "fallback/0.json", "{",
+           "fallback/1", "C", "fallback/1.json", "{",
+           NULL);
 
        new_iteration(false);
 
@@ -605,7 +631,10 @@ START_TEST(test_https_cleanup)
        map.path = "https/51";
        cache_commit_file(&map);
        cleanup_cache();
-       ck_filesystem("fallback", "fallback/0", "A", "fallback/2", "B", NULL);
+       ck_filesystem("fallback",
+           "fallback/0", "A", "fallback/0.json", "{",
+           "fallback/2", "B", "fallback/2.json", "{",
+           NULL);
 
        new_iteration(false);
 
@@ -625,7 +654,10 @@ START_TEST(test_rrdp_cleanup)
 
        setup_test();
 
-       ck_assert_int_eq(0, system("mkdir rrdp/0 rrdp/1 rrdp/2 rrdp/3"));
+       ck_assert_int_eq(0, mkdir("rrdp/0", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("rrdp/1", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("rrdp/2", CACHE_FILEMODE));
+       ck_assert_int_eq(0, mkdir("rrdp/3", CACHE_FILEMODE));
 
        ck_assert_int_eq(0, file_write_txt("rrdp/0/0", "A"));
        ck_assert_int_eq(0, file_write_txt("rrdp/0/1", "B"));
@@ -651,9 +683,9 @@ START_TEST(test_rrdp_cleanup)
        queue_commit(notif, "rsync://domain/mod/rpp3", "rrdp/3/0", "rrdp/3/2");
        cleanup_cache();
        ck_filesystem("fallback",
-           "fallback/0/0", "A", "fallback/0/1", "B",
-           "fallback/1/0", "E", "fallback/1/1", "F",
-           "fallback/2/0", "G", "fallback/2/1", "I",
+           "fallback/0/0", "A", "fallback/0/1", "B", "fallback/0.json", "{",
+           "fallback/1/0", "E", "fallback/1/1", "F", "fallback/1.json", "{",
+           "fallback/2/0", "G", "fallback/2/1", "I", "fallback/2.json", "{",
            NULL);
 
        new_iteration(false);
@@ -664,9 +696,9 @@ START_TEST(test_rrdp_cleanup)
        queue_commit(notif, "rsync://domain/mod/rpp3", "fallback/2/0", "rrdp/3/1");
        cleanup_cache();
        ck_filesystem("fallback",
-           "fallback/0/0", "A", "fallback/0/1", "B",
-           "fallback/2/0", "G", "fallback/2/2", "H",
-           "fallback/3/0", "C", "fallback/3/1", "D",
+           "fallback/0/0", "A", "fallback/0/1", "B", "fallback/0.json", "{",
+           "fallback/2/0", "G", "fallback/2/2", "H", "fallback/2.json", "{",
+           "fallback/3/0", "C", "fallback/3/1", "D", "fallback/3.json", "{",
            NULL);
 
        new_iteration(false);
@@ -757,12 +789,157 @@ START_TEST(test_context)
 }
 END_TEST
 
+static void
+ck_rrdp(struct rrdp_state *expected, struct rrdp_state *actual)
+{
+       struct cache_file *expf, *actf, *tmp;
+       struct rrdp_hash *exph, *acth;
+
+       if (expected == NULL) {
+               ck_assert_ptr_eq(NULL, actual);
+               return;
+       }
+
+       ck_assert_ptr_ne(NULL, actual);
+       ck_assert_str_eq(expected->session.session_id, actual->session.session_id);
+       ck_assert_int_eq(0, BN_cmp(expected->session.serial.num, actual->session.serial.num));
+       ck_assert_str_eq(expected->session.serial.str, actual->session.serial.str);
+
+       ck_assert_int_eq(HASH_COUNT(expected->files), HASH_COUNT(actual->files));
+       HASH_ITER(hh, expected->files, expf, tmp) {
+               HASH_FIND(hh, actual->files, expf->map.url, strlen(expf->map.url), actf);
+               ck_assert_ptr_ne(NULL, actf);
+               ck_assert_str_eq(expf->map.url, actf->map.url);
+               ck_assert_str_eq(expf->map.path, actf->map.path);
+       }
+
+       acth = STAILQ_FIRST(&expected->delta_hashes);
+       STAILQ_FOREACH(exph, &expected->delta_hashes, hook) {
+               ck_assert_ptr_ne(NULL, acth);
+               ck_assert(memcmp(exph->bytes, acth->bytes, sizeof(exph->bytes)) == 0);
+               acth = STAILQ_NEXT(acth, hook);
+       }
+       ck_assert_ptr_eq(NULL, acth);
+}
+
+static void
+ck_json(struct cache_node *src)
+{
+       struct cache_node *dst;
+       json_t *json;
+
+       json = node2json(src);
+       json_dumpf(json, stdout, JSON_INDENT(2));
+       printf("\n");
+       ck_assert_ptr_ne(NULL, json);
+       dst = json2node(json);
+       json_decref(json);
+
+       ck_assert_ptr_ne(NULL, dst);
+       ck_assert_str_eq(src->map.url, dst->map.url);
+       ck_assert_str_eq(src->map.path, dst->map.path);
+       ck_assert_int_eq(DLS_OUTDATED, dst->state);     /* Must be reset */
+       ck_assert_int_eq(0, dst->dlerr);                /* Must be reset */
+       ck_assert_int_eq(src->attempt_ts, dst->attempt_ts);
+       ck_assert_int_eq(src->success_ts, dst->success_ts);
+       ck_assert(INTEGER_cmp(&src->mft.num, &dst->mft.num) == 0);
+       ck_assert_int_eq(src->mft.update, dst->mft.update);
+       ck_rrdp(src->rrdp, dst->rrdp);
+
+       delete_node(NULL, src, NULL);
+       delete_node(NULL, dst, NULL);
+}
+
+START_TEST(test_json_min)
+{
+       struct cache_node *node = pzalloc(sizeof(struct cache_node));
+
+       node->map.url = pstrdup("https://a.b.c/sample.cer");
+       node->map.path = pstrdup("tmp/sample.cer");
+       node->state = DLS_FRESH;
+       node->dlerr = ENOENT;
+
+       ck_json(node);
+}
+
+START_TEST(test_json_rrdp_min)
+{
+       struct cache_node *node = pzalloc(sizeof(struct cache_node));
+
+       node->map.url = pstrdup("https://a.b.c/sample.cer");
+       node->map.path = pstrdup("rrdp/123");
+       node->state = DLS_FRESH;
+       node->dlerr = ENOENT;
+
+       node->rrdp = pmalloc(sizeof(struct rrdp_state));
+       node->rrdp->session.session_id = pstrdup("session");
+       node->rrdp->session.serial.num = BN_create();
+       ck_assert_ptr_ne(NULL, node->rrdp->session.serial.num);
+       BN_add_word(node->rrdp->session.serial.num, 1357);
+       node->rrdp->session.serial.str = pstrdup("1357");
+       cache_file_add(node->rrdp, pstrdup("rsync://a.b.c/d/e.mft"), pstrdup("rrdp/123/0"));
+       cseq_init(&node->rrdp->seq, node->map.path, 1, false);
+       STAILQ_INIT(&node->rrdp->delta_hashes);
+
+       ck_json(node);
+}
+
+START_TEST(test_json_max)
+{
+       struct cache_node *node = pzalloc(sizeof(struct cache_node));
+       struct rrdp_hash *hash;
+
+       node->map.url = pstrdup("https://a.b.c/sample.cer");
+       node->map.path = pstrdup("rrdp/123");
+       node->state = DLS_FRESH;
+       node->dlerr = ENOENT;
+       node->attempt_ts = 1234;
+       node->success_ts = 4321;
+       ck_assert_int_eq(0, asn_long2INTEGER(&node->mft.num, 5678));
+       node->mft.update = 8765;
+
+       node->rrdp = pmalloc(sizeof(struct rrdp_state));
+       node->rrdp->session.session_id = pstrdup("session");
+       node->rrdp->session.serial.num = BN_create();
+       ck_assert_ptr_ne(NULL, node->rrdp->session.serial.num);
+       BN_add_word(node->rrdp->session.serial.num, 1357);
+       node->rrdp->session.serial.str = pstrdup("1357");
+       cache_file_add(node->rrdp, pstrdup("rsync://a.b.c/d/e.mft"), pstrdup("rrdp/123/0"));
+       cache_file_add(node->rrdp, pstrdup("rsync://a.b.c/d/f.crl"), pstrdup("rrdp/123/1"));
+       cseq_init(&node->rrdp->seq, node->map.path, 2, false);
+       STAILQ_INIT(&node->rrdp->delta_hashes);
+       hash = pmalloc(sizeof(struct rrdp_hash));
+       memset(&hash->bytes, 1, sizeof(hash->bytes));
+       STAILQ_INSERT_HEAD(&node->rrdp->delta_hashes, hash, hook);
+       hash = pmalloc(sizeof(struct rrdp_hash));
+       memset(&hash->bytes, 2, sizeof(hash->bytes));
+       STAILQ_INSERT_HEAD(&node->rrdp->delta_hashes, hash, hook);
+
+       ck_json(node);
+}
+
+START_TEST(test_json_weirdurl)
+{
+       struct cache_node *node = pzalloc(sizeof(struct cache_node));
+
+       node->map.url = pstrdup("https://a.b.c/notif.xml\trsync://a.b.c/rpp");
+       node->map.path = pstrdup("tmp/sample.cer");
+       node->state = DLS_FRESH;
+       node->dlerr = ENOENT;
+       node->attempt_ts = 1234;
+       node->success_ts = 4321;
+       ck_assert_int_eq(0, asn_long2INTEGER(&node->mft.num, 5678));
+       node->mft.update = 8765;
+
+       ck_json(node);
+}
+
 /* Boilerplate */
 
 static Suite *create_suite(void)
 {
        Suite *suite;
-       TCase *rsync, *https, *rrdp, *multi;
+       TCase *rsync, *https, *rrdp, *multi, *json;
 
        rsync = tcase_create("rsync");
        tcase_add_test(rsync, test_cache_download_rsync);
@@ -780,11 +957,18 @@ static Suite *create_suite(void)
        multi = tcase_create("multi-protocol");
        tcase_add_test(multi, test_context);
 
+       json = tcase_create("json");
+       tcase_add_test(json, test_json_min);
+       tcase_add_test(json, test_json_rrdp_min);
+       tcase_add_test(json, test_json_max);
+       tcase_add_test(json, test_json_weirdurl);
+
        suite = suite_create("local-cache");
        suite_add_tcase(suite, rsync);
        suite_add_tcase(suite, https);
        suite_add_tcase(suite, rrdp);
        suite_add_tcase(suite, multi);
+       suite_add_tcase(suite, json);
 
        return suite;
 }
@@ -796,12 +980,17 @@ int main(void)
        int tests_failed;
 
        dls[0] = "Fort\n";
+
        if (mkdir("tmp", CACHE_FILEMODE) < 0 && errno != EEXIST) {
                fprintf(stderr, "mkdir('tmp/'): %s\n", strerror(errno));
                return 1;
        }
-       if (chdir("tmp") < 0) {
-               fprintf(stderr, "chdir('tmp/'): %s\n", strerror(errno));
+       if (mkdir("tmp/cache", CACHE_FILEMODE) < 0 && errno != EEXIST) {
+               fprintf(stderr, "mkdir('tmp/cache'): %s\n", strerror(errno));
+               return 1;
+       }
+       if (chdir("tmp/cache") < 0) {
+               fprintf(stderr, "chdir('tmp/cache'): %s\n", strerror(errno));
                return 1;
        }
 
index dd8c6889de2df396dc6596b919fd4d188f24a7d2..445354b08e4560746ea3ae88b61241bffb553479 100644 (file)
 #include "types/map.c"
 #include "types/url.c"
 
-static void
-ck_rrdp_session(char const *session, char const *serial,
-    struct rrdp_session *actual)
-{
-       BIGNUM *bn;
-
-       ck_assert_str_eq(session, actual->session_id);
-       ck_assert_str_eq(serial, actual->serial.str);
-
-       bn = BN_new();
-       ck_assert_ptr_ne(NULL, bn);
-       ck_assert_int_eq(strlen(serial), BN_dec2bn(&bn, serial));
-       ck_assert_int_eq(0, BN_cmp(bn, actual->serial.num));
-       BN_free(bn);
-}
-
-static struct rrdp_state *
-create_rrdp_state(char const *session, char const *serial, ...)
-{
-       struct rrdp_state *state;
-       struct rrdp_hash *hash;
-       int dh_byte;
-       va_list args;
-
-       state = pmalloc(sizeof(struct rrdp_state));
-
-       state->session.session_id = pstrdup(session);
-       state->session.serial.str = pstrdup(serial);
-       state->session.serial.num = NULL; /* Not needed for now. */
-       state->files = NULL;
-       STAILQ_INIT(&state->delta_hashes);
-
-       va_start(args, serial);
-       while ((dh_byte = va_arg(args, int)) != 0) {
-               hash = pmalloc(sizeof(struct rrdp_hash));
-               memset(hash->bytes, dh_byte, sizeof(hash->bytes));
-               STAILQ_INSERT_TAIL(&state->delta_hashes, hash, hook);
-       }
-       va_end(args);
-
-       return state;
-}
-
 START_TEST(test_xmlChar_NULL_assumption)
 {
        xmlChar *xmlstr;
@@ -610,161 +567,10 @@ START_TEST(test_parse_snapshot_bad_publish)
 }
 END_TEST
 
-START_TEST(test_2s_simple)
-{
-       struct rrdp_state *state;
-       json_t *json, *jdeltas;
-       char const *str;
-
-       state = create_rrdp_state("session", "1234", 0);
-
-       json = rrdp_state2json(state);
-       ck_assert_ptr_ne(NULL, json);
-
-       rrdp_state_free(state);
-       state = NULL;
-
-       ck_assert_int_eq(0, json_get_str(json, TAGNAME_SESSION, &str));
-       ck_assert_str_eq("session", str);
-       ck_assert_int_eq(0, json_get_str(json, TAGNAME_SERIAL, &str));
-       ck_assert_str_eq("1234", str);
-       ck_assert_int_eq(ENOENT, json_get_array(json, TAGNAME_DELTAS, &jdeltas));
-
-       ck_assert_int_eq(0, rrdp_json2state(json, &state));
-       ck_rrdp_session("session", "1234", &state->session);
-       ck_assert_uint_eq(true, STAILQ_EMPTY(&state->delta_hashes));
-
-       json_decref(json);
-       rrdp_state_free(state);
-}
-END_TEST
-
-static void
-ck_hash(struct rrdp_hash *hash, unsigned char chara)
-{
-       size_t i;
-       for (i = 0; i < sizeof(hash->bytes); i++)
-               ck_assert_uint_eq(chara, hash->bytes[i]);
-}
-
-START_TEST(test_2s_more)
-{
-       struct rrdp_state *state;
-       struct rrdp_hash *hash;
-       json_t *json, *jdeltas;
-       char const *str;
-
-       state = create_rrdp_state("session",
-           "123456789012345678901234567890123456789012",
-           0xAA, 0xBB, 0xCD, 0);
-
-       json = rrdp_state2json(state);
-       ck_assert_ptr_ne(NULL, json);
-
-       rrdp_state_free(state);
-       state = NULL;
-
-       ck_assert_int_eq(0, json_get_str(json, TAGNAME_SESSION, &str));
-       ck_assert_str_eq("session", str);
-       ck_assert_int_eq(0, json_get_str(json, TAGNAME_SERIAL, &str));
-       ck_assert_str_eq("123456789012345678901234567890123456789012", str);
-       ck_assert_int_eq(0, json_get_array(json, TAGNAME_DELTAS, &jdeltas));
-       ck_assert_uint_eq(3, json_array_size(jdeltas));
-       ck_assert_str_eq("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
-           json_string_value(json_array_get(jdeltas, 0)));
-       ck_assert_str_eq("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
-           json_string_value(json_array_get(jdeltas, 1)));
-       ck_assert_str_eq("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
-           json_string_value(json_array_get(jdeltas, 2)));
-
-       ck_assert_int_eq(0, rrdp_json2state(json, &state));
-       ck_rrdp_session("session", "123456789012345678901234567890123456789012", &state->session);
-       hash = STAILQ_FIRST(&state->delta_hashes);
-       ck_assert_ptr_ne(NULL, hash);
-       ck_hash(hash, 0xAA);
-       hash = STAILQ_NEXT(hash, hook);
-       ck_assert_ptr_ne(NULL, hash);
-       ck_hash(hash, 0xBB);
-       hash = STAILQ_NEXT(hash, hook);
-       ck_assert_ptr_ne(NULL, hash);
-       ck_hash(hash, 0xCD);
-       hash = STAILQ_NEXT(hash, hook);
-       ck_assert_ptr_eq(NULL, hash);
-
-       json_decref(json);
-       rrdp_state_free(state);
-}
-END_TEST
-
-void
-ck_json2state(int expected, char const *json_str)
-{
-       json_t *json;
-       json_error_t error;
-       struct rrdp_state *state;
-
-       json = json_loads(json_str, 0, &error);
-       ck_assert_ptr_ne(NULL, json);
-
-       state = NULL;
-       ck_assert_int_eq(expected, rrdp_json2state(json, &state));
-
-       json_decref(json);
-       if (state != NULL)
-               rrdp_state_free(state);
-}
-
-START_TEST(test_2s_errors)
-{
-       struct rrdp_state state = { 0 };
-
-       ck_assert_ptr_eq(NULL, rrdp_state2json(&state));
-       state.session.session_id = "sid";
-       ck_assert_ptr_eq(NULL, rrdp_state2json(&state));
-
-       ck_json2state(ENOENT, "{}");
-       ck_json2state(0, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":\"123\" }");
-       ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":null, \"" TAGNAME_SERIAL "\":\"123\" }");
-       ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":null }");
-       ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":123, \"" TAGNAME_SERIAL "\":\"123\" }");
-       ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":123 }");
-       ck_json2state(ENOENT, "{ \"" TAGNAME_SESSION "\":\"sss\" }");
-       ck_json2state(ENOENT, "{ \"" TAGNAME_SERIAL "\":\"123\" }");
-       ck_json2state(-EINVAL,
-           "{ \"" TAGNAME_SESSION "\":\"sss\","
-             "\"" TAGNAME_SERIAL "\":\"123\","
-             "\"" TAGNAME_DELTAS "\":null }");
-       ck_json2state(-EINVAL,
-           "{ \"" TAGNAME_SESSION "\":\"sss\","
-             "\"" TAGNAME_SERIAL "\":\"123\","
-             "\"" TAGNAME_DELTAS "\":\"123\" }");
-       ck_json2state(-EINVAL,
-           "{ \"" TAGNAME_SESSION "\":\"sss\","
-             "\"" TAGNAME_SERIAL "\":\"123\","
-             "\"" TAGNAME_DELTAS "\":{} }");
-       ck_json2state(0,
-           "{ \"" TAGNAME_SESSION "\":\"sss\","
-             "\"" TAGNAME_SERIAL "\":\"123\","
-             "\"" TAGNAME_DELTAS "\":[] }");
-       ck_json2state(-EINVAL,
-           "{ \"" TAGNAME_SESSION "\":\"sss\","
-             "\"" TAGNAME_SERIAL "\":\"123\","
-             "\"" TAGNAME_DELTAS "\":[ 1 ] }");
-       ck_json2state(-EINVAL,
-           "{ \"" TAGNAME_SESSION "\":\"sss\","
-             "\"" TAGNAME_SERIAL "\":\"123\","
-             "\"" TAGNAME_DELTAS "\":[ \"111\" ] }");
-       ck_json2state(0,
-           "{ \"" TAGNAME_SESSION "\":\"sss\","
-             "\"" TAGNAME_SERIAL "\":\"123\","
-             "\"" TAGNAME_DELTAS "\":[ \"1111111111111111111111111111111111111111111111111111111111111111\" ] }");
-}
-END_TEST
-
 static Suite *create_suite(void)
 {
        Suite *suite;
-       TCase *misc, *parse, *cf;
+       TCase *misc, *parse;
 
        misc = tcase_create("misc");
        tcase_add_test(misc, test_xmlChar_NULL_assumption);
@@ -783,15 +589,9 @@ static Suite *create_suite(void)
        tcase_add_test(parse, test_parse_notification_bad_uri);
        tcase_add_test(parse, test_parse_snapshot_bad_publish);
 
-       cf = tcase_create("cachefile");
-       tcase_add_test(parse, test_2s_simple);
-       tcase_add_test(parse, test_2s_more);
-       tcase_add_test(parse, test_2s_errors);
-
        suite = suite_create("RRDP");
        suite_add_tcase(suite, misc);
        suite_add_tcase(suite, parse);
-       suite_add_tcase(suite, cf);
 
        return suite;
 }
index 447779db98016930a153b19dc701ceb6d5713cdc..1082e4608d75e9bebdc78e3a407f8cc73fc3447f 100644 (file)
@@ -6,12 +6,15 @@
 #include "common.c"
 #include "file.c"
 #include "hash.c"
+#include "json_util.c"
 #include "mock.c"
 #include "mock_https.c"
 #include "relax_ng.c"
 #include "rrdp.c"
 #include "rrdp_util.h"
 #include "types/map.c"
+#include "types/path.c"
+#include "types/str.c"
 #include "types/url.c"
 
 /* Utils */