From: Alberto Leiva Popper Date: Mon, 4 Mar 2024 01:50:05 +0000 (-0600) Subject: Finally, implement draft-spaghetti-sidrops-rrdp-desynchronization-00 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1b4e20cc9366dd56ca3a4a6a8a17e87b47dbe77c;p=thirdparty%2FFORT-validator.git Finally, implement draft-spaghetti-sidrops-rrdp-desynchronization-00 - Remember old delta hashes. - If one of the delta hashes changes in the notification, the session needs to be re-snapshot'd, even if the session ID and serial seem consistent. --- diff --git a/man/fort.8 b/man/fort.8 index 0eed73e3..14bb63db 100644 --- a/man/fort.8 +++ b/man/fort.8 @@ -868,6 +868,10 @@ Maximum deltas to explode per RRDP session, per iteration. .P (If the RRDP notification lists more than this amount of unprocessed deltas, Fort will reset the session, exploding the snapshot instead.) +.P +Per draft-spaghetti-sidrops-rrdp-desynchronization's recommendation, this is +also the maximum number of delta hashes Fort will remember per RRDP session, to +detect session desynchronization. .RE .P diff --git a/src/cache/local_cache.c b/src/cache/local_cache.c index 9d98bd4b..0085f7d9 100644 --- a/src/cache/local_cache.c +++ b/src/cache/local_cache.c @@ -33,6 +33,8 @@ struct cache_node { time_t ts; } success; + struct cachefile_notification *notif; + UT_hash_handle hh; /* Hash table hook */ }; @@ -54,6 +56,7 @@ struct rpki_cache { #define TAGNAME_ATTEMPT_TS "attempt-timestamp" #define TAGNAME_ATTEMPT_ERR "attempt-result" #define TAGNAME_SUCCESS_TS "success-timestamp" +#define TAGNAME_NOTIF "notification" #define TYPEVALUE_TA_RSYNC "TA (rsync)" #define TYPEVALUE_TA_HTTP "TA (HTTP)" @@ -226,6 +229,7 @@ cache_tmpfile(char **filename) error = pb_init_cache(&pb, NULL, TMPDIR); if (error) return error; + error = pb_append_u32(&pb, atomic_fetch_add(&file_counter, 1u)); if (error) { pb_cleanup(&pb); @@ -240,8 +244,7 @@ static char * get_tal_json_filename(struct rpki_cache *cache) { struct path_builder pb; - return pb_init_cache(&pb, cache->tal, TAL_METAFILE) - ? NULL : pb.string; + return pb_init_cache(&pb, cache->tal, TAL_METAFILE) ? NULL : pb.string; } static struct cache_node * @@ -251,6 +254,7 @@ json2node(struct rpki_cache *cache, json_t *json) char const *type_str; enum uri_type type; char const *url; + json_t *notif; int error; node = pzalloc(sizeof(struct cache_node)); @@ -282,6 +286,22 @@ json2node(struct rpki_cache *cache, json_t *json) goto fail; } + if (type == UT_NOTIF) { + error = json_get_object(json, TAGNAME_NOTIF, ¬if); + switch (error) { + case 0: + error = rrdp_json2notif(notif, &node->notif); + if (error) + goto fail; + break; + case ENOENT: + node->notif = NULL; + break; + default: + goto fail; + } + } + error = uri_create(&node->url, cache->tal, type, NULL, url); if (error) { pr_op_err("Cannot parse '%s' into a URI.", url); @@ -309,22 +329,27 @@ json2node(struct rpki_cache *cache, json_t *json) fail: uri_refput(node->url); + rrdp_notif_free(node->notif); free(node); return NULL; } -static struct cache_node* -find_node(struct rpki_cache *cache, char const *luri) +static struct cache_node * +find_node(struct rpki_cache *cache, struct rpki_uri *uri) { + char const *key; struct cache_node *result; - HASH_FIND_STR(cache->ht, luri, result); + + key = uri_get_global(uri); + HASH_FIND_STR(cache->ht, key, result); + return result; } static void add_node(struct rpki_cache *cache, struct cache_node *node) { - char const *key = uri_get_local(node->url); + char const *key = uri_get_global(node->url); size_t keylen = strlen(key); HASH_ADD_KEYPTR(hh, cache->ht, key, keylen, node); } @@ -392,6 +417,7 @@ node2json(struct cache_node *node) { json_t *json; char const *type; + json_t *notification; json = json_object(); if (json == NULL) @@ -418,6 +444,13 @@ node2json(struct cache_node *node) goto cancel; if (json_add_str(json, TAGNAME_URL, uri_get_global(node->url))) goto cancel; + if (node->notif != NULL) { + notification = rrdp_notif2json(node->notif); + if (notification == NULL) + goto cancel; + if (json_add_obj(json, TAGNAME_NOTIF, notification)) + goto cancel; + } if (json_add_date(json, TAGNAME_ATTEMPT_TS, node->attempt.ts)) goto cancel; if (json_add_int(json, TAGNAME_ATTEMPT_ERR, node->attempt.result)) @@ -601,10 +634,12 @@ cache_check(struct rpki_uri *url) /** * @ims and @changed only on HTTP. + * @ims can be zero, which means "no IMS." + * @changed can be NULL. */ int -cache_download(struct rpki_cache *cache, struct rpki_uri *uri, - curl_off_t ims, bool *changed) +cache_download(struct rpki_cache *cache, struct rpki_uri *uri, bool *changed, + struct cachefile_notification ***notif) { struct rpki_uri *url; struct cache_node *node; @@ -617,15 +652,16 @@ cache_download(struct rpki_cache *cache, struct rpki_uri *uri, if (error) return error; - node = find_node(cache, uri_get_local(url)); + node = find_node(cache, url); if (node != NULL) { - uri_refput(url); - if (was_recently_downloaded(cache, node)) - return node->attempt.result; - url = node->url; + if (was_recently_downloaded(cache, node)) { + error = node->attempt.result; + goto end; + } } else { node = pzalloc(sizeof(struct cache_node)); node->url = url; + uri_refget(url); add_node(cache, node); } @@ -634,7 +670,7 @@ cache_download(struct rpki_cache *cache, struct rpki_uri *uri, case UT_NOTIF: case UT_TMP: error = config_get_http_enabled() - ? http_download(url, ims, changed) + ? http_download(url, node->success.ts, changed) : cache_check(url); break; case UT_TA_RSYNC: @@ -656,26 +692,25 @@ cache_download(struct rpki_cache *cache, struct rpki_uri *uri, node->success.ts = node->attempt.ts; } +end: + uri_refput(url); + if (!error && (notif != NULL)) + *notif = &node->notif; return error; } static int download(struct rpki_cache *cache, struct rpki_uri *uri, uris_dl_cb cb, void *arg) { - time_t ims = 0; int error; pr_val_debug("Trying URL %s...", uri_get_global(uri)); switch (uri_get_type(uri)) { case UT_TA_HTTP: - error = file_get_mtim(uri_get_local(uri), &ims); - if (error) - break; - /* Fall through */ case UT_TA_RSYNC: case UT_RPP: - error = cache_download(cache, uri, ims, NULL); + error = cache_download(cache, uri, NULL, NULL); break; case UT_NOTIF: error = rrdp_update(uri); @@ -804,7 +839,7 @@ __cache_recover(struct rpki_cache *cache, struct uri_list *uris, if (get_url(cursor.uri, cache->tal, &url) != 0) continue; - cursor.node = find_node(cache, uri_get_local(url)); + cursor.node = find_node(cache, url); uri_refput(url); if (cursor.node == NULL) continue; @@ -847,6 +882,7 @@ delete_node(struct rpki_cache *cache, struct cache_node *node) { HASH_DEL(cache->ht, node); uri_refput(node->url); + rrdp_notif_free(node->notif); free(node); } @@ -892,17 +928,22 @@ get_days_ago(int days) static void cleanup_tmp(struct rpki_cache *cache, struct cache_node *node) { + enum uri_type type; char const *path; int error; - if (uri_get_type(node->url) == UT_TMP) { - path = uri_get_local(node->url); - pr_op_debug("Deleting temporal file '%s'.", path); - error = file_rm_f(path); - if (error) - pr_op_err("Could not delete '%s': %s", path, strerror(error)); + type = uri_get_type(node->url); + if (type != UT_NOTIF && type != UT_TMP) + return; + + path = uri_get_local(node->url); + pr_op_debug("Deleting temporal file '%s'.", path); + error = file_rm_f(path); + if (error) + pr_op_err("Could not delete '%s': %s", path, strerror(error)); + + if (type != UT_NOTIF) delete_node(cache, node); - } } static void @@ -913,6 +954,9 @@ cleanup_node(struct rpki_cache *cache, struct cache_node *node, int error; path = uri_get_local(node->url); + if (uri_get_type(node->url) == UT_NOTIF) + goto skip_file; + error = file_exists(path); switch (error) { case 0: @@ -927,6 +971,7 @@ cleanup_node(struct rpki_cache *cache, struct cache_node *node, uri_op_get_printable(node->url), error, strerror(error)); } +skip_file: if (!is_node_fresh(node, last_week)) { pr_op_debug("Deleting expired cache element %s.", path); file_rm_rf(path); diff --git a/src/cache/local_cache.h b/src/cache/local_cache.h index 19253512..83718933 100644 --- a/src/cache/local_cache.h +++ b/src/cache/local_cache.h @@ -1,7 +1,6 @@ #ifndef SRC_CACHE_LOCAL_CACHE_H_ #define SRC_CACHE_LOCAL_CACHE_H_ -#include #include "types/uri.h" struct rpki_cache; @@ -15,8 +14,11 @@ struct rpki_cache *cache_create(char const *); /* Will destroy the cache object, but not the cache directory itself, obv. */ void cache_destroy(struct rpki_cache *); +struct cachefile_notification; /* FIXME */ + /* Downloads @uri into the cache */ -int cache_download(struct rpki_cache *, struct rpki_uri *uri, curl_off_t, bool *); +int cache_download(struct rpki_cache *, struct rpki_uri *uri, bool *, + struct cachefile_notification ***); /* * The callback should return diff --git a/src/config.c b/src/config.c index f2203c97..9186cd15 100644 --- a/src/config.c +++ b/src/config.c @@ -134,6 +134,11 @@ struct rpki_config { * (If the RRDP notification lists more than this amount of * unprocessed deltas, Fort will reset the session, exploding * the snapshot instead.) + * + * Per draft-spaghetti-sidrops-rrdp-desynchronization's + * recommendation, this is also the maximum number of delta + * hashes Fort will remember per RRDP session, to detect session + * desynchronization. */ unsigned int delta_threshold; } rrdp; diff --git a/src/data_structure/path_builder.c b/src/data_structure/path_builder.c index 5dbee72c..d75daa10 100644 --- a/src/data_structure/path_builder.c +++ b/src/data_structure/path_builder.c @@ -7,8 +7,6 @@ #include "log.h" #include "crypto/hash.h" -#define SHA256_LEN (256 >> 3) /* 256 / 8, bits -> bytes */ - /* These are arbitrary; feel free to change them. */ #ifndef INITIAL_CAPACITY /* Unit tests want to override this */ #define INITIAL_CAPACITY 128u diff --git a/src/file.c b/src/file.c index d3a64b14..768dd955 100644 --- a/src/file.c +++ b/src/file.c @@ -154,30 +154,6 @@ file_valid(char const *file_name) return true; } -int -file_get_mtim(char const *file, time_t *ims) -{ - struct stat meta; - int error; - - if (stat(file, &meta) != 0) { - error = errno; - /* - * This happens to be most convenient for callers, - * because they all want to convert the ims to a curl_off_t ATM. - */ - *ims = 0; - return (error == ENOENT) ? 0 : error; - } - -#ifdef __APPLE__ - *ims = meta.st_mtime; /* Seriously, Apple? */ -#else - *ims = meta.st_mtim.tv_sec; -#endif - return 0; -} - /* * Like remove(), but don't care if the file is already deleted. */ diff --git a/src/file.h b/src/file.h index f62b00b6..d0d603bd 100644 --- a/src/file.h +++ b/src/file.h @@ -32,7 +32,6 @@ void file_free(struct file_contents *); int file_exists(char const *); bool file_valid(char const *); -int file_get_mtim(char const *, time_t *); int file_rm_f(char const *); int file_rm_rf(char const *); diff --git a/src/json_util.c b/src/json_util.c index 439caf6f..a814af7c 100644 --- a/src/json_util.c +++ b/src/json_util.c @@ -227,3 +227,11 @@ json_add_date(json_t *parent, char const *name, time_t value) return 0; } + +int json_add_obj(json_t *parent, char const *name, json_t *value) +{ + if (json_object_set_new(parent, name, value)) + return pr_op_err("Cannot add '%s' to json; unknown cause.", name); + + return 0; +} diff --git a/src/json_util.h b/src/json_util.h index 33d32292..93a41d01 100644 --- a/src/json_util.h +++ b/src/json_util.h @@ -36,5 +36,6 @@ bool json_valid_members_count(json_t *, size_t); int json_add_int(json_t *, char const *, int); int json_add_str(json_t *, char const *, char const *); int json_add_date(json_t *, char const *, time_t); +int json_add_obj(json_t *, char const *, json_t *); #endif /* SRC_JSON_UTIL_H_ */ diff --git a/src/object/certificate.c b/src/object/certificate.c index 920a561c..d1a7d92c 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -1937,7 +1937,6 @@ static struct rpki_uri * download_rpp(struct sia_uris *uris) { struct rpki_uri *uri; - struct rpki_cache *cache; int error; if (uris->rpp.len == 0) { @@ -1945,9 +1944,8 @@ download_rpp(struct sia_uris *uris) return NULL; } - cache = validation_cache(state_retrieve()); - error = cache_download_alt(cache, &uris->rpp, UT_NOTIF, UT_RPP, - retrieve_uri, &uri); + error = cache_download_alt(validation_cache(state_retrieve()), + &uris->rpp, UT_NOTIF, UT_RPP, retrieve_uri, &uri); return error ? NULL : uri; } diff --git a/src/rrdp.c b/src/rrdp.c index d0a306af..7015e4e1 100644 --- a/src/rrdp.c +++ b/src/rrdp.c @@ -3,11 +3,13 @@ #include #include #include +#include #include "alloc.h" #include "common.h" #include "config.h" #include "file.h" +#include "json_util.h" #include "log.h" #include "thread_var.h" #include "cache/local_cache.h" @@ -35,7 +37,7 @@ /* These are supposed to be unbounded */ struct rrdp_serial { BIGNUM *num; - char *str; /* for printing */ + char *str; /* String version of @num. */ }; struct rrdp_session { @@ -43,10 +45,9 @@ struct rrdp_session { struct rrdp_serial serial; }; -/* The hash is sometimes omitted. */ struct file_metadata { struct rpki_uri *uri; - unsigned char *hash; + unsigned char *hash; /* Array. Sometimes omitted. */ size_t hash_len; }; @@ -91,6 +92,27 @@ typedef enum { HR_IGNORE, } hash_requirement; +#define RRDP_HASH_LEN SHA256_DIGEST_LENGTH + +struct rrdp_hash { + unsigned char bytes[RRDP_HASH_LEN]; + STAILQ_ENTRY(rrdp_hash) hook; +}; + +/* + * Subset of the notification that is relevant to the TAL's cachefile. + */ +struct cachefile_notification { + struct rrdp_session session; + /* + * The 1st one contains the hash of the session.serial delta. + * The 2nd one contains the hash of the session.serial - 1 delta. + * The 3rd one contains the hash of the session.serial - 2 delta. + * And so on. + */ + STAILQ_HEAD(, rrdp_hash) delta_hashes; +}; + static BIGNUM * BN_create(void) { @@ -226,6 +248,26 @@ parse_string(xmlTextReaderPtr reader, char const *attr) return result; } +static int +parse_uri(xmlTextReaderPtr reader, struct rpki_uri *notif, + struct rpki_uri **result) +{ + xmlChar *xmlattr; + int error; + + xmlattr = parse_string(reader, RRDP_ATTR_URI); + if (xmlattr == NULL) + return -EINVAL; + + error = uri_create(result, + tal_get_file_name(validation_tal(state_retrieve())), + (notif != NULL) ? UT_CAGED : UT_TMP, + notif, (char const *)xmlattr); + + xmlFree(xmlattr); + return error; +} + static unsigned int hexchar2uint(xmlChar xmlchar) { @@ -239,18 +281,18 @@ hexchar2uint(xmlChar xmlchar) } static int -hexstr2sha256(xmlChar *hexstr, unsigned char **result) +hexstr2sha256(xmlChar *hexstr, unsigned char **result, size_t *hash_len) { unsigned char *hash; unsigned int digit; size_t i; - if (xmlStrlen(hexstr) != 2 * SHA256_DIGEST_LENGTH) + if (xmlStrlen(hexstr) != 2 * RRDP_HASH_LEN) return EINVAL; - hash = pmalloc(SHA256_DIGEST_LENGTH); + hash = pmalloc(RRDP_HASH_LEN); - for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { + for (i = 0; i < RRDP_HASH_LEN; i++) { digit = hexchar2uint(hexstr[2 * i]); if (digit > 15) goto fail; @@ -263,6 +305,7 @@ hexstr2sha256(xmlChar *hexstr, unsigned char **result) } *result = hash; + *hash_len = RRDP_HASH_LEN; return 0; fail: @@ -271,7 +314,7 @@ fail: } static int -parse_hash(xmlTextReaderPtr reader, hash_requirement hr, char const *attr, +parse_hash(xmlTextReaderPtr reader, hash_requirement hr, unsigned char **result, size_t *result_len) { xmlChar *xmlattr; @@ -280,20 +323,18 @@ parse_hash(xmlTextReaderPtr reader, hash_requirement hr, char const *attr, if (hr == HR_IGNORE) return 0; - xmlattr = xmlTextReaderGetAttribute(reader, BAD_CAST attr); + xmlattr = xmlTextReaderGetAttribute(reader, BAD_CAST RRDP_ATTR_HASH); if (xmlattr == NULL) return (hr == HR_MANDATORY) - ? pr_val_err("Tag is missing the '%s' attribute.", attr) + ? pr_val_err("Tag is missing the '" RRDP_ATTR_HASH "' attribute.") : 0; - error = hexstr2sha256(xmlattr, result); + error = hexstr2sha256(xmlattr, result, result_len); xmlFree(xmlattr); if (error) - return pr_val_err("The '%s' xml attribute does not appear to be a SHA-256 hash.", - attr); - *result_len = SHA256_DIGEST_LENGTH; + return pr_val_err("The '" RRDP_ATTR_HASH "' xml attribute does not appear to be a SHA-256 hash."); return 0; } @@ -413,26 +454,15 @@ static int parse_file_metadata(xmlTextReaderPtr reader, struct rpki_uri *notif, hash_requirement hr, struct file_metadata *meta) { - xmlChar *uri; int error; memset(meta, 0, sizeof(*meta)); - uri = parse_string(reader, RRDP_ATTR_URI); - if (uri == NULL) - return -EINVAL; - error = uri_create(&meta->uri, - tal_get_file_name(validation_tal(state_retrieve())), - (notif != NULL) ? UT_CAGED : UT_TMP, notif, (char const *)uri); - xmlFree(uri); + error = parse_uri(reader, notif, &meta->uri); if (error) return error; - if (hr == HR_IGNORE) - return 0; - - error = parse_hash(reader, hr, RRDP_ATTR_HASH, &meta->hash, - &meta->hash_len); + error = parse_hash(reader, hr, &meta->hash, &meta->hash_len); if (error) { uri_refput(meta->uri); meta->uri = NULL; @@ -471,10 +501,7 @@ parse_publish(xmlTextReaderPtr reader, struct rpki_uri *notif, return error; /* rfc8181#section-2.2 but considering optional hash */ - if (tag->meta.hash_len > 0) - return validate_hash(&tag->meta); - - return 0; + return (tag->meta.hash != NULL) ? validate_hash(&tag->meta) : 0; } static int @@ -758,6 +785,41 @@ parse_snapshot(struct update_notification *notif) xml_read_snapshot, &ctx); } +static int +validate_session_desync(struct cachefile_notification *old_notif, + struct update_notification *new_notif) +{ + struct rrdp_hash *old_delta; + struct file_metadata *new_delta; + size_t i; + size_t delta_threshold; + + if (strcmp(old_notif->session.session_id, new_notif->session.session_id) != 0) { + pr_val_debug("The Notification's session ID changed."); + return EINVAL; + } + + old_delta = STAILQ_FIRST(&old_notif->delta_hashes); + delta_threshold = config_get_rrdp_delta_threshold(); + + for (i = 0; i < delta_threshold; i++) { + if (old_delta == NULL) + return 0; /* Cache has few deltas */ + if (i >= new_notif->deltas.len) + return 0; /* Notification has few deltas */ + + new_delta = &new_notif->deltas.array[i].meta; + if (memcmp(old_delta->bytes, new_delta->hash, RRDP_HASH_LEN) != 0) { + pr_val_debug("Notification delta hash does not match cached delta hash; RRDP session desynchronization detected."); + return EINVAL; + } + + old_delta = STAILQ_NEXT(old_delta, hook); + } + + return 0; /* First $delta_threshold delta hashes match */ +} + static int handle_snapshot(struct update_notification *notif) { @@ -780,7 +842,7 @@ handle_snapshot(struct update_notification *notif) * Maybe stream it instead. * Same for deltas. */ - error = cache_download(validation_cache(state), uri, 0, NULL); + error = cache_download(validation_cache(state), uri, NULL, NULL); if (error) goto end; error = validate_hash(¬if->snapshot); @@ -853,7 +915,7 @@ handle_delta(struct update_notification *notif, struct notification_delta *delta pr_val_debug("Processing delta '%s'.", uri_val_get_printable(uri)); fnstack_push_uri(uri); - error = cache_download(validation_cache(state_retrieve()), uri, 0, NULL); + error = cache_download(validation_cache(state_retrieve()), uri, NULL, NULL); if (error) goto end; error = parse_delta(notif, delta); @@ -872,7 +934,6 @@ handle_deltas(struct update_notification *notif, struct rrdp_serial *serial) array_index d; int error; - /* No elements, send error so that the snapshot is processed */ if (notif->deltas.len == 0) { pr_val_warn("There's no delta list to process."); return -ENOENT; @@ -907,30 +968,84 @@ handle_deltas(struct update_notification *notif, struct rrdp_serial *serial) return 0; } -static int -get_metadata(struct rpki_uri *uri, struct rrdp_session *result) +static void +init_notif(struct update_notification *new, struct cachefile_notification *old) { - struct stat st; - struct update_notification notification; - int error; + size_t dn; + size_t i; + struct rrdp_hash *hash; + + old->session = new->session; + memset(&new->session, 0, sizeof(new->session)); + STAILQ_INIT(&old->delta_hashes); + + dn = config_get_rrdp_delta_threshold(); + if (new->deltas.len < dn) + dn = new->deltas.len; - if (stat(uri_get_local(uri), &st) != 0) { - error = errno; - return (error == ENOENT) ? 0 : error; + for (i = 0; i < dn; i++) { + hash = pmalloc(sizeof(struct rrdp_hash)); + memcpy(hash->bytes, new->deltas.array[i].meta.hash, RRDP_HASH_LEN); + STAILQ_INSERT_TAIL(&old->delta_hashes, hash, hook); } +} - /* - * TODO (fine) optimize by not reading everything, - * or maybe keep it if it doesn't change. - */ - error = parse_notification(uri, ¬ification); - if (error) - return error; +static void +drop_notif(struct cachefile_notification *notif) +{ + struct rrdp_hash *hash; - *result = notification.session; - memset(¬ification.session, 0, sizeof(notification.session)); - update_notification_cleanup(¬ification); - return 0; + session_cleanup(¬if->session); + while (!STAILQ_EMPTY(¬if->delta_hashes)) { + hash = STAILQ_FIRST(¬if->delta_hashes); + STAILQ_REMOVE_HEAD(¬if->delta_hashes, hook); + free(hash); + } +} + +static void +update_notif(struct update_notification *new, struct cachefile_notification *old) +{ + BIGNUM *delta_bn; + BN_ULONG delta; + size_t d, dn; + struct rrdp_hash *hash; + + delta_bn = BN_new(); + if (!BN_sub(delta_bn, new->session.serial.num, old->session.serial.num)) { + // FIXME + } + if (BN_is_negative(delta_bn)) { + // FIXME + } + + delta = BN_get_word(delta_bn); + if (delta > new->deltas.len) { + // FIXME + } + + BN_free(old->session.serial.num); + free(old->session.serial.str); + old->session.serial = new->session.serial; + new->session.serial.num = NULL; + new->session.serial.str = NULL; + + dn = delta; + STAILQ_FOREACH(hash, &old->delta_hashes, hook) + dn++; + + for (d = new->deltas.len - delta; d < new->deltas.len; d++) { + hash = pmalloc(sizeof(struct rrdp_hash)); + memcpy(hash->bytes, new->deltas.array[d].meta.hash, RRDP_HASH_LEN); + STAILQ_INSERT_TAIL(&old->delta_hashes, hash, hook); + } + + while (dn > config_get_rrdp_delta_threshold()) { + hash = STAILQ_FIRST(&old->delta_hashes); + STAILQ_REMOVE_HEAD(&old->delta_hashes, hook); + free(hash); + dn--; + } } /* @@ -943,27 +1058,16 @@ get_metadata(struct rpki_uri *uri, struct rrdp_session *result) int rrdp_update(struct rpki_uri *uri) { - struct rrdp_session old = { 0 }; + struct cachefile_notification **__old, *old; struct update_notification new; - time_t ims; bool changed; int error; fnstack_push_uri(uri); pr_val_debug("Processing notification."); - error = get_metadata(uri, &old); - if (error) - goto end; - pr_val_debug("Old session/serial: %s/%s", old.session_id, - old.serial.str); - - error = file_get_mtim(uri_get_local(uri), &ims); - if (error) - return error; - error = cache_download(validation_cache(state_retrieve()), uri, - ims, &changed); + &changed, &__old); if (error) goto end; if (!changed) { @@ -977,24 +1081,41 @@ rrdp_update(struct rpki_uri *uri) pr_val_debug("New session/serial: %s/%s", new.session.session_id, new.session.serial.str); - if (old.session_id == NULL || old.serial.num == NULL) { + old = *__old; + if (old == NULL) { pr_val_debug("This is a new Notification."); error = handle_snapshot(&new); + if (!error) { + *__old = pmalloc(sizeof(struct cachefile_notification)); + init_notif(&new, *__old); + } goto revert_notification; } - if (strcmp(old.session_id, new.session.session_id) != 0) { - pr_val_debug("The Notification's session ID changed."); + error = validate_session_desync(old, &new); + if (error) { + pr_val_debug("Falling back to snapshot."); error = handle_snapshot(&new); + if (!error) { + drop_notif(old); + init_notif(&new, old); + } goto revert_notification; } - if (BN_cmp(old.serial.num, new.session.serial.num) != 0) { + if (BN_cmp(old->session.serial.num, new.session.serial.num) != 0) { pr_val_debug("The Notification's serial changed."); - error = handle_deltas(&new, &old.serial); - if (error) { + error = handle_deltas(&new, &old->session.serial); + if (!error) { + update_notif(&new, old); + } else { + /* Error msg already printed. */ pr_val_debug("Falling back to snapshot."); error = handle_snapshot(&new); + if (!error) { + drop_notif(old); + init_notif(&new, old); + } } goto revert_notification; } @@ -1004,7 +1125,210 @@ rrdp_update(struct rpki_uri *uri) revert_notification: update_notification_cleanup(&new); end: - session_cleanup(&old); fnstack_pop(); return error; } + +#define TAGNAME_SESSION "session_id" +#define TAGNAME_SERIAL "serial" +#define TAGNAME_DELTAS "deltas" + +/* binary to char */ +static char +hash_b2c(unsigned char bin) +{ + bin &= 0xF; + return (bin < 10) ? (bin + '0') : (bin + 'a' - 10); +} + +json_t * +rrdp_notif2json(struct cachefile_notification *notif) +{ + json_t *json; + json_t *deltas; + char hash_str[2 * RRDP_HASH_LEN + 1]; + struct rrdp_hash *hash; + size_t i; + + if (notif == NULL) + return NULL; + + json = json_object(); + if (json == NULL) + enomem_panic(); + + if (json_add_str(json, TAGNAME_SESSION, notif->session.session_id)) + goto fail; + if (json_add_str(json, TAGNAME_SERIAL, notif->session.serial.str)) + goto fail; + + if (STAILQ_EMPTY(¬if->delta_hashes)) + return json; /* Happy path, but unlikely. */ + + deltas = json_array(); + if (deltas == NULL) + enomem_panic(); + if (json_add_obj(json, TAGNAME_DELTAS, deltas)) + goto fail; + + hash_str[2 * RRDP_HASH_LEN] = '\0'; + STAILQ_FOREACH(hash, ¬if->delta_hashes, hook) { + for (i = 0; i < RRDP_HASH_LEN; i++) { + hash_str[2 * i ] = hash_b2c(hash->bytes[i] >> 4); + hash_str[2 * i + 1] = hash_b2c(hash->bytes[i] ); + } + if (json_array_append(deltas, json_string(hash_str))) + goto fail; + } + + return json; + +fail: + json_decref(json); + return NULL; +} + +static char +hash_c2b(char chara) +{ + if ('a' <= chara && chara <= 'f') + return chara - 'a' + 10; + if ('A' <= chara && chara <= 'F') + return chara - 'A' + 10; + if ('0' <= chara && chara <= '9') + return chara - '0'; + return -1; +} + +static int +json2dh(json_t *json, struct rrdp_hash **result) +{ + char const *src; + size_t srclen; + struct rrdp_hash *dst; + char digit; + size_t i; + + src = json_string_value(json); + if (src == NULL) + return pr_op_err("Hash is not a string."); + + srclen = strlen(src); + if (srclen != 2 * RRDP_HASH_LEN) + return pr_op_err("Hash is not %d characters long.", 2 * RRDP_HASH_LEN); + + dst = pmalloc(sizeof(struct rrdp_hash)); + for (i = 0; i < RRDP_HASH_LEN; i++) { + digit = hash_c2b(src[2 * i]); + if (digit == -1) + goto bad_char; + dst->bytes[i] = digit << 4; + digit = hash_c2b(src[2 * i + 1]); + if (digit == -1) + goto bad_char; + dst->bytes[i] |= digit; + } + + *result = dst; + return 0; + +bad_char: + free(dst); + return pr_op_err("Invalid characters in hash: %c%c", src[2 * i], src[2 * i] + 1); +} + +static void +clear_delta_hashes(struct cachefile_notification *notif) +{ + struct rrdp_hash *hash; + + while (!STAILQ_EMPTY(¬if->delta_hashes)) { + hash = STAILQ_FIRST(¬if->delta_hashes); + STAILQ_REMOVE_HEAD(¬if->delta_hashes, hook); + free(hash); + } +} + +int +rrdp_json2notif(json_t *json, struct cachefile_notification **result) +{ + struct cachefile_notification *notif; + char const *str; + json_t *jdeltas; + size_t d, dn; + struct rrdp_hash *hash; + int error; + + notif = pzalloc(sizeof(struct cachefile_notification)); + STAILQ_INIT(¬if->delta_hashes); + + error = json_get_str(json, TAGNAME_SESSION, &str); + if (error) { + if (error > 0) + pr_op_err("Node is missing the '" TAGNAME_SESSION "' tag."); + goto revert_notif; + } + notif->session.session_id = pstrdup(str); + + error = json_get_str(json, TAGNAME_SERIAL, &str); + if (error) { + if (error > 0) + pr_op_err("Node is missing the '" TAGNAME_SERIAL "' tag."); + goto revert_session; + } + notif->session.serial.str = pstrdup(str); + + notif->session.serial.num = BN_new(); + if (notif->session.serial.num == NULL) + enomem_panic(); + if (!BN_dec2bn(¬if->session.serial.num, notif->session.serial.str)) { + error = pr_op_err("Not a serial number: %s", notif->session.serial.str); + goto revert_serial; + } + + error = json_get_array(json, TAGNAME_DELTAS, &jdeltas); + if (error) { + if (error > 0) + goto success; + goto revert_serial; + } + + dn = json_array_size(jdeltas); + if (dn == 0) + goto success; + if (dn > config_get_rrdp_delta_threshold()) + dn = config_get_rrdp_delta_threshold(); + + for (d = 0; d < dn; d++) { + error = json2dh(json_array_get(jdeltas, d), &hash); + if (error) + goto revert_deltas; + STAILQ_INSERT_TAIL(¬if->delta_hashes, hash, hook); + } + +success: + *result = notif; + return 0; + +revert_deltas: + clear_delta_hashes(notif); +revert_serial: + BN_free(notif->session.serial.num); + free(notif->session.serial.str); +revert_session: + free(notif->session.session_id); +revert_notif: + free(notif); + return error; +} + +void +rrdp_notif_free(struct cachefile_notification *notif) +{ + if (notif == NULL) + return; + + session_cleanup(¬if->session); + clear_delta_hashes(notif); + free(notif); +} diff --git a/src/rrdp.h b/src/rrdp.h index f3de0a36..757dd46c 100644 --- a/src/rrdp.h +++ b/src/rrdp.h @@ -3,6 +3,13 @@ #include "types/uri.h" +struct cachefile_notification; + int rrdp_update(struct rpki_uri *); +json_t *rrdp_notif2json(struct cachefile_notification *); +int rrdp_json2notif(json_t *, struct cachefile_notification **); + +void rrdp_notif_free(struct cachefile_notification *); + #endif /* SRC_RRDP_H_ */ diff --git a/src/slurm/slurm_loader.c b/src/slurm/slurm_loader.c index 2c0abd04..06e49819 100644 --- a/src/slurm/slurm_loader.c +++ b/src/slurm/slurm_loader.c @@ -1,6 +1,8 @@ #include "slurm/slurm_loader.h" #include +#include + #include "alloc.h" #include "log.h" #include "config.h" @@ -185,12 +187,7 @@ are_csum_lists_equals(struct slurm_csum_list *new_list, SLIST_FOREACH(newcsum, new_list, next) { SLIST_FOREACH(old, old_list, next) { - - if (newcsum->csum_len != old->csum_len) - continue; - - if (memcmp(newcsum->csum, old->csum, - newcsum->csum_len) == 0) { + if (memcmp(newcsum->csum, old->csum, SHA256_DIGEST_LENGTH) == 0) { found = true; break; } diff --git a/src/types/uri.c b/src/types/uri.c index 753c3b5c..d4ee1a09 100644 --- a/src/types/uri.c +++ b/src/types/uri.c @@ -415,12 +415,13 @@ autocomplete_local(struct rpki_uri *uri, char const *tal, switch (uri->type) { case UT_TA_RSYNC: case UT_RPP: + case UT_MFT: return map_simple(uri, tal, "rsync"); case UT_TA_HTTP: - case UT_NOTIF: return map_simple(uri, tal, "https"); + case UT_NOTIF: case UT_TMP: return cache_tmpfile(&uri->local); @@ -429,7 +430,6 @@ autocomplete_local(struct rpki_uri *uri, char const *tal, case UT_AIA: case UT_SO: - case UT_MFT: uri->local = NULL; return 0; } diff --git a/test/cache/local_cache_test.c b/test/cache/local_cache_test.c index 054c3f67..824818fb 100644 --- a/test/cache/local_cache_test.c +++ b/test/cache/local_cache_test.c @@ -102,6 +102,9 @@ http_download(struct rpki_uri *uri, curl_off_t ims, bool *changed) } MOCK_ABORT_INT(rrdp_update, struct rpki_uri *uri) +__MOCK_ABORT(rrdp_notif2json, json_t *, NULL, struct cachefile_notification *notif) +MOCK_VOID(rrdp_notif_free, struct cachefile_notification *notif) +MOCK_ABORT_INT(rrdp_json2notif, json_t *json, struct cachefile_notification **result) /* Helpers */ @@ -134,7 +137,7 @@ run_cache_download(char const *url, int expected_error, https_counter = 0; ck_assert_int_eq(0, uri_create(&uri, TAL_FILE, type, NULL, url)); - ck_assert_int_eq(expected_error, cache_download(cache, uri, 0, NULL)); + ck_assert_int_eq(expected_error, cache_download(cache, uri, NULL, NULL)); ck_assert_uint_eq(rsync_calls, rsync_counter); ck_assert_uint_eq(https_calls, https_counter); diff --git a/test/rrdp_test.c b/test/rrdp_test.c index ba6385ec..7aeec251 100644 --- a/test/rrdp_test.c +++ b/test/rrdp_test.c @@ -5,6 +5,7 @@ #include "alloc.c" #include "common.c" #include "file.c" +#include "json_util.c" #include "mock.c" #include "rrdp.c" #include "crypto/base64.c" @@ -31,7 +32,7 @@ cache_tmpfile(char **filename) } MOCK_ABORT_INT(cache_download, struct rpki_cache *cache, struct rpki_uri *uri, - curl_off_t ims, bool *changed) + bool *changed, struct cachefile_notification ***notif) MOCK_ABORT_VOID(fnstack_pop, void) MOCK_ABORT_VOID(fnstack_push_uri, struct rpki_uri *uri) MOCK_ABORT_PTR(validation_cache, rpki_cache, struct validation *state) @@ -43,6 +44,47 @@ MOCK_UINT(config_get_rrdp_delta_threshold, 64, void) /* Mocks end */ +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 cachefile_notification * +create_cachefile_notif(char const *session, char const *serial, ...) +{ + struct cachefile_notification *notif; + struct rrdp_hash *hash; + int dh_byte; + va_list args; + + notif = pmalloc(sizeof(struct cachefile_notification)); + + notif->session.session_id = pstrdup(session); + notif->session.serial.str = pstrdup(serial); + notif->session.serial.num = NULL; /* Not needed for now. */ + STAILQ_INIT(¬if->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(¬if->delta_hashes, hash, hook); + } + va_end(args); + + return notif; +} + START_TEST(test_xmlChar_NULL_assumption) { xmlChar *xmlstr; @@ -90,38 +132,41 @@ START_TEST(test_hexstr2sha256) { char *hex; unsigned char *sha = NULL; + size_t sha_len; unsigned int i; hex = "01"; - ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha)); + ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len)); ck_assert_ptr_eq(NULL, sha); hex = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; - ck_assert_int_eq(0, hexstr2sha256((xmlChar *) hex, &sha)); + sha_len = 0; + ck_assert_int_eq(0, hexstr2sha256((xmlChar *) hex, &sha, &sha_len)); ck_assert_ptr_ne(NULL, sha); for (i = 0; i < 32; i++) ck_assert_uint_eq(i, sha[i]); + ck_assert_uint_eq(32, sha_len); free(sha); sha = NULL; hex = "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; - ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha)); + ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len)); ck_assert_ptr_eq(NULL, sha); hex = " 00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; - ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha)); + ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len)); ck_assert_ptr_eq(NULL, sha); hex = "0001020g0405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; - ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha)); + ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len)); ck_assert_ptr_eq(NULL, sha); hex = "0001020g0405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1"; - ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha)); + ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len)); ck_assert_ptr_eq(NULL, sha); hex = "0001020g0405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2"; - ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha)); + ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len)); ck_assert_ptr_eq(NULL, sha); } END_TEST @@ -147,18 +192,26 @@ __sort_deltas(struct notification_deltas *deltas, unsigned int max_serial, return error; } +/* Not really designed to be used with args > END (because of @str). */ static void add_serials(struct notification_deltas *deltas, ...) { struct notification_delta delta = { 0 }; - unsigned long cursor; + int cursor; + char str[6]; + int written; va_list vl; va_start(vl, deltas); - while ((cursor = va_arg(vl, unsigned long)) != END) { + while ((cursor = va_arg(vl, int)) != END) { delta.serial.num = BN_create(); if (!BN_set_word(delta.serial.num, cursor)) ck_abort_msg("BN_set_word() returned zero."); + + written = snprintf(str, sizeof(str), "%d", cursor) < sizeof(str); + ck_assert(1 <= written && written <= sizeof(str)); + delta.serial.str = pstrdup(str); + notification_deltas_add(deltas, &delta); } va_end(vl); @@ -188,28 +241,36 @@ START_TEST(test_sort_deltas) { struct notification_deltas deltas; + /* No deltas */ notification_deltas_init(&deltas); ck_assert_int_eq(0, __sort_deltas(&deltas, 0, "0")); ck_assert_int_eq(0, __sort_deltas(&deltas, 1, "1")); ck_assert_int_eq(0, __sort_deltas(&deltas, 2, "2")); - add_serials(&deltas, 0, END); - ck_assert_int_eq(0, __sort_deltas(&deltas, 0, "0")); - validate_serials(&deltas, 0, END); + /* 1 delta */ + add_serials(&deltas, 2, END); + ck_assert_int_eq(0, __sort_deltas(&deltas, 2, "2")); + validate_serials(&deltas, 2, END); - ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 2, "2")); + /* Delta serial doesn't match session serial */ + ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 4, "4")); + ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 3, "3")); ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 1, "1")); + ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 0, "0")); - add_serials(&deltas, 1, 2, 3, END); - ck_assert_int_eq(0, __sort_deltas(&deltas, 3, "3")); - validate_serials(&deltas, 0, 1, 2, 3, END); + /* More than 1 delta, already sorted */ + add_serials(&deltas, 1, 3, 4, END); + ck_assert_int_eq(0, __sort_deltas(&deltas, 4, "4")); + validate_serials(&deltas, 1, 2, 3, 4, END); - ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 4, "4")); - ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 2, "2")); + /* More than 1 delta, they don't match session serial */ + ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 5, "5")); + ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 3, "3")); notification_deltas_cleanup(&deltas, notification_delta_cleanup); notification_deltas_init(&deltas); + /* More than 1 delta, not already sorted but otherwise functional */ add_serials(&deltas, 3, 0, 1, 2, END); ck_assert_int_eq(0, __sort_deltas(&deltas, 3, "3")); validate_serials(&deltas, 0, 1, 2, 3, END); @@ -217,14 +278,21 @@ START_TEST(test_sort_deltas) notification_deltas_cleanup(&deltas, notification_delta_cleanup); notification_deltas_init(&deltas); + /* Same, but order completely backwards */ add_serials(&deltas, 4, 3, 2, 1, 0, END); ck_assert_int_eq(0, __sort_deltas(&deltas, 4, "4")); validate_serials(&deltas, 0, 1, 2, 3, 4, END); + /* Same, but deltas don't match session serial */ ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 5, "5")); ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 3, "3")); notification_deltas_cleanup(&deltas, notification_delta_cleanup); + notification_deltas_init(&deltas); + + /* More than 1 delta, 1 serial missing */ + add_serials(&deltas, 1, 2, 4, END); + ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 4, "4")); } END_TEST @@ -435,27 +503,188 @@ START_TEST(test_parse_snapshot_bad_publish) } END_TEST +START_TEST(test_2s_simple) +{ + struct cachefile_notification *notif; + json_t *json, *jdeltas; + char const *str; + + notif = create_cachefile_notif("session", "1234", 0); + + json = rrdp_notif2json(notif); + ck_assert_ptr_ne(NULL, json); + + rrdp_notif_free(notif); + notif = 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_json2notif(json, ¬if)); + ck_rrdp_session("session", "1234", ¬if->session); + ck_assert_uint_eq(true, STAILQ_EMPTY(¬if->delta_hashes)); + + json_decref(json); + rrdp_notif_free(notif); +} +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 cachefile_notification *notif; + struct rrdp_hash *hash; + json_t *json, *jdeltas; + char const *str; + + notif = create_cachefile_notif("session", + "123456789012345678901234567890123456789012", + 0xAA, 0xBB, 0xCD, 0); + + json = rrdp_notif2json(notif); + ck_assert_ptr_ne(NULL, json); + + rrdp_notif_free(notif); + notif = 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_json2notif(json, ¬if)); + ck_rrdp_session("session", "123456789012345678901234567890123456789012", ¬if->session); + hash = STAILQ_FIRST(¬if->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_notif_free(notif); +} +END_TEST + +void +ck_json2notif(int expected, char const *json_str) +{ + json_t *json; + json_error_t error; + struct cachefile_notification *notif; + + json = json_loads(json_str, 0, &error); + ck_assert_ptr_ne(NULL, json); + + notif = NULL; + ck_assert_int_eq(expected, rrdp_json2notif(json, ¬if)); + + json_decref(json); + if (notif == NULL) + rrdp_notif_free(notif); +} + +START_TEST(test_2s_errors) +{ + struct cachefile_notification notif = { 0 }; + + ck_assert_ptr_eq(NULL, rrdp_notif2json(NULL)); + ck_assert_ptr_eq(NULL, rrdp_notif2json(¬if)); + notif.session.session_id = "sid"; + ck_assert_ptr_eq(NULL, rrdp_notif2json(¬if)); + + ck_json2notif(ENOENT, "{}"); + ck_json2notif(0, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":\"123\" }"); + ck_json2notif(-EINVAL, "{ \"" TAGNAME_SESSION "\":null, \"" TAGNAME_SERIAL "\":\"123\" }"); + ck_json2notif(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":null }"); + ck_json2notif(-EINVAL, "{ \"" TAGNAME_SESSION "\":123, \"" TAGNAME_SERIAL "\":\"123\" }"); + ck_json2notif(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":123 }"); + ck_json2notif(ENOENT, "{ \"" TAGNAME_SESSION "\":\"sss\" }"); + ck_json2notif(ENOENT, "{ \"" TAGNAME_SERIAL "\":\"123\" }"); + ck_json2notif(-EINVAL, + "{ \"" TAGNAME_SESSION "\":\"sss\"," + "\"" TAGNAME_SERIAL "\":\"123\"," + "\"" TAGNAME_DELTAS "\":null }"); + ck_json2notif(-EINVAL, + "{ \"" TAGNAME_SESSION "\":\"sss\"," + "\"" TAGNAME_SERIAL "\":\"123\"," + "\"" TAGNAME_DELTAS "\":\"123\" }"); + ck_json2notif(-EINVAL, + "{ \"" TAGNAME_SESSION "\":\"sss\"," + "\"" TAGNAME_SERIAL "\":\"123\"," + "\"" TAGNAME_DELTAS "\":{} }"); + ck_json2notif(0, + "{ \"" TAGNAME_SESSION "\":\"sss\"," + "\"" TAGNAME_SERIAL "\":\"123\"," + "\"" TAGNAME_DELTAS "\":[] }"); + ck_json2notif(-EINVAL, + "{ \"" TAGNAME_SESSION "\":\"sss\"," + "\"" TAGNAME_SERIAL "\":\"123\"," + "\"" TAGNAME_DELTAS "\":[ 1 ] }"); + ck_json2notif(-EINVAL, + "{ \"" TAGNAME_SESSION "\":\"sss\"," + "\"" TAGNAME_SERIAL "\":\"123\"," + "\"" TAGNAME_DELTAS "\":[ \"111\" ] }"); + ck_json2notif(0, + "{ \"" TAGNAME_SESSION "\":\"sss\"," + "\"" TAGNAME_SERIAL "\":\"123\"," + "\"" TAGNAME_DELTAS "\":[ \"1111111111111111111111111111111111111111111111111111111111111111\" ] }"); +} +END_TEST + static Suite *xml_load_suite(void) { Suite *suite; - TCase *misc; + TCase *misc, *parse, *cf; misc = tcase_create("misc"); tcase_add_test(misc, test_xmlChar_NULL_assumption); tcase_add_test(misc, test_hexstr2sha256); tcase_add_test(misc, test_sort_deltas); - tcase_add_test(misc, test_parse_notification_ok); - tcase_add_test(misc, test_parse_notification_0deltas); - tcase_add_test(misc, test_parse_notification_large_serial); - tcase_add_test(misc, test_parse_notification_bad_xmlns); - tcase_add_test(misc, test_parse_notification_bad_session_id); - tcase_add_test(misc, test_parse_notification_bad_serial); - tcase_add_test(misc, test_parse_notification_bad_hash); - tcase_add_test(misc, test_parse_notification_bad_uri); - tcase_add_test(misc, test_parse_snapshot_bad_publish); + + parse = tcase_create("parse"); + tcase_add_test(parse, test_parse_notification_ok); + tcase_add_test(parse, test_parse_notification_0deltas); + tcase_add_test(parse, test_parse_notification_large_serial); + tcase_add_test(parse, test_parse_notification_bad_xmlns); + tcase_add_test(parse, test_parse_notification_bad_session_id); + tcase_add_test(parse, test_parse_notification_bad_serial); + tcase_add_test(parse, test_parse_notification_bad_hash); + 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; } diff --git a/test/rtr/db/vrps_test.c b/test/rtr/db/vrps_test.c index cd91a629..d8529e05 100644 --- a/test/rtr/db/vrps_test.c +++ b/test/rtr/db/vrps_test.c @@ -70,8 +70,7 @@ static unsigned int deltas_lifetime = 5; MOCK_UINT(config_get_deltas_lifetime, deltas_lifetime, void) MOCK_ABORT_ENUM(config_get_output_format, output_format, void) -MOCK_ABORT_INT(hash_local_file, char const *uri, unsigned char *result, - unsigned int *result_len) +MOCK_ABORT_INT(hash_local_file, char const *uri, unsigned char *result) /* Test functions */ diff --git a/test/tal_test.c b/test/tal_test.c index 8adf18f0..671f9065 100644 --- a/test/tal_test.c +++ b/test/tal_test.c @@ -16,7 +16,7 @@ MOCK_ABORT_VOID(cache_setup, void) MOCK(cache_create, struct rpki_cache *, NULL, char const *tal) MOCK_VOID(cache_destroy, struct rpki_cache *cache) MOCK_ABORT_INT(cache_download, struct rpki_cache *cache, struct rpki_uri *uri, - curl_off_t ims, bool *changed) + bool *changed, struct cachefile_notification ***notif) MOCK_ABORT_INT(cache_download_alt, struct rpki_cache *cache, struct uri_list *uris, enum uri_type http_type, enum uri_type rsync_type, uris_dl_cb cb, void *arg) diff --git a/test/types/uri_test.c b/test/types/uri_test.c index e80f308a..46585074 100644 --- a/test/types/uri_test.c +++ b/test/types/uri_test.c @@ -21,10 +21,10 @@ MOCK_ABORT_INT(rrdp_update, struct rpki_uri *uri) int cache_tmpfile(char **filename) { - static bool used = false; + static unsigned int used = 1; - if (used) { - ck_abort_msg("cache_tmpfile() called a second time!"); + if (used > 2) { + ck_abort_msg("cache_tmpfile() called a third time!"); return -EINVAL; } @@ -116,7 +116,7 @@ START_TEST(test_constructor) ck_assert_int_eq(0, URI_CREATE(uri, UT_NOTIF, "https://a.b.c/notification.xml")); ck_assert_str_eq("https://a.b.c/notification.xml", uri_get_global(uri)); - ck_assert_str_eq("tmp/test.tal/https/a.b.c/notification.xml", uri_get_local(uri)); + ck_assert_str_eq("tmp/tmp/0", uri_get_local(uri)); uri_refput(uri); ck_assert_int_eq(0, URI_CREATE(uri, UT_TMP, "https://a.b.c/snapshot.xml"));