]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Patch the PDU handler's locking
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Thu, 9 May 2019 19:55:48 +0000 (14:55 -0500)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Thu, 9 May 2019 20:06:19 +0000 (15:06 -0500)
Side effects:

- New typedef: `serial_t`. Because I keep forgetting the serial data
  type we're using.
- Remove `enum delta_op` in favor of `FLAG_*`. The latter still should
  probably be converted into an enum and renamed though, especially
  since its instances are never actually used as bitwise flags.
- Moved `struct vrp`'s `flag` field into `struct delta`, because it's
  not always used in `vrp`.

Also, to improve performance a little, we're no longer canceling deltas
on cache responses when there's only one serial, because the ROA hash
table already guarantees their uniqueness.

24 files changed:
src/Makefile.am
src/address.c
src/clients.c
src/clients.h
src/data_structure/array_list.h
src/notify.c
src/rtr/db/delta.c
src/rtr/db/delta.h
src/rtr/db/roa.c
src/rtr/db/roa_table.c
src/rtr/db/vrp.h
src/rtr/db/vrps.c
src/rtr/db/vrps.h
src/rtr/err_pdu.c
src/rtr/pdu.h
src/rtr/pdu_handler.c
src/rtr/pdu_sender.c
src/rtr/pdu_sender.h
src/rtr/rtr.c
test/Makefile.am
test/rtr/db/impersonator.c [new file with mode: 0644]
test/rtr/db/roa_table_test.c
test/rtr/db/vrps_test.c [new file with mode: 0644]
test/rtr/pdu_handler_test.c [new file with mode: 0644]

index c7ce4cdfec0dde827929e1be36e8fe1f27774023..afa7c6ec109d6c40f38c55b10ae12454c491b8f1 100644 (file)
@@ -10,28 +10,33 @@ fort_SOURCES  = main.c
 fort_SOURCES += address.h address.c
 fort_SOURCES += algorithm.h algorithm.c
 fort_SOURCES += certificate_refs.h certificate_refs.c
+fort_SOURCES += clients.c clients.h
+fort_SOURCES += common.c common.h
 fort_SOURCES += console_handler.h console_handler.c
-fort_SOURCES += common.h
 fort_SOURCES += config.h config.c
 fort_SOURCES += debug.h debug.c
 fort_SOURCES += extension.h extension.c
 fort_SOURCES += file.h file.c
+fort_SOURCES += json_parser.c json_parser.h
 fort_SOURCES += line_file.h line_file.c
 fort_SOURCES += log.h log.c
 fort_SOURCES += nid.h nid.c
+fort_SOURCES += notify.c notify.h
 fort_SOURCES += random.h random.c
 fort_SOURCES += resource.h resource.c
 fort_SOURCES += rpp.h rpp.c
+fort_SOURCES += slurm_db.c slurm_db.h
+fort_SOURCES += slurm_loader.c slurm_loader.h
+fort_SOURCES += slurm_parser.c slurm_parser.h
 fort_SOURCES += sorted_array.h sorted_array.c
 fort_SOURCES += state.h state.c
 fort_SOURCES += str.h str.c
 fort_SOURCES += thread_var.h thread_var.c
+fort_SOURCES += updates_daemon.c updates_daemon.h
 fort_SOURCES += uri.h uri.c
 fort_SOURCES += json_handler.h json_handler.c
 fort_SOURCES += validation_handler.h validation_handler.c
 
-fort_SOURCES += rsync/rsync.h rsync/rsync.c
-
 fort_SOURCES += asn1/content_info.h asn1/content_info.c
 fort_SOURCES += asn1/decode.h asn1/decode.c
 fort_SOURCES += asn1/oid.h asn1/oid.c
@@ -46,8 +51,6 @@ fort_SOURCES += config/types.h
 fort_SOURCES += config/uint.c config/uint.h
 fort_SOURCES += config/uint32.c config/uint32.h
 
-fort_SOURCES += data_structure/array_list.h
-
 fort_SOURCES += crypto/base64.h crypto/base64.c
 fort_SOURCES += crypto/hash.h crypto/hash.c
 
@@ -69,15 +72,7 @@ fort_SOURCES += resource/ip4.h resource/ip4.c
 fort_SOURCES += resource/ip6.h resource/ip6.c
 fort_SOURCES += resource/asn.h resource/asn.c
 
-fort_SOURCES += array_list.h
-fort_SOURCES += clients.c clients.h
-fort_SOURCES += common.c common.h
-fort_SOURCES += json_parser.c json_parser.h
-fort_SOURCES += notify.c notify.h
-fort_SOURCES += slurm_db.c slurm_db.h
-fort_SOURCES += slurm_loader.c slurm_loader.h
-fort_SOURCES += slurm_parser.c slurm_parser.h
-fort_SOURCES += updates_daemon.c updates_daemon.h
+fort_SOURCES += rsync/rsync.h rsync/rsync.c
 
 fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h
 fort_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h
index 6766302136fbef159e7f691d5f0b4509990ddfb1..562026bf03fe9bf6f1af5a13a45dd9a1218cd2f4 100644 (file)
@@ -377,7 +377,7 @@ addr2str6(struct in6_addr *addr, char *buffer)
        return inet_ntop(AF_INET6, addr, buffer, INET6_ADDRSTRLEN);
 }
 
-static int const
+static int
 str2addr4(const char *addr, struct in_addr *dst)
 {
        if (!inet_pton(AF_INET, addr, dst))
@@ -385,7 +385,7 @@ str2addr4(const char *addr, struct in_addr *dst)
        return 0;
 }
 
-static int const
+static int
 str2addr6(const char *addr, struct in6_addr *dst)
 {
        if (!inet_pton(AF_INET6, addr, dst))
@@ -438,8 +438,8 @@ str_to_prefix_length(const char *text, uint8_t *dst, uint8_t max_value)
                return -EINVAL;
        }
        /* An underflow or overflow will be considered here */
-       if (len < 0 || max_value < len)
-               return pr_err("Prefix length (%ld) is out of range (0-%d).",
+       if (max_value < len)
+               return pr_err("Prefix length (%lu) is out of range (0-%u).",
                    len, max_value);
 
        *dst = (uint8_t) len;
index fd5e031ec99f74dbf1e401bdae1cf5e56cf21b25..c212d72692ba8853e4eac62d7e5be82ac4e45119 100644 (file)
@@ -23,7 +23,7 @@ static struct hashable_client *table;
 /** Read/write lock, which protects @table and its inhabitants. */
 static pthread_rwlock_t lock;
 /** Serial number from which deltas must be stored */
-static uint32_t min_serial;
+static serial_t min_serial;
 
 int
 clients_db_init(void)
@@ -111,7 +111,7 @@ clients_add(int fd, struct sockaddr_storage *addr, uint8_t rtr_version)
 }
 
 void
-clients_update_serial(int fd, uint32_t serial)
+clients_update_serial(int fd, serial_t serial)
 {
        struct hashable_client *cur_client;
 
@@ -126,11 +126,11 @@ unlock:
        rwlock_unlock(&lock);
 }
 
-uint32_t
+serial_t
 clients_get_min_serial(void)
 {
        struct hashable_client *current, *ptr;
-       uint32_t result;
+       serial_t result;
 
        rwlock_write_lock(&lock);
        if (HASH_COUNT(table) == 0)
index 7be04f82a9ab72e47a184b8df7f2e640b2b921bc..5055e1442d0d7f8cdd644c117a851645d365ea99 100644 (file)
@@ -2,6 +2,7 @@
 #define SRC_CLIENTS_H_
 
 #include <arpa/inet.h>
+#include "rtr/db/vrp.h"
 
 #define ERTR_VERSION_MISMATCH 8754983
 
@@ -14,17 +15,17 @@ struct client {
        };
        in_port_t sin_port;
        uint8_t rtr_version;
-       uint32_t serial_number;
+       serial_t serial_number;
 };
 
 int clients_db_init(void);
 
 int clients_add(int, struct sockaddr_storage *, uint8_t);
-void clients_update_serial(int, uint32_t);
+void clients_update_serial(int, serial_t);
 void clients_forget(int);
 typedef int (*clients_foreach_cb)(struct client const *, void *);
 int clients_foreach(clients_foreach_cb, void *);
-uint32_t clients_get_min_serial(void);
+serial_t clients_get_min_serial(void);
 
 void clients_db_destroy(void);
 
index a4f5e08997680321b7f17d775b06fefa8681f587..e87c6be54175cf3dcbe0cd6e8ff4f8ad1594a77e 100644 (file)
                size_t capacity;                                        \
        }
 
-#define DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type)                   \
-       static void                                                     \
+#define DECLARE_ARRAY_LIST_FUNCTIONS(name, elem_type)                  \
+       void name##_init(struct name *);                                \
+       void name##_cleanup(struct name *, void (*cb)(elem_type *));    \
+       int name##_add(struct name *list, elem_type *elem);
+
+#define DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type, modifiers)                \
+       modifiers void                                                  \
        name##_init(struct name *list)                                  \
        {                                                               \
                list->array = NULL;                                     \
@@ -25,7 +30,7 @@
                list->capacity = 0;                                     \
        }                                                               \
                                                                        \
-       static void                                                     \
+       modifiers void                                                  \
        name##_cleanup(struct name *list, void (*cb)(elem_type *))      \
        {                                                               \
                array_index i;                                          \
@@ -36,7 +41,7 @@
        }                                                               \
                                                                        \
        /* Will store a shallow copy, not @elem */                      \
-       static int                                                      \
+       modifiers int                                                   \
        name##_add(struct name *list, elem_type *elem)                  \
        {                                                               \
                elem_type *tmp;                                         \
@@ -66,7 +71,7 @@
 
 #define ARRAY_LIST(name, elem_type)                                    \
        DEFINE_ARRAY_LIST_STRUCT(name, elem_type);                      \
-       DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type)
+       DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type)
 
 /* c = cursor */
 #define ARRAYLIST_FOREACH(list, c) for (                               \
