]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Finally, implement draft-spaghetti-sidrops-rrdp-desynchronization-00
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 4 Mar 2024 01:50:05 +0000 (19:50 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 4 Mar 2024 01:50:05 +0000 (19:50 -0600)
- 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.

19 files changed:
man/fort.8
src/cache/local_cache.c
src/cache/local_cache.h
src/config.c
src/data_structure/path_builder.c
src/file.c
src/file.h
src/json_util.c
src/json_util.h
src/object/certificate.c
src/rrdp.c
src/rrdp.h
src/slurm/slurm_loader.c
src/types/uri.c
test/cache/local_cache_test.c
test/rrdp_test.c
test/rtr/db/vrps_test.c
test/tal_test.c
test/types/uri_test.c

index 0eed73e382596bc2d3dbfe78f96ff4b9c478033a..14bb63dbfcb3e7e398243837c4fb79d1d3942fc1 100644 (file)
@@ -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
 
index 9d98bd4bf1d4daeb1903b49864302ce297b73c24..0085f7d9dd7be09e602c1ec338c05b0bc22cf1dc 100644 (file)
@@ -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, &notif);
+               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);
index 1925351256d269700d8e19a1d3deb9bd5df0e746..8371893336d626cb903a1316d4d4fe55c9462632 100644 (file)
@@ -1,7 +1,6 @@
 #ifndef SRC_CACHE_LOCAL_CACHE_H_
 #define SRC_CACHE_LOCAL_CACHE_H_
 
-#include <curl/curl.h>
 #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
index f2203c979356a2f552e95b7781195d9866705461..9186cd154a4cb6f118aa29ab735b902f8f4ae39e 100644 (file)
@@ -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;
index 5dbee72c2a74fa46a466fcc04528b2035f84b21d..d75daa10549ef446a4f19919b9941ec001f35485 100644 (file)
@@ -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
index d3a64b1497bc9bda40da0ce8a5ae57c5c1b372bf..768dd9550a546f872adfd679feeb79af47d7d9fd 100644 (file)
@@ -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.
  */
index f62b00b6d4e822ef8ddfc6fecea12b4c2bcd986e..d0d603bd6bf749b3e93c79cec8c7911ef1fefb7b 100644 (file)
@@ -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 *);
index 439caf6fdb925a8665a55553e2071079f801ebee..a814af7cc27a6ddfb0047863dc4f6c95e48a731b 100644 (file)
@@ -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;
+}
index 33d322926c5c5821a8f9b71001cc592e543ef6a5..93a41d01e855a288f10ba7ba8b465f794fba39c5 100644 (file)
@@ -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_ */
index 920a561ccd02d592512b82a84d31105b91943456..d1a7d92c4e51b4a3632b0a0545442442f93989d8 100644 (file)
@@ -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;
 }
 
index d0a306afb7508b4c5b3d0b3650b22c9cbe8f3ea9..7015e4e1a488066cd418e33ee7bbe286ec3d6b3e 100644 (file)
@@ -3,11 +3,13 @@
 #include <ctype.h>
 #include <openssl/bn.h>
 #include <openssl/evp.h>
+#include <sys/queue.h>
 
 #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(&notif->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, &notification);
-       if (error)
-               return error;
+static void
+drop_notif(struct cachefile_notification *notif)
+{
+       struct rrdp_hash *hash;
 
-       *result = notification.session;
-       memset(&notification.session, 0, sizeof(notification.session));
-       update_notification_cleanup(&notification);
-       return 0;
+       session_cleanup(&notif->session);
+       while (!STAILQ_EMPTY(&notif->delta_hashes)) {
+               hash = STAILQ_FIRST(&notif->delta_hashes);
+               STAILQ_REMOVE_HEAD(&notif->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(&notif->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, &notif->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(&notif->delta_hashes)) {
+               hash = STAILQ_FIRST(&notif->delta_hashes);
+               STAILQ_REMOVE_HEAD(&notif->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(&notif->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(&notif->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(&notif->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(&notif->session);
+       clear_delta_hashes(notif);
+       free(notif);
+}
index f3de0a366eead6bace92541acba1358d08d45b2b..757dd46ce65df9c70fd5f86b4be1a58563ebcc5b 100644 (file)
@@ -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_ */
index 2c0abd0400ab0e06ba64e935d73e140f94400ced..06e498198f7ed1eb70cc56fb7f28d2ea0e70dd1b 100644 (file)
@@ -1,6 +1,8 @@
 #include "slurm/slurm_loader.h"
 
 #include <errno.h>
+#include <openssl/sha.h>
+
 #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;
                        }
index 753c3b5c2e6fcb418bcb7e3a587af15d39cda4c4..d4ee1a0911002098427acdfae2c0761ac4fac6dd 100644 (file)
@@ -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;
        }
index 054c3f6754960cd83606602a937770935833a2cf..824818fbd79bcfefbbf61654c30dee715f8ebb4e 100644 (file)
@@ -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);
 
index ba6385ec5f2dceac9097495c10d5ce7f9fc7831c..7aeec25170bccf6de2c18fbc1f7d30573bdbf61c 100644 (file)
@@ -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(&notif->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(&notif->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, &notif));
+       ck_rrdp_session("session", "1234", &notif->session);
+       ck_assert_uint_eq(true, STAILQ_EMPTY(&notif->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, &notif));
+       ck_rrdp_session("session", "123456789012345678901234567890123456789012", &notif->session);
+       hash = STAILQ_FIRST(&notif->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, &notif));
+
+       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(&notif));
+       notif.session.session_id = "sid";
+       ck_assert_ptr_eq(NULL, rrdp_notif2json(&notif));
+
+       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;
 }
index cd91a62966a5214a4ce4b2925729c2ae82326ef2..d8529e059b247f1f78fff227698ec591b61c49d3 100644 (file)
@@ -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 */
 
index 8adf18f0ad6fdf31d091e792d9040513ef1131ed..671f90654fba9d81b38199b5263a081251ce4b96 100644 (file)
@@ -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)
index e80f308aeedab5b303d25455ab219c5afe97b5d4..46585074f1ac2a98e9d8384716f535ae390214a1 100644 (file)
@@ -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"));