]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
VRPS: Clean up validation core
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Fri, 2 Jul 2021 23:58:19 +0000 (18:58 -0500)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Sat, 3 Jul 2021 04:45:36 +0000 (23:45 -0500)
It was pretty messy; I had to rewrite a good chunk of it.

== Problem 1 ==

It was discarding meaningful validation results when miscellaneous
errors prevented the deltas array from being built.

Deltas are optional; as long as Fort has the snapshot of the latest
tree, it doesn't technically need deltas. They speed up synchronization,
but in the worst case scenario, the RTR server can keep pushing Cache
Resets.

Severity: Warning. Memory allocation failures are the only eventuality
that might prevent the deltas array from being built.

== Problem 2 ==

The database was always keeping one serial's worth of obsolete deltas.

Cleaned up, saves a potentially large amount of memory.

Severity: Fine. Not a memory leak.

== Problem 3 ==

The code computed deltas even whene there were no routers listening.
Routers are the only delta consumers, so there was no need to waste all
that time.

Severity: Fine; performance quirk.

== Problem 4 ==

I found an RTR client implementation (Cloudflare's rpki-rtr-client) that
hangs when the first serial is zero. Fort's first serial is now 1.

Severity: Warning. This is rpki-rtr-client's fault, but any client
implementations are prone to the same bug. The new solution is more
future-proof.

== Problem 5 ==

It seems it wasn't cleaning the deltas array when all routers were known
to have bogus serials. This was the code:

/* Its the first element or reached end, nothing to purge */
if (group == state.deltas.array ||
   (group - state.deltas.array) == state.deltas.len)
return 0;

If you reached the end of the deltas array, and the minimum router
serial is larger than all the array serials, then all deltas are
useless; you're supposed to purge all of them.

Severity: Fine. It was pretty hard to trigger, and not a memory leak.

src/rtr/db/vrps.c
src/rtr/db/vrps.h
test/rtr/db/vrps_test.c
test/rtr/pdu_handler_test.c

index 4c9568f944cecefa93f9ed9a68614710671eb8ed..87d83326b4c728cfd07deaf899d813d2ddbab2a6 100644 (file)
@@ -17,8 +17,6 @@
 #include "slurm/slurm_loader.h"
 #include "thread/thread_pool.h"
 
-#define START_SERIAL           0
-
 DEFINE_ARRAY_LIST_FUNCTIONS(deltas_db, struct delta_group, )
 
 struct vrp_node {
@@ -55,7 +53,20 @@ struct state {
        /* Last valid SLURM applied to base */
        struct db_slurm *slurm;
 
-       serial_t next_serial;
+       /*
+        * This is the serial number of base.
+        *
+        * At least one RTR client implementation (Cloudflare's rpki-rtr-client)
+        * malfunctions if the validator uses zero as the first serial, so this
+        * value behaves as follows:
+        *
+        * serial = 0. After every successful validation cycle, serial++.
+        *
+        * Do not use this value to check whether we already finished our first
+        * validation. (Use base != NULL for that.) Zero is totally a valid
+        * serial, particularly when the integer wraps.
+        */
+       serial_t serial;
        uint16_t v0_session_id;
        uint16_t v1_session_id;
 };
@@ -98,7 +109,7 @@ vrps_init(void)
         * "desynchronization" (more at RFC 6810 'Glossary' and
         * 'Fields of a PDU')
         */
-       state.next_serial = START_SERIAL;
+       state.serial = 0;
 
        /* Get the bits that'll fit in session_id */
        now = 0;
@@ -208,182 +219,148 @@ __perform_standalone_validation(struct db_table **result)
 }
 
 /*
- * Make an empty dummy delta array.
- * It's annoying, but temporary. (Until it expires.) Otherwise, it'd be a pain
- * to have to check NULL delta_group.deltas all the time.
+ * Remove unnecessary deltas from the database.
+ * Unnecessary deltas = those whose serial < min_serial.
  */
-static int
-create_empty_delta(struct deltas **deltas)
+static void
+cleanup_deltas(serial_t min_serial)
 {
-       struct delta_group deltas_node;
-       int error;
+       struct delta_group *initial;
+       struct delta_group *rm;
+       array_index i;
 
-       error = deltas_create(deltas);
-       if (error)
-               return error;
+       /*
+        * TODO the array is sorted by serial, but it's supposed to employ
+        * serial arithmetic. > is incorrect.
+        */
+       ARRAYLIST_FOREACH(&state.deltas, initial, i)
+               if (initial->serial > min_serial)
+                       break;
 
-       deltas_node.serial = state.next_serial;
-       deltas_node.deltas = *deltas;
-       error = deltas_db_add(&state.deltas, &deltas_node);
-       if (error)
-               deltas_refput(*deltas);
-       return error;
+       for (rm = state.deltas.array; rm < initial; rm++)
+               deltas_refput(rm->deltas);
+
+       state.deltas.len -= (initial - state.deltas.array);
+       memmove(state.deltas.array, initial,
+           state.deltas.len * sizeof(struct delta_group));
 }
 
-/**
- * Reallocate the array of @db starting at @start, the length and capacity are
- * calculated according to the new start.
- */
 static int
-resize_deltas_db(struct deltas_db *db, struct delta_group *start)
+__compute_deltas(struct db_table *old_base, struct db_table *new_base,
+    bool *notify_clients)
 {
-       struct delta_group *tmp, *ptr;
+       struct deltas *deltas; /* Deltas in raw form */
+       struct delta_group deltas_node; /* Deltas in database node form */
+       serial_t min_serial;
+       int error;
 
-       db->len -= (start - db->array);
-       while (db->len < db->capacity / 2)
-               db->capacity /= 2;
-       tmp = malloc(sizeof(struct delta_group) * db->capacity);
-       if (tmp == NULL)
-               return pr_enomem();
+       error = 0;
 
-       memcpy(tmp, start, db->len * sizeof(struct delta_group));
-       /* Release memory allocated */
-       for (ptr = db->array; ptr < start; ptr++)
-               deltas_refput(ptr->deltas);
-       free(db->array);
-       db->array = tmp;
+       /* No clients listening = no need for deltas */
+       if (clients_get_min_serial(&min_serial) == -ENOENT)
+               goto purge_deltas;
 
-       return 0;
-}
+       if (notify_clients != NULL)
+               *notify_clients = true;
 
-/*
- * Lock must be requested before calling this function
- */
-static int
-vrps_purge(struct deltas **deltas)
-{
-       struct delta_group *group;
-       array_index i;
-       serial_t min_serial;
+       /* First version of the database = No deltas */
+       if (old_base == NULL)
+               goto purge_deltas;
+
+       /*
+        * Failure on computing deltas = latest database version lacks deltas,
+        * which renders all previous deltas useless. (Because clients always
+        * want the latest.)
+        */
+       error = compute_deltas(old_base, new_base, &deltas);
+       if (error)
+               goto purge_deltas;
 
-       if (clients_get_min_serial(&min_serial) != 0) {
-               /* Nobody will need deltas, just leave an empty one */
-               deltas_refput(*deltas);
-               deltas_db_cleanup(&state.deltas, deltagroup_cleanup);
-               deltas_db_init(&state.deltas);
-               return create_empty_delta(deltas);
+       if (deltas_is_empty(deltas)) {
+               if (notify_clients != NULL)
+                       *notify_clients = false;
+               deltas_refput(deltas);
+               goto success; /* Happy path when the DB doesn't change. */
        }
 
-       /* Assume its ordered by serial, so get the new initial pointer */
-       ARRAYLIST_FOREACH(&state.deltas, group, i)
-               if (group->serial >= min_serial)
-                       break;
+       deltas_node.serial = state.serial;
+       deltas_node.deltas = deltas;
+       /* On success, ownership of deltas is transferred to state.deltas. */
+       error = deltas_db_add(&state.deltas, &deltas_node);
+       if (error) {
+               deltas_refput(deltas);
+               goto purge_deltas;
+       }
 
-       /* Its the first element or reached end, nothing to purge */
-       if (group == state.deltas.array ||
-           (group - state.deltas.array) == state.deltas.len)
-               return 0;
+       /* Happy path when the DB changes. (Fall through) */
+
+success:
+       cleanup_deltas(min_serial);
+       return 0;
 
-       return resize_deltas_db(&state.deltas, group);
+purge_deltas:
+       deltas_db_cleanup(&state.deltas, deltagroup_cleanup);
+       return error;
 }
 
 static int
-__vrps_update(bool *changed)
+__vrps_update(bool *notify_clients)
 {
        struct db_table *old_base;
        struct db_table *new_base;
-       struct deltas *deltas; /* Deltas in raw form */
-       struct delta_group deltas_node; /* Deltas in database node form */
-       serial_t min_serial;
        int error;
 
-       if (changed)
-               *changed = false;
+       if (notify_clients)
+               *notify_clients = false;
        old_base = NULL;
        new_base = NULL;
 
        error = __perform_standalone_validation(&new_base);
        if (error)
                return error;
-
        error = slurm_apply(&new_base, &state.slurm);
-       if (error)
-               goto revert_base;
-
-       rwlock_write_lock(&state_lock);
+       if (error) {
+               db_table_destroy(new_base);
+               return error;
+       }
 
-       if (state.base != NULL) {
-               error = compute_deltas(state.base, new_base, &deltas);
-               if (error) {
-                       rwlock_unlock(&state_lock);
-                       goto revert_base;
-               }
+       /*
+        * At this point, new_base is completely valid. Even if we error out
+        * later, report the ROAs.
+        *
+        * This is done after the validation, not during it, to prevent
+        * duplicate ROAs.
+        */
+       output_print_data(new_base);
 
-               if (deltas_is_empty(deltas)) {
-                       rwlock_unlock(&state_lock);
-                       goto revert_deltas; /* error == 0 is good */
-               }
+       rwlock_write_lock(&state_lock);
 
-               /* Just store deltas if someone will care about it */
-               if (clients_get_min_serial(&min_serial) == 0) {
-                       deltas_node.serial = state.next_serial;
-                       deltas_node.deltas = deltas;
-                       error = deltas_db_add(&state.deltas, &deltas_node);
-                       if (error) {
-                               rwlock_unlock(&state_lock);
-                               goto revert_deltas;
-                       }
-               }
+       old_base = state.base; /* Postpone destruction, to release lock ASAP. */
+       state.base = new_base;
+       state.serial++;
 
+       /*
+        * TODO after refactoring vrps_foreach_filtered_delta(), move this out
+        * of the mutex. You don't really need the mutex to compute the deltas;
+        * vrps_update() is supposed to be the only writer.
+        */
+       error = __compute_deltas(old_base, new_base, notify_clients);
+       if (error) {
                /*
-                * Postpone destruction of the old database,
-                * to release the lock ASAP.
+                * Deltas are nice-to haves. As long as state.base is correct,
+                * the validator can continue serving the routers.
+                * (Albeit less efficiently.)
+                * So drop a warning and keep going.
                 */
-               old_base = state.base;
-
-               /* Remove unnecessary deltas */
-               error = vrps_purge(&deltas);
-               if (error) {
-                       rwlock_unlock(&state_lock);
-                       goto revert_base;
-               }
-       } else {
-               /* There's also an empty base, don't alter state */
-               if (db_table_roa_count(new_base) +
-                   db_table_router_key_count(new_base) == 0) {
-                       rwlock_unlock(&state_lock);
-                       error = 0; /* OK (said explicitly) */
-                       goto revert_base;
-               }
-               error = create_empty_delta(&deltas);
-               if (error) {
-                       rwlock_unlock(&state_lock);
-                       goto revert_base;
-               }
+               pr_op_warn("Deltas could not be computed: %s", strerror(error));
        }
 
-       if (changed)
-               *changed = true;
-       state.base = new_base;
-       state.next_serial++;
-
        rwlock_unlock(&state_lock);
 
        if (old_base != NULL)
                db_table_destroy(old_base);
 
-       /* Print after validation to avoid duplicated info */
-       output_print_data(new_base);
-
        return 0;
-
-revert_deltas:
-       deltas_refput(deltas);
-revert_base:
-       /* Print info that was already validated */
-       output_print_data(new_base);
-       db_table_destroy(new_base);
-       return error;
 }
 
 int
@@ -395,18 +372,17 @@ vrps_update(bool *changed)
        int error;
 
        /*
-        * This wrapper is mainly for log informational data, so if there's no
-        * need don't do unnecessary calls
+        * This wrapper is mainly intended to log informational data, so if
+        * there's no need, don't do unnecessary calls.
         */
        if (!log_op_enabled(LOG_INFO))
                return __vrps_update(changed);
 
        pr_op_info("Starting validation.");
-       serial = START_SERIAL;
        if (config_get_mode() == SERVER) {
                error = get_last_serial_number(&serial);
                if (!error)
-                       pr_op_info("- Current serial number is %u.", serial);
+                       pr_op_info("- Serial before validation: %u", serial);
        }
 
        time(&start);
@@ -429,11 +405,8 @@ vrps_update(bool *changed)
                pr_op_info("- Valid Prefixes: %u", db_table_roa_count(state.base));
                pr_op_info("- Valid Router Keys: %u",
                    db_table_router_key_count(state.base));
-               if (config_get_mode() == SERVER) {
-                       pr_op_info("- %s serial number is %u.",
-                           serial == state.next_serial - 1 ? "Current" : "New",
-                           state.next_serial - 1);
-               }
+               if (config_get_mode() == SERVER)
+                       pr_op_info("- Serial: %u", state.serial);
                rwlock_unlock(&state_lock);
        } while(0);
        pr_op_info("- Real execution time: %ld secs.", exec_time);
@@ -606,29 +579,36 @@ int
 vrps_get_deltas_from(serial_t from, serial_t *to, struct deltas_db *result)
 {
        struct delta_group *group;
+       serial_t first_serial;
+       serial_t last_serial;
        array_index i;
-       bool from_found;
        int error;
 
-       from_found = false;
-
        error = rwlock_read_lock(&state_lock);
        if (error)
                return error;
 
-       if (state.base == NULL) {
+       if (state.base == NULL)
+               goto try_again; /* Database still under construction. */
+       if (from == state.serial) {
+               /* Client already has the latest serial. */
                rwlock_unlock(&state_lock);
-               return -EAGAIN;
+               *to = from;
+               return 0;
        }
+       if (state.deltas.len == 0)
+               goto reset_database; /* No deltas available. */
 
-       ARRAYLIST_FOREACH(&state.deltas, group, i) {
-               if (!from_found) {
-                       if (group->serial == from) {
-                               from_found = true;
-                               *to = group->serial;
-                       }
-                       continue;
-               }
+       first_serial = state.deltas.array[0].serial - 1;
+       last_serial = state.deltas.array[state.deltas.len - 1].serial;
+
+       if (from < first_serial)
+               goto reset_database; /* Delta was already deleted. */
+       if (from > last_serial)
+               goto reset_database; /* Serial is invalid. */
+
+       for (i = from - first_serial; i < state.deltas.len; i++) {
+               group = &state.deltas.array[i];
 
                error = deltas_db_add(result, group);
                if (error) {
@@ -637,11 +617,19 @@ vrps_get_deltas_from(serial_t from, serial_t *to, struct deltas_db *result)
                }
 
                deltas_refget(group->deltas);
-               *to = group->serial;
        }
 
        rwlock_unlock(&state_lock);
-       return from_found ? 0 : -ESRCH;
+       *to = last_serial;
+       return 0;
+
+try_again:
+       rwlock_unlock(&state_lock);
+       return -EAGAIN;
+
+reset_database:
+       rwlock_unlock(&state_lock);
+       return -ESRCH;
 }
 
 int
@@ -654,7 +642,7 @@ get_last_serial_number(serial_t *result)
                return error;
 
        if (state.base != NULL)
-               *result = state.next_serial - 1;
+               *result = state.serial;
        else
                error = -EAGAIN;
 
index 956b3d189808cd51520453b88dbace58e2da8c42..3d45e15b653d10b4cce053ab973b32e4f16f686a 100644 (file)
 #include "data_structure/array_list.h"
 #include "rtr/db/delta.h"
 
-/*
- * Deltas that share a serial.
- */
 struct delta_group {
        serial_t serial;
+       /* snapshot(serial - 1) + deltas = snapshot(serial) */
        struct deltas *deltas;
 };
 
index fde5e5de5b0b2fa274a48a12720209676a138503..34e7912475f0a2ab2f7440d6141ac7615afb5655 100644 (file)
  * 4: Router key, ASN 0
  * 5: Router key, ASN 1
  */
-static const bool iteration0_base[] = { 1, 0, 1, 0, 1, 0, };
-static const bool iteration1_base[] = { 1, 1, 1, 1, 1, 1, };
-static const bool iteration2_base[] = { 0, 1, 0, 1, 0, 1, };
-static const bool iteration3_base[] = { 1, 0, 1, 0, 1, 0, };
+static const bool iteration1_base[] = { 1, 0, 1, 0, 1, 0, };
+static const bool iteration2_base[] = { 1, 1, 1, 1, 1, 1, };
+static const bool iteration3_base[] = { 0, 1, 0, 1, 0, 1, };
+static const bool iteration4_base[] = { 1, 0, 1, 0, 1, 0, };
 
 /*
  * DELTA
@@ -46,26 +46,26 @@ static const bool iteration3_base[] = { 1, 0, 1, 0, 1, 0, };
  * 5: Withdrawal, RK,   ASN 1   11: Announcement, RK,   ASN 1
  */
 
-static const bool deltas_0to0[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
-
-static const bool deltas_0to1[] = { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, };
 static const bool deltas_1to1[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
 
-static const bool deltas_0to2[] = { 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, };
-static const bool deltas_1to2[] = { 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_1to2[] = { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, };
 static const bool deltas_2to2[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
 
+static const bool deltas_1to3[] = { 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, };
+static const bool deltas_2to3[] = { 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_3to3[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+
 /* Deltas with rules that override each other */
-static const bool deltas_0to3_ovrd[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, };
-static const bool deltas_1to3_ovrd[] = { 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, };
-static const bool deltas_2to3_ovrd[] = { 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, };
-static const bool deltas_3to3_ovrd[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_1to4_ovrd[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, };
+static const bool deltas_2to4_ovrd[] = { 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, };
+static const bool deltas_3to4_ovrd[] = { 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, };
+static const bool deltas_4to4_ovrd[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
 
 /* Deltas cleaned up */
-static const bool deltas_0to3_clean[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
-static const bool deltas_1to3_clean[] = { 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, };
-static const bool deltas_2to3_clean[] = { 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, };
-static const bool deltas_3to3_clean[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_1to4_clean[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_2to4_clean[] = { 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_3to4_clean[] = { 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, };
+static const bool deltas_4to4_clean[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
 
 /* Impersonator functions */
 
@@ -339,18 +339,18 @@ check_deltas(serial_t from, serial_t to, bool const *expected_deltas,
 }
 
 static void
-check_no_deltas(serial_t from, serial_t to)
+check_no_deltas(serial_t from)
 {
-       serial_t actual_serial;
+       serial_t actual_to;
        struct deltas_db deltas;
 
        deltas_db_init(&deltas);
-       ck_assert_int_eq(-ESRCH, vrps_get_deltas_from(from, &actual_serial,
+       ck_assert_int_eq(-ESRCH, vrps_get_deltas_from(from, &actual_to,
            &deltas));
 }
 
 static void
-create_deltas_0to1(struct deltas_db *deltas, serial_t *serial, bool *changed,
+create_deltas_1to2(struct deltas_db *deltas, serial_t *serial, bool *changed,
     bool *iterated_entries)
 {
        current_min_serial = 0;
@@ -367,16 +367,16 @@ create_deltas_0to1(struct deltas_db *deltas, serial_t *serial, bool *changed,
 
        /* First validation: One tree, no deltas */
        ck_assert_int_eq(0, vrps_update(changed));
-       check_serial(0);
-       check_base(0, iteration0_base);
-       check_deltas(0, 0, deltas_0to0, false);
-
-       /* Second validation: One tree, added deltas */
-       ck_assert_int_eq(0, vrps_update(changed));
        check_serial(1);
        check_base(1, iteration1_base);
-       check_deltas(0, 1, deltas_0to1, false);
        check_deltas(1, 1, deltas_1to1, false);
+
+       /* Second validation: One tree, added deltas */
+       ck_assert_int_eq(0, vrps_update(changed));
+       check_serial(2);
+       check_base(2, iteration2_base);
+       check_deltas(1, 2, deltas_1to2, false);
+       check_deltas(2, 2, deltas_2to2, false);
 }
 
 START_TEST(test_basic)
@@ -386,15 +386,15 @@ START_TEST(test_basic)
        bool changed;
        bool iterated_entries[12];
 
-       create_deltas_0to1(&deltas, &serial, &changed, iterated_entries);
+       create_deltas_1to2(&deltas, &serial, &changed, iterated_entries);
 
        /* Third validation: One tree, removed deltas */
        ck_assert_int_eq(0, vrps_update(&changed));
-       check_serial(2);
-       check_base(2, iteration2_base);
-       check_deltas(0, 2, deltas_0to2, false);
-       check_deltas(1, 2, deltas_1to2, false);
-       check_deltas(2, 2, deltas_2to2, false);
+       check_serial(3);
+       check_base(3, iteration3_base);
+       check_deltas(1, 3, deltas_1to3, false);
+       check_deltas(2, 3, deltas_2to3, false);
+       check_deltas(3, 3, deltas_3to3, false);
 
        vrps_destroy();
 }
@@ -407,21 +407,21 @@ START_TEST(test_delta_forget)
        bool changed;
        bool iterated_entries[12];
 
-       create_deltas_0to1(&deltas, &serial, &changed, iterated_entries);
+       create_deltas_1to2(&deltas, &serial, &changed, iterated_entries);
 
        /*
-        * Assume that the client(s) already have serial 1 (serial 2 will be
-        * created) so serial 0 isn't needed anymore.
+        * Assume that the client(s) already have serial 2 (serial 3 will be
+        * created) so serial 1 isn't needed anymore.
         */
-       current_min_serial = 1;
+       current_min_serial = 2;
 
-       /* Third validation: One tree, removed deltas and delta 0 removed */
+       /* Third validation: One tree, removed deltas and delta 1 removed */
        ck_assert_int_eq(0, vrps_update(&changed));
-       check_serial(2);
-       check_base(2, iteration2_base);
-       check_no_deltas(0, 2);
-       check_deltas(1, 2, deltas_1to2, false);
-       check_deltas(2, 2, deltas_2to2, false);
+       check_serial(3);
+       check_base(3, iteration3_base);
+       check_no_deltas(1);
+       check_deltas(2, 3, deltas_2to3, false);
+       check_deltas(3, 3, deltas_3to3, false);
 
        vrps_destroy();
 
@@ -437,30 +437,30 @@ START_TEST(test_delta_ovrd)
        bool changed;
        bool iterated_entries[12];
 
-       create_deltas_0to1(&deltas, &serial, &changed, iterated_entries);
+       create_deltas_1to2(&deltas, &serial, &changed, iterated_entries);
 
        /* Third validation: One tree, removed deltas */
        ck_assert_int_eq(0, vrps_update(&changed));
-       check_serial(2);
-       check_base(2, iteration2_base);
-       check_deltas(0, 2, deltas_0to2, false);
-       check_deltas(1, 2, deltas_1to2, false);
-       check_deltas(2, 2, deltas_2to2, false);
+       check_serial(3);
+       check_base(3, iteration3_base);
+       check_deltas(1, 3, deltas_1to3, false);
+       check_deltas(2, 3, deltas_2to3, false);
+       check_deltas(3, 3, deltas_3to3, false);
 
        /* Fourth validation with deltas that override each other */
        ck_assert_int_eq(0, vrps_update(&changed));
-       check_serial(3);
-       check_base(3, iteration3_base);
-       check_deltas(0, 3, deltas_0to3_ovrd, false);
-       check_deltas(1, 3, deltas_1to3_ovrd, false);
-       check_deltas(2, 3, deltas_2to3_ovrd, false);
-       check_deltas(3, 3, deltas_3to3_ovrd, false);
+       check_serial(4);
+       check_base(4, iteration4_base);
+       check_deltas(1, 4, deltas_1to4_ovrd, false);
+       check_deltas(2, 4, deltas_2to4_ovrd, false);
+       check_deltas(3, 4, deltas_3to4_ovrd, false);
+       check_deltas(4, 4, deltas_4to4_ovrd, false);
 
        /* Check "cleaned up" deltas */
-       check_deltas(0, 3, deltas_0to3_clean, true);
-       check_deltas(1, 3, deltas_1to3_clean, true);
-       check_deltas(2, 3, deltas_2to3_clean, true);
-       check_deltas(3, 3, deltas_3to3_clean, true);
+       check_deltas(1, 4, deltas_1to4_clean, true);
+       check_deltas(2, 4, deltas_2to4_clean, true);
+       check_deltas(3, 4, deltas_3to4_clean, true);
+       check_deltas(4, 4, deltas_4to4_clean, true);
 
        vrps_destroy();
 
index 74b22b6635adec4a13305c8d7b09da74dc5d2d91..94148e208fa61f32e0d2b2f3b05b8c8be70f5d38 100644 (file)
@@ -272,14 +272,8 @@ START_TEST(test_typical_exchange)
        init_serial_query(&request, &client_pdu, 0);
 
        /* From serial 0: Define expected server response */
-       expected_pdu_add(PDU_TYPE_CACHE_RESPONSE);
-       expected_pdu_add(PDU_TYPE_IPV4_PREFIX);
-       expected_pdu_add(PDU_TYPE_IPV6_PREFIX);
-       expected_pdu_add(PDU_TYPE_ROUTER_KEY);
-       expected_pdu_add(PDU_TYPE_IPV4_PREFIX);
-       expected_pdu_add(PDU_TYPE_IPV6_PREFIX);
-       expected_pdu_add(PDU_TYPE_ROUTER_KEY);
-       expected_pdu_add(PDU_TYPE_END_OF_DATA);
+       /* Server doesn't have serial 0. */
+       expected_pdu_add(PDU_TYPE_CACHE_RESET);
 
        /* From serial 0: Run and validate */
        ck_assert_int_eq(0, handle_serial_query_pdu(0, &request));
@@ -293,6 +287,9 @@ START_TEST(test_typical_exchange)
        expected_pdu_add(PDU_TYPE_IPV4_PREFIX);
        expected_pdu_add(PDU_TYPE_IPV6_PREFIX);
        expected_pdu_add(PDU_TYPE_ROUTER_KEY);
+       expected_pdu_add(PDU_TYPE_IPV4_PREFIX);
+       expected_pdu_add(PDU_TYPE_IPV6_PREFIX);
+       expected_pdu_add(PDU_TYPE_ROUTER_KEY);
        expected_pdu_add(PDU_TYPE_END_OF_DATA);
 
        /* From serial 1: Run and validate */
@@ -304,12 +301,26 @@ START_TEST(test_typical_exchange)
 
        /* From serial 2: Define expected server response */
        expected_pdu_add(PDU_TYPE_CACHE_RESPONSE);
+       expected_pdu_add(PDU_TYPE_IPV4_PREFIX);
+       expected_pdu_add(PDU_TYPE_IPV6_PREFIX);
+       expected_pdu_add(PDU_TYPE_ROUTER_KEY);
        expected_pdu_add(PDU_TYPE_END_OF_DATA);
 
        /* From serial 2: Run and validate */
        ck_assert_int_eq(0, handle_serial_query_pdu(0, &request));
        ck_assert_uint_eq(false, has_expected_pdus());
 
+       /* From serial 3: Init client request */
+       init_serial_query(&request, &client_pdu, 3);
+
+       /* From serial 3: Define expected server response */
+       expected_pdu_add(PDU_TYPE_CACHE_RESPONSE);
+       expected_pdu_add(PDU_TYPE_END_OF_DATA);
+
+       /* From serial 3: Run and validate */
+       ck_assert_int_eq(0, handle_serial_query_pdu(0, &request));
+       ck_assert_uint_eq(false, has_expected_pdus());
+
        /* Clean up */
        vrps_destroy();
 }