index 40e7485abd3fd30be1e86ccda3e2f5edeba2b430..949ade2f0215ee1bba4e24e660a6f4cd06e9ace9 100644 (file)
@@ -11,27 +11,24 @@ static int
 send_notify(struct client const *client, void *arg)
 {
        struct sender_common common;
-       uint32_t *serial = arg;
-       uint16_t session_id;
+       serial_t *serial = arg;
        int error;
 
        /* Send Serial Notify PDU */
-       session_id = get_current_session_id(client->rtr_version);
-       init_sender_common(&common, client->fd, client->rtr_version,
-           &session_id, serial, NULL);
-       error = send_serial_notify_pdu(&common);
+       init_sender_common(&common, client->fd, client->rtr_version);
+       error = send_serial_notify_pdu(&common, *serial);
 
-       /* Error? Log it */
+       /* Error? Log it... */
        if (error)
-               pr_warn("Error sending notify PDU to client");
+               pr_warn("Error code %d sending notify PDU to client.", error);
 
-       return 0; /* Do not interrupt notify to other clients */
+       return 0; /* ...but do not interrupt notify to other clients */
 }
 
 int
 notify_clients(void)
 {
-       uint32_t serial;
+       serial_t serial;
        int error;
 
        error = get_last_serial_number(&serial);
index d53085f0f413acee02e0eccbd9988ce74247d9e0..b8ffc9984e3638b1e75526b3993d698ae28304df 100644 (file)
@@ -26,6 +26,9 @@ struct deltas {
                struct deltas_v6 adds;
                struct deltas_v6 removes;
        } v6;
+
+       /* TODO (now) atomic */
+       unsigned int references;
 };
 
 int
@@ -41,24 +44,34 @@ deltas_create(struct deltas **_result)
        deltas_v4_init(&result->v4.removes);
        deltas_v6_init(&result->v6.adds);
        deltas_v6_init(&result->v6.removes);
+       result->references = 1;
 
        *_result = result;
        return 0;
 }
 
 void
-deltas_destroy(struct deltas *deltas)
+deltas_get(struct deltas *deltas)
+{
+       deltas->references++;
+}
+
+void
+deltas_put(struct deltas *deltas)
 {
-       deltas_v4_cleanup(&deltas->v4.adds, NULL);
-       deltas_v4_cleanup(&deltas->v4.removes, NULL);
-       deltas_v6_cleanup(&deltas->v6.adds, NULL);
-       deltas_v6_cleanup(&deltas->v6.removes, NULL);
-       free(deltas);
+       deltas->references--;
+       if (deltas->references == 0) {
+               deltas_v4_cleanup(&deltas->v4.adds, NULL);
+               deltas_v4_cleanup(&deltas->v4.removes, NULL);
+               deltas_v6_cleanup(&deltas->v6.adds, NULL);
+               deltas_v6_cleanup(&deltas->v6.removes, NULL);
+               free(deltas);
+       }
 }
 
 int
 deltas_add_roa_v4(struct deltas *deltas, uint32_t as, struct v4_address *addr,
-    enum delta_op op)
+    int op)
 {
        struct delta_v4 delta = {
                .as = as,
@@ -67,18 +80,18 @@ deltas_add_roa_v4(struct deltas *deltas, uint32_t as, struct v4_address *addr,
        };
 
        switch (op) {
-       case DELTA_ADD:
+       case FLAG_ANNOUNCEMENT:
                return deltas_v4_add(&deltas->v4.adds, &delta);
-       case DELTA_RM:
+       case FLAG_WITHDRAWAL:
                return deltas_v4_add(&deltas->v4.removes, &delta);
        }
 
-       return pr_crit("Unknown delta operation: %u", op);
+       return pr_crit("Unknown delta operation: %d", op);
 }
 
 int
 deltas_add_roa_v6(struct deltas *deltas, uint32_t as, struct v6_address *addr,
-    enum delta_op op)
+    int op)
 {
        struct delta_v6 delta = {
                .as = as,
@@ -87,13 +100,13 @@ deltas_add_roa_v6(struct deltas *deltas, uint32_t as, struct v6_address *addr,
        };
 
        switch (op) {
-       case DELTA_ADD:
+       case FLAG_ANNOUNCEMENT:
                return deltas_v6_add(&deltas->v6.adds, &delta);
-       case DELTA_RM:
+       case FLAG_WITHDRAWAL:
                return deltas_v6_add(&deltas->v6.removes, &delta);
        }
 
-       return pr_crit("Unknown delta operation: %u", op);
+       return pr_crit("Unknown delta operation: %d", op);
 }
 
 bool
@@ -106,22 +119,23 @@ deltas_is_empty(struct deltas *deltas)
 }
 
 static int
