#include "task.h"
#include "types/array.h"
#include "types/path.h"
+#include "types/str.h"
#include "types/url.h"
#include "types/uthash.h"
DLS_FRESH,
};
+struct cache_table;
+
/*
* This is a delicate structure; pay attention.
*
* 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 */
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
__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);
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 *);
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");
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", ¬ification);
+ 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)
{
if (error)
return error;
- if (load_index_file() != 0) {
+ if (load_index() != 0) {
error = reset_cache_dir();
if (error)
goto fail;
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)
{
bool changed;
int error;
- error = rrdp_update(¬if->map, notif->success_ts,
- &changed, ¬if->rrdp);
+ error = rrdp_update(¬if->map, notif->success_ts, &changed,
+ ¬if->rrdp);
if (error)
return error;
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.
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);
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");
}
{
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)
struct cache_commit *commit;
struct cache_node *fb;
array_index i;
+ int error;
while (!STAILQ_EMPTY(&commits)) {
commit = STAILQ_FIRST(&commits);
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);
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++) {
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);
}
cache_commit(void)
{
cleanup_cache();
- write_index_file();
+ file_write_txt(METAFILE, "{ \"fort-version\": \"" PACKAGE_VERSION "\" }");
unlock_cache();
flush_nodes();
}
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 */
/* 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;
}
}
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;
}
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 *);
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
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)
{
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 *
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);
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);
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 == */
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;
"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.",
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;
}
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;
}
}
*/
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;
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++) {
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);
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);
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;
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)
#include "thread_var.h"
#include "types/arraylist.h"
#include "types/path.h"
+#include "types/str.h"
#include "types/url.h"
#include "types/uthash.h"
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)
{
{
struct publish tag = { 0 };
struct cache_file *file;
- size_t len;
+ char *path;
int error;
error = parse_publish(reader, &tag);
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) {
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);
* 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;
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) {
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);
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;
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
} 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:
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));
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;
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)
{
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;
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;
}
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");
+ }
+}
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_ */
#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.
*/
#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 **);
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
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
#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 */
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 */
{
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 *
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"));
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);
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);
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);
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);
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"));
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);
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);
}
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);
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;
}
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;
}
#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;
}
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);
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;
}
#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 */