-__foreach_v4(struct deltas_v4 *array, vrp_foreach_cb cb, void *arg,
-    uint8_t flags)
+__foreach_v4(struct deltas_v4 *array, delta_foreach_cb cb, void *arg,
+    serial_t serial, uint8_t flags)
 {
+       struct delta delta;
        struct delta_v4 *d;
-       struct vrp vrp;
        int error;
 
-       vrp.addr_fam = AF_INET;
-       vrp.flags = flags;
+       delta.serial = serial;
+       delta.vrp.addr_fam = AF_INET;
+       delta.flags = flags;
 
        ARRAYLIST_FOREACH(array, d) {
-               vrp.asn = d->as;
-               vrp.prefix.v4 = d->prefix.addr;
-               vrp.prefix_length = d->prefix.len;
-               vrp.max_prefix_length = d->max_length;
-               error = cb(&vrp, arg);
+               delta.vrp.asn = d->as;
+               delta.vrp.prefix.v4 = d->prefix.addr;
+               delta.vrp.prefix_length = d->prefix.len;
+               delta.vrp.max_prefix_length = d->max_length;
+               error = cb(&delta, arg);
                if (error)
                        return error;
        }
@@ -130,22 +144,23 @@ __foreach_v4(struct deltas_v4 *array, vrp_foreach_cb cb, void *arg,
 }
 
 static int
-__foreach_v6(struct deltas_v6 *array, vrp_foreach_cb cb, void *arg,
-    uint8_t flags)
+__foreach_v6(struct deltas_v6 *array, delta_foreach_cb cb, void *arg,
+    serial_t serial, uint8_t flags)
 {
+       struct delta delta;
        struct delta_v6 *d;
-       struct vrp vrp;
        int error;
 
-       vrp.addr_fam = AF_INET6;
-       vrp.flags = flags;
+       delta.serial = serial;
+       delta.vrp.addr_fam = AF_INET6;
+       delta.flags = flags;
 
        ARRAYLIST_FOREACH(array, d) {
-               vrp.asn = d->as;
-               vrp.prefix.v6 = d->prefix.addr;
-               vrp.prefix_length = d->prefix.len;
-               vrp.max_prefix_length = d->max_length;
-               error = cb(&vrp, arg);
+               delta.vrp.asn = d->as;
+               delta.vrp.prefix.v6 = d->prefix.addr;
+               delta.vrp.prefix_length = d->prefix.len;
+               delta.vrp.max_prefix_length = d->max_length;
+               error = cb(&delta, arg);
                if (error)
                        return error;
        }
@@ -154,19 +169,26 @@ __foreach_v6(struct deltas_v6 *array, vrp_foreach_cb cb, void *arg,
 }
 
 int
-deltas_foreach(struct deltas *deltas, vrp_foreach_cb cb, void *arg)
+deltas_foreach(serial_t serial, struct deltas *deltas, delta_foreach_cb cb,
+    void *arg)
 {
        int error;
 
-       error = __foreach_v4(&deltas->v4.adds, cb, arg, FLAG_ANNOUNCEMENT);
+       error = __foreach_v4(&deltas->v4.adds, cb, arg, serial,
+           FLAG_ANNOUNCEMENT);
        if (error)
                return error;
-       error = __foreach_v4(&deltas->v4.removes, cb, arg, FLAG_WITHDRAWAL);
+
+       error = __foreach_v4(&deltas->v4.removes, cb, arg, serial,
+           FLAG_WITHDRAWAL);
        if (error)
                return error;
 
-       error = __foreach_v6(&deltas->v6.adds, cb, arg, FLAG_ANNOUNCEMENT);
+       error = __foreach_v6(&deltas->v6.adds, cb, arg, serial,
+           FLAG_ANNOUNCEMENT);
        if (error)
                return error;
-       return __foreach_v6(&deltas->v6.removes, cb, arg, FLAG_WITHDRAWAL);
+
+       return __foreach_v6(&deltas->v6.removes, cb, arg, serial,
+           FLAG_WITHDRAWAL);
 }
index 076b0dba33b69d2c8e750edef96cc53b611cf616..1d2a0701791f12a6a3468547f69927d41b865c7e 100644 (file)
@@ -4,22 +4,16 @@
 #include "rtr/db/roa.h"
 #include "rtr/db/vrp.h"
 
-enum delta_op {
-       DELTA_ADD,
-       DELTA_RM,
-};
-
 struct deltas;
 
 int deltas_create(struct deltas **);
-void deltas_destroy(struct deltas *);
+void deltas_get(struct deltas *);
+void deltas_put(struct deltas *);
 
-int deltas_add_roa_v4(struct deltas *, uint32_t, struct v4_address *,
-    enum delta_op);
-int deltas_add_roa_v6(struct deltas *, uint32_t, struct v6_address *,
-    enum delta_op);
+int deltas_add_roa_v4(struct deltas *, uint32_t, struct v4_address *, int);
+int deltas_add_roa_v6(struct deltas *, uint32_t, struct v6_address *, int);
 
 bool deltas_is_empty(struct deltas *);
-int deltas_foreach(struct deltas *, vrp_foreach_cb , void *);
+int deltas_foreach(serial_t, struct deltas *, delta_foreach_cb , void *);
 
 #endif /* SRC_DELTA_H_ */
index 15aefcc0a8a149022384a23a321c2d5da702ec1d..1ea7d7573ffa3ca03f4e8c63df73afe769406ac7 100644 (file)
@@ -1,7 +1,7 @@
 #include "rtr/db/roa.h"
 
-DEFINE_ARRAY_LIST_FUNCTIONS(v4_addresses, struct v4_address)
-DEFINE_ARRAY_LIST_FUNCTIONS(v6_addresses, struct v6_address)
+DEFINE_ARRAY_LIST_FUNCTIONS(v4_addresses, struct v4_address, static)
+DEFINE_ARRAY_LIST_FUNCTIONS(v6_addresses, struct v6_address, static)
 
 int
 roa_create(uint32_t as, struct roa **_result)
index 52a0b42d179a451d439b523bef654ba31af794e6..ab528e1173968c4c79c309be475e47abb0079955 100644 (file)
@@ -83,7 +83,6 @@ create_roa(uint32_t asn, uint8_t max_length)
 
        roa->data.asn = asn;
        roa->data.max_prefix_length = max_length;
-       roa->data.flags = FLAG_ANNOUNCEMENT;
 
        return roa;
 }
@@ -133,7 +132,7 @@ rtrhandler_handle_roa_v6(struct roa_table *table, uint32_t asn,
 }
 
 static int
-add_delta(struct deltas *deltas, struct hashable_roa *roa, enum delta_op op)
+add_delta(struct deltas *deltas, struct hashable_roa *roa, int op)
 {
        union {
                struct v4_address v4;
@@ -163,7 +162,7 @@ add_delta(struct deltas *deltas, struct hashable_roa *roa, enum delta_op op)
  */
 static int
 add_deltas(struct hashable_roa *roas1, struct hashable_roa *roas2,
-    struct deltas *deltas, enum delta_op op)
+    struct deltas *deltas, int op)
 {
        struct hashable_roa *n1; /* A node from @roas1 */
        struct hashable_roa *n2; /* A node from @roas2 */
@@ -192,10 +191,10 @@ compute_deltas(struct roa_table *old, struct roa_table *new,
        if (error)
                return error;
 
-       error = add_deltas(new->roas, old->roas, deltas, DELTA_ADD);
+       error = add_deltas(new->roas, old->roas, deltas, FLAG_ANNOUNCEMENT);
        if (error)
                goto fail;
-       error = add_deltas(old->roas, new->roas, deltas, DELTA_RM);
+       error = add_deltas(old->roas, new->roas, deltas, FLAG_WITHDRAWAL);
        if (error)
                goto fail;
 
@@ -203,6 +202,6 @@ compute_deltas(struct roa_table *old, struct roa_table *new,
        return 0;
 
 fail:
-       deltas_destroy(deltas);
+       deltas_put(deltas);
        return error;
 }
index 3ffc040335c9d97216067e796a2b0d9829455af4..c002d928f5eb0f10bbb179f9f339c49721cd23ae 100644 (file)
@@ -7,6 +7,8 @@
 #define FLAG_WITHDRAWAL                0
 #define FLAG_ANNOUNCEMENT      1
 
+typedef uint32_t serial_t;
+
 struct vrp {
        uint32_t        asn;
        /*
@@ -21,9 +23,16 @@ struct vrp {
        uint8_t prefix_length;
        uint8_t max_prefix_length;
        uint8_t addr_fam;
-       uint8_t flags;
+       /* uint8_t      flags; */ /* TODO (now) commit remove */
+};
+
+struct delta {
+       serial_t serial;
+       struct vrp vrp;
+       uint8_t flags;
 };
 
-typedef int (*vrp_foreach_cb)(struct vrp *, void *);
+typedef int (*vrp_foreach_cb)(struct vrp const *, void *);
+typedef int (*delta_foreach_cb)(struct delta const *, void *);
 
 #endif /* SRC_RTR_DB_VRP_H_ */
index 9e3e0882736a5ab7eb179bb5d8ec521e1a1ebd2c..c9a8aef4e1da8e3037995aba3513b716cbb07382 100644 (file)
@@ -1,12 +1,9 @@
 #include "vrps.h"
 
 #include <pthread.h>
-#include <stdbool.h>
-#include <string.h>
+#include <time.h>
 #include "clients.h"
 #include "common.h"
-#include "validation_handler.h"
-#include "data_structure/array_list.h"
 #include "object/tal.h"
 #include "rtr/db/roa_table.h"
 
 
 #define START_SERIAL           0
 
-struct delta {
-       uint32_t serial;
-       struct deltas *deltas;
-};
-
-ARRAY_LIST(deltas_db, struct delta)
+DEFINE_ARRAY_LIST_FUNCTIONS(deltas_db, struct delta_group, )
 
 struct state {
        /**
@@ -36,7 +28,11 @@ struct state {
        /** ROA changes to @base over time. */
        struct deltas_db deltas;
 
-       uint32_t current_serial;
+       /*
+        * TODO (whatever) Maybe rename this. It's not really the current
+        * serial; it's the next one.
+        */
+       serial_t current_serial;
        uint16_t v0_session_id;
        uint16_t v1_session_id;
        time_t last_modified_date;
@@ -45,10 +41,10 @@ struct state {
 /** Read/write lock, which protects @state and its inhabitants. */
 static pthread_rwlock_t lock;
 
-static void
-delta_destroy(struct delta *delta)
+void
+deltagroup_cleanup(struct delta_group *group)
 {
-       deltas_destroy(delta->deltas);
+       deltas_put(group->deltas);
 }
 
 int
@@ -57,7 +53,6 @@ vrps_init(void)
        int error;
 
        state.base = NULL;
-
        deltas_db_init(&state.deltas);
 
        /*
@@ -66,7 +61,6 @@ vrps_init(void)
         * 'Fields of a PDU')
         */
        state.current_serial = START_SERIAL;
-
        /* Get the bits that'll fit in session_id */
        state.v0_session_id = time(NULL) & 0xFFFF;
        /* Minus 1 to prevent same ID */
@@ -76,7 +70,7 @@ vrps_init(void)
 
        error = pthread_rwlock_init(&lock, NULL);
        if (error) {
-               deltas_db_cleanup(&state.deltas, delta_destroy);
+               deltas_db_cleanup(&state.deltas, deltagroup_cleanup);
                return pr_errno(error, "pthread_rwlock_init() errored");
        }
 
@@ -88,7 +82,7 @@ vrps_destroy(void)
 {
        if (state.base != NULL)
                roa_table_destroy(state.base);
-       deltas_db_cleanup(&state.deltas, delta_destroy);
+       deltas_db_cleanup(&state.deltas, deltagroup_cleanup);
        pthread_rwlock_destroy(&lock); /* Nothing to do with error code */
 }
 
@@ -140,26 +134,24 @@ __perform_standalone_validation(struct roa_table **result)
        return 0;
 }
 
-
-
 /**
  * Reallocate the array of @db starting at @start, the length and capacity are
  * calculated according to the new start.
  */
 static void
-resize_deltas_db(struct deltas_db *db, struct delta *start)
+resize_deltas_db(struct deltas_db *db, struct delta_group *start)
 {
-       struct delta *tmp;
+       struct delta_group *tmp;
 
        db->len -= (start - db->array);
        while (db->len < db->capacity / 2)
                db->capacity /= 2;
-       tmp = malloc(sizeof(struct delta) * db->capacity);
+       tmp = malloc(sizeof(struct delta_group) * db->capacity);
        if (tmp == NULL) {
                pr_enomem();
                return;
        }
-       memcpy(tmp, start, db->len * sizeof(struct delta));
+       memcpy(tmp, start, db->len * sizeof(struct delta_group));
        free(db->array);
        db->array = tmp;
 }
@@ -167,22 +159,22 @@ resize_deltas_db(struct deltas_db *db, struct delta *start)
 static void
 vrps_purge(void)
 {
-       struct delta *d;
+       struct delta_group *group;
        uint32_t min_serial;
 
        min_serial = clients_get_min_serial();
 
        /** Assume is ordered by serial, so get the new initial pointer */
-       ARRAYLIST_FOREACH(&state.deltas, d)
-               if (d->serial >= min_serial)
+       ARRAYLIST_FOREACH(&state.deltas, group)
+               if (group->serial >= min_serial)
                        break;
 
        /** Is the first element or reached end, nothing to purge */
-       if (d == state.deltas.array ||
-           (d - state.deltas.array) == state.deltas.len)
+       if (group == state.deltas.array ||
+           (group - state.deltas.array) == state.deltas.len)
                return;
 
-       resize_deltas_db(&state.deltas, d);
+       resize_deltas_db(&state.deltas, group);
 }
 
 int
@@ -191,7 +183,7 @@ vrps_update(bool *changed)
        struct roa_table *old_base;
        struct roa_table *new_base;
        struct deltas *deltas; /* Deltas in raw form */
-       struct delta deltas_node; /* Deltas in database node form */
+       struct delta_group deltas_node; /* Deltas in database node form */
        int error;
 
        *changed = false;
@@ -232,6 +224,27 @@ vrps_update(bool *changed)
 
                /** Remove unnecessary deltas */
                vrps_purge();
+
+       } else {
+               /*
+                * 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.
+                */
+               error = deltas_create(&deltas);
+               if (error) {
+                       rwlock_unlock(&lock);
+                       goto revert_base;
+               }
+
+               deltas_node.serial = state.current_serial;
+               deltas_node.deltas = deltas;
+               error = deltas_db_add(&state.deltas, &deltas_node);
+               if (error) {
+                       rwlock_unlock(&lock);
+                       goto revert_deltas;
+               }
        }
 
        *changed = true;
@@ -245,75 +258,15 @@ vrps_update(bool *changed)
        return 0;
 
 revert_deltas:
-       deltas_destroy(deltas);
+       deltas_put(deltas);
 revert_base:
        roa_table_destroy(new_base);
        return error;
 }
 
-/*
- * Get a status to know the difference between the delta with serial SERIAL and
- * the last delta at DB.
- *
- * If SERIAL is received as NULL, and there's data at DB then the status will
- * be DIFF_AVAILABLE.
- *
- * This function can only fail due to critical r/w lock bugs.
- */
-int
-deltas_db_status(uint32_t *serial, enum delta_status *result)
-{
-       struct delta *delta;
-       int error;
-
-       error = rwlock_read_lock(&lock);
-       if (error)
-               return error;
-
-       if (state.base == NULL) {
-               *result = DS_NO_DATA_AVAILABLE;
-               goto rlock_succeed;
-       }
-
-       /* No serial to match, and there's data at DB */
-       if (serial == NULL) {
-               *result = DS_DIFF_AVAILABLE;
-               goto rlock_succeed;
-       }
-
-       /* Is the last version? */
-       if (*serial == (state.current_serial - 1)) {
-               *result = DS_NO_DIFF;
-               goto rlock_succeed;
-       }
-
-       /* The first serial isn't at deltas */
-       if (*serial == START_SERIAL) {
-               *result = DS_DIFF_AVAILABLE;
-               goto rlock_succeed;
-       }
-
-       /* Get the delta corresponding to the serial */
-       ARRAYLIST_FOREACH(&state.deltas, delta)
-               if (delta->serial == *serial) {
-                       *result = DS_DIFF_AVAILABLE;
-                       goto rlock_succeed;
-               }
-
-       /* No match yet, release lock */
-       rwlock_unlock(&lock);
-
-       /* Reached end, diff can't be determined */
-       *result = DS_DIFF_UNDETERMINED;
-       return 0;
-
-rlock_succeed:
-       rwlock_unlock(&lock);
-       return 0;
-}
-
+/* TODO (whatever) @serial is a dumb hack. */
 int
-vrps_foreach_base_roa(vrp_foreach_cb cb, void *arg)
+vrps_foreach_base_roa(vrp_foreach_cb cb, void *arg, serial_t *serial)
 {
        int error;
 
@@ -321,18 +274,36 @@ vrps_foreach_base_roa(vrp_foreach_cb cb, void *arg)
        if (error)
                return error;
 
-       if (state.base != NULL)
+       if (state.base != NULL) {
                error = roa_table_foreach_roa(state.base, cb, arg);
+               *serial = state.current_serial - 1;
+       } else {
+               error = -EAGAIN;
+       }
 
        rwlock_unlock(&lock);
 
        return error;
 }
 
+/**
+ * Adds to @result the deltas whose serial > @from.
+ *
+ * The result code is one of the following:
+ *
+ * 0: No errors.
+ * -EAGAIN: No data available; database still under construction.
+ * -ESRCH: @from was not found.
+ * Anything else: Generic fail.
+ *
+ * As usual, only 0 guarantees valid out parameters. (@to and @result.)
+ * (But note that @result is supposed to be already initialized, so caller will
+ * have to clean it up regardless of error.)
+ */
 int
-vrps_foreach_delta_roa(uint32_t from, uint32_t to, vrp_foreach_cb cb, void *arg)
+vrps_get_deltas_from(serial_t from, serial_t *to, struct deltas_db *result)
 {
-       struct delta *d;
+       struct delta_group *group;
        bool from_found;
        int error;
 
@@ -342,27 +313,36 @@ vrps_foreach_delta_roa(uint32_t from, uint32_t to, vrp_foreach_cb cb, void *arg)
        if (error)
                return error;
 
-       ARRAYLIST_FOREACH(&state.deltas, d) {
+       if (state.base == NULL) {
+               rwlock_unlock(&lock);
+               return -EAGAIN;
+       }
+
+       ARRAYLIST_FOREACH(&state.deltas, group) {
                if (!from_found) {
-                       if (d->serial > from)
+                       if (group->serial == from) {
                                from_found = true;
-                       else
-                               continue;
+                               *to = group->serial;
+                       }
+                       continue;
                }
-               if (d->serial > to)
-                       break;
-               error = deltas_foreach(d->deltas, cb, arg);
-               if (error)
-                       break;
+
+               error = deltas_db_add(result, group);
+               if (error) {
+                       rwlock_unlock(&lock);
+                       return error;
+               }
+
+               deltas_get(group->deltas);
+               *to = group->serial;
        }
 
        rwlock_unlock(&lock);
-
-       return error;
+       return from_found ? 0 : -ESRCH;
 }
 
 int
-get_last_serial_number(uint32_t *result)
+get_last_serial_number(serial_t *result)
 {
        int error;
 
@@ -370,11 +350,14 @@ get_last_serial_number(uint32_t *result)
        if (error)
                return error;
 
-       *result = state.current_serial - 1;
+       if (state.base != NULL)
+               *result = state.current_serial - 1;
+       else
+               error = -EAGAIN;
 
        rwlock_unlock(&lock);
 
-       return 0;
+       return error;
 }
 
 uint16_t
index 173cd37f0246217e43a9bcb16a52489181373242..631f0c52157ad6121ada9ffa139fbc8141a590f1 100644 (file)
@@ -2,29 +2,36 @@
 #define SRC_VRPS_H_
 
 #include <stdbool.h>
-#include "rtr/db/vrp.h"
-
-enum delta_status {
-       /** There's no data at the DB */
-       DS_NO_DATA_AVAILABLE,
-       /** The diff can't be determined */
-       DS_DIFF_UNDETERMINED,
-       /** There's no difference */
-       DS_NO_DIFF,
-       /** There are diffs between SERIAL and the last DB serial */
-       DS_DIFF_AVAILABLE,
+#include "data_structure/array_list.h"
+#include "rtr/db/delta.h"
+
+/*
+ * Deltas that share a serial.
+ */
+struct delta_group {
+       serial_t serial;
+       struct deltas *deltas;
 };
 
+void deltagroup_cleanup(struct delta_group *);
+
+DEFINE_ARRAY_LIST_STRUCT(deltas_db, struct delta_group);
+DECLARE_ARRAY_LIST_FUNCTIONS(deltas_db, struct delta_group)
+
 int vrps_init(void);
 void vrps_destroy(void);
 
 int vrps_update(bool *);
-int deltas_db_status(uint32_t *, enum delta_status *);
 
-int vrps_foreach_base_roa(vrp_foreach_cb, void *);
-int vrps_foreach_delta_roa(uint32_t, uint32_t, vrp_foreach_cb, void *);
+/*
+ * The following three functions return -EAGAIN when vrps_update() has never
+ * been called, or while it's still building the database.
+ * Handle gracefully.
+ */
+int vrps_foreach_base_roa(vrp_foreach_cb, void *, serial_t *);
+int vrps_get_deltas_from(serial_t, serial_t *, struct deltas_db *);
+int get_last_serial_number(serial_t *);
 
-int get_last_serial_number(uint32_t *);
 uint16_t get_current_session_id(uint8_t);
 
 #endif /* SRC_VRPS_H_ */
index c18b23028744a9c765cd7d498c1a0099dbbe9ba5..06db7b2de0715bdbc1289fbc95631cdadcdf2c97 100644 (file)
@@ -4,6 +4,11 @@
 #include "pdu_sender.h"
 #include "log.h"
 
+/*
+ * TODO (urgent) According to the function below, NO_DATA_AVAILABLE is not
+ * fatal. However, some callers of this function are terminating the connection
+ * regardless of that.
+ */
 int
 err_pdu_send(int fd, uint8_t version, uint16_t code, void *err_pdu_header,
     char const *message)
index 54424bede4fdbbe728f7f60cb20b0415f6d1fd5f..103a21f0acccd7d0ce03eef66e10fd11237b6d12 100644 (file)
@@ -9,14 +9,18 @@
 #define RTR_V0 0
 #define RTR_V1 1
 
-#define PDU_TYPE_SERIAL_NOTIFY         0
-#define PDU_TYPE_CACHE_RESPONSE                3
-#define PDU_TYPE_IPV4_PREFIX           4
-#define PDU_TYPE_IPV6_PREFIX           6
-#define PDU_TYPE_END_OF_DATA           7
-#define PDU_TYPE_CACHE_RESET           8
-#define PDU_TYPE_ROUTER_KEY            9
-#define PDU_TYPE_ERROR_REPORT          10
+enum pdu_type {
+       PDU_TYPE_SERIAL_NOTIFY =        0,
+       PDU_TYPE_SERIAL_QUERY =         1,
+       PDU_TYPE_RESET_QUERY =          2,
+       PDU_TYPE_CACHE_RESPONSE =       3,
+       PDU_TYPE_IPV4_PREFIX =          4,
+       PDU_TYPE_IPV6_PREFIX =          6,
+       PDU_TYPE_END_OF_DATA =          7,
+       PDU_TYPE_CACHE_RESET =          8,
+       PDU_TYPE_ROUTER_KEY =           9,
+       PDU_TYPE_ERROR_REPORT =         10,
+};
 
 struct pdu_header {
        uint8_t protocol_version;
index c7fd219ec8725772aee803fc09069b63dd96b44e..462c6c815bffdeac349fd7bb061335dfa921ef6b 100644 (file)
@@ -16,7 +16,7 @@ warn_unexpected_pdu(int fd, void *pdu, char const *pdu_name)
        struct pdu_header *pdu_header = pdu;
        pr_warn("Unexpected %s PDU received", pdu_name);
        err_pdu_send(fd, pdu_header->protocol_version, ERR_PDU_UNSUP_PDU_TYPE,
-           pdu_header, "Unexpected PDU received");
+           pdu_header, "PDU is unexpected or out of order.");
        return -EINVAL;
 }
 
@@ -26,136 +26,124 @@ handle_serial_notify_pdu(int fd, void *pdu)
        return warn_unexpected_pdu(fd, pdu, "Serial Notify");
 }
 
-static int
-send_commmon_exchange(struct sender_common *common,
-    int (*pdu_sender)(struct sender_common *))
+int
+handle_serial_query_pdu(int fd, void *pdu)
 {
+       struct serial_query_pdu *received = pdu;
+       struct sender_common common;
+       struct deltas_db deltas;
+       serial_t final_serial;
        int error;
 
+       init_sender_common(&common, fd, received->header.protocol_version);
+       /*
+        * RFC 6810 and 8210:
+        * "If [...] either the router or the cache finds that the value of the
+        * Session ID is not the same as the other's, the party which detects
+        * the mismatch MUST immediately terminate the session with an Error
+        * Report PDU with code 0 ("Corrupt Data")"
+        */
+       if (received->header.m.session_id != common.session_id)
+               return err_pdu_send(fd, common.version, ERR_PDU_CORRUPT_DATA,
+                   &received->header, "Session ID doesn't match.");
+
        /*
-        * TODO (urgent) On certain errors, shouldn't we send error PDUs or
+        * TODO (now) On certain errors, shouldn't we send error PDUs or
         * something?
         */
 
-       /* Send Cache response PDU */
-       error = send_cache_response_pdu(common);
+       /*
+        * For the record, there are two reasons why we want to work on a
+        * (shallow) copy of the deltas (as opposed to eg. a foreach):
+        * 1. We need to remove deltas that cancel each other.
+        *    (Which can't be done directly on the DB.)
+        * 2. It's probably best not to hold the VRPS read lock while writing
+        *    PDUs, to minimize writer stagnation.
+        */
+
+       deltas_db_init(&deltas);
+       error = vrps_get_deltas_from(received->serial_number, &final_serial,
+           &deltas);
+       if (error == -EAGAIN) {
+               error = err_pdu_send(fd, common.version,
+                   ERR_PDU_NO_DATA_AVAILABLE, NULL, NULL);
+               goto end;
+       }
+       if (error == -ESRCH) {
+               /* https://tools.ietf.org/html/rfc6810#section-6.3 */
+               error = send_cache_reset_pdu(&common);
+               goto end;
+       }
        if (error)
-               return error;
+               goto end;
 
-       /* Send Payload PDUs */
-       error = pdu_sender(common);
+       /*
+        * https://tools.ietf.org/html/rfc6810#section-6.2
+        * (Except the end of data PDU.)
+        */
+
+       error = send_cache_response_pdu(&common);
        if (error)
-               return error;
+               goto end;
+       error = send_pdus_delta(&deltas, &common);
+       if (error)
+               goto end; /* TODO (now) maybe send something? */
+       error = send_end_of_data_pdu(&common, final_serial);
 
-       /* Send End of data PDU */
-       return send_end_of_data_pdu(common);
+end:
+       deltas_db_cleanup(&deltas, deltagroup_cleanup);
+       return error;
 }
 
-/*
- * TODO (urgent) The semaphoring is bonkers. The code keeps locking, storing a
- * value, unlocking, locking again, and using the old value.
- * It doesn't look like it's a problem for now, but eventually will be, when old
- * delta forgetting is implemented.
- * I'm going to defer this because it shouldn't be done during the merge.
- */
-int
-handle_serial_query_pdu(int fd, void *pdu)
-{
-       struct serial_query_pdu *received = pdu;
+struct base_roa_args {
+       bool started;
        struct sender_common common;
+       serial_t last_serial;
+};
+
+static int
+send_base_roa(struct vrp const *vrp, void *arg)
+{
+       struct base_roa_args *args = arg;
        int error;
-       enum delta_status updates;
-       uint32_t current_serial;
-       uint16_t session_id;
-       uint8_t version;
 
-       /*
-        * RFC 6810 and 8210:
-        * "If [...] either the router or the cache finds that the value of the
-        * Session ID is not the same as the other's, the party which detects
-        * the mismatch MUST immediately terminate the session with an Error
-        * Report PDU with code 0 ("Corrupt Data")"
-        */
-       version = received->header.protocol_version;
-       session_id = get_current_session_id(version);
-       if (received->header.m.session_id != session_id)
-               return err_pdu_send(fd, version, ERR_PDU_CORRUPT_DATA,
-                   &received->header, NULL);
-       if (get_last_serial_number(&current_serial) != 0)
-               goto critical;
-
-       init_sender_common(&common, fd, version, &session_id,
-           &received->serial_number, &current_serial);
-
-       if (deltas_db_status(common.start_serial, &updates) != 0)
-               goto critical;
-
-       switch (updates) {
-       case DS_NO_DATA_AVAILABLE:
-               /* https://tools.ietf.org/html/rfc8210#section-8.4 */
-               return err_pdu_send(fd, version, ERR_PDU_NO_DATA_AVAILABLE,
-                   NULL, NULL);
-       case DS_DIFF_UNDETERMINED:
-               /* https://tools.ietf.org/html/rfc8210#section-8.3 */
-               return send_cache_reset_pdu(&common);
-       case DS_DIFF_AVAILABLE:
-               /* https://tools.ietf.org/html/rfc8210#section-8.2 */
-               return send_commmon_exchange(&common, send_pdus_delta);
-       case DS_NO_DIFF:
-               /* Typical exchange with no Payloads */
-               error = send_cache_response_pdu(&common);
+       if (!args->started) {
+               error = send_cache_response_pdu(&args->common);
                if (error)
                        return error;
-               return send_end_of_data_pdu(&common);
+               args->started = true;
        }
 
-       pr_warn("Reached 'unreachable' code");
-       return -EINVAL;
-
-critical:
-       return err_pdu_send(fd, version, ERR_PDU_INTERNAL_ERROR,
-           &received->header, NULL);
+       /* TODO (now) maybe send something on error? */
+       return send_prefix_pdu(&args->common, vrp, FLAG_ANNOUNCEMENT);
 }
 
 int
 handle_reset_query_pdu(int fd, void *pdu)
 {
        struct reset_query_pdu *received = pdu;
-       struct sender_common common;
-       uint32_t current_serial;
-       uint16_t session_id;
-       uint8_t version;
-       enum delta_status updates;
-
-       version = received->header.protocol_version;
-       session_id = get_current_session_id(version);
-       if (get_last_serial_number(&current_serial) != 0)
-               goto critical;
-
-       init_sender_common(&common, fd, version, &session_id, NULL,
-           &current_serial);
-
-       if (deltas_db_status(NULL, &updates) != 0)
-               goto critical;
-       switch (updates) {
-       case DS_NO_DATA_AVAILABLE:
-               /* https://tools.ietf.org/html/rfc8210#section-8.4 */
-               return err_pdu_send(fd, version, ERR_PDU_NO_DATA_AVAILABLE,
-                   NULL, NULL);
-       case DS_DIFF_AVAILABLE:
-               /* https://tools.ietf.org/html/rfc8210#section-8.1 */
-               return send_commmon_exchange(&common, send_pdus_base);
-       case DS_DIFF_UNDETERMINED:
-       case DS_NO_DIFF:
-               break;
-       }
+       struct base_roa_args args;
+       serial_t current_serial;
+       int error;
 
-       pr_warn("Reached 'unreachable' code");
-       return -EINVAL;
+       args.started = false;
+       init_sender_common(&args.common, fd, received->header.protocol_version);
+
+       /*
+        * It's probably best not to work on a copy, because the tree is large.
+        * Unfortunately, this means we'll have to encourage writer stagnation,
+        * but most clients are supposed to request far more serial queries than
+        * reset queries.
+        */
+
+       error = vrps_foreach_base_roa(send_base_roa, &args, &current_serial);
+       if (error == -EAGAIN)
+               return err_pdu_send(fd, args.common.version,
+                   ERR_PDU_NO_DATA_AVAILABLE, NULL, NULL);
+       if (error)
+               return error;
 
-critical:
-       return err_pdu_send(fd, version, ERR_PDU_INTERNAL_ERROR,
-           &received->header, NULL);
+       return send_end_of_data_pdu(&args.common, current_serial);
 }
 
 int
index 3dd12cbf65729124b8feed1c43cd46504a7bc942..827a7bc035b674852b53a75817bedddddd74ec07 100644 (file)
@@ -21,7 +21,7 @@
 
 
 struct vrp_node {
-       struct vrp vrp;
+       struct delta delta;
        SLIST_ENTRY(vrp_node) next;
 };
 
@@ -29,14 +29,11 @@ struct vrp_node {
 SLIST_HEAD(vrp_slist, vrp_node);
 
 void
-init_sender_common(struct sender_common *common, int fd, uint8_t version,
-    uint16_t *session_id, uint32_t *start_serial, uint32_t *end_serial)
+init_sender_common(struct sender_common *common, int fd, uint8_t version)
 {
        common->fd = fd;
        common->version = version;
-       common->session_id = session_id == NULL ? 0 : session_id;
-       common->start_serial = start_serial;
-       common->end_serial = end_serial;
+       common->session_id = get_current_session_id(version);
 }
 /*
  * Set all the header values, EXCEPT length field.
@@ -160,16 +157,16 @@ send_response(int fd, unsigned char *data, size_t data_len)
 }
 
 int
-send_serial_notify_pdu(struct sender_common *common)
+send_serial_notify_pdu(struct sender_common *common, serial_t start_serial)
 {
        struct serial_notify_pdu pdu;
        unsigned char data[BUFFER_SIZE];
        size_t len;
 
        set_header_values(&pdu.header, common->version, PDU_TYPE_SERIAL_NOTIFY,
-           *common->session_id);
+           common->session_id);
 
-       pdu.serial_number = *common->start_serial;
+       pdu.serial_number = start_serial;
        pdu.header.length = length_serial_notify_pdu(&pdu);
 
        len = serialize_serial_notify_pdu(&pdu, data);
@@ -201,8 +198,8 @@ send_cache_response_pdu(struct sender_common *common)
        size_t len;
 
        /* This PDU has only the header */
-       set_header_values(&pdu.header, common->version,
-           PDU_TYPE_CACHE_RESPONSE, *common->session_id);
+       set_header_values(&pdu.header, common->version, PDU_TYPE_CACHE_RESPONSE,
+           common->session_id);
        pdu.header.length = HEADER_LENGTH;
 
        len = serialize_cache_response_pdu(&pdu, data);
@@ -211,7 +208,8 @@ send_cache_response_pdu(struct sender_common *common)
 }
 
 static int
-send_ipv4_prefix_pdu(struct sender_common *common, struct vrp *vrp)
+send_ipv4_prefix_pdu(struct sender_common *common, struct vrp const *vrp,
+    uint8_t flags)
 {
        struct ipv4_prefix_pdu pdu;
        unsigned char data[BUFFER_SIZE];
@@ -220,7 +218,7 @@ send_ipv4_prefix_pdu(struct sender_common *common, struct vrp *vrp)
        set_header_values(&pdu.header, common->version, PDU_TYPE_IPV4_PREFIX,
            0);
 
-       pdu.flags = vrp->flags;
+       pdu.flags = flags;
        pdu.prefix_length = vrp->prefix_length;
        pdu.max_length = vrp->max_prefix_length;
        pdu.zero = 0;
@@ -234,7 +232,8 @@ send_ipv4_prefix_pdu(struct sender_common *common, struct vrp *vrp)
 }
 
 static int
-send_ipv6_prefix_pdu(struct sender_common *common, struct vrp *vrp)
+send_ipv6_prefix_pdu(struct sender_common *common, struct vrp const *vrp,
+    uint8_t flags)
 {
        struct ipv6_prefix_pdu pdu;
        unsigned char data[BUFFER_SIZE];
@@ -243,7 +242,7 @@ send_ipv6_prefix_pdu(struct sender_common *common, struct vrp *vrp)
        set_header_values(&pdu.header, common->version, PDU_TYPE_IPV6_PREFIX,
            0);
 
-       pdu.flags = vrp->flags;
+       pdu.flags = flags;
        pdu.prefix_length = vrp->prefix_length;
        pdu.max_length = vrp->max_prefix_length;
        pdu.zero = 0;
@@ -257,26 +256,21 @@ send_ipv6_prefix_pdu(struct sender_common *common, struct vrp *vrp)
 }
 
 int
-send_prefix_pdu(struct vrp *vrp, void *arg)
+send_prefix_pdu(struct sender_common *common, struct vrp const *vrp,
+    uint8_t flags)
 {
        switch (vrp->addr_fam) {
        case AF_INET:
-               return send_ipv4_prefix_pdu(arg, vrp);
+               return send_ipv4_prefix_pdu(common, vrp, flags);
        case AF_INET6:
-               return send_ipv6_prefix_pdu(arg, vrp);
+               return send_ipv6_prefix_pdu(common, vrp, flags);
        }
 
        return -EINVAL;
 }
 
-int
-send_pdus_base(struct sender_common *common)
-{
-       return vrps_foreach_base_roa(send_prefix_pdu, common);
-}
-
 static bool
-vrp_equals(struct vrp *left, struct vrp *right)
+vrp_equals(struct vrp const *left, struct vrp const *right)
 {
        return left->asn == right->asn
            && left->addr_fam == right->addr_fam
@@ -289,18 +283,27 @@ vrp_equals(struct vrp *left, struct vrp *right)
                right->prefix.v6.s6_addr32)));
 }
 
+static int
+vrp_simply_send(struct delta const *delta, void *arg)
+{
+       return send_prefix_pdu(arg, &delta->vrp, delta->flags);
+}
+
 /**
- * Remove the announcements/withdrawals that override each other
+ * Remove the announcements/withdrawals that override each other.
+ *
+ * (Note: We're assuming the array is already duplicateless enough thanks to the
+ * hash table.)
  */
 static int
-vrp_ovrd_remove(struct vrp *vrp, void *arg)
+vrp_ovrd_remove(struct delta const *delta, void *arg)
 {
        struct vrp_node *ptr;
        struct vrp_slist *filtered_vrps = arg;
 
        SLIST_FOREACH(ptr, filtered_vrps, next)
-               if (vrp_equals(vrp, &ptr->vrp) &&
-                   vrp->flags != ptr->vrp.flags) {
+               if (vrp_equals(&delta->vrp, &ptr->delta.vrp) &&
+                   delta->flags != ptr->delta.flags) {
                        SLIST_REMOVE(filtered_vrps, ptr, vrp_node, next);
                        free(ptr);
                        return 0;
@@ -310,27 +313,46 @@ vrp_ovrd_remove(struct vrp *vrp, void *arg)
        if (ptr == NULL)
                return pr_enomem();
 
-       ptr->vrp = *vrp;
+       ptr->delta = *delta;
        SLIST_INSERT_HEAD(filtered_vrps, ptr, next);
        return 0;
 }
 
 int
-send_pdus_delta(struct sender_common *common)
+send_pdus_delta(struct deltas_db *deltas, struct sender_common *common)
 {
        struct vrp_slist filtered_vrps;
+       struct delta_group *group;
        struct vrp_node *ptr;
        int error;
 
+       /*
+        * Short circuit: Entries that share serial are already guaranteed to
+        * not contradict each other, so no filtering required.
+        */
+       if (deltas->len == 1) {
+               group = &deltas->array[0];
+               return deltas_foreach(group->serial, group->deltas,
+                   vrp_simply_send, common);
+       }
+
+       /*
+        * Filter: Remove entries that cancel each other.
+        * (We'll have to build a separate list because the database nodes
+        * are immutable.)
+        */
        SLIST_INIT(&filtered_vrps);
-       error = vrps_foreach_delta_roa(*common->start_serial,
-           *common->end_serial, vrp_ovrd_remove, &filtered_vrps);
-       if (error)
-               goto release_list;
+       ARRAYLIST_FOREACH(deltas, group) {
+               error = deltas_foreach(group->serial, group->deltas,
+                   vrp_ovrd_remove, &filtered_vrps);
+               if (error)
+                       goto release_list;
+       }
 
-       /** Now send the filtered deltas */
+       /* Now send the filtered deltas */
        SLIST_FOREACH(ptr, &filtered_vrps, next) {
-               error = send_prefix_pdu(&ptr->vrp, common);
+               error = send_prefix_pdu(common, &ptr->delta.vrp,
+                   ptr->delta.flags);
                if (error)
                        break;
        }
@@ -346,7 +368,7 @@ release_list:
 }
 
 int
-send_end_of_data_pdu(struct sender_common *common)
+send_end_of_data_pdu(struct sender_common *common, serial_t end_serial)
 {
        struct end_of_data_pdu pdu;
        unsigned char data[BUFFER_SIZE];
@@ -354,8 +376,8 @@ send_end_of_data_pdu(struct sender_common *common)
        int error;
 
        set_header_values(&pdu.header, common->version, PDU_TYPE_END_OF_DATA,
-           *common->session_id);
-       pdu.serial_number = *common->end_serial;
+           common->session_id);
+       pdu.serial_number = end_serial;
        if (common->version == RTR_V1) {
                pdu.refresh_interval = config_get_refresh_interval();
                pdu.retry_interval = config_get_retry_interval();
@@ -381,8 +403,7 @@ struct pdu_header *err_pdu_header, char const *message)
        unsigned char data[BUFFER_SIZE];
        size_t len;
 
-       set_header_values(&pdu.header, version, PDU_TYPE_ERROR_REPORT,
-           code);
+       set_header_values(&pdu.header, version, PDU_TYPE_ERROR_REPORT, code);
 
        pdu.error_pdu_length = 0;
        pdu.erroneous_pdu = (void *)err_pdu_header;
index 94b6f196543b9758e06352a45c44ded09d275215..4464a7a2c7aae896ec0d64287538217475516e46 100644 (file)
@@ -2,24 +2,22 @@
 #define SRC_RTR_PDU_SENDER_H_
 
 #include "pdu.h"
+#include "rtr/db/vrps.h"
 
 struct sender_common {
        int fd;
        uint8_t version;
-       uint16_t *session_id;
-       uint32_t *start_serial;
-       uint32_t *end_serial;
+       uint16_t session_id;
 };
 
-void init_sender_common(struct sender_common *, int, uint8_t, uint16_t *,
-    uint32_t *, uint32_t *);
+void init_sender_common(struct sender_common *, int, uint8_t);
 
-int send_serial_notify_pdu(struct sender_common *);
+int send_serial_notify_pdu(struct sender_common *, serial_t);
 int send_cache_reset_pdu(struct sender_common *);
 int send_cache_response_pdu(struct sender_common *);
-int send_pdus_base(struct sender_common *);
-int send_pdus_delta(struct sender_common *);
-int send_end_of_data_pdu(struct sender_common *);
+int send_prefix_pdu(struct sender_common *, struct vrp const *, uint8_t);
+int send_pdus_delta(struct deltas_db *, struct sender_common *);
+int send_end_of_data_pdu(struct sender_common *, serial_t);
 int send_error_report_pdu(int, uint8_t, uint16_t, struct pdu_header *,
     char const *);
 
index b2cf7fdcd4645defc9cd754b52849280d99ab141..8bc2627644b3548fb89b75e2f7203b964558fe20 100644 (file)
@@ -189,8 +189,7 @@ client_thread_cb(void *param_void)
                /* Protocol Version Negotiation */
                if (rtr_version != RTR_VERSION_SUPPORTED) {
                        err_pdu_send(param->client_fd, RTR_VERSION_SUPPORTED,
-                           ERR_PDU_UNSUP_PROTO_VERSION,
-                           (struct pdu_header *) pdu, NULL);
+                           ERR_PDU_UNSUP_PROTO_VERSION, pdu, NULL);
                        return end_client(param->client_fd, meta, pdu);
                }
                /* RTR Version ready, now update client */
@@ -199,10 +198,10 @@ client_thread_cb(void *param_void)
                if (err) {
                        if (err == -ERTR_VERSION_MISMATCH) {
                                err_pdu_send(param->client_fd, rtr_version,
-                                   (rtr_version == RTR_V0
-                                   ? ERR_PDU_UNSUP_PROTO_VERSION
-                                   : ERR_PDU_UNEXPECTED_PROTO_VERSION),
-                                   (struct pdu_header *) pdu, NULL);
+                                   (rtr_version == RTR_V0)
+                                       ? ERR_PDU_UNSUP_PROTO_VERSION
+                                       : ERR_PDU_UNEXPECTED_PROTO_VERSION,
+                                   pdu, NULL);
                        }
                        return end_client(param->client_fd, meta, pdu);
                }
index 6cb82fb6377739ab7e1117969a26b7d99a5785b1..2873d3ee213d30e6fa07049195b2e3fb7d4b9720 100644 (file)
@@ -23,10 +23,12 @@ BASIC_MODULES += impersonator.c
 check_PROGRAMS  = address.test
 check_PROGRAMS += clients.test
 check_PROGRAMS += line_file.test
+check_PROGRAMS += pdu_handler.test
 check_PROGRAMS += rsync.test
 check_PROGRAMS += roa_table.test
 #check_PROGRAMS += tal.test
 check_PROGRAMS += vcard.test
+check_PROGRAMS += vrps.test
 #check_PROGRAMS += rtr/pdu.test
 #check_PROGRAMS += rtr/primitive_reader.test
 TESTS = ${check_PROGRAMS}
@@ -48,6 +50,17 @@ line_file_test_SOURCES += ../src/line_file.c ../src/line_file.h
 line_file_test_SOURCES += line_file_test.c
 line_file_test_LDADD = ${MY_LDADD}
 
+pdu_handler_test_SOURCES  = ${BASIC_MODULES}
+pdu_handler_test_SOURCES += ../src/common.c
+pdu_handler_test_SOURCES += ../src/rtr/pdu_handler.c
+pdu_handler_test_SOURCES += ../src/rtr/err_pdu.c
+pdu_handler_test_SOURCES += ../src/rtr/db/delta.c
+pdu_handler_test_SOURCES += ../src/rtr/db/roa_table.c
+pdu_handler_test_SOURCES += ../src/rtr/db/vrps.c
+pdu_handler_test_SOURCES += rtr/db/impersonator.c
+pdu_handler_test_SOURCES += rtr/pdu_handler_test.c
+pdu_handler_test_LDADD = ${MY_LDADD}
+
 roa_table_test_SOURCES  = ${BASIC_MODULES}
 roa_table_test_SOURCES += ../src/rtr/db/delta.c
 roa_table_test_SOURCES += ../src/rtr/db/roa_table.c
@@ -74,6 +87,15 @@ vcard_test_SOURCES  = ${BASIC_MODULES}
 vcard_test_SOURCES += vcard_test.c
 vcard_test_LDADD = ${MY_LDADD}
 
+vrps_test_SOURCES  = ${BASIC_MODULES}
+vrps_test_SOURCES += ../src/common.c
+vrps_test_SOURCES += ../src/rtr/db/delta.c
+vrps_test_SOURCES += ../src/rtr/db/roa_table.c
+vrps_test_SOURCES += ../src/rtr/db/vrps.c
+vrps_test_SOURCES += rtr/db/impersonator.c
+vrps_test_SOURCES += rtr/db/vrps_test.c
+vrps_test_LDADD = ${MY_LDADD}
+
 #rtr_pdu_test_SOURCES  = ${BASIC_MODULES}
 #rtr_pdu_test_SOURCES += rtr/pdu_test.c
 #rtr_pdu_test_SOURCES += rtr/stream.c
diff --git a/test/rtr/db/impersonator.c b/test/rtr/db/impersonator.c
new file mode 100644 (file)
index 0000000..3e2f3a9
--- /dev/null
@@ -0,0 +1,58 @@
+#include "object/tal.h"
+
+#include <check.h>
+
+static int iteration = 0;
+
+static void
+add_v4(struct validation_handler *handler, uint32_t as)
+{
+       struct ipv4_prefix prefix;
+       prefix.addr.s_addr = htonl(0xC0000200);
+       prefix.len = 24;
+       ck_assert_int_eq(0, handler->handle_roa_v4(as, &prefix, 32,
+           handler->arg));
+}
+
+static void
+add_v6(struct validation_handler *handler, uint32_t as)
+{
+       struct ipv6_prefix prefix;
+       prefix.addr.s6_addr32[0] = htonl(0x20010DB8);
+       prefix.addr.s6_addr32[1] = 0;
+       prefix.addr.s6_addr32[2] = 0;
+       prefix.addr.s6_addr32[3] = 0;
+       prefix.len = 96;
+       ck_assert_int_eq(0, handler->handle_roa_v6(as, &prefix, 120,
+           handler->arg));
+}
+
+int
+perform_standalone_validation(struct validation_handler *handler)
+{
+       ck_assert_int_eq(0, handler->reset(handler->arg));
+
+       switch (iteration) {
+       case 0:
+               add_v4(handler, 0);
+               add_v6(handler, 0);
+               break;
+       case 1:
+               add_v4(handler, 0);
+               add_v6(handler, 0);
+               add_v4(handler, 1);
+               add_v6(handler, 1);
+               break;
+       case 2:
+       case 3:
+               add_v4(handler, 1);
+               add_v6(handler, 1);
+               break;
+       default:
+               ck_abort_msg("perform_standalone_validation() was called too many times (%d).",
+                   iteration);
+       }
+
+       iteration++;
+       return 0;
+}
index 6b3850f84b5d456bc163b284b78cb7afc7d294ca..7b11577fe576127a2cfb8df6fbbbc99f22b4284b 100644 (file)
@@ -11,8 +11,8 @@ static bool roas_found[TOTAL_ROAS];
 static unsigned int total_found;
 
 static bool
-vrp_equals_v4(struct vrp *vrp, uint8_t as, uint32_t addr, uint8_t prefix_len,
-    uint8_t max_prefix_len)
+vrp_equals_v4(struct vrp const *vrp, uint8_t as, uint32_t addr,
+    uint8_t prefix_len, uint8_t max_prefix_len)
 {
        return (AF_INET == vrp->addr_fam)
            && (as == vrp->asn)
@@ -22,8 +22,8 @@ vrp_equals_v4(struct vrp *vrp, uint8_t as, uint32_t addr, uint8_t prefix_len,
 }
 
 static bool
-vrp_equals_v6(struct vrp *vrp, uint8_t as, uint32_t addr, uint8_t prefix_len,
-    uint8_t max_prefix_len)
+vrp_equals_v6(struct vrp const *vrp, uint8_t as, uint32_t addr,
+    uint8_t prefix_len, uint8_t max_prefix_len)
 {
        return (AF_INET6 == vrp->addr_fam)
            && (as == vrp->asn)
@@ -45,7 +45,7 @@ update_found(array_index index)
 }
 
 static int
-foreach_cb(struct vrp *vrp, void *arg)
+foreach_cb(struct vrp const *vrp, void *arg)
 {
        char const *str;
 
@@ -156,7 +156,7 @@ START_TEST(test_basic)
        for (i = 0; i < TOTAL_ROAS; i++)
                ck_assert_int_eq(true, roas_found[i]);
 
-       roa_table_put(table);
+       roa_table_destroy(table);
 }
 END_TEST
 
diff --git a/test/rtr/db/vrps_test.c b/test/rtr/db/vrps_test.c
new file mode 100644 (file)
index 0000000..c6dfccb
--- /dev/null
@@ -0,0 +1,254 @@
+#include <check.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "common.h"
+#include "thread_var.h"
+#include "validation_handler.h"
+#include "rtr/db/vrps.h"
+
+/* -- Expected database descriptors -- */
+
+/*
+ * BASE
+ * 0: IPv4, ASN 0
+ * 1: IPv4, ASN 1
+ * 2: IPv6, ASN 0
+ * 3: IPv6, ASN 1
+ */
+static const bool iteration0_base[] = { 1, 0, 1, 0, };
+static const bool iteration1_base[] = { 1, 1, 1, 1, };
+static const bool iteration2_base[] = { 0, 1, 0, 1, };
+
+/*
+ * DELTA
+ * 0: Withdrawal, IPv4, ASN 0    4: Announcement, IPv4, ASN 0
+ * 1: Withdrawal, IPv4, ASN 1    5: Announcement, IPv4, ASN 1
+ * 2: Withdrawal, IPv6, ASN 0    6: Announcement, IPv6, ASN 0
+ * 3: Withdrawal, IPv6, ASN 1    7: Announcement, IPv6, ASN 1
+ */
+
+static const bool deltas_0to0[] = { 0, 0, 0, 0, 0, 0, 0, 0, };
+
+static const bool deltas_0to1[] = { 0, 0, 0, 0, 0, 1, 0, 1, };
+static const bool deltas_1to1[] = { 0, 0, 0, 0, 0, 0, 0, 0, };
+
+static const bool deltas_0to2[] = { 1, 0, 1, 0, 0, 1, 0, 1, };
+static const bool deltas_1to2[] = { 1, 0, 1, 0, 0, 0, 0, 0, };
+static const bool deltas_2to2[] = { 0, 0, 0, 0, 0, 0, 0, 0, };
+
+/* Impersonator functions */
+
+serial_t
+clients_get_min_serial(void)
+{
+       return 0;
+}
+
+/* Test functions */
+
+static int
+vrp_fail(struct vrp const *vrp, void *arg)
+{
+       char const *addr;
+
+       switch (vrp->addr_fam) {
+       case AF_INET:
+               addr = v4addr2str(&vrp->prefix.v4);
+               break;
+       case AF_INET6:
+               addr = v6addr2str(&vrp->prefix.v6);
+               break;
+       default:
+               addr = "unknown";
+       }
+
+       ck_abort_msg("Expected no callbacks, got VRP %u/%s/%u/%u.",
+           vrp->asn, addr, vrp->prefix_length, vrp->max_prefix_length);
+}
+
+static array_index
+get_vrp_index(struct vrp const *vrp)
+{
+       array_index family_bit;
+
+       switch (vrp->addr_fam) {
+       case AF_INET:
+               ck_assert_uint_eq(htonl(0xC0000200), vrp->prefix.v4.s_addr);
+               ck_assert_uint_eq(24, vrp->prefix_length);
+               ck_assert_uint_eq(32, vrp->max_prefix_length);
+               family_bit = 0;
+               break;
+
+       case AF_INET6:
+               ck_assert_uint_eq(htonl(0x20010DB8), vrp->prefix.v6.s6_addr32[0]);
+               ck_assert_uint_eq(0, vrp->prefix.v6.s6_addr32[1]);
+               ck_assert_uint_eq(0, vrp->prefix.v6.s6_addr32[2]);
+               ck_assert_uint_eq(0, vrp->prefix.v6.s6_addr32[3]);
+               ck_assert_uint_eq(96, vrp->prefix_length);
+               ck_assert_uint_eq(120, vrp->max_prefix_length);
+               family_bit = 1;
+               break;
+
+       default:
+               ck_abort_msg("VRP has unknown protocol: %u", vrp->addr_fam);
+       }
+
+       ck_assert_msg(vrp->asn <= 1, "Unexpected AS number: %u", vrp->asn);
+
+       return (family_bit << 1) | (vrp->asn << 0);
+}
+
+static array_index
+get_delta_index(struct delta const *delta)
+{
+       array_index result;
+
+       result = get_vrp_index(&delta->vrp);
+       ck_assert_msg(delta->flags <= 1, "Unexpected flags: %u", delta->flags);
+
+       return (delta->flags << 2) | result;
+}
+
+static int
+vrp_check(struct vrp const *vrp, void *arg)
+{
+       bool *array = arg;
+       array_index index;
+
+       index = get_vrp_index(vrp);
+       ck_assert_uint_eq(false, array[index]);
+       array[index] = true;
+
+       return 0;
+}
+
+static int
+delta_check(struct delta const *delta, void *arg)
+{
+       bool *array = arg;
+       array_index index;
+
+       index = get_delta_index(delta);
+       ck_assert_uint_eq(false, array[index]);
+       array[index] = true;
+
+       return 0;
+
+}
+
+static void
+check_serial(serial_t expected_serial)
+{
+       serial_t actual_serial;
+       ck_assert_int_eq(0, get_last_serial_number(&actual_serial));
+       ck_assert_uint_eq(expected_serial, actual_serial);
+}
+
+static void
+check_base(serial_t expected_serial, bool const *expected_base)
+{
+       serial_t actual_serial;
+       bool actual_base[4];
+       array_index i;
+
+       memset(actual_base, 0, sizeof(actual_base));
+       ck_assert_int_eq(0, vrps_foreach_base_roa(vrp_check, actual_base,
+           &actual_serial));
+       ck_assert_uint_eq(expected_serial, actual_serial);
+       for (i = 0; i < ARRAY_LEN(actual_base); i++)
+               ck_assert_uint_eq(expected_base[i], actual_base[i]);
+}
+
+static void
+check_deltas(serial_t from, serial_t to, bool const *expected_deltas)
+{
+       serial_t actual_serial;
+       bool actual_deltas[8];
+       struct deltas_db deltas;
+       struct delta_group *group;
+       array_index i;
+
+       deltas_db_init(&deltas);
+       ck_assert_int_eq(0, vrps_get_deltas_from(from, &actual_serial,
+           &deltas));
+       ck_assert_uint_eq(to, actual_serial);
+
+       memset(actual_deltas, 0, sizeof(actual_deltas));
+       ARRAYLIST_FOREACH(&deltas, group)
+               ck_assert_int_eq(0, deltas_foreach(group->serial, group->deltas,
+                   delta_check, actual_deltas));
+       for (i = 0; i < ARRAY_LEN(actual_deltas); i++)
+               ck_assert_uint_eq(expected_deltas[i], actual_deltas[i]);
+}
+
+START_TEST(test_basic)
+{
+       struct deltas_db deltas;
+       serial_t serial;
+       bool changed;
+       bool iterated_entries[8];
+
+       deltas_db_init(&deltas);
+
+       ck_assert_int_eq(0, vrps_init());
+
+       /* First validation not yet performed: Tell routers to wait */
+       ck_assert_int_eq(-EAGAIN, get_last_serial_number(&serial));
+       ck_assert_int_eq(-EAGAIN, vrps_foreach_base_roa(vrp_fail,
+           iterated_entries, &serial));
+       ck_assert_int_eq(-EAGAIN, vrps_get_deltas_from(0, &serial, &deltas));
+
+       /* 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);
+
+       /* 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);
+       check_deltas(1, 1, deltas_1to1);
+
+       /* 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);
+       check_deltas(1, 2, deltas_1to2);
+       check_deltas(2, 2, deltas_2to2);
+
+       vrps_destroy();
+}
+END_TEST
+
+Suite *pdu_suite(void)
+{
+       Suite *suite;
+       TCase *core;
+
+       core = tcase_create("Core");
+       tcase_add_test(core, test_basic);
+
+       suite = suite_create("VRP Database");
+       suite_add_tcase(suite, core);
+       return suite;
+}
+
+int main(void)
+{
+       Suite *suite;
+       SRunner *runner;
+       int tests_failed;
+
+       suite = pdu_suite();
+
+       runner = srunner_create(suite);
+       srunner_run_all(runner, CK_NORMAL);
+       tests_failed = srunner_ntests_failed(runner);
+       srunner_free(runner);
+
+       return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/test/rtr/pdu_handler_test.c b/test/rtr/pdu_handler_test.c
new file mode 100644 (file)
index 0000000..b4629dc
--- /dev/null
@@ -0,0 +1,352 @@
+#include <check.h>
+#include <stdlib.h>
+#include <sys/queue.h>
+
+#include "validation_handler.h"
+#include "rtr/pdu.h"
+#include "rtr/pdu_handler.h"
+#include "rtr/pdu_sender.h"
+#include "rtr/db/vrps.h"
+
+/* TODO (now) test the removal of deltas that cancel each other */
+/* TODO (now) test delta forgetting */
+
+/* Helper functions */
+
+struct expected_pdu {
+       uint8_t pdu_type;
+       STAILQ_ENTRY(expected_pdu) list_hook;
+};
+
+static STAILQ_HEAD(, expected_pdu) expected_pdus = STAILQ_HEAD_INITIALIZER(expected_pdus);
+
+static void
+expected_pdu_add(uint8_t pdu_type)
+{
+       struct expected_pdu *pdu;
+
+       pdu = malloc(sizeof(struct expected_pdu));
+       ck_assert_ptr_ne(NULL, pdu);
+
+       pdu->pdu_type = pdu_type;
+       STAILQ_INSERT_TAIL(&expected_pdus, pdu, list_hook);
+}
+
+static uint8_t
+pop_expected_pdu(void)
+{
+       struct expected_pdu *pdu;
+       uint8_t result;
+
+       pdu = STAILQ_FIRST(&expected_pdus);
+       ck_assert_ptr_ne(NULL, pdu);
+       result = pdu->pdu_type;
+       STAILQ_REMOVE(&expected_pdus, pdu, expected_pdu, list_hook);
+       free(pdu);
+
+       return result;
+}
+
+static bool
+has_expected_pdus(void)
+{
+       return !STAILQ_EMPTY(&expected_pdus);
+}
+
+static void
+init_db_full(void)
+{
+       bool changed;
+       ck_assert_int_eq(0, vrps_init());
+       ck_assert_int_eq(0, vrps_update(&changed));
+       ck_assert_uint_eq(true, changed);
+       ck_assert_int_eq(0, vrps_update(&changed));
+       ck_assert_uint_eq(true, changed);
+       ck_assert_int_eq(0, vrps_update(&changed));
+       ck_assert_uint_eq(true, changed);
+}
+
+static void
+init_reset_query(struct reset_query_pdu *query)
+{
+       query->header.protocol_version = RTR_V0;
+       query->header.pdu_type = PDU_TYPE_RESET_QUERY;
+       query->header.m.reserved = 0;
+       query->header.length = 8;
+}
+
+static void
+init_serial_query(struct serial_query_pdu *query, uint32_t serial)
+{
+       query->header.protocol_version = RTR_V0;
+       query->header.pdu_type = PDU_TYPE_SERIAL_QUERY;
+       query->header.m.session_id = get_current_session_id(RTR_V0);
+       query->header.length = 12;
+       query->serial_number = serial;
+}
+
+/* Impersonator functions */
+
+serial_t
+clients_get_min_serial(void)
+{
+       return 0;
+}
+
+void
+init_sender_common(struct sender_common *common, int fd, uint8_t version)
+{
+       common->fd = fd;
+       common->version = version;
+       common->session_id = get_current_session_id(version);
+}
+
+int
+send_cache_reset_pdu(struct sender_common *common)
+{
+       pr_info("    Server sent Cache Reset.");
+       ck_assert_int_eq(pop_expected_pdu(), PDU_TYPE_CACHE_RESET);
+       return 0;
+}
+
+int
+send_cache_response_pdu(struct sender_common *common)
+{
+       pr_info("    Server sent Cache Response.");
+       ck_assert_int_eq(pop_expected_pdu(), PDU_TYPE_CACHE_RESPONSE);
+       return 0;
+}
+
+int
+send_prefix_pdu(struct sender_common *common, struct vrp const *vrp,
+    uint8_t flags)
+{
+       /*
+        * We don't care about order.
+        * If the server is expected to return `M` IPv4 PDUs and `N` IPv6 PDUs,
+        * we'll just check `M + N` contiguous Prefix PDUs.
+        */
+       uint8_t pdu_type = pop_expected_pdu();
+       pr_info("    Server sent Prefix PDU.");
+       ck_assert_msg(pdu_type == PDU_TYPE_IPV4_PREFIX
+           || pdu_type == PDU_TYPE_IPV6_PREFIX,
+           "Server's PDU type is %d, not one of the IP Prefixes.", pdu_type);
+       return 0;
+}
+
+static int
+handle_delta(struct delta const *delta, void *arg)
+{
+       ck_assert_int_eq(0, send_prefix_pdu(arg, &delta->vrp, delta->flags));
+       return 0;
+}
+
+int
+send_pdus_delta(struct deltas_db *deltas, struct sender_common *common)
+{
+       struct delta_group *group;
+
+       ARRAYLIST_FOREACH(deltas, group)
+               ck_assert_int_eq(0, deltas_foreach(group->serial, group->deltas,
+                   handle_delta, common));
+
+       return 0;
+}
+
+int
+send_end_of_data_pdu(struct sender_common *common, serial_t end_serial)
+{
+       pr_info("    Server sent End of Data.");
+       ck_assert_int_eq(pop_expected_pdu(), PDU_TYPE_END_OF_DATA);
+       return 0;
+}
+
+int
+send_error_report_pdu(int fd, uint8_t version, uint16_t code,
+struct pdu_header *err_pdu_header, char const *message)
+{
+       pr_info("    Server sent Error Report %u: '%s'", code, message);
+       ck_assert_int_eq(pop_expected_pdu(), PDU_TYPE_ERROR_REPORT);
+       return 0;
+}
+
+/* Tests */
+
+/* https://tools.ietf.org/html/rfc6810#section-6.1 */
+START_TEST(test_start_or_restart)
+{
+       struct reset_query_pdu client_pdu;
+
+       pr_info("-- Start or Restart --");
+
+       /* Prepare DB */
+       init_db_full();
+
+       /* Init client request */
+       init_reset_query(&client_pdu);
+
+       /* 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_END_OF_DATA);
+
+       /* Run and validate */
+       ck_assert_int_eq(0, handle_reset_query_pdu(0, &client_pdu));
+       ck_assert_uint_eq(false, has_expected_pdus());
+
+       /* Clean up */
+       vrps_destroy();
+}
+END_TEST
+
+/* https://tools.ietf.org/html/rfc6810#section-6.2 */
+START_TEST(test_typical_exchange)
+{
+       struct serial_query_pdu client_pdu;
+
+       pr_info("-- Typical Exchange --");
+
+       /* Prepare DB */
+       init_db_full();
+
+       /* From serial 0: Init client request */
+       init_serial_query(&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_IPV4_PREFIX);
+       expected_pdu_add(PDU_TYPE_IPV6_PREFIX);
+       expected_pdu_add(PDU_TYPE_END_OF_DATA);
+
+       /* From serial 0: Run and validate */
+       ck_assert_int_eq(0, handle_serial_query_pdu(0, &client_pdu));
+       ck_assert_uint_eq(false, has_expected_pdus());
+
+       /* From serial 1: Init client request */
+       init_serial_query(&client_pdu, 1);
+
+       /* From serial 1: 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_END_OF_DATA);
+
+       /* From serial 1: Run and validate */
+       ck_assert_int_eq(0, handle_serial_query_pdu(0, &client_pdu));
+       ck_assert_uint_eq(false, has_expected_pdus());
+
+       /* From serial 2: Init client request */
+       init_serial_query(&client_pdu, 2);
+
+       /* From serial 2: Define expected server response */
+       expected_pdu_add(PDU_TYPE_CACHE_RESPONSE);
+       expected_pdu_add(PDU_TYPE_END_OF_DATA);
+
+       /* From serial 2: Run and validate */
+       ck_assert_int_eq(0, handle_serial_query_pdu(0, &client_pdu));
+       ck_assert_uint_eq(false, has_expected_pdus());
+
+       /* Clean up */
+       vrps_destroy();
+}
+END_TEST
+
+/* https://tools.ietf.org/html/rfc6810#section-6.3 */
+START_TEST(test_no_incremental_update_available)
+{
+       struct serial_query_pdu serial_query;
+
+       pr_info("-- No Incremental Update Available --");
+
+       /* Prepare DB */
+       init_db_full();
+
+       /* Init client request */
+       init_serial_query(&serial_query, 10000);
+
+       /* Define expected server response */
+       expected_pdu_add(PDU_TYPE_CACHE_RESET);
+
+       /* Run and validate */
+       ck_assert_int_eq(0, handle_serial_query_pdu(0, &serial_query));
+       ck_assert_uint_eq(false, has_expected_pdus());
+
+       /* The Reset Query is already tested in start_or_restart. */
+
+       /* Clean up */
+       vrps_destroy();
+}
+END_TEST
+
+/* https://tools.ietf.org/html/rfc6810#section-6.4 */
+START_TEST(test_cache_has_no_data_available)
+{
+       struct serial_query_pdu serial_query;
+       struct reset_query_pdu reset_query;
+
+       pr_info("-- Cache Has No Data Available --");
+
+       /* Prepare DB */
+       ck_assert_int_eq(0, vrps_init());
+
+       /* Serial Query: Init client request */
+       init_serial_query(&serial_query, 0);
+
+       /* Serial Query: Define expected server response */
+       expected_pdu_add(PDU_TYPE_ERROR_REPORT);
+
+       /* Serial Query: Run and validate */
+       ck_assert_int_eq(0, handle_serial_query_pdu(0, &serial_query));
+       ck_assert_uint_eq(false, has_expected_pdus());
+
+       /* Reset Query: Init client request */
+       init_reset_query(&reset_query);
+
+       /* Reset Query: Define expected server response */
+       expected_pdu_add(PDU_TYPE_ERROR_REPORT);
+
+       /* Reset Query: Run and validate */
+       ck_assert_int_eq(0, handle_reset_query_pdu(0, &reset_query));
+       ck_assert_uint_eq(false, has_expected_pdus());
+
+       /* Clean up */
+       vrps_destroy();
+}
+END_TEST
+
+Suite *pdu_suite(void)
+{
+       Suite *suite;
+       TCase *core;
+
+       core = tcase_create("RFC6810-Defined Protocol Sequences");
+       tcase_add_test(core, test_start_or_restart);
+       tcase_add_test(core, test_typical_exchange);
+       tcase_add_test(core, test_no_incremental_update_available);
+       tcase_add_test(core, test_cache_has_no_data_available);
+
+       /* TODO (now) add an unhappy path TCase. */
+
+       suite = suite_create("PDU Handler");
+       suite_add_tcase(suite, core);
+       return suite;
+}
+
+int main(void)
+{
+       Suite *suite;
+       SRunner *runner;
+       int tests_failed;
+
+       suite = pdu_suite();
+
+       runner = srunner_create(suite);
+       srunner_run_all(runner, CK_NORMAL);
+       tests_failed = srunner_ntests_failed(runner);
+       srunner_free(runner);
+
+       return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}