]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Merge remote-tracking branch 'rtrserver/master'
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Thu, 4 Apr 2019 18:45:23 +0000 (12:45 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 8 Apr 2019 19:44:59 +0000 (14:44 -0500)
Checkpoint: console validation is stable, server is not.

53 files changed:
1  2 
.gitignore
Makefile.am
README.md
configure.ac
man/rpki-validator.8
src/Makefile.am
src/array_list.h
src/clients.c
src/clients.h
src/common.h
src/config.c
src/config.h
src/config/boolean.c
src/config/filename_format.c
src/config/str.c
src/config/str.h
src/config/string_array.c
src/config/string_array.h
src/config/sync_strategy.c
src/config/types.h
src/config/uint.c
src/config/uint.h
src/config/uint32.c
src/config/uint32.h
src/json_handler.c
src/json_handler.h
src/main.c
src/object/ghostbusters.c
src/object/ghostbusters.h
src/object/roa.c
src/object/roa.h
src/object/tal.c
src/object/tal.h
src/rpp.c
src/rtr/err_pdu.c
src/rtr/err_pdu.h
src/rtr/pdu.c
src/rtr/pdu.h
src/rtr/pdu_handler.c
src/rtr/pdu_sender.c
src/rtr/pdu_sender.h
src/rtr/pdu_serializer.c
src/rtr/primitive_reader.c
src/rtr/rtr.c
src/str.h
src/thread_var.c
src/thread_var.h
src/updates_daemon.c
src/vrps.c
src/vrps.h
test/Makefile.am
test/address_test.c
test/rtr/stream.c

diff --cc .gitignore
index b9aa85d0edd9cade7a21049e26ce229b33b84d25,612676172bf89fc064105901b9a7e2c997341d0e..b902dc8acf6dcd65889f5752c7b2b2aae6c4399b
@@@ -35,7 -35,7 +35,7 @@@
  *.i*86
  *.x86_64
  *.hex
- rpki_validator
 -rtr_server
++src/fort
  
  # Debug files
  *.dSYM/
diff --cc Makefile.am
index 7652996cf73aa5fe396b2061a4e03bb336f6861d,f2b73326b656ffd2814c19c2c784bff6bbd885bf..aece299a1e6c3ef9ed0e9ec1755bc27dfaeb6adf
@@@ -1,4 -1,14 +1,15 @@@
+ # GNU wants us to include some files that we really don't want; therefore
+ # "foreign". The files are
+ #
+ # - AUTHORS: This should be inferred from the (automatic) git history, not some
+ #   error prone, manually-maintained file!
+ # - ChangeLog: This is included in the main page of the site, which can be found
+ #   in the gh-pages branch. Don't want to (nor should I) repeat myself.
+ # - NEWS: Same as ChangeLog.
+ # - README: We prefer the much gayer "README.md" version, so no thanks.
+ #
+ # Man, GNU conventions need a 21 century overhaul badly.
  AUTOMAKE_OPTIONS = foreign
  
- SUBDIRS = src $(MAYBE_TESTS)
- DIST_SUBDIRS = src
 -SUBDIRS = src man test
++SUBDIRS = src man $(MAYBE_TESTS)
++DIST_SUBDIRS = src man
diff --cc README.md
index c0379a5b03217550b2c9f076a3b3e9668e5d7a7b,8cea4709dbb75c5ea11eb5319bf8551a2974e615..3b1eeb3ef2aeaf560e9894ce7b7a31e703ee17ce
+++ b/README.md
@@@ -1,6 -1,8 +1,6 @@@
 -# FORT RTR server
 +# FORT
  
- An RPKI Validator.
 -An RTR server compliant to [RFC 6810](https://tools.ietf.org/html/rfc6810) (at least for now).
 -
 -More documentation about FORT at FORT's site [https://nicmx.github.io/FORT-validator/](https://nicmx.github.io/FORT-validator/).
++An RPKI Validator and RTR Server.
  
  **Still under development!**
  
@@@ -8,11 -10,9 +8,13 @@@
  
  Dependencies:
  
- 2. [tomlc99](https://github.com/cktan/tomlc99)
 +1. libcrypto ([LibreSSL](http://www.libressl.org/) or [OpenSSL](https://www.openssl.org/))
+ 1. [jansson](https://github.com/akheron/jansson)
 +3. [libcmscodec](https://github.com/ydahhrk/libcmscodec)
 +4. [rsync](http://rsync.samba.org/)
  
+ After all the dependencies are installed, run:
++
  ```
  ./autogen.sh
  ./configure
@@@ -20,4 -20,32 +22,36 @@@ mak
  make install
  ```
  
 -## Configuration
 +More documentation at [https://nicmx.github.io/FORT-validator/](https://nicmx.github.io/FORT-validator/).
++
++## RTR Configuration
++
++> TODO Move this
+ The RTR server reads the configuration from a JSON file, learn about it at FORT's site [RTR Server arguments](https://nicmx.github.io/FORT-validator/doc/rtr-server.html).
+ Here's an example of a valid configuration file (assuming that the CSV file returned by FORT's validator is located at `/tmp/fort/roas.csv`):
+ ```javascript
+ {
+   "listen": {
+     "address": "127.0.0.1",
+     "port": "8323",
+     "queue": 10
+   },
+   "vrps": {
+     "location": "/tmp/fort/roas.csv",
+     "checkInterval": 60
+   }
+ }
+ ```
+ ## Execution
+ The executable needs only one argument: the location of the configuration file. So, assuming that the configuration file is located at `/home/fort/rtr.conf`, use the flag `-f` to indicate such location and run the server:
+ ```
+ $ rtr_server -f /home/fort/rtr.conf
+ ```
 -That's it! The server will be listening on the configured port for any RTR client that wishes to establish a connection and exchange for validated ROA payloads.
++That's it! The server will be listening on the configured port for any RTR client that wishes to establish a connection and exchange for validated ROA payloads.
diff --cc configure.ac
index da71f204171ed264b91ea93de29593260959ea9c,2a6aa36aad135c0b74af050f3bec15ec94ed0358..1846be3aa7e1f25620ae67a53255fd9944b09046
@@@ -3,7 -3,7 +3,7 @@@
  
  AC_PREREQ([2.69])
  # TODO change the bug report address
- AC_INIT([rpki-validator], [0.0.1], [ydahhrk@gmail.com])
 -AC_INIT([rtr-server], [0.0.1], [ydahhrk@gmail.com])
++AC_INIT([fort], [0.0.1], [ydahhrk@gmail.com])
  AC_CONFIG_SRCDIR([src/main.c])
  AM_INIT_AUTOMAKE([subdir-objects])
  
@@@ -21,58 -23,12 +21,49 @@@ AC_CHECK_HEADER_STDBOO
  # Checks for library functions.
  AC_FUNC_MALLOC
  AC_CHECK_FUNCS([memset socket])
 -AC_SEARCH_LIBS([pthread_create], [pthread])
 +AC_SEARCH_LIBS([pthread_create], [pthread], [],
 +      [AC_MSG_ERROR([unable to find the pthread() function])]
 +)
 +AC_SEARCH_LIBS([ber_decode], [cmscodec], [],
 +      [AC_MSG_ERROR([unable to find the ber_decode() function])]
 +)
 +AC_SEARCH_LIBS([d2i_X509_bio], [crypto], [],
 +      [AC_MSG_ERROR([unable to find the d2i_X509_bio() function])]
 +)
- AC_SEARCH_LIBS([toml_parse], [toml], [],
-       [AC_MSG_ERROR([unable to find the toml_parse() function])]
- )
 +AC_SEARCH_LIBS([backtrace],[execinfo],[],
 +      [AC_MSG_ERROR([unable to find backtrace() function])]
 +)
 +
 +# Dependencies managed by pkg-config
  
 -# Uhhh... this one starts with "PKG_" so it's probably different.
 -# No idea.
 -PKG_CHECK_MODULES([CHECK], [check])
+ PKG_CHECK_MODULES([JANSSON], [jansson])
 +# By the way: Apparently PKG_CHECK_MODULES is poor practice now. I can't tell;
 +# it's always the same guy complaining about it in Stack Overflow.
 +# (Main one: https://stackoverflow.com/questions/10220946)
 +# But I couldn't make check work with AC_SEARCH_LIBS, and (probably due to
 +# typical obscure bullshit autotools reasoning) I have no idea why.
 +
 +AC_ARG_WITH(
 +      [unit-tests],
 +      AS_HELP_STRING(
 +              [--with-unit-tests],
-               [Generate unit tests. (Requires pkg-config and check)]
++              [Generate unit tests. (Requires check)]
 +      )
 +)
 +
 +# https://www.gnu.org/software/automake/manual/html_node/Conditional-Subdirectories.html
 +# "Subdirectories with AM_CONDITIONAL" never worked for me. The problem might
 +# be that it puts `MAYBE_TESTS` in `.PRECIOUS`, which maybe is a bug in the
 +# autotools. (I honestly can't tell. They are so incredibly poorly designed.)
 +# The code below is implemented as "Subdirectories with AC_SUBST."
- #
- # https://autotools.io/pkgconfig/pkg_check_modules.html#pkgconfig.pkg_check_modules.optional
- # Note: Since Check is the only current user of pkg-config, I don't want to
- # force pkg-config as a dependency; it should only be needed by people who want
- # to unit test. That's why I chose to use an `if` (which skips the
- # `PKG_CHECK_MODULES` on false) instead of an `AS_IF` (which evaluates all paths
- # apparently). In other words, if you ever want to add another pkg-config
- # dependency, you will need the `AS_IF` version.
 +if test "x$with_unit_tests" = "xyes"; then
 +      PKG_CHECK_MODULES([CHECK], [check])
 +      MAYBE_TESTS=test
 +else
 +      MAYBE_TESTS=
 +fi
 +AC_SUBST([MAYBE_TESTS])
 +
  # Spit out the makefiles.
- AC_OUTPUT(Makefile src/Makefile test/Makefile)
+ AC_OUTPUT(Makefile src/Makefile man/Makefile test/Makefile)
index 427a6eb9cfca7a1d0963b03a34a24114f09d11f1,0000000000000000000000000000000000000000..c5c946b8909d52ec9e13d05c67cc20abdf6b570a
mode 100644,000000..100644
--- /dev/null
@@@ -1,161 -1,0 +1,161 @@@
- Path to a TOML file from which additional configuration will be read.
 +.TH rpki-validator 8 2019-03-5 v0.0.1-beta "RPKI certificate path validator"
 +
 +.SH NAME
 +rpki-validator - Actually still unnamed officially.
 +
 +.SH OPTIONS
 +
 +--help
 +.RS 4
 +Print long usage message.
 +.RE
 +.P
 +
 +--usage
 +.RS 4
 +Print short usage message.
 +.RE
 +.P
 +
 +--version
 +.RS 4
 +Print program version.
 +.RE
 +.P
 +
 +--configuration-file=<file>
 +.RS 4
++Path to a JSON file from which additional configuration will be read.
 +.RE
 +.P
 +
 +--local-repository=<directory>
 +.RS 4
 +Path to a directory where the local cache of the repository will be stored
 +and/or read.
 +.RE
 +.P
 +
 +--sync-strategy=(off|strict|root|root-except-ta)
 +.RS 4
 +RSYNC download strategy.
 +.P
 +off
 +.RS 4
 +Skip all RSYNCs. (Validate the existing cache repository pointed by --local-repository.)
 +.RE
 +.P
 +strict
 +.RS 4
 +RSYNC every repository publication point separately. Only skip publication
 +points that have already been downloaded during the current validation cycle.
 +(Assuming each synchronization is recursive.)
 +.P
 +For example, suppose the validator gets certificates whose caRepository access
 +methods (in their Subject Information Access extensions) point to the following
 +publication points:
 +.P
 +1. rsync://rpki.example.com/foo/bar/
 +.br
 +2. rsync://rpki.example.com/foo/qux/
 +.br
 +3. rsync://rpki.example.com/foo/bar/
 +.br
 +4. rsync://rpki.example.com/foo/corge/grault/
 +.br
 +5. rsync://rpki.example.com/foo/corge/
 +.br
 +6. rsync://rpki.example.com/foo/corge/waldo/
 +.P
 +A validator following the `strict` strategy would download `bar`, download
 +`qux`, skip `bar`, download `corge/grault`, download `corge` and skip
 +`corge/waldo`.
 +.P
 +This is the slowest, but also the strictly correct sync strategy.
 +.RE
 +.P
 +root
 +.RS 4
 +For each publication point found, guess the root of its repository and RSYNC
 +that instead. Then skip any subsequent children of said root.
 +.P
 +(To guess the root of a repository, the validator counts four slashes, and
 +prunes the rest of the URL.)
 +.P
 +Reusing the caRepository URLs from the `strict` strategy (above) as example, a
 +validator following the `root` strategy would download
 +`rsync://rpki.example.com/foo`, and then skip everything else.
 +.P
 +Assuming that the repository is specifically structured to be found within as
 +few roots as possible, and they contain minimal RPKI-unrelated noise files, this
 +is the fastest synchronization strategy. At time of writing, this is true for
 +all the current official repositories.
 +.RE
 +.P
 +root-except-ta
 +.RS 4
 +Synchronizes the root certificate (the one pointed by the TAL) in 'strict' mode,
 +and once it's validated, synchronizes the rest of the repository in 'root' mode.
 +.P
 +Useful if you want 'root', but the root certificate is separated from the rest
 +of the repository. Also useful if you don't want the validator to download the
 +entire repository without first confirming the integrity and legitimacy of the
 +root certificate.
 +.RE
 +.RE
 +.P
 +
 +--maximum-certificate-depth=<unsigned integer>
 +.RS 4
 +Maximum allowable certificate chain length.
 +.P
 +(Required to prevent loops and "other degenerate forms of the logical RPKI
 +hierarchy." (RFC 6481))
 +.RE
 +.P
 +
 +--tal=<file>
 +.RS 4
 +Path to the TAL file the validation will sprawl from.
 +.P
 +The TAL ("Trust Anchor Locator") is a text file that lists a few URLs which can
 +be used to access the "Trust Anchor" (the root of a particular RPKI tree) and
 +its public key. (See RFC 7730.)
 +.RE
 +.P
 +
 +--shuffle-uris
 +.RS 4
 +Shuffle URIs in the TAL before accessing them.
 +.RE
 +.P
 +
 +--color-output
 +.RS 4
 +Print ANSI color codes.
 +.RE
 +.P
 +
 +--output-file-name-format=(global-url|local-path|file-name)
 +.RS 4
 +Decides which version of file names should be printed during most debug/error
 +messages.
 +.P
 +Suppose a certificate was downloaded from `rsync://rpki.example.com/foo/bar/baz.cer` into the local cache `repository/`:
 +.P
 +global-url
 +.RS 4
 +will print the certificate's name as `rsync://rpki.example.com/foo/bar/baz.cer`.
 +.RE
 +.P
 +local-path
 +.RS 4
 +will print the certificate's name as `repository/rpki.example.com/foo/bar/baz.cer`.
 +.RE
 +.P
 +file-name
 +.RS 4
 +will print the certificate's name as `baz.cer`.
 +.RE
 +.P
 +.RE
diff --cc src/Makefile.am
index c4bb88b7c5698fce6c39684239907ba69587e9be,74a897ab2e0d514b7f00306a4596d8f46056bd96..c6b56a885802d0033442af8fb3881812806d779d
@@@ -1,80 -1,38 +1,97 @@@
 -AM_CFLAGS = -pedantic -Wall -std=gnu11 -O3
 -AM_LDFLAGS = 
 -
 -bin_PROGRAMS = rtr_server
 -
 -rtr_server_SOURCES = main.c
 -rtr_server_SOURCES += address.c address.h
 -rtr_server_SOURCES += array_list.h
 -rtr_server_SOURCES += clients.c clients.h
 -rtr_server_SOURCES += common.c common.h
 -rtr_server_SOURCES += configuration.c configuration.h
 -rtr_server_SOURCES += csv.c csv.h
 -rtr_server_SOURCES += line_file.c line_file.h
 -rtr_server_SOURCES += notify.c notify.h
 -rtr_server_SOURCES += updates_daemon.c updates_daemon.h
 -rtr_server_SOURCES += vrps.c vrps.h
 -
 -rtr_server_SOURCES += rtr/err_pdu.c rtr/err_pdu.h
 -rtr_server_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h
 -rtr_server_SOURCES += rtr/pdu_sender.c rtr/pdu_sender.h
 -rtr_server_SOURCES += rtr/pdu_serializer.c rtr/pdu_serializer.h
 -rtr_server_SOURCES += rtr/pdu.c rtr/pdu.h
 -rtr_server_SOURCES += rtr/primitive_reader.c rtr/primitive_reader.h
 -rtr_server_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h
 -rtr_server_SOURCES += rtr/rtr.c rtr/rtr.h
 -
 -rtr_server_LDADD = ${JANSSON_LIBS}
 +# Comment these out during releases.
 +# Also increase optimization I guess (-O0 -> -On)
 +CFLAGS_DEBUG  = -DDEBUG
 +LDFLAGS_DEBUG = -rdynamic
 +
- bin_PROGRAMS = rpki_validator
- rpki_validator_SOURCES  = main.c
- rpki_validator_SOURCES += address.h address.c
- rpki_validator_SOURCES += algorithm.h algorithm.c
- rpki_validator_SOURCES += array_list.h
- rpki_validator_SOURCES += certificate_refs.h certificate_refs.c
- rpki_validator_SOURCES += common.h
- rpki_validator_SOURCES += config.h config.c
- rpki_validator_SOURCES += debug.h debug.c
- rpki_validator_SOURCES += extension.h extension.c
- rpki_validator_SOURCES += file.h file.c
- rpki_validator_SOURCES += line_file.h line_file.c
- rpki_validator_SOURCES += log.h log.c
- rpki_validator_SOURCES += nid.h nid.c
- rpki_validator_SOURCES += random.h random.c
- rpki_validator_SOURCES += resource.h resource.c
- rpki_validator_SOURCES += rpp.h rpp.c
- rpki_validator_SOURCES += sorted_array.h sorted_array.c
- rpki_validator_SOURCES += state.h state.c
- rpki_validator_SOURCES += str.h str.c
- rpki_validator_SOURCES += thread_var.h thread_var.c
- rpki_validator_SOURCES += uri.h uri.c
- rpki_validator_SOURCES += toml_handler.h toml_handler.c
- rpki_validator_SOURCES += rsync/rsync.h rsync/rsync.c
- rpki_validator_SOURCES += asn1/content_info.h asn1/content_info.c
- rpki_validator_SOURCES += asn1/decode.h asn1/decode.c
- rpki_validator_SOURCES += asn1/oid.h asn1/oid.c
- rpki_validator_SOURCES += asn1/signed_data.h asn1/signed_data.c
- rpki_validator_SOURCES += config/boolean.c config/boolean.h
- rpki_validator_SOURCES += config/filename_format.h config/filename_format.c
- rpki_validator_SOURCES += config/out_file.h config/out_file.c
- rpki_validator_SOURCES += config/str.c config/str.h
- rpki_validator_SOURCES += config/string_array.h config/string_array.c
- rpki_validator_SOURCES += config/sync_strategy.h config/sync_strategy.c
- rpki_validator_SOURCES += config/types.h
- rpki_validator_SOURCES += config/uint.c config/uint.h
- rpki_validator_SOURCES += crypto/base64.h crypto/base64.c
- rpki_validator_SOURCES += crypto/hash.h crypto/hash.c
- rpki_validator_SOURCES += object/certificate.h object/certificate.c
- rpki_validator_SOURCES += object/crl.h object/crl.c
- rpki_validator_SOURCES += object/ghostbusters.h object/ghostbusters.c
- rpki_validator_SOURCES += object/manifest.h object/manifest.c
- rpki_validator_SOURCES += object/name.h object/name.c
- rpki_validator_SOURCES += object/roa.h object/roa.c
- rpki_validator_SOURCES += object/signed_object.h object/signed_object.c
- rpki_validator_SOURCES += object/tal.h object/tal.c
- rpki_validator_SOURCES += object/vcard.h object/vcard.c
- rpki_validator_SOURCES += resource/ip4.h resource/ip4.c
- rpki_validator_SOURCES += resource/ip6.h resource/ip6.c
- rpki_validator_SOURCES += resource/asn.h resource/asn.c
- rpki_validator_CFLAGS  = -Wall
++bin_PROGRAMS = fort
++
++fort_SOURCES  = main.c
++
++fort_SOURCES += address.h address.c
++fort_SOURCES += algorithm.h algorithm.c
++fort_SOURCES += array_list.h
++fort_SOURCES += certificate_refs.h certificate_refs.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 += line_file.h line_file.c
++fort_SOURCES += log.h log.c
++fort_SOURCES += nid.h nid.c
++fort_SOURCES += random.h random.c
++fort_SOURCES += resource.h resource.c
++fort_SOURCES += rpp.h rpp.c
++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 += uri.h uri.c
++fort_SOURCES += json_handler.h json_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
++fort_SOURCES += asn1/signed_data.h asn1/signed_data.c
++
++fort_SOURCES += config/boolean.c config/boolean.h
++fort_SOURCES += config/filename_format.h config/filename_format.c
++fort_SOURCES += config/str.c config/str.h
++fort_SOURCES += config/string_array.h config/string_array.c
++fort_SOURCES += config/sync_strategy.h config/sync_strategy.c
++fort_SOURCES += config/types.h
++fort_SOURCES += config/uint.c config/uint.h
++fort_SOURCES += config/uint32.c config/uint32.h
++
++fort_SOURCES += crypto/base64.h crypto/base64.c
++fort_SOURCES += crypto/hash.h crypto/hash.c
++
++fort_SOURCES += object/certificate.h object/certificate.c
++fort_SOURCES += object/crl.h object/crl.c
++fort_SOURCES += object/ghostbusters.h object/ghostbusters.c
++fort_SOURCES += object/manifest.h object/manifest.c
++fort_SOURCES += object/name.h object/name.c
++fort_SOURCES += object/roa.h object/roa.c
++fort_SOURCES += object/signed_object.h object/signed_object.c
++fort_SOURCES += object/tal.h object/tal.c
++fort_SOURCES += object/vcard.h object/vcard.c
++
++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 += notify.c notify.h
++fort_SOURCES += updates_daemon.c updates_daemon.h
++fort_SOURCES += vrps.c vrps.h
++
++fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h
++fort_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h
++fort_SOURCES += rtr/pdu_sender.c rtr/pdu_sender.h
++fort_SOURCES += rtr/pdu_serializer.c rtr/pdu_serializer.h
++fort_SOURCES += rtr/pdu.c rtr/pdu.h
++fort_SOURCES += rtr/primitive_reader.c rtr/primitive_reader.h
++fort_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h
++fort_SOURCES += rtr/rtr.c rtr/rtr.h
++
++fort_CFLAGS  = -Wall
 +# Feel free to temporarily remove this one if you're not using gcc 7.3.0.
- rpki_validator_CFLAGS += $(GCC_WARNS)
- rpki_validator_CFLAGS += -std=gnu99 -O0 -g $(CFLAGS_DEBUG)
- rpki_validator_LDFLAGS = $(LDFLAGS_DEBUG)
++fort_CFLAGS += $(GCC_WARNS)
++fort_CFLAGS += -std=gnu99 -O0 -g $(CFLAGS_DEBUG)
++fort_LDFLAGS = $(LDFLAGS_DEBUG)
++fort_LDADD   = ${JANSSON_LIBS}
  
  # I'm tired of scrolling up, but feel free to comment this out.
--GCC_WARNS  = -fmax-errors=1
++#GCC_WARNS  = -fmax-errors=1
  
  # Please ready some arguments if you want to permanently remove some of these
  # flags. I gave a quick read to the documentation of all of these warnings, and
  # generally liked each of them.
--GCC_WARNS += -Wpedantic -pedantic-errors -Waddress -Walloc-zero -Walloca
++GCC_WARNS = -Wpedantic -pedantic-errors -Waddress -Walloc-zero -Walloca
  GCC_WARNS += -Wno-aggressive-loop-optimizations -Warray-bounds=2 -Wbool-compare
  GCC_WARNS += -Wbool-operation -Wno-builtin-declaration-mismatch -Wcast-align
  GCC_WARNS += -Wcast-qual -Wchar-subscripts -Wchkp -Wclobbered -Wcomment
@@@ -137,4 -95,4 +154,4 @@@ GCC_WARNS += -Wframe-larger-than=1024 -
  # Can't use because of dependencies: -Waggregate-return
  # Want to add, but needs work: -Wconversion, -Wsign-compare, -Wsign-conversion
  # Seem to require other compiler features: -Wchkp, -Wstack-protector,
--#     -Wstrict-aliasing, -Wstrict-overflow
++#     -Wstrict-aliasing
index 213e322a154d47f004c1abdd7fa1e95527dea910,ff00c9344434c338983448ecc183f43b0a702be0..0453c1f1a9dee1dcf57265005bc66b38ba4c4d47
@@@ -1,7 -1,9 +1,9 @@@
  #ifndef SRC_ARRAY_LIST_H_
  #define SRC_ARRAY_LIST_H_
  
 -#include <err.h>
  #include <errno.h>
+ #include <stdlib.h>
++#include "log.h"
  
  #define ARRAY_LIST(name, elem_type)                                   \
        struct name {                                                   \
@@@ -20,7 -22,7 +22,7 @@@
                list->len = 0;                                          \
                list->array = malloc(list->capacity                     \
                    * sizeof(elem_type));                               \
--              return (list->array != NULL) ? 0 : -ENOMEM;             \
++              return (list->array != NULL) ? 0 : pr_enomem();         \
        }                                                               \
                                                                        \
        static void                                                     \
diff --cc src/clients.c
index 0000000000000000000000000000000000000000,0dcfb8f4b74347658795633531e9652e969560f4..b3752af7e31bfa2a35c50208ab0b4f9a9d618811
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,171 +1,171 @@@
 -              warnx( "Clients DB couldn't be initialized");
+ #include "clients.h"
+ #include "array_list.h"
+ #include "common.h"
+ #define SADDR_IN(addr) ((struct sockaddr_in *)addr)
+ #define SADDR_IN6(addr) ((struct sockaddr_in6 *)addr)
+ ARRAY_LIST(clientsdb, struct client)
+ struct clientsdb clients_db;
+ /* Read and Write locks */
+ sem_t rlock, wlock;
+ /* Readers counter */
+ unsigned int rcounter;
+ int
+ clients_db_init(void)
+ {
+       int error;
+       error = clientsdb_init(&clients_db);
+       if (error)
 -                              if (ptr->sin_addr.s_addr ==
++              return error;
+       sem_init(&rlock, 0, 1);
+       sem_init(&wlock, 0, 1);
+       rcounter = 0;
+       return error;
+ }
+ static struct client *
+ get_client(struct sockaddr_storage *addr)
+ {
+       struct client *ptr;
+       read_lock(&rlock, &wlock, &rcounter);
+       ARRAYLIST_FOREACH(&clients_db, ptr)
+               if (ptr->sin_family == addr->ss_family) {
+                       if (ptr->sin_family == AF_INET) {
 -                                  ptr->sin6_addr.s6_addr32,
++                              if (ptr->addr.sin.s_addr ==
+                                   SADDR_IN(addr)->sin_addr.s_addr &&
+                                   ptr->sin_port ==
+                                   SADDR_IN(addr)->sin_port) {
+                                       read_unlock(&rlock, &wlock, &rcounter);
+                                       return ptr;
+                               }
+                       } else if (ptr->sin_family == AF_INET6)
+                               if (IN6_ARE_ADDR_EQUAL(
 -              client.sin_addr = SADDR_IN(addr)->sin_addr;
++                                  ptr->addr.sin6.s6_addr32,
+                                   SADDR_IN6(addr)->sin6_addr.s6_addr32) &&
+                                   ptr->sin_port ==
+                                   SADDR_IN6(addr)->sin6_port) {
+                                       read_unlock(&rlock, &wlock, &rcounter);
+                                       return ptr;
+                               }
+               }
+       read_unlock(&rlock, &wlock, &rcounter);
+       return NULL;
+ }
+ static int
+ create_client(int fd, struct sockaddr_storage *addr, u_int8_t rtr_version)
+ {
+       struct client client;
+       int error;
+       client.fd = fd;
+       client.sin_family = addr->ss_family;
+       if (addr->ss_family == AF_INET) {
 -              client.sin6_addr = SADDR_IN6(addr)->sin6_addr;
++              client.addr.sin = SADDR_IN(addr)->sin_addr;
+               client.sin_port = SADDR_IN(addr)->sin_port;
+       } else if (addr->ss_family == AF_INET6) {
 -              warnx("Couldn't allocate new clients DB");
 -              return;
++              client.addr.sin6 = SADDR_IN6(addr)->sin6_addr;
+               client.sin_port = SADDR_IN6(addr)->sin6_port;
+       }
+       client.rtr_version = rtr_version;
+       sem_wait(&wlock);
+       error = clientsdb_add(&clients_db, &client);
+       sem_post(&wlock);
+       return error;
+ }
+ /*
+  * If the ADDR isn't already stored, store it; otherwise update its file
+  * descriptor.
+  *
+  * Return the creation/update result.
+  *
+  * Code error -EINVAL will be returned when a client exists but its RTR version
+  * isn't the same as in the DB.
+  */
+ int
+ update_client(int fd, struct sockaddr_storage *addr, u_int8_t rtr_version)
+ {
+       struct client *client;
+       client = get_client(addr);
+       if (client == NULL)
+               return create_client(fd, addr, rtr_version);
+       /*
+        * Isn't ready to handle distinct version on clients reconnection, but
+        * for now there's no problem since only RTR v0 is supported.
+        */
+       if (client->rtr_version != rtr_version)
+               return -EINVAL;
+       client->fd = fd;
+       return 0;
+ }
+ size_t
+ client_list(struct client **clients)
+ {
+       size_t len;
+       read_lock(&rlock, &wlock, &rcounter);
+       *clients = clients_db.array;
+       len = clients_db.len;
+       read_unlock(&rlock, &wlock, &rcounter);
+       return len;
+ }
+ static void
+ client_destroy(struct client *client)
+ {
+       /* Didn't allocate something, so do nothing */
+ }
+ void
+ clients_forget(int fd)
+ {
+       struct clientsdb *new_db;
+       struct client *ptr;
+       new_db = malloc(sizeof(struct clientsdb));
+       if (new_db == NULL) {
++              pr_err("Couldn't allocate new clients DB");
++              return; /* TODO This is not acceptable... */
+       }
+       clientsdb_init(new_db);
+       read_lock(&rlock, &wlock, &rcounter);
+       ARRAYLIST_FOREACH(&clients_db, ptr)
+               if (ptr->fd != fd)
+                       clientsdb_add(new_db, ptr);
+       read_unlock(&rlock, &wlock, &rcounter);
+       sem_wait(&wlock);
+       clientsdb_cleanup(&clients_db, client_destroy);
+       clients_db = *new_db;
+       sem_post(&wlock);
+       free(new_db);
+ }
+ void
+ clients_db_destroy(void)
+ {
+       sem_wait(&wlock);
+       clientsdb_cleanup(&clients_db, client_destroy);
+       sem_post(&wlock);
+       sem_destroy(&wlock);
+       sem_destroy(&rlock);
+ }
diff --cc src/clients.h
index 0000000000000000000000000000000000000000,b80be4c38618c47753fe0fe5dbac14d099d4f579..571f15ca5dc9b1db539c0a01760f6e55fec00d07
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,24 +1,24 @@@
 -              struct in_addr sin_addr;
 -              struct in6_addr sin6_addr;
 -      };
+ #ifndef SRC_CLIENTS_H_
+ #define SRC_CLIENTS_H_
+ #include <arpa/inet.h>
+ struct client {
+       int fd;
+       sa_family_t sin_family;
+       union {
++              struct in_addr sin;
++              struct in6_addr sin6;
++      } addr;
+       in_port_t sin_port;
+       u_int8_t rtr_version;
+ };
+ int clients_db_init(void);
+ int update_client(int, struct sockaddr_storage *, u_int8_t);
+ size_t client_list(struct client **);
+ void clients_forget(int);
+ void clients_db_destroy(void);
+ #endif /* SRC_CLIENTS_H_ */
diff --cc src/common.h
index b56db53d4f850dea89e266872d7e84ece1dc0c81,042b5bd8e3aafee43501ff265153191a929780f4..b8ade4bc7e53447cacb5462d1aac28d3c603562d
@@@ -1,18 -1,19 +1,29 @@@
 -#ifndef _SRC_COMMON_H_
 -#define _SRC_COMMON_H_
 +#ifndef SRC_RTR_COMMON_H_
 +#define SRC_RTR_COMMON_H_
  
 -#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
 -
 -#define EUNIMPLEMENTED 566456
+ #include <semaphore.h>
 +/* "I think that this is not supposed to be implemented." */
 +#define ENOTSUPPORTED 3172
 +/* "I haven't implemented this yet." */
 +#define ENOTIMPLEMENTED 3173
 +/*
 + * "URI was not RSYNC; ignore it."
 + * Not really an error. The RFCs usually declare URI lists; usually only one of
 + * them is required to be RSYNC and the others should be skipped (until we
 + * start supporting them.)
 + */
 +#define ENOTRSYNC 3174
  
- #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
+ /*
 - * FYI: The error functions are warn() and warnx().
 - * warn() automatically appends the errno string message (strerror()), warnx()
 - * does not.
++ * If you're wondering why I'm not using -abs(error), it's because abs(INT_MIN)
++ * overflows, so gcc complains sometimes.
+  */
++#define ENSURE_NEGATIVE(error) (((error) < 0) ? (error) : -(error))
++
++#define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0]))
+ void read_lock(sem_t *, sem_t *, unsigned int *);
+ void read_unlock(sem_t *, sem_t *, unsigned int *);
  
 -#endif /* _SRC_COMMON_H_ */
 +#endif /* SRC_RTR_COMMON_H_ */
diff --cc src/config.c
index 6c1c69facbc87086bf3070dba61de21b920d4dd3,0000000000000000000000000000000000000000..6677388f67c804c8897f11c47beebb9ed7b95b24
mode 100644,000000..100644
--- /dev/null
@@@ -1,640 -1,0 +1,728 @@@
- #include "toml_handler.h"
 +#include "config.h"
 +
 +#include <limits.h>
 +#include <stdlib.h>
 +#include <string.h>
 +#include <errno.h>
 +#include <getopt.h>
++#include <sys/socket.h>
 +
 +#include "common.h"
++#include "json_handler.h"
 +#include "log.h"
- #include "config/out_file.h"
 +#include "config/boolean.h"
- /**
-  * Please note that this is actually two `for`s stacked together, so don't use
-  * `break` nor `continue` to get out.
-  */
- #define FOREACH_OPTION(groups, grp, opt, type)                        \
-       for (grp = groups; grp->name != NULL; grp++)            \
-               for (opt = grp->options; opt->id != 0; opt++)   \
-                       if ((opt->availability == 0) ||         \
-                           (opt->availability & type))
 +#include "config/str.h"
 +#include "config/uint.h"
-       /** TAL file name/location. */
++#include "config/uint32.h"
 +
 +/**
 + * To add a member to this structure,
 + *
 + * 1. Add it.
 + * 2. Add its metadata somewhere in @groups.
 + * 3. Add default value to set_default_values().
 + * 4. Create the getter.
 + *
 + * Assuming you don't need to create a data type, that should be all.
 + */
 +struct rpki_config {
-        * Shuffle uris in tal?
-        * (https://tools.ietf.org/html/rfc7730#section-3, last paragraphs)
++      /** TAL file name or directory. TODO support directories */
 +      char *tal;
 +      /** Path of our local clone of the repository */
 +      char *local_repository;
 +      /** Synchronization (currently only RSYNC) download strategy. */
 +      enum sync_strategy sync_strategy;
 +      /**
-       bool shuffle_uris;
++       * Handle TAL URIs in random order?
++       * (https://tools.ietf.org/html/rfc7730#section-3, last
++       * paragraphs)
 +       */
-               /** Output stream where the valid ROAs will be dumped. */
-               struct config_out_file roa_output;
-       } output;
++      bool shuffle_tal_uris;
 +      /**
 +       * rfc6487#section-7.2, last paragraph.
 +       * Prevents arbitrarily long paths and loops.
 +       */
 +      unsigned int maximum_certificate_depth;
 +
++      struct {
++              /** The listener address of the RTR server. */
++              char *address;
++              /** TODO document */
++              char *port;
++              /** Maximum accepted client connections */
++              unsigned int queue;
++
++              /** Interval used to look for updates at VRPs location */
++              /* TODO rename */
++              unsigned int vrps_check_interval;
++
++              /** Intervals use at RTR v1 End of data PDU **/
++              u_int32_t refresh_interval;
++              u_int32_t retry_interval;
++              u_int32_t expire_interval;
++      } server;
++
 +      struct {
 +              char *program;
 +              struct {
 +                      struct string_array flat;
 +                      struct string_array recursive;
 +              } args;
 +      } rsync;
 +
 +      struct {
 +              /** Print ANSI color codes? */
 +              bool color;
 +              /** Format in which file names will be printed. */
 +              enum filename_format filename_format;
- DECLARE_HANDLE_FN(handle_toml);
++      } log;
 +};
 +
 +static void print_usage(FILE *, bool);
 +
 +#define DECLARE_HANDLE_FN(name)                                               \
 +      static int name(                                                \
 +          struct option_field const *,                                \
 +          char *                                                      \
 +      )
 +DECLARE_HANDLE_FN(handle_help);
 +DECLARE_HANDLE_FN(handle_usage);
 +DECLARE_HANDLE_FN(handle_version);
- static const struct option_field global_fields[] = {
++DECLARE_HANDLE_FN(handle_json);
 +
 +static char const *program_name;
 +static struct rpki_config rpki_config;
 +
 +/**
 + * An option that takes no arguments, is not correlated to any rpki_config
 + * fields, and is entirely managed by its handler function.
 + */
 +static const struct global_type gt_callback = {
 +      .has_arg = no_argument,
 +};
 +
-               .handler = handle_toml,
-               .doc = "TOML file additional configuration will be read from",
++static const struct option_field options[] = {
++
++      /* ARGP-only, non-fields */
 +      {
 +              .id = 'h',
 +              .name = "help",
 +              .type = &gt_callback,
 +              .handler = handle_help,
 +              .doc = "Give this help list",
 +              .availability = AVAILABILITY_GETOPT,
 +      }, {
 +              .id = 1000,
 +              .name = "usage",
 +              .type = &gt_callback,
 +              .handler = handle_usage,
 +              .doc = "Give a short usage message",
 +              .availability = AVAILABILITY_GETOPT,
 +      }, {
 +              .id = 'V',
 +              .name = "version",
 +              .type = &gt_callback,
 +              .handler = handle_version,
 +              .doc = "Print program version",
 +              .availability = AVAILABILITY_GETOPT,
 +      }, {
 +              .id = 'f',
 +              .name = "configuration-file",
 +              .type = &gt_string,
-       { 0 },
- };
++              .handler = handle_json,
++              .doc = "JSON file additional configuration will be read from",
 +              .arg_doc = "<file>",
 +              .availability = AVAILABILITY_GETOPT,
++      },
++
++      /* Root fields */
++      {
++              .id = 't',
++              .name = "tal",
++              .type = &gt_string,
++              .offset = offsetof(struct rpki_config, tal),
++              .doc = "Path to the TAL file",
++              .arg_doc = "<file>",
 +      }, {
 +              .id = 'r',
 +              .name = "local-repository",
 +              .type = &gt_string,
 +              .offset = offsetof(struct rpki_config, local_repository),
 +              .doc = "Directory where the repository local cache will be stored/read",
 +              .arg_doc = "<directory>",
 +      }, {
 +              .id = 1001,
 +              .name = "sync-strategy",
 +              .type = &gt_sync_strategy,
 +              .offset = offsetof(struct rpki_config, sync_strategy),
 +              .doc = "RSYNC download strategy",
++      }, {
++              .id = 2000,
++              .name = "shuffle-uris",
++              .type = &gt_bool,
++              .offset = offsetof(struct rpki_config, shuffle_tal_uris),
++              .doc = "Shuffle URIs in the TAL before accessing them",
 +      }, {
 +              .id = 1002,
 +              .name = "maximum-certificate-depth",
 +              .type = &gt_u_int,
 +              .offset = offsetof(struct rpki_config,
 +                  maximum_certificate_depth),
 +              .doc = "Maximum allowable certificate chain length",
 +              .min = 1,
 +              /**
 +               * It cannot be UINT_MAX, because then the actual number will
 +               * overflow and will never be bigger than this.
 +               */
 +              .max = UINT_MAX - 1,
 +      },
- static const struct option_field tal_fields[] = {
 +
-               .id = 't',
-               .name = "tal",
++      /* Server fields */
 +      {
-               .offset = offsetof(struct rpki_config, tal),
-               .doc = "Path to the TAL file",
-               .arg_doc = "<file>",
++              .id = 5000,
++              .name = "server.address",
 +              .type = &gt_string,
-               .id = 2000,
-               .name = "shuffle-uris",
-               .type = &gt_bool,
-               .offset = offsetof(struct rpki_config, shuffle_uris),
-               .doc = "Shuffle URIs in the TAL before accessing them",
++              .offset = offsetof(struct rpki_config, server.address),
++              .doc = "The listener address of the RTR server.",
 +      }, {
-       { 0 },
- };
++              .id = 5001,
++              .name = "server.port",
++              .type = &gt_string,
++              .offset = offsetof(struct rpki_config, server.port),
++              .doc = "", /* TODO */
++      }, {
++              .id = 5003,
++              .name = "server.queue",
++              .type = &gt_u_int,
++              .offset = offsetof(struct rpki_config, server.queue),
++              .doc = "Maximum accepted client connections",
++              .min = 1,
++              .max = SOMAXCONN,
++      }, {
++              .id = 5004,
++              .name = "server.vrps-check-interval",
++              .type = &gt_u_int,
++              .offset = offsetof(struct rpki_config, server.vrps_check_interval),
++              .doc = "Interval used to look for updates at VRPs location",
++              /*
++               * RFC 6810 and 8210:
++               * The cache MUST rate-limit Serial Notifies to no more frequently than
++               * one per minute.
++               */
++              .min = 60,
++              .max = 7200,
++      }, {
++              .id = 5005,
++              .name = "server.rtr-interval.refresh",
++              .type = &gt_u_int32,
++              .offset = offsetof(struct rpki_config, server.refresh_interval),
++              .doc = "Intervals use at RTR v1 End of data PDU",
++              .min = 1,
++              .max = 86400,
++      }, {
++              .id = 5006,
++              .name = "server.rtr-interval.retry",
++              .type = &gt_u_int32,
++              .offset = offsetof(struct rpki_config, server.retry_interval),
++              .doc = "",
++              .min = 1,
++              .max = 7200,
++      }, {
++              .id = 5007,
++              .name = "server.rtr-interval.expire",
++              .type = &gt_u_int32,
++              .offset = offsetof(struct rpki_config, server.expire_interval),
++              .doc = "",
++              .min = 600,
++              .max = 172800,
 +      },
- static const struct option_field rsync_fields[] = {
 +
-               .name = "program",
++      /* RSYNC fields */
 +      {
 +              .id = 3000,
-               .availability = AVAILABILITY_TOML,
++              .name = "rsync.program",
 +              .type = &gt_string,
 +              .offset = offsetof(struct rpki_config, rsync.program),
 +              .doc = "Name of the program needed to execute an RSYNC",
 +              .arg_doc = "<path to program>",
-               .name = "arguments-recursive",
++              .availability = AVAILABILITY_JSON,
 +      }, {
 +              .id = 3001,
-               .availability = AVAILABILITY_TOML,
++              .name = "rsync.arguments-recursive",
 +              .type = &gt_string_array,
 +              .offset = offsetof(struct rpki_config, rsync.args.recursive),
 +              .doc = "RSYNC program arguments that will trigger a recursive RSYNC",
-               .name = "arguments-flat",
++              .availability = AVAILABILITY_JSON,
 +      }, {
 +              .id = 3002,
-               .availability = AVAILABILITY_TOML,
++              .name = "rsync.arguments-flat",
 +              .type = &gt_string_array,
 +              .offset = offsetof(struct rpki_config, rsync.args.flat),
 +              .doc = "RSYNC program arguments that will trigger a non-recursive RSYNC",
-       { 0 },
- };
++              .availability = AVAILABILITY_JSON,
 +      },
- static const struct option_field output_fields[] = {
 +
-               .name = "color-output",
++      /* Logging fields */
 +      {
 +              .id = 'c',
-               .offset = offsetof(struct rpki_config, output.color),
++              .name = "log.color-output",
 +              .type = &gt_bool,
-               .name = "output-file-name-format",
++              .offset = offsetof(struct rpki_config, log.color),
 +              .doc = "Print ANSI color codes.",
 +      }, {
 +              .id = 4000,
-               .offset = offsetof(struct rpki_config, output.filename_format),
++              .name = "log.file-name-format",
 +              .type = &gt_filename_format,
-       }, {
-               .id = 'o',
-               .name = "roa-output-file",
-               .type = &gt_out_file,
-               .offset = offsetof(struct rpki_config, output.roa_output),
-               .doc = "File where the valid ROAs will be dumped.",
++              .offset = offsetof(struct rpki_config, log.filename_format),
 +              .doc = "File name variant to print during debug/error messages",
-       { 0 },
- };
 +      },
- static const struct group_fields groups[] = {
-       {
-               .name = "root",
-               .options = global_fields,
-       }, {
-               .name = "tal",
-               .options = tal_fields,
-       }, {
-               .name = "rsync",
-               .options = rsync_fields,
-       }, {
-               .name = "output",
-               .options = output_fields,
-       },
-       { NULL },
 +
- handle_toml(struct option_field const *field, char *file_name)
++      { 0 },
 +};
 +
 +/**
 + * Returns true if @field is the descriptor of one of the members of the
 + * struct rpki_config structure, false otherwise.
 + * (Alternatively: Returns true if @field->offset is valid, false otherwise.)
 + */
 +static bool
 +is_rpki_config_field(struct option_field const *field)
 +{
 +      return field->handler == NULL;
 +}
 +
 +void *
 +get_rpki_config_field(struct option_field const *field)
 +{
 +      return ((unsigned char *) &rpki_config) + field->offset;
 +}
 +
 +static int
 +handle_help(struct option_field const *field, char *arg)
 +{
 +      print_usage(stdout, true);
 +      exit(0);
 +}
 +
 +static int
 +handle_usage(struct option_field const *field, char *arg)
 +{
 +      print_usage(stdout, false);
 +      exit(0);
 +}
 +
 +static int
 +handle_version(struct option_field const *field, char *arg)
 +{
 +      printf("0.0.1\n");
 +      exit(0);
 +}
 +
 +static int
-       struct group_fields const *group;
++handle_json(struct option_field const *field, char *file_name)
 +{
 +      return set_config_from_file(file_name);
 +}
 +
 +static bool
 +is_alphanumeric(int chara)
 +{
 +      return ('a' <= chara && chara <= 'z')
 +          || ('A' <= chara && chara <= 'Z')
 +          || ('0' <= chara && chara <= '9');
 +}
 +
 +/**
 + * "struct option" is the array that getopt expects.
 + * "struct args_flag" is our option metadata.
 + */
 +static int
 +construct_getopt_options(struct option **_long_opts, char **_short_opts)
 +{
-       FOREACH_OPTION(groups, group, opt, AVAILABILITY_GETOPT) {
 +      struct option_field const *opt;
 +      struct option *long_opts;
 +      char *short_opts;
 +      unsigned int total_long_options;
 +      unsigned int total_short_options;
 +
 +      total_long_options = 0;
 +      total_short_options = 0;
-       FOREACH_OPTION(groups, group, opt, AVAILABILITY_GETOPT) {
++      FOREACH_OPTION(options, opt, AVAILABILITY_GETOPT) {
 +              total_long_options++;
 +              if (is_alphanumeric(opt->id)) {
 +                      total_short_options++;
 +                      if (opt->type->has_arg != no_argument)
 +                              total_short_options++; /* ":" */
 +              }
 +      }
 +
 +      /* +1 NULL end, means end of array. */
 +      long_opts = calloc(total_long_options + 1, sizeof(struct option));
 +      if (long_opts == NULL)
 +              return pr_enomem();
 +      short_opts = malloc(total_short_options + 1);
 +      if (short_opts == NULL) {
 +              free(long_opts);
 +              return pr_enomem();
 +      }
 +
 +      *_long_opts = long_opts;
 +      *_short_opts = short_opts;
 +
-       struct group_fields const *grp;
++      FOREACH_OPTION(options, opt, AVAILABILITY_GETOPT) {
 +              long_opts->name = opt->name;
 +              long_opts->has_arg = opt->type->has_arg;
 +              long_opts->flag = NULL;
 +              long_opts->val = opt->id;
 +              long_opts++;
 +
 +              if (is_alphanumeric(opt->id)) {
 +                      *short_opts = opt->id;
 +                      short_opts++;
 +                      if (opt->type->has_arg != no_argument) {
 +                              *short_opts = ':';
 +                              short_opts++;
 +                      }
 +              }
 +      }
 +
 +      *short_opts = '\0';
 +      return 0;
 +}
 +
 +static void
 +print_config(void)
 +{
-       FOREACH_OPTION(groups, grp, opt, 0xFFFF)
 +      struct option_field const *opt;
 +
 +      pr_info("Configuration {");
 +      pr_indent_add();
 +
-                       opt->type->print(grp, opt, get_rpki_config_field(opt));
++      FOREACH_OPTION(options, opt, 0xFFFF)
 +              if (is_rpki_config_field(opt) && opt->type->print != NULL)
-       if (rpki_config.local_repository == NULL)
-               return pr_enomem();
++                      opt->type->print(opt, get_rpki_config_field(opt));
 +
 +      pr_indent_rm();
 +      pr_info("}");
 +}
 +
 +static int
 +set_default_values(void)
 +{
 +      static char const *default_rsync_args[] = {
 +              "--recursive",
 +              "--delete",
 +              "--times",
 +              "--contimeout=20",
 +              "$REMOTE",
 +              "$LOCAL",
 +      };
 +
 +      int error;
 +
 +      /*
 +       * Values that might need to be freed WILL be freed, so use heap
 +       * duplicates.
 +       */
 +
++      rpki_config.server.address = NULL;
++      rpki_config.server.port = strdup("323");
++      if (rpki_config.server.port == NULL)
++              return pr_enomem();
++
++      rpki_config.server.queue = 10;
++      rpki_config.server.vrps_check_interval = 60;
++      rpki_config.server.refresh_interval = 3600;
++      rpki_config.server.retry_interval = 600;
++      rpki_config.server.expire_interval = 7200;
++
 +      rpki_config.tal = NULL;
 +
 +      rpki_config.local_repository = strdup("repository/");
-       rpki_config.shuffle_uris = false;
++      if (rpki_config.local_repository == NULL) {
++              error = pr_enomem();
++              goto revert_port;
++      }
 +
 +      rpki_config.sync_strategy = SYNC_ROOT;
-       rpki_config.output.color = false;
-       rpki_config.output.filename_format = FNF_GLOBAL;
-       rpki_config.output.roa_output.fd = NULL;
-       rpki_config.output.roa_output.file_name = NULL;
++      rpki_config.shuffle_tal_uris = false;
 +      rpki_config.maximum_certificate_depth = 32;
 +
 +      rpki_config.rsync.program = strdup("rsync");
 +      if (rpki_config.rsync.program == NULL) {
 +              error = pr_enomem();
 +              goto revert_repository;
 +      }
 +
 +      error = string_array_init(&rpki_config.rsync.args.recursive,
 +          default_rsync_args, ARRAY_LEN(default_rsync_args));
 +      if (error)
 +              goto revert_rsync_program;
 +      /* Simply remove --recursive and --delete. */
 +      error = string_array_init(&rpki_config.rsync.args.flat,
 +          default_rsync_args + 2, ARRAY_LEN(default_rsync_args) - 2);
 +      if (error)
 +              goto revert_recursive_array;
 +
-       struct group_fields const *group;
++      rpki_config.log.color = false;
++      rpki_config.log.filename_format = FNF_GLOBAL;
 +
 +      return 0;
 +
 +revert_recursive_array:
 +      string_array_cleanup(&rpki_config.rsync.args.recursive);
 +revert_rsync_program:
 +      free(rpki_config.rsync.program);
 +revert_repository:
 +      free(rpki_config.local_repository);
++revert_port:
++      free(rpki_config.server.port);
 +      return error;
 +}
 +
 +static int
 +validate_config(void)
 +{
 +      return (rpki_config.tal != NULL)
 +          ? 0
 +          : pr_err("The TAL file (--tal) is mandatory.");
 +}
 +
 +static void
 +print_usage(FILE *stream, bool print_doc)
 +{
-       FOREACH_OPTION(groups, group, option, AVAILABILITY_GETOPT) {
 +      struct option_field const *option;
 +      char const *arg_doc;
 +
 +      fprintf(stream, "Usage: %s\n", program_name);
-       struct group_fields const *group;
++      FOREACH_OPTION(options, option, AVAILABILITY_GETOPT) {
 +              fprintf(stream, "\t[");
 +              fprintf(stream, "--%s", option->name);
 +
 +              if (option->arg_doc != NULL)
 +                      arg_doc = option->arg_doc;
 +              else if (option->type->arg_doc != NULL)
 +                      arg_doc = option->type->arg_doc;
 +              else
 +                      arg_doc = NULL;
 +
 +              switch (option->type->has_arg) {
 +              case no_argument:
 +                      break;
 +              case optional_argument:
 +              case required_argument:
 +                      if (arg_doc != NULL)
 +                              fprintf(stream, "=%s", arg_doc);
 +                      break;
 +              }
 +
 +              fprintf(stream, "]\n");
 +
 +              if (print_doc)
 +                      fprintf(stream, "\t    (%s)\n", option->doc);
 +      }
 +}
 +
 +
 +static int
 +handle_opt(int opt)
 +{
-       FOREACH_OPTION(groups, group, option, AVAILABILITY_GETOPT) {
 +      struct option_field const *option;
 +
- void
- get_group_fields(struct group_fields const **group_fields)
++      FOREACH_OPTION(options, option, AVAILABILITY_GETOPT) {
 +              if (option->id == opt) {
 +                      return is_rpki_config_field(option)
 +                          ? option->type->parse.argv(option, optarg,
 +                                get_rpki_config_field(option))
 +                          : option->handler(option, optarg);
 +              }
 +      }
 +
 +      pr_err("Unrecognized option: %d", opt);
 +      return -ESRCH;
 +}
 +
 +int
 +handle_flags_config(int argc, char **argv)
 +{
 +      struct option *long_opts;
 +      char *short_opts;
 +      int opt;
 +      int error;
 +
 +      program_name = argv[0];
 +      error = set_default_values();
 +      if (error)
 +              return error;
 +
 +      long_opts = NULL;
 +      short_opts = NULL;
 +      error = construct_getopt_options(&long_opts, &short_opts);
 +      if (error)
 +              goto end; /* Error msg already printed. */
 +
 +      while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL))
 +          != -1) {
 +              error = handle_opt(opt);
 +              if (error)
 +                      goto end;
 +      }
 +
 +      /*
 +       * This triggers when the user runs something like
 +       * `rpki-validator disable-rsync` instead of
 +       * `rpki-validator --disable-rsync`.
 +       * This program does not have unflagged payload.
 +       */
 +      if (optind < argc) {
 +              error = pr_err("I don't know what '%s' is.", argv[optind]);
 +              goto end;
 +      }
 +
 +      error = validate_config();
 +
 +end:
 +      if (error)
 +              free_rpki_config();
 +      else
 +              print_config();
 +
 +      free(long_opts);
 +      free(short_opts);
 +      return error;
 +
 +}
 +
-       *group_fields = groups;
++struct option_field const *
++get_option_metadatas(void)
++{
++      return options;
++}
++
++char const *
++config_get_server_address(void)
 +{
- config_get_shuffle_uris(void)
++      return rpki_config.server.address;
++}
++
++char const *
++config_get_server_port(void)
++{
++      return rpki_config.server.port;
++}
++
++int
++config_get_server_queue(void)
++{
++      /*
++       * The range of this is 1-<small number>, so adding signedness is safe.
++       */
++      return rpki_config.server.queue;
++}
++
++unsigned int
++config_get_vrps_check_interval(void)
++{
++      return rpki_config.server.vrps_check_interval;
++}
++
++u_int32_t
++config_get_refresh_interval(void)
++{
++      return rpki_config.server.refresh_interval;
++}
++
++u_int32_t
++config_get_retry_interval(void)
++{
++      return rpki_config.server.retry_interval;
++}
++
++u_int32_t
++config_get_expire_interval(void)
++{
++      return rpki_config.server.expire_interval;
 +}
 +
 +char const *
 +config_get_tal(void)
 +{
 +      return rpki_config.tal;
 +}
 +
 +char const *
 +config_get_local_repository(void)
 +{
 +      return rpki_config.local_repository;
 +}
 +
 +enum sync_strategy
 +config_get_sync_strategy(void)
 +{
 +      return rpki_config.sync_strategy;
 +}
 +
 +bool
-       return rpki_config.shuffle_uris;
++config_get_shuffle_tal_uris(void)
 +{
-       return rpki_config.output.color;
++      return rpki_config.shuffle_tal_uris;
 +}
 +
 +unsigned int
 +config_get_max_cert_depth(void)
 +{
 +      return rpki_config.maximum_certificate_depth;
 +}
 +
 +bool
 +config_get_color_output(void)
 +{
-       return rpki_config.output.filename_format;
- }
- FILE *
- config_get_roa_output(void)
- {
-       return (rpki_config.output.roa_output.fd != NULL)
-           ? rpki_config.output.roa_output.fd
-           : stdout;
++      return rpki_config.log.color;
 +}
 +
 +enum filename_format
 +config_get_filename_format(void)
 +{
-       pr_crit("Invalid sync strategy: '%u'", rpki_config.sync_strategy);
++      return rpki_config.log.filename_format;
 +}
 +
 +char *
 +config_get_rsync_program(void)
 +{
 +      return rpki_config.rsync.program;
 +}
 +
 +struct string_array const *
 +config_get_rsync_args(bool is_ta)
 +{
 +      switch (rpki_config.sync_strategy) {
 +      case SYNC_ROOT:
 +              return &rpki_config.rsync.args.recursive;
 +      case SYNC_ROOT_EXCEPT_TA:
 +              return is_ta
 +                  ? &rpki_config.rsync.args.flat
 +                  : &rpki_config.rsync.args.recursive;
 +      case SYNC_STRICT:
 +              return &rpki_config.rsync.args.flat;
 +      case SYNC_OFF:
 +              break;
 +      }
 +
-       struct group_fields const *group;
++      pr_crit("Invalid sync strategy: '%u'",
++          rpki_config.sync_strategy);
 +      /*
 +       * Return something usable anyway; don't want to check NULL.
 +       * This is supposed to be unreachable code anyway.
 +       */
 +      return &rpki_config.rsync.args.recursive;
 +}
 +
 +void
 +free_rpki_config(void)
 +{
-       FOREACH_OPTION(groups, group, option, 0xFFFF)
 +      struct option_field const *option;
 +
++      FOREACH_OPTION(options, option, 0xFFFF)
 +              if (is_rpki_config_field(option) && option->type->free != NULL)
 +                      option->type->free(get_rpki_config_field(option));
 +}
diff --cc src/config.h
index 341968c889e4472d33a78290f72e274070475564,0000000000000000000000000000000000000000..1eca1e09b8c9e2e57750df6ece99802c1cb259c6
mode 100644,000000..100644
--- /dev/null
@@@ -1,31 -1,0 +1,39 @@@
- bool config_get_shuffle_uris(void);
 +#ifndef SRC_CONFIG_H_
 +#define SRC_CONFIG_H_
 +
 +#include <stdbool.h>
++#include <stdint.h>
 +
 +#include "config/filename_format.h"
 +#include "config/sync_strategy.h"
 +#include "config/string_array.h"
 +#include "config/types.h"
 +
 +/* Init/destroy */
 +int handle_flags_config(int , char **);
 +void free_rpki_config(void);
 +
 +/* Getters */
++char const *config_get_server_address(void);
++char const *config_get_server_port(void);
++int config_get_server_queue(void);
++unsigned int config_get_vrps_check_interval(void);
++uint32_t config_get_refresh_interval(void);
++uint32_t config_get_retry_interval(void);
++uint32_t config_get_expire_interval(void);
++
 +char const *config_get_tal(void);
 +char const *config_get_local_repository(void);
 +enum sync_strategy config_get_sync_strategy(void);
- FILE *config_get_roa_output(void);
++bool config_get_shuffle_tal_uris(void);
 +unsigned int config_get_max_cert_depth(void);
 +bool config_get_color_output(void);
 +enum filename_format config_get_filename_format(void);
- /* Needed public by the TOML module */
 +char *config_get_rsync_program(void);
 +struct string_array const *config_get_rsync_args(bool);
 +
- void get_group_fields(struct group_fields const **);
++/* Needed public by the JSON module */
 +void *get_rpki_config_field(struct option_field const *);
++struct option_field const *get_option_metadatas(void);
 +
 +#endif /* SRC_CONFIG_H_ */
index 0a6ceb2f25ad09c1d797aec77964aea91393751d,0000000000000000000000000000000000000000..2c909d237460f5ad3120104f0987b5fc29465038
mode 100644,000000..100644
--- /dev/null
@@@ -1,66 -1,0 +1,57 @@@
- print_bool(struct group_fields const *group, struct option_field const *field,
-     void *_value)
 +#include "config/boolean.h"
 +
 +#include <getopt.h>
 +#include <stdbool.h>
 +#include <string.h>
 +#include "log.h"
 +
++#define DEREFERENCE(void_value) (*((bool *) void_value))
++
 +static void
-       bool *value = _value;
-       pr_info("%s.%s: %s", group->name, field->name,
-           (*value) ? "true" : "false");
++print_bool(struct option_field const *field, void *value)
 +{
-       bool *value = result;
++      pr_info("%s: %s", field->name, DEREFERENCE(value) ? "true" : "false");
 +}
 +
 +static int
 +parse_argv_bool(struct option_field const *field, char const *str, void *result)
 +{
-               *value = true;
 +      if (str == NULL) {
-               *value = true;
++              DEREFERENCE(result) = true;
 +              return 0;
 +      }
 +
 +      if (strcmp(str, "true") == 0) {
-               *value = false;
++              DEREFERENCE(result) = true;
 +              return 0;
 +      }
 +
 +      if (strcmp(str, "false") == 0) {
- parse_toml_bool(struct option_field const *opt, struct toml_table_t *toml,
-     void *_result)
++              DEREFERENCE(result) = false;
 +              return 0;
 +      }
 +
 +      return pr_err("Cannot parse '%s' as a bool (true|false).", str);
 +}
 +
 +static int
-       const char *raw;
-       int value;
-       bool *result;
-       raw = toml_raw_in(toml, opt->name);
-       if (raw == NULL)
-               return 0;
-       if (toml_rtob(raw, &value) == -1)
-               return pr_err("Cannot parse '%s' as a boolean.", raw);
++parse_json_bool(struct option_field const *opt, struct json_t *json,
++    void *result)
 +{
-       result = _result;
-       *result = value;
++      if (!json_is_boolean(json)) {
++              return pr_err("The '%s' element is not a JSON boolean.",
++                  opt->name);
++      }
 +
-       .parse.toml = parse_toml_bool,
++      DEREFERENCE(result) = json_boolean_value(json);
 +      return 0;
 +}
 +
 +const struct global_type gt_bool = {
 +      .has_arg = no_argument,
 +      .size = sizeof(bool),
 +      .print = print_bool,
 +      .parse.argv = parse_argv_bool,
++      .parse.json = parse_json_bool,
 +      .arg_doc = "true|false",
 +};
index 4bc805da63ed7af1458d6294ef66056ff741d4af,0000000000000000000000000000000000000000..a9241f59e63206f6a64e9a807f994e47f5c3ab45
mode 100644,000000..100644
--- /dev/null
@@@ -1,80 -1,0 +1,70 @@@
- print_filename_format(struct group_fields const *group,
-     struct option_field const *field, void *value)
 +#include "config/filename_format.h"
 +
 +#include <getopt.h>
 +#include <stdlib.h>
 +#include <string.h>
 +
 +#include "log.h"
 +#include "config/str.h"
 +
 +#define FNF_VALUE_GLOBAL "global-url"
 +#define FNF_VALUE_LOCAL "local-path"
 +#define FNF_VALUE_NAME "file-name"
 +
++#define DEREFERENCE(void_value) (*((enum filename_format *) void_value))
++
 +static void
-       enum filename_format *format = value;
++print_filename_format(struct option_field const *field, void *value)
 +{
-       switch (*format) {
 +      char const *str = "<unknown>";
 +
-       pr_info("%s.%s: %s", group->name, field->name, str);
++      switch (DEREFERENCE(value)) {
 +      case FNF_GLOBAL:
 +              str = FNF_VALUE_GLOBAL;
 +              break;
 +      case FNF_LOCAL:
 +              str = FNF_VALUE_LOCAL;
 +              break;
 +      case FNF_NAME:
 +              str = FNF_VALUE_NAME;
 +              break;
 +      }
 +
-     void *_result)
++      pr_info("%s: %s", field->name, str);
 +}
 +
 +static int
 +parse_argv_filename_format(struct option_field const *field, char const *str,
-       enum filename_format *result = _result;
++    void *result)
 +{
-               *result = FNF_GLOBAL;
 +      if (strcmp(str, FNF_VALUE_GLOBAL) == 0)
-               *result = FNF_LOCAL;
++              DEREFERENCE(result) = FNF_GLOBAL;
 +      else if (strcmp(str, FNF_VALUE_LOCAL) == 0)
-               *result = FNF_NAME;
++              DEREFERENCE(result) = FNF_LOCAL;
 +      else if (strcmp(str, FNF_VALUE_NAME) == 0)
- parse_toml_filename_format(struct option_field const *opt,
-     struct toml_table_t *toml, void *_result)
++              DEREFERENCE(result) = FNF_NAME;
 +      else
 +              return pr_err("Unknown file name format: '%s'", str);
 +
 +      return 0;
 +}
 +
 +static int
-       char *string;
++parse_json_filename_format(struct option_field const *opt, json_t *json,
++    void *result)
 +{
-       error = parse_toml_string(toml, opt->name, &string);
-       if (error)
-               return error;
-       if (string == NULL)
-               return 0;
-       error = parse_argv_filename_format(opt, string, _result);
-       free(string);
-       return error;
++      char const *string;
 +      int error;
 +
-       .parse.toml = parse_toml_filename_format,
++      error = parse_json_string(json, opt->name, &string);
++      return error ? error : parse_argv_filename_format(opt, string, result);
 +}
 +
 +const struct global_type gt_filename_format = {
 +      .has_arg = required_argument,
 +      .size = sizeof(enum filename_format),
 +      .print = print_filename_format,
 +      .parse.argv = parse_argv_filename_format,
++      .parse.json = parse_json_filename_format,
 +      .arg_doc = FNF_VALUE_GLOBAL "|" FNF_VALUE_LOCAL "|" FNF_VALUE_NAME,
 +};
index 4be654bd8d42337b0866db29d66c78367ade0867,0000000000000000000000000000000000000000..8388056246771748f6b5a9c51dec4f349cac5a31
mode 100644,000000..100644
--- /dev/null
@@@ -1,93 -1,0 +1,76 @@@
- string_print(struct group_fields const *group, struct option_field const *field,
-     void *value)
 +#include "config/str.h"
 +
 +#include <getopt.h>
 +#include <stdlib.h>
 +#include <string.h>
 +#include "log.h"
 +
++#define DEREFERENCE(void_value) (*((char **) void_value))
++
 +static void
 +__string_free(char **string)
 +{
 +      free(*string);
 +      *string = NULL;
 +}
 +
 +static void
-       pr_info("%s.%s: %s", group->name, field->name, *((char **) value));
++string_print(struct option_field const *field, void *value)
 +{
-     void *_result)
++      pr_info("%s: %s", field->name, DEREFERENCE(value));
 +}
 +
 +static int
 +string_parse_argv(struct option_field const *field, char const *str,
-       char **result = _result;
++    void *result)
 +{
-       /* tomlc99 frees @str early, so work with a copy. */
-       *result = strdup(str);
-       return ((*result) != NULL) ? 0 : pr_enomem();
 +      if (field->type->has_arg != required_argument || str == NULL) {
 +              return pr_err("String options ('%s' in this case) require an argument.",
 +                  field->name);
 +      }
 +
 +      /* Remove the previous value (usually the default). */
 +      __string_free(result);
 +
- string_parse_toml(struct option_field const *opt, struct toml_table_t *toml,
-     void *_result)
++      DEREFERENCE(result) = strdup(str);
++      return (DEREFERENCE(result) != NULL) ? 0 : pr_enomem();
 +}
 +
 +static int
-       char *tmp;
-       char **result;
++string_parse_json(struct option_field const *opt, json_t *json, void *result)
 +{
-       error = parse_toml_string(toml, opt->name, &tmp);
-       if (error)
-               return error;
-       if (tmp == NULL)
-               return 0;
-       result = _result;
-       __string_free(result);
-       *result = tmp;
-       return 0;
++      char const *string;
 +      int error;
 +
-       .parse.toml = string_parse_toml,
++      error = parse_json_string(json, opt->name, &string);
++      return error ? error : string_parse_argv(opt, string, result);
 +}
 +
 +static void
 +string_free(void *string)
 +{
 +      __string_free(string);
 +}
 +
 +const struct global_type gt_string = {
 +      .has_arg = required_argument,
 +      .size = sizeof(char *),
 +      .print = string_print,
 +      .parse.argv = string_parse_argv,
- parse_toml_string(struct toml_table_t *toml, char const *name, char **result)
++      .parse.json = string_parse_json,
 +      .free = string_free,
 +      .arg_doc = "<string>",
 +};
 +
++/**
++ * *result must not be freed nor long-term stored.
++ */
 +int
-       const char *raw;
-       char *value;
-       raw = toml_raw_in(toml, name);
-       if (raw == NULL) {
-               *result = NULL;
-               return 0;
-       }
-       if (toml_rtos(raw, &value) == -1)
-               return pr_err("Cannot parse '%s' as a string.", raw);
++parse_json_string(json_t *json, char const *name, char const **result)
 +{
-       *result = value;
++      if (!json_is_string(json))
++              return pr_err("The '%s' element is not a JSON string.", name);
 +
++      *result = json_string_value(json);
 +      return 0;
 +}
index 7cb592ed62be85bcf351585c997480668639b81c,0000000000000000000000000000000000000000..f78f7292451201a4588b8747248577e0776ae11f
mode 100644,000000..100644
--- /dev/null
@@@ -1,10 -1,0 +1,10 @@@
- int parse_toml_string(struct toml_table_t *, char const *, char **);
 +#ifndef SRC_CONFIG_STR_H_
 +#define SRC_CONFIG_STR_H_
 +
 +#include "config/types.h"
 +
 +extern const struct global_type gt_string;
 +
++int parse_json_string(json_t *, char const *, char const **);
 +
 +#endif /* SRC_CONFIG_STR_H_ */
index 69f38415ed7c9a2789d48fc2036105e51c86b404,0000000000000000000000000000000000000000..ccbb68037bede2e378992d96370d252865395317
mode 100644,000000..100644
--- /dev/null
@@@ -1,124 -1,0 +1,140 @@@
- string_array_print(struct group_fields const *group,
-     struct option_field const *field, void *_value)
 +#include "config/string_array.h"
 +
 +#include <errno.h>
 +#include <getopt.h>
 +#include <stdlib.h>
 +#include <string.h>
++
 +#include "log.h"
++#include "config/str.h"
 +
 +int
 +string_array_init(struct string_array *array, char const *const *values,
 +    size_t len)
 +{
 +      size_t i;
 +
 +      array->length = len;
 +
 +      array->array = calloc(len, sizeof(char *));
 +      if (array->array == NULL)
 +              return -ENOMEM;
 +
 +      for (i = 0; i < len; i++) {
 +              array->array[i] = strdup(values[i]);
 +              if (array->array[i] == NULL) {
 +                      string_array_cleanup(array);
 +                      return -ENOMEM;
 +              }
 +      }
 +
 +      return 0;
 +}
 +
 +void
 +string_array_cleanup(struct string_array *array)
 +{
 +      size_t i;
 +      for (i = 0; i < array->length; i++)
 +              free(array->array[i]);
 +      free(array->array);
 +}
 +
 +static void
 +__string_array_free(struct string_array *array)
 +{
 +      string_array_cleanup(array);
 +      array->array = NULL;
 +      array->length = 0;
 +}
 +
 +static void
-       pr_info("%s.%s:", group->name, field->name);
++string_array_print(struct option_field const *field, void *_value)
 +{
 +      struct string_array *value = _value;
 +      size_t i;
 +
- string_array_parse_toml(struct option_field const *opt,
-     struct toml_table_t *toml, void *_result)
++      pr_info("%s:", field->name);
 +      pr_indent_add();
 +
 +      if (value->length == 0)
 +              pr_info("<Nothing>");
 +      else for (i = 0; i < value->length; i++)
 +              pr_info("%s", value->array[i]);
 +
 +      pr_indent_rm();
 +}
 +
 +static int
-       toml_array_t *array;
-       int array_len;
-       int i;
-       const char *raw;
-       struct string_array *result = _result;
++string_array_parse_json(struct option_field const *opt, json_t *json,
++    void *_result)
 +{
-       array = toml_array_in(toml, opt->name);
-       if (array == NULL)
++      struct string_array *result;
++      json_t *child;
++      size_t i, len;
++      char const *tmp;
 +      int error;
 +
-       array_len = toml_array_nelem(array);
++      if (!json_is_array(json)) {
++              return pr_err("The '%s' element is not a JSON array.",
++                  opt->name);
++      }
++
++      len = json_array_size(json);
++      if (len == 0) {
++              __string_array_free(_result);
 +              return 0;
-       result->array = malloc(array_len * sizeof(char *));
++      }
++
++      for (i = 0; i < len; i++) {
++              child = json_array_get(json, i);
++              if (!json_is_string(child)) {
++                      return pr_err("'%s' array element #%zu is not a string.",
++                          opt->name, i);
++              }
++      }
++
++      result = _result;
 +
 +      /* Remove the previous value (usually the default). */
 +      __string_array_free(result);
 +
-       result->length = array_len;
++      result->array = calloc(len, sizeof(char *));
 +      if (result->array == NULL)
 +              return pr_enomem();
-       for (i = 0; i < array_len; i++) {
-               raw = toml_raw_at(array, i);
-               if (raw == NULL) {
-                       error = pr_crit("Array index %d is NULL.", i);
++      result->length = len;
 +
-               }
-               if (toml_rtos(raw, &result->array[i]) == -1) {
-                       error = pr_err("Cannot parse '%s' as a string.", raw);
++      for (i = 0; i < len; i++) {
++              error = parse_json_string(json_array_get(json, i),
++                  "array element", &tmp);
++              if (error)
 +                      goto fail;
-       free(result->array);
-       result->length = 0;
++
++              result->array[i] = strdup(tmp);
++              if (result->array[i] == NULL) {
++                      error = pr_enomem();
 +                      goto fail;
 +              }
 +      }
 +
 +      return 0;
 +
 +fail:
-       .parse.toml = string_array_parse_toml,
++      __string_array_free(result);
 +      return error;
 +}
 +
 +static void
 +string_array_free(void *array)
 +{
 +      __string_array_free(array);
 +}
 +
 +const struct global_type gt_string_array = {
 +      .has_arg = required_argument,
 +      .size = sizeof(char *const *),
 +      .print = string_array_print,
++      .parse.json = string_array_parse_json,
 +      .free = string_array_free,
 +      .arg_doc = "<sequence of strings>",
 +};
index 74989eaa9553314b36183a87862ecd1f8f7ea8a5,0000000000000000000000000000000000000000..ce0a836fdc8812598051c7ad3a379cbcbfceb7d8
mode 100644,000000..100644
--- /dev/null
@@@ -1,17 -1,0 +1,21 @@@
 +#ifndef SRC_CONFIG_STRING_ARRAY_H_
 +#define SRC_CONFIG_STRING_ARRAY_H_
 +
 +#include <stddef.h>
 +#include "config/types.h"
 +
 +struct string_array {
++      /*
++       * BTW: The array size can be zero, in which case this will be NULL.
++       * TODO Remember to handle properly.
++       */
 +      char **array;
 +      size_t length;
 +};
 +
 +extern const struct global_type gt_string_array;
 +
 +int string_array_init(struct string_array *, char const *const *, size_t);
 +void string_array_cleanup(struct string_array *);
 +
 +#endif /* SRC_CONFIG_STRING_ARRAY_H_ */
index d2522e185eb5ea80984e47e72aec881c92574161,0000000000000000000000000000000000000000..e6b13e481878f3af232d7d566376e0e7622e4ffb
mode 100644,000000..100644
--- /dev/null
@@@ -1,89 -1,0 +1,79 @@@
- print_sync_strategy(struct group_fields const *group,
-     struct option_field const *field, void *value)
 +#include "config/sync_strategy.h"
 +
 +#include <getopt.h>
 +#include <stdlib.h>
 +#include <string.h>
 +
 +#include "log.h"
 +#include "config/str.h"
 +
 +#define SYNC_VALUE_OFF                        "off"
 +#define SYNC_VALUE_STRICT             "strict"
 +#define SYNC_VALUE_ROOT                       "root"
 +#define SYNC_VALUE_ROOT_EXCEPT_TA     "root-except-ta"
 +
++#define DEREFERENCE(void_value) (*((enum sync_strategy *) void_value))
++
 +static void
-       enum sync_strategy *strategy = value;
++print_sync_strategy(struct option_field const *field, void *value)
 +{
-       switch (*strategy) {
 +      char const *str = "<unknown>";
 +
-       pr_info("%s.%s: %s", group->name, field->name, str);
++      switch (DEREFERENCE(value)) {
 +      case SYNC_OFF:
 +              str = SYNC_VALUE_OFF;
 +              break;
 +      case SYNC_STRICT:
 +              str = SYNC_VALUE_STRICT;
 +              break;
 +      case SYNC_ROOT:
 +              str = SYNC_VALUE_ROOT;
 +              break;
 +      case SYNC_ROOT_EXCEPT_TA:
 +              str = SYNC_VALUE_ROOT_EXCEPT_TA;
 +              break;
 +      }
 +
-     void *_result)
++      pr_info("%s: %s", field->name, str);
 +}
 +
 +static int
 +parse_argv_sync_strategy(struct option_field const *field, char const *str,
-       enum sync_strategy *result = _result;
++    void *result)
 +{
-               *result = SYNC_OFF;
 +      if (strcmp(str, SYNC_VALUE_OFF) == 0)
-               *result = SYNC_STRICT;
++              DEREFERENCE(result) = SYNC_OFF;
 +      else if (strcmp(str, SYNC_VALUE_STRICT) == 0)
-               *result = SYNC_ROOT;
++              DEREFERENCE(result) = SYNC_STRICT;
 +      else if (strcmp(str, SYNC_VALUE_ROOT) == 0)
-               *result = SYNC_ROOT_EXCEPT_TA;
++              DEREFERENCE(result) = SYNC_ROOT;
 +      else if (strcmp(str, SYNC_VALUE_ROOT_EXCEPT_TA) == 0)
- parse_toml_sync_strategy(struct option_field const *opt,
-     struct toml_table_t *toml, void *_result)
++              DEREFERENCE(result) = SYNC_ROOT_EXCEPT_TA;
 +      else
 +              return pr_err("Unknown synchronization strategy: '%s'", str);
 +
 +      return 0;
 +}
 +
 +static int
-       char *string;
-       error = parse_toml_string(toml, opt->name, &string);
-       if (error)
-               return error;
-       if (string == NULL)
-               return 0;
-       error = parse_argv_sync_strategy(opt, string, _result);
++parse_json_sync_strategy(struct option_field const *opt, struct json_t *json,
++    void *result)
 +{
++      char const *string;
 +      int error;
-       free(string);
-       return error;
 +
-       .parse.toml = parse_toml_sync_strategy,
++      error = parse_json_string(json, opt->name, &string);
++      return error ? error : parse_argv_sync_strategy(opt, string, result);
 +}
 +
 +const struct global_type gt_sync_strategy = {
 +      .has_arg = required_argument,
 +      .size = sizeof(enum sync_strategy),
 +      .print = print_sync_strategy,
 +      .parse.argv = parse_argv_sync_strategy,
++      .parse.json = parse_json_sync_strategy,
 +      .arg_doc = SYNC_VALUE_OFF
 +          "|" SYNC_VALUE_STRICT
 +          "|" SYNC_VALUE_ROOT
 +          "|" SYNC_VALUE_ROOT_EXCEPT_TA,
 +};
index bdb5d7bf34e31b1702ef2867e36f044a7d80bbc2,0000000000000000000000000000000000000000..56abb8b8aa71cb69d827073fe1a9006d97bde09f
mode 100644,000000..100644
--- /dev/null
@@@ -1,139 -1,0 +1,135 @@@
- #include <toml.h>
 +#ifndef SRC_CONFIG_TYPES_H_
 +#define SRC_CONFIG_TYPES_H_
 +
++#include <jansson.h>
 +#include <stdint.h>
 +#include <stdio.h>
- /** This option can be set from the TOML file. */
- #define AVAILABILITY_TOML (1 << 1)
 +
 +struct option_field;
 +struct group_fields;
 +
 +/** This option can be set from the command line. */
 +#define AVAILABILITY_GETOPT (1 << 0)
-     struct group_fields const *,
++/** This option can be set from the JSON file. */
++#define AVAILABILITY_JSON (1 << 1)
 +
 +typedef void (*print_function)(
- typedef int (*toml_parse_function)(
 +    struct option_field const *,
 +    void *
 +);
 +typedef int (*argv_parse_function)(
 +    struct option_field const *,
 +    char const *,
 +    void *
 +);
-     struct toml_table_t *,
++typedef int (*json_parse_function)(
 +    struct option_field const *,
- struct group_fields {
-       char const *name;
-       struct option_field const *options;
- };
++    struct json_t *,
 +    void *
 +);
 +typedef int (*handler_function)(
 +    struct option_field const *,
 +    char *
 +);
 +
 +struct option_field {
 +      /*
 +       * Must be zero, alphanumeric or >= 1000.
 +       * If zero, signals the end of the array.
 +       * If alphanumeric, it's the short option name character.
 +       * Otherwise it's just a non-printable identifier.
 +       * Must be unique across all option fields.
 +       * Mandatory.
 +       */
 +      int id;
 +      /**
 +       * For example, if the option name is '--potato', then @name is
 +       * "potato".
 +       * Mandatory.
 +       */
 +      char const *name;
 +
 +      /** Data type. Mandatory. */
 +      struct global_type const *type;
 +      /**
 +       * Number of bytes between the beginning of the struct rpki_config
 +       * and the position where this option is stored.
 +       * Only relevant when @handler == NULL.
 +       */
 +      size_t offset;
 +      /** Overrides @type->parser and @offset. Optional. */
 +      handler_function handler;
 +
 +      /**
 +       * Explanation of the field, for user consumption during --help.
 +       * Meant to be short; the bulk of it should be found in the manpage.
 +       * Probably should not include punctuation at the end.
 +       * Mandatory.
 +       */
 +      const char *doc;
 +      /** Overrides type->arg_doc. Optional. */
 +      char const *arg_doc;
 +      /**
 +       * AVAILABILITY_* flags above.
 +       * Default availability is everywhere.
 +       * Optional.
 +       */
 +      int availability;
 +      unsigned int min;
 +      unsigned int max;
 +};
 +
-                * Converts from a TOML node to this data type.
-                * If the node is not present in the file, this function should
-                * do nothing.
++#define FOREACH_OPTION(opts, opt, type) \
++      for (opt = opts; opt->id != 0; opt++) \
++              if ((opt->availability == 0) || (opt->availability & type))
 +
 +struct global_type {
 +      /** Same as struct option.has_arg. Mandatory. */
 +      int has_arg;
 +      /**
 +       * Number of bytes this data type uses in the rpki_config structure.
 +       * Optional. Defaults to zero, obviously.
 +       */
 +      size_t size;
 +
 +      /**
 +       * Prints this data type during the print_config() function.
 +       * Optional.
 +       */
 +      print_function print;
 +
 +      /** If the option's handler is not NULL, this is optional. */
 +      struct {
 +              /**
 +               * Convers from string to this data type.
 +               * Optional if there are no fields of this type that are read
 +               * from argv.
 +               */
 +              argv_parse_function argv;
 +              /**
-                * from TOML files.
++               * Converts from a JSON node to this data type.
 +               * Optional if there are no fields of this type that are read
-               toml_parse_function toml;
++               * from JSON files.
 +               */
++              json_parse_function json;
 +      } parse;
 +
 +      /**
 +       * Function that will release this data type.
 +       * If the option's handler is not NULL, this is optional.
 +       *
 +       * IMPORTANT: This function might be called twice in succession.
 +       * Therefore, make sure that it nullifies the value, and reacts properly
 +       * when the input is NULL.
 +       */
 +      void (*free)(void *);
 +
 +      /**
 +       * Descriptor of this type's payload. Printed in usage documentation.
 +       * For example, in `--tal=<file>`, @arg_doc is "<file>".
 +       * The type might have no payload, so this is optional.
 +       */
 +      char const *arg_doc;
 +};
 +
 +#endif /* SRC_CONFIG_TYPES_H_ */
index 920bcf8680de7cb26db05f118e19d5986fdd0b1f,0000000000000000000000000000000000000000..16c043742f96e189a126cd28e8797c4e3a1d6f18
mode 100644,000000..100644
--- /dev/null
@@@ -1,74 -1,0 +1,67 @@@
- print_u_int(struct group_fields const *group, struct option_field const *field,
-     void *value)
 +#include "config/uint.h"
 +
 +#include <getopt.h>
 +#include <errno.h>
 +#include <stdlib.h>
 +#include "log.h"
 +
 +static void
-       pr_info("%s.%s: %u", group->name, field->name,
-           *((unsigned int *) value));
++print_u_int(struct option_field const *field, void *value)
 +{
- static int
++      pr_info("%s: %u", field->name, *((unsigned int *) value));
 +}
 +
-     void *_result)
++int
 +parse_argv_u_int(struct option_field const *field, char const *str,
-       int *result;
++    void *result)
 +{
 +      unsigned long parsed;
-       result = _result;
-       *result = parsed;
 +
 +      if (field->type->has_arg != required_argument || str == NULL) {
 +              return pr_err("Integer options ('%s' in this case) require an argument.",
 +                  field->name);
 +      }
 +
 +      errno = 0;
 +      parsed = strtoul(str, NULL, 10);
 +      if (errno)
 +              return pr_errno(errno, "'%s' is not an unsigned integer", str);
 +
 +      if (parsed < field->min || field->max < parsed) {
 +              return pr_err("'%lu' is out of bounds (%u-%u).", parsed,
 +                  field->min, field->max);
 +      }
 +
- static int
- parse_toml_u_int(struct option_field const *opt, struct toml_table_t *toml,
-     void *_result)
++      *((unsigned int *) result) = parsed;
 +      return 0;
 +}
 +
-       const char *raw;
-       int64_t value;
-       unsigned int *result;
++int
++parse_json_u_int(struct option_field const *opt, json_t *json, void *result)
 +{
-       raw = toml_raw_in(toml, opt->name);
-       if (raw == NULL)
-               return 0;
-       if (toml_rtoi(raw, &value) == -1)
-               return pr_err("Cannot parse '%s' as an integer.", raw);
++      json_int_t value;
 +
-       result = _result;
-       *result = value;
++      if (!json_is_integer(json)) {
++              return pr_err("The '%s' element is not a JSON integer.",
++                  opt->name);
++      }
++
++      value = json_integer_value(json);
 +
 +      if (value < opt->min || opt->max < value) {
 +              return pr_err("Integer '%s' is out of range (%u-%u).",
 +                  opt->name, opt->min, opt->max);
 +      }
 +
-       .parse.toml = parse_toml_u_int,
++      *((unsigned int *) result) = value;
 +      return 0;
 +}
 +
 +const struct global_type gt_u_int = {
 +      .has_arg = required_argument,
 +      .size = sizeof(unsigned int),
 +      .print = print_u_int,
 +      .parse.argv = parse_argv_u_int,
++      .parse.json = parse_json_u_int,
 +      .arg_doc = "<unsigned integer>",
 +};
index 5623385d6b1ff3b52ee07e3d25defc52f51b1a01,0000000000000000000000000000000000000000..4108cc7d23a8c78cdf00974439b240e63a3ea997
mode 100644,000000..100644
--- /dev/null
@@@ -1,8 -1,0 +1,11 @@@
 +#ifndef SRC_CONFIG_UINT_H_
 +#define SRC_CONFIG_UINT_H_
 +
 +#include "config/types.h"
 +
 +extern const struct global_type gt_u_int;
 +
++int parse_argv_u_int(struct option_field const *, char const *, void *);
++int parse_json_u_int(struct option_field const *, struct json_t *, void *);
++
 +#endif /* SRC_CONFIG_UINT_H_ */
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..05b704ff737a35aca500a0e23da57b7888b01c0e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,54 @@@
++#include "config/uint.h"
++
++#include <getopt.h>
++#include <errno.h>
++#include <stdlib.h>
++
++#include "log.h"
++#include "config/uint.h"
++
++static void
++print_u_int32(struct option_field const *field, void *value)
++{
++      pr_info("%s: %u", field->name, *((u_int32_t *) value));
++}
++
++static int
++parse_argv_u_int32(struct option_field const *field, char const *str,
++    void *result)
++{
++      unsigned int tmp;
++      int error;
++
++      error = parse_argv_u_int(field, str, &tmp);
++      if (error)
++              return error;
++
++      /* Range already validated (from field->min and field->max). */
++      *((u_int32_t *) result) = tmp;
++      return 0;
++}
++
++static int
++parse_json_u_int32(struct option_field const *opt, json_t *json, void *result)
++{
++      unsigned int tmp;
++      int error;
++
++      error = parse_json_u_int(opt, json, &tmp);
++      if (error)
++              return error;
++
++      /* Range already validated (from opt->min and opt->max). */
++      *((u_int32_t *) result) = tmp;
++      return 0;
++}
++
++const struct global_type gt_u_int32 = {
++      .has_arg = required_argument,
++      .size = sizeof(u_int32_t),
++      .print = print_u_int32,
++      .parse.argv = parse_argv_u_int32,
++      .parse.json = parse_json_u_int32,
++      .arg_doc = "<32-bit unsigned integer>",
++};
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1689229ad2f14fb9bfd1d3a44134d67b72451356
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,8 @@@
++#ifndef SRC_CONFIG_UINT32_H_
++#define SRC_CONFIG_UINT32_H_
++
++#include "config/types.h"
++
++extern const struct global_type gt_u_int32;
++
++#endif /* SRC_CONFIG_UINT32_H_ */
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6d7488c12d5e0a5a77e6935445ffcc0447cafbc5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,79 @@@
++#include "json_handler.h"
++
++#include <errno.h>
++#include <string.h>
++
++#include "config.h"
++#include "log.h"
++#include "config/types.h"
++
++int
++find_json(struct json_t *root, char const *full_name, json_t **result)
++{
++      struct {
++              char *opt_name; /* full token sequence string */
++              char *token; /* current token */
++              char *saveptr; /* state needed by strtok_r */
++      } strtok;
++      struct json_t *node;
++
++      /* strtok_r() needs a non-const string */
++      strtok.opt_name = strdup(full_name);
++      if (strtok.opt_name == NULL)
++              return pr_enomem();
++
++      node = root;
++      strtok.token = strtok_r(strtok.opt_name, ".", &strtok.saveptr);
++
++      while (node != NULL && strtok.token != NULL) {
++              node = json_object_get(node, strtok.token);
++              strtok.token = strtok_r(NULL, ".", &strtok.saveptr);
++      }
++
++      free(strtok.opt_name);
++      *result = node;
++      return 0;
++}
++
++static int
++json_to_config(struct json_t *root)
++{
++      struct option_field const *opt;
++      struct json_t *child;
++      int error;
++
++      FOREACH_OPTION(get_option_metadatas(), opt, AVAILABILITY_JSON) {
++              error = find_json(root, opt->name, &child);
++              if (error)
++                      return error;
++              if (child == NULL)
++                      continue;
++
++              error = opt->type->parse.json(opt, child,
++                  get_rpki_config_field(opt));
++              if (error)
++                      return error;
++      }
++
++      return 0;
++}
++
++int
++set_config_from_file(char *file)
++{
++      json_t *root;
++      json_error_t json_error;
++      int error;
++
++      root = json_load_file(file, JSON_REJECT_DUPLICATES, &json_error);
++      if (root == NULL) {
++              pr_err("JSON error on line %d, column %d: %s",
++                  json_error.line, json_error.column, json_error.text);
++              return -ENOENT;
++      }
++
++      error = json_to_config(root);
++
++      json_decref(root);
++      return error;
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..931970de07190549d1a095b78370dc3bac85f8ce
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,6 @@@
++#ifndef SRC_JSON_HANDLER_H_
++#define SRC_JSON_HANDLER_H_
++
++int set_config_from_file(char *);
++
++#endif /* SRC_JSON_HANDLER_H_ */
diff --cc src/main.c
index d902db448ecebeaa0152bbe37b86da61a29ba8a3,5496b58592b1428155ca414b142a80b847cd8ae0..d236e2bc2a9ae2db23b5f6f7a645ddb12234d5a4
- #include <err.h>
--#include <errno.h>
- #include <getopt.h>
 -#include <stdio.h>
 -#include <stdlib.h>
 -#include <unistd.h>
--
- #include "common.h"
 -#include "rtr/rtr.h"
+ #include "clients.h"
 -#include "configuration.h"
 -#include "csv.h"
 +#include "config.h"
 +#include "debug.h"
 +#include "extension.h"
- #include "log.h"
 +#include "nid.h"
- #include "rpp.h"
 +#include "thread_var.h"
- #include "object/certificate.h"
- #include "object/manifest.h"
- #include "object/tal.h"
+ #include "vrps.h"
 +#include "rsync/rsync.h"
++#include "rtr/rtr.h"
  
- /**
-  * Performs the whole validation walkthrough on uri @uri, which is assumed to
-  * have been extracted from a TAL.
 -/*
 - * This program is an RTR server.
 - *
 - * RTR ("RPKI-to-Router") is a protocol (defined in RFCs 6810 and 8210) that
 - * reports the work of an RPKI validator (cryptographcally-verified
 - * attestations that define the ASN that owns a given routing prefix). It is
 - * normally served to routers who wish to verify BGP claims.
-- */
 -int
 -main(int argc, char *argv[])
 +static int
- handle_tal_uri(struct tal *tal, struct rpki_uri const *uri)
++start_rtr_server(void)
  {
-       /*
-        * Because of the way the foreach iterates, this function must return
-        *
-        * - 0 on soft errors.
-        * - `> 0` on URI handled successfully.
-        * - `< 0` on hard errors.
-        *
-        * A "soft error" is "the connection to the preferred URI fails, or the
-        * retrieved CA certificate public key does not match the TAL public
-        * key." (RFC 7730)
-        *
-        * A "hard error" is any other error.
-        */
-       struct validation *state;
 -      int err;
 -      char *json_file = NULL;
 -      int c;
 +      int error;
  
-       error = download_files(uri, true);
-       if (error) {
-               return pr_warn("TAL URI '%s' could not be RSYNC'd.",
-                   uri->global);
 -      while ((c = getopt(argc, argv, "f:")) != -1) {
 -              switch (c) {
 -              case 'f':
 -                      json_file = optarg;
 -                      break;
 -              case '?':
 -                      fprintf(stdout, "usage: %s -f <file name>\n", argv[0]);
 -                      return 0;
 -              }
--      }
-       error = validation_prepare(&state, tal);
++      error = deltas_db_init();
 +      if (error)
-               return -abs(error);
-       pr_debug_add("TAL URI '%s' {", uri_get_printable(uri));
++              goto end1;
  
-       if (!uri_is_certificate(uri)) {
-               pr_err("TAL file does not point to a certificate. (Expected .cer, got '%s')",
-                   uri_get_printable(uri));
-               error = -EINVAL;
-               goto end;
 -      if (json_file == NULL) {
 -              fprintf(stderr, "Missing flag '-f <file name>'\n");
 -              return -EINVAL;
--      }
++      error = clients_db_init();
++      if (error)
++              goto end2;
  
-       error = certificate_traverse(NULL, uri, NULL, true);
-       if (error) {
-               switch (validation_pubkey_state(state)) {
-               case PKS_INVALID:
-                       error = 0;
-                       break;
-               case PKS_VALID:
-               case PKS_UNTESTED:
-                       error = -abs(error);
-                       break;
-               }
-       } else {
-               error = 1;
 -      err = config_init(json_file);
 -      if (err) {
 -              /*
 -               * TODO Special scenario, if the VRPs location doesn't exists
 -               * just send a warning (logged by the config_init function).
 -               *
 -               * This should be fixed later.
 -               */
 -              err = (err == -ENOENT ? 0 : err);
 -              goto end1;
--      }
++      error = rtr_listen();
++      rtr_cleanup(); /* TODO shouldn't this only happen on !error? */
  
- end:
-       validation_destroy(state);
-       pr_debug_rm("}");
-       return error;
 -      err = deltas_db_init();
 -      if (err)
 -              goto end1;
++      clients_db_destroy();
++end2: deltas_db_destroy();
++end1: return error;
 +}
  
 -      err = clients_db_init();
 -      if (err)
 -              goto end2;
 +int
 +main(int argc, char **argv)
 +{
-       struct tal *tal;
 +      int error;
  
 -      err = csv_parse_vrps_file();
 -      if (err)
 -              goto end3;
 +      print_stack_trace_on_segfault();
  
-       thvar_init();
-       fnstack_init();
 -      err = rtr_listen();
++      error = thvar_init();
++      if (error)
++              return error;
  
 -      rtr_cleanup();
 -end3:
 -      clients_db_destroy();
 -end2:
 -      deltas_db_destroy();
 -end1:
 -      config_cleanup();
 -      return err;
 +      error = handle_flags_config(argc, argv);
 +      if (error)
 +              return error;
 +
 +      error = rsync_init();
 +      if (error)
-               goto end1;
++              goto revert_config;
 +      error = nid_init();
 +      if (error)
-               goto end2;
++              goto revert_rsync;
 +      error = extension_init();
 +      if (error)
-               goto end2;
-       fnstack_push(config_get_tal());
++              goto revert_rsync;
 +
-       error = tal_load(config_get_tal(), &tal);
-       if (!error) {
-               if (config_get_shuffle_uris())
-                       tal_shuffle_uris(tal);
-               error = foreach_uri(tal, handle_tal_uri);
-               if (error > 0)
-                       error = 0;
-               else if (error == 0)
-                       error = pr_err("None of the URIs of the TAL yielded a successful traversal.");
++      error = perform_standalone_validation(NULL);
++      if (error)
++              goto revert_rsync;
 +
-               tal_destroy(tal);
-       }
++      if (config_get_server_address() != NULL)
++              error = start_rtr_server();
++      /* Otherwise, no server requested. */
 +
- end2:
++revert_rsync:
 +      rsync_destroy();
- end1:
++revert_config:
 +      free_rpki_config();
-       fnstack_cleanup();
 +      return error;
  }
index e39634fb62a5b9e0742634728daf15ad588c126d,0000000000000000000000000000000000000000..72c28515d842645b212c500a4f797c054ef3442a
mode 100644,000000..100644
--- /dev/null
@@@ -1,44 -1,0 +1,44 @@@
- handle_ghostbusters(struct rpki_uri const *uri, struct rpp *pp,
 +#include "object/ghostbusters.h"
 +
 +#include "log.h"
 +#include "thread_var.h"
 +#include "asn1/oid.h"
 +#include "asn1/signed_data.h"
 +#include "object/signed_object.h"
 +#include "vcard.h"
 +
 +static int
 +handle_vcard(OCTET_STRING_t *vcard, void *arg)
 +{
 +      return handle_ghostbusters_vcard(vcard);
 +}
 +
 +int
++ghostbusters_traverse(struct rpki_uri const *uri, struct rpp *pp,
 +    STACK_OF(X509_CRL) *crls)
 +{
 +      static OID oid = OID_GHOSTBUSTERS;
 +      struct oid_arcs arcs = OID2ARCS("ghostbusters", oid);
 +      struct signed_object_args sobj_args;
 +      int error;
 +
 +      pr_debug_add("Ghostbusters '%s' {", uri->global);
 +      fnstack_push_uri(uri);
 +
 +      error = signed_object_args_init(&sobj_args, uri, crls, true);
 +      if (error)
 +              goto end1;
 +
 +      error = signed_object_decode(&sobj_args, &arcs, handle_vcard, NULL);
 +      if (error)
 +              goto end2;
 +
 +      error = refs_validate_ee(&sobj_args.refs, pp, sobj_args.uri);
 +
 +end2:
 +      signed_object_args_cleanup(&sobj_args);
 +end1:
 +      pr_debug_rm("}");
 +      fnstack_pop();
 +      return error;
 +}
index d5610cb7356c173e290f49ea2acf883c013eb038,0000000000000000000000000000000000000000..b4bbbd8c517f85cb6b6805176203b6209b4b1cfb
mode 100644,000000..100644
--- /dev/null
@@@ -1,11 -1,0 +1,11 @@@
- int handle_ghostbusters(struct rpki_uri const *, struct rpp *,
 +#ifndef SRC_OBJECT_GHOSTBUSTERS_H_
 +#define SRC_OBJECT_GHOSTBUSTERS_H_
 +
 +#include <openssl/x509.h>
 +#include "uri.h"
 +#include "rpp.h"
 +
++int ghostbusters_traverse(struct rpki_uri const *, struct rpp *,
 +    STACK_OF(X509_CRL) *);
 +
 +#endif /* SRC_OBJECT_GHOSTBUSTERS_H_ */
index 7ad3e73088c33aff3f8658399ec66dc4e5696f25,0000000000000000000000000000000000000000..9fe42919f09b87dd2cf764411e298423de746c79
mode 100644,000000..100644
--- /dev/null
@@@ -1,236 -1,0 +1,233 @@@
-       char str[INET_ADDRSTRLEN];
-       const char *str2;
 +#include "object/roa.h"
 +
 +#include <errno.h>
 +#include <arpa/inet.h>
 +#include <libcmscodec/RouteOriginAttestation.h>
 +
 +#include "config.h"
 +#include "log.h"
 +#include "thread_var.h"
 +#include "asn1/decode.h"
 +#include "asn1/oid.h"
 +#include "object/signed_object.h"
 +
 +#include <sys/socket.h>
 +
 +static int
 +roa_decode(OCTET_STRING_t *string, void *arg)
 +{
 +      return asn1_decode_octet_string(string, &asn_DEF_RouteOriginAttestation,
 +          arg);
 +}
 +
 +static int
 +print_addr4(struct resources *parent, unsigned long asn,
 +    struct ROAIPAddress *roa_addr)
 +{
 +      struct ipv4_prefix prefix;
 +      unsigned long max_length;
-       str2 = inet_ntop(AF_INET, &prefix.addr, str, sizeof(str));
-       if (str2 == NULL)
-               return pr_err("inet_ntop() returned NULL.");
 +      int error;
 +
 +      error = prefix4_decode(&roa_addr->address, &prefix);
 +      if (error)
 +              return error;
 +
 +      if (roa_addr->maxLength != NULL) {
 +              error = asn_INTEGER2ulong(roa_addr->maxLength, &max_length);
 +              if (error) {
 +                      if (errno)
 +                              pr_errno(errno, "Error casting ROA's IPv4 maxLength");
 +                      return pr_err("The ROA's IPv4 maxLength isn't a valid unsigned long");
 +              }
 +
 +              if (max_length > 32) {
 +                      return pr_err("maxLength (%lu) is out of bounds (0-32).",
 +                          max_length);
 +              }
 +
 +              if (prefix.len > max_length) {
 +                      return pr_err("Prefix length (%u) > maxLength (%lu)",
 +                          prefix.len, max_length);
 +              }
 +
 +      } else {
 +              max_length = prefix.len;
 +      }
 +
-               return pr_err("ROA is not allowed to advertise %s/%u.", str2,
-                   prefix.len);
 +      if (!resources_contains_ipv4(parent, &prefix)) {
-       fprintf(config_get_roa_output(), "AS%lu,%s/%u,%lu\n", asn, str2,
-           prefix.len, max_length);
-       return 0;
++              return pr_err("ROA is not allowed to advertise %s/%u.",
++                  v4addr2str(&prefix.addr), prefix.len);
 +      }
 +
-       char str[INET6_ADDRSTRLEN];
-       const char *str2;
++      /* TODO I think we're not validating asn boundaries */
++      return roa_handle_v4(asn, &prefix, max_length);
++}
++
++int
++roa_handle_v4(u_int32_t asn, struct ipv4_prefix *prefix, u_int8_t max_length)
++{
++      return -ENOTIMPLEMENTED;
 +}
 +
 +static int
 +print_addr6(struct resources *parent, unsigned long asn,
 +    struct ROAIPAddress *roa_addr)
 +{
 +      struct ipv6_prefix prefix;
 +      unsigned long max_length;
-       str2 = inet_ntop(AF_INET6, &prefix.addr, str, sizeof(str));
-       if (str2 == NULL)
-               return pr_err("inet_ntop() returned NULL.");
 +      int error;
 +
 +      error = prefix6_decode(&roa_addr->address, &prefix);
 +      if (error)
 +              return error;
 +
 +      if (roa_addr->maxLength != NULL) {
 +              error = asn_INTEGER2ulong(roa_addr->maxLength, &max_length);
 +              if (error) {
 +                      if (errno)
 +                              pr_errno(errno, "Error casting ROA's IPv6 maxLength");
 +                      return pr_err("The ROA's IPv6 maxLength isn't a valid unsigned long");
 +              }
 +
 +              if (max_length > 128) {
 +                      return pr_err("maxLength (%lu) is out of bounds (0-128).",
 +                          max_length);
 +              }
 +
 +              if (prefix.len > max_length) {
 +                      return pr_err("Prefix length (%u) > maxLength (%lu)",
 +                          prefix.len, max_length);
 +              }
 +
 +      } else {
 +              max_length = prefix.len;
 +      }
 +
-               return pr_err("ROA is not allowed to advertise %s/%u.", str2,
-                   prefix.len);
 +      if (!resources_contains_ipv6(parent, &prefix)) {
-       fprintf(config_get_roa_output(), "AS%lu,%s/%u,%lu\n", asn, str2,
-           prefix.len, max_length);
-       return 0;
++              return pr_err("ROA is not allowed to advertise %s/%u.",
++                  v6addr2str(&prefix.addr), prefix.len);
 +      }
 +
- handle_roa(struct rpki_uri const *uri, struct rpp *pp,
++      return roa_handle_v6(asn, &prefix, max_length);
++}
++
++int
++roa_handle_v6(u_int32_t asn, struct ipv6_prefix *prefix, u_int8_t max_length)
++{
++      return -ENOTIMPLEMENTED;
 +}
 +
 +static int
 +print_addr(struct resources *parent, ASID_t *as_id, uint8_t family,
 +    struct ROAIPAddress *roa_addr)
 +{
 +      unsigned long asn;
 +
 +      if (asn_INTEGER2ulong(as_id, &asn) != 0) {
 +              if (errno)
 +                      pr_errno(errno, "Error casting ROA's AS ID value");
 +              return pr_err("ROA's AS ID couldn't be parsed as unsigned long");
 +      }
 +
 +      switch (family) {
 +      case 1: /* IPv4 */
 +              return print_addr4(parent, asn, roa_addr);
 +      case 2: /* IPv6 */
 +              return print_addr6(parent, asn, roa_addr);
 +      }
 +
 +      return pr_err("Unknown family value: %u", family);
 +}
 +
 +static int
 +__handle_roa(struct RouteOriginAttestation *roa, struct resources *parent)
 +{
 +      struct ROAIPAddressFamily *block;
 +      unsigned long version;
 +      int b;
 +      int a;
 +      int error;
 +
 +      if (roa->version != NULL) {
 +              error = asn_INTEGER2ulong(roa->version, &version);
 +              if (error) {
 +                      if (errno)
 +                              pr_errno(errno, "Error casting ROA's version");
 +                      return pr_err("The ROA's version isn't a valid long");
 +              }
 +              /* rfc6482#section-3.1 */
 +              if (version != 0)
 +                      return pr_err("ROA's version (%lu) is nonzero.", version);
 +      }
 +
 +      /* rfc6482#section-3.3 */
 +
 +      if (roa->ipAddrBlocks.list.array == NULL)
 +              return pr_crit("ipAddrBlocks array is NULL.");
 +
 +      for (b = 0; b < roa->ipAddrBlocks.list.count; b++) {
 +              block = roa->ipAddrBlocks.list.array[b];
 +              if (block == NULL)
 +                      return pr_err("Address block array element is NULL.");
 +
 +              if (block->addressFamily.size != 2)
 +                      goto family_error;
 +              if (block->addressFamily.buf[0] != 0)
 +                      goto family_error;
 +              if (block->addressFamily.buf[1] != 1
 +                  && block->addressFamily.buf[1] != 2)
 +                      goto family_error;
 +
 +              if (block->addresses.list.array == NULL)
 +                      return pr_err("ROA's address list array is NULL.");
 +              for (a = 0; a < block->addresses.list.count; a++) {
 +                      error = print_addr(parent, &roa->asID,
 +                          block->addressFamily.buf[1],
 +                          block->addresses.list.array[a]);
 +                      if (error)
 +                              return error;
 +              }
 +      }
 +
 +      return 0;
 +
 +family_error:
 +      return pr_err("ROA's IP family is not v4 or v6.");
 +}
 +
 +int
++roa_traverse(struct rpki_uri const *uri, struct rpp *pp,
 +    STACK_OF(X509_CRL) *crls)
 +{
 +      static OID oid = OID_ROA;
 +      struct oid_arcs arcs = OID2ARCS("roa", oid);
 +      struct signed_object_args sobj_args;
 +      struct RouteOriginAttestation *roa;
 +      int error;
 +
 +      pr_debug_add("ROA '%s' {", uri_get_printable(uri));
 +      fnstack_push_uri(uri);
 +
 +      error = signed_object_args_init(&sobj_args, uri, crls, false);
 +      if (error)
 +              goto end1;
 +
 +      error = signed_object_decode(&sobj_args, &arcs, roa_decode, &roa);
 +      if (error)
 +              goto end2;
 +
 +      error = __handle_roa(roa, sobj_args.res);
 +      if (error)
 +              goto end3;
 +
 +      error = refs_validate_ee(&sobj_args.refs, pp, sobj_args.uri);
 +
 +end3:
 +      ASN_STRUCT_FREE(asn_DEF_RouteOriginAttestation, roa);
 +end2:
 +      signed_object_args_cleanup(&sobj_args);
 +end1:
 +      pr_debug_rm("}");
 +      fnstack_pop();
 +      return error;
 +}
index c77b7da1db270844e3b00a3f0482a409404d0ca2,0000000000000000000000000000000000000000..1ce475a9e991415945000bd11fdeeb7fc2810fde
mode 100644,000000..100644
--- /dev/null
@@@ -1,10 -1,0 +1,15 @@@
- int handle_roa(struct rpki_uri const *, struct rpp *, STACK_OF(X509_CRL) *);
 +#ifndef SRC_OBJECT_ROA_H_
 +#define SRC_OBJECT_ROA_H_
 +
 +#include <openssl/x509.h>
++
++#include "address.h"
 +#include "rpp.h"
 +#include "uri.h"
 +
++int roa_traverse(struct rpki_uri const *, struct rpp *, STACK_OF(X509_CRL) *);
++
++int roa_handle_v4(u_int32_t, struct ipv4_prefix *, u_int8_t);
++int roa_handle_v6(u_int32_t, struct ipv6_prefix *, u_int8_t);
 +
 +#endif /* SRC_OBJECT_ROA_H_ */
index 8e8f912a644ae80d72cc0912895a77df7152be97,0000000000000000000000000000000000000000..11b799fc1ef95a5ab326ef1e103d865157049ebe
mode 100644,000000..100644
--- /dev/null
@@@ -1,264 -1,0 +1,360 @@@
 +#define _GNU_SOURCE
 +
 +#include "tal.h"
 +
 +#include <errno.h>
 +#include <stdbool.h>
 +#include <stdlib.h>
 +#include <string.h>
 +#include <sys/stat.h>
 +#include <openssl/evp.h>
 +
 +#include "common.h"
++#include "config.h"
 +#include "line_file.h"
 +#include "log.h"
 +#include "random.h"
++#include "state.h"
++#include "thread_var.h"
 +#include "crypto/base64.h"
++#include "object/certificate.h"
++#include "rsync/rsync.h"
 +
 +struct uris {
 +      char **array; /* This is an array of string pointers. */
 +      unsigned int count;
 +      unsigned int size;
 +};
 +
 +struct tal {
 +      char const *file_name;
 +      struct uris uris;
 +      unsigned char *spki; /* Decoded; not base64. */
 +      size_t spki_len;
 +};
 +
 +static int
 +uris_init(struct uris *uris)
 +{
 +      uris->count = 0;
 +      uris->size = 4; /* Most TALs only define one. */
 +      uris->array = malloc(uris->size * sizeof(char *));
 +      return (uris->array != NULL) ? 0 : -ENOMEM;
 +}
 +
 +static void
 +uris_destroy(struct uris *uris)
 +{
 +      unsigned int i;
 +      for (i = 0; i < uris->count; i++)
 +              free(uris->array[i]);
 +      free(uris->array);
 +}
 +
 +static int
 +uris_add(struct uris *uris, char *uri)
 +{
 +      char **tmp;
 +
 +      if (uris->count + 1 >= uris->size) {
 +              uris->size *= 2;
 +              tmp = realloc(uris->array, uris->size * sizeof(char *));
 +              if (tmp == NULL)
 +                      return pr_enomem();
 +              uris->array = tmp;
 +      }
 +
 +      uris->array[uris->count++] = uri;
 +      return 0;
 +}
 +
 +static int
 +read_uris(struct line_file *lfile, struct uris *uris)
 +{
 +      char *uri;
 +      int error;
 +
 +      error = lfile_read(lfile, &uri);
 +      if (error)
 +              return error;
 +
 +      if (uri == NULL)
 +              return pr_err("TAL file is empty.");
 +      if (strcmp(uri, "") == 0) {
 +              free(uri);
 +              return pr_err("There's no URI in the first line of the TAL.");
 +      }
 +
 +      error = uris_add(uris, uri);
 +      if (error)
 +              return error;
 +
 +      do {
 +              error = lfile_read(lfile, &uri);
 +              if (error)
 +                      return error;
 +
 +              if (uri == NULL)
 +                      return pr_err("TAL file ended prematurely. (Expected URI list, blank line and public key.)");
 +              if (strcmp(uri, "") == 0) {
 +                      free(uri);
 +                      return 0; /* Happy path */
 +              }
 +
 +              error = uris_add(uris, uri);
 +              if (error)
 +                      return error;
 +      } while (true);
 +}
 +
 +/*
 + * Will usually allocate slightly more because of the newlines, but I'm fine
 + * with it.
 + */
 +static size_t
 +get_spki_alloc_size(struct line_file *lfile)
 +{
 +      struct stat st;
 +      size_t result;
 +
 +      stat(lfile_name(lfile), &st);
 +      result = st.st_size - lfile_offset(lfile);
 +
 +      return EVP_DECODE_LENGTH(result);
 +}
 +
 +static int
 +read_spki(struct line_file *lfile, struct tal *tal)
 +{
 +      BIO *encoded; /* base64 encoded. */
 +      size_t alloc_size;
 +      int error;
 +
 +      alloc_size = get_spki_alloc_size(lfile);
 +      tal->spki = malloc(alloc_size);
 +      if (tal->spki == NULL)
 +              return -ENOMEM;
 +
 +      encoded = BIO_new_fp(lfile_fd(lfile), BIO_NOCLOSE);
 +      if (encoded == NULL) {
 +              free(tal->spki);
 +              return crypto_err("BIO_new_fp() returned NULL");
 +      }
 +
 +      error = base64_decode(encoded, tal->spki, alloc_size, &tal->spki_len);
 +      if (error)
 +              free(tal->spki);
 +
 +      BIO_free(encoded);
 +      return error;
 +}
 +
 +/**
 + * @file_name is expected to outlive @result.
 + */
 +int
 +tal_load(char const *file_name, struct tal **result)
 +{
 +      struct line_file *lfile;
 +      struct tal *tal;
 +      int error;
 +
 +      error = lfile_open(file_name, &lfile);
 +      if (error) {
 +              pr_errno(error, "Error opening file '%s'", file_name);
 +              goto fail4;
 +      }
 +
 +      tal = malloc(sizeof(struct tal));
 +      if (tal == NULL) {
 +              error = -ENOMEM;
 +              goto fail3;
 +      }
 +
 +      tal->file_name = file_name;
 +
 +      error = uris_init(&tal->uris);
 +      if (error)
 +              goto fail2;
 +
 +      error = read_uris(lfile, &tal->uris);
 +      if (error)
 +              goto fail1;
 +
 +      error = read_spki(lfile, tal);
 +      if (error)
 +              goto fail1;
 +
 +      lfile_close(lfile);
 +      *result = tal;
 +      return 0;
 +
 +fail1:
 +      uris_destroy(&tal->uris);
 +fail2:
 +      free(tal);
 +fail3:
 +      lfile_close(lfile);
 +fail4:
 +      return error;
 +}
 +
 +void tal_destroy(struct tal *tal)
 +{
 +      if (tal == NULL)
 +              return;
 +
 +      uris_destroy(&tal->uris);
 +      free(tal->spki);
 +      free(tal);
 +}
 +
 +int
 +foreach_uri(struct tal *tal, foreach_uri_cb cb)
 +{
 +      struct rpki_uri uri;
 +      unsigned int i;
 +      int error;
 +
 +      for (i = 0; i < tal->uris.count; i++) {
 +              error = uri_init_str(&uri, tal->uris.array[i],
 +                  strlen(tal->uris.array[i]));
 +              if (error == ENOTRSYNC) {
 +                      /* Log level should probably be INFO. */
 +                      pr_debug("TAL has non-RSYNC URI; ignoring.");
 +                      continue;
 +              }
 +              if (error)
 +                      return error;
 +
 +              error = cb(tal, &uri);
 +              uri_cleanup(&uri);
 +              if (error)
 +                      return error;
 +      }
 +
 +      return 0;
 +}
 +
 +void
 +tal_shuffle_uris(struct tal *tal)
 +{
 +      char **array = tal->uris.array;
 +      unsigned int count = tal->uris.count;
 +      char *tmp;
 +      long random_index;
 +      unsigned int i;
 +
 +      random_init();
 +
 +      for (i = 0; i < count; i++) {
 +              tmp = array[i];
 +              random_index = random_at_most(count - 1 - i) + i;
 +              array[i] = array[random_index];
 +              array[random_index] = tmp;
 +      }
 +}
 +
 +char const *
 +tal_get_file_name(struct tal *tal)
 +{
 +      return tal->file_name;
 +}
 +
 +void
 +tal_get_spki(struct tal *tal, unsigned char const **buffer, size_t *len)
 +{
 +      *buffer = tal->spki;
 +      *len = tal->spki_len;
 +}
++
++/**
++ * Performs the whole validation walkthrough on uri @uri, which is assumed to
++ * have been extracted from a TAL.
++ */
++static int
++handle_tal_uri(struct tal *tal, struct rpki_uri const *uri)
++{
++      /*
++       * Because of the way the foreach iterates, this function must return
++       *
++       * - 0 on soft errors.
++       * - `> 0` on URI handled successfully.
++       * - `< 0` on hard errors.
++       *
++       * A "soft error" is "the connection to the preferred URI fails, or the
++       * retrieved CA certificate public key does not match the TAL public
++       * key." (RFC 7730)
++       *
++       * A "hard error" is any other error.
++       */
++
++      struct validation *state;
++      int error;
++
++      error = download_files(uri, true);
++      if (error) {
++              return pr_warn("TAL URI '%s' could not be RSYNC'd.",
++                  uri->global);
++      }
++
++      error = validation_prepare(&state, tal);
++      if (error)
++              return ENSURE_NEGATIVE(error);
++
++      pr_debug_add("TAL URI '%s' {", uri_get_printable(uri));
++
++      if (!uri_is_certificate(uri)) {
++              pr_err("TAL file does not point to a certificate. (Expected .cer, got '%s')",
++                  uri_get_printable(uri));
++              error = -EINVAL;
++              goto end;
++      }
++
++      error = certificate_traverse(NULL, uri, NULL, true);
++      if (error) {
++              switch (validation_pubkey_state(state)) {
++              case PKS_INVALID:
++                      error = 0;
++                      break;
++              case PKS_VALID:
++              case PKS_UNTESTED:
++                      error = ENSURE_NEGATIVE(error);
++                      break;
++              }
++      } else {
++              error = 1;
++      }
++
++end:  validation_destroy(state);
++      pr_debug_rm("}");
++      return error;
++}
++
++/* TODO set @updated */
++int
++perform_standalone_validation(bool *updated)
++{
++      struct tal *tal;
++      int error;
++
++      fnstack_init();
++      fnstack_push(config_get_tal());
++
++      error = tal_load(config_get_tal(), &tal);
++      if (error)
++              goto end;
++
++      if (config_get_shuffle_tal_uris())
++              tal_shuffle_uris(tal);
++      error = foreach_uri(tal, handle_tal_uri);
++      if (error > 0)
++              error = 0;
++      else if (error == 0)
++              error = pr_err("None of the URIs of the TAL yielded a successful traversal.");
++
++end:  tal_destroy(tal);
++      fnstack_pop();
++      fnstack_cleanup();
++      return error;
++}
index e092989faf077f20c2fae4795c953935a9a1a788,0000000000000000000000000000000000000000..a79ee43ac2b893f0d757ee8f394dc8d66b82823e
mode 100644,000000..100644
--- /dev/null
@@@ -1,21 -1,0 +1,23 @@@
 +#ifndef TAL_OBJECT_H_
 +#define TAL_OBJECT_H_
 +
 +/* This is RFC 7730. */
 +
 +#include <stddef.h>
 +#include "uri.h"
 +
 +struct tal;
 +
 +int tal_load(char const *, struct tal **);
 +void tal_destroy(struct tal *);
 +
 +typedef int (*foreach_uri_cb)(struct tal *, struct rpki_uri const *);
 +int foreach_uri(struct tal *, foreach_uri_cb);
 +void tal_shuffle_uris(struct tal *);
 +
 +char const *tal_get_file_name(struct tal *);
 +void tal_get_spki(struct tal *, unsigned char const **, size_t *);
 +
++int perform_standalone_validation(bool *);
++
 +#endif /* TAL_OBJECT_H_ */
diff --cc src/rpp.c
index bcb7eda27bbb6594dd2061db7cebae4b6848a97e,0000000000000000000000000000000000000000..a546b4561aa3e2502d57ab447bfa62a44bc07f2d
mode 100644,000000..100644
--- /dev/null
+++ b/src/rpp.c
@@@ -1,161 -1,0 +1,161 @@@
-               handle_roa(uri, pp, crls);
 +#include "rpp.h"
 +
 +#include <stdlib.h>
 +#include "array_list.h"
 +#include "log.h"
 +#include "thread_var.h"
 +#include "uri.h"
 +#include "object/certificate.h"
 +#include "object/crl.h"
 +#include "object/ghostbusters.h"
 +#include "object/roa.h"
 +
 +ARRAY_LIST(uris, struct rpki_uri)
 +
 +/** A Repository Publication Point (RFC 6481), as described by some manifest. */
 +struct rpp {
 +      struct uris certs; /* Certificates */
 +
 +      struct rpki_uri crl; /* Certificate Revocation List */
 +      bool crl_set;
 +
 +      /* The Manifest is not needed for now. */
 +
 +      struct uris roas; /* Route Origin Attestations */
 +
 +      struct uris ghostbusters;
 +};
 +
 +struct rpp *
 +rpp_create(void)
 +{
 +      struct rpp *result;
 +
 +      result = malloc(sizeof(struct rpp));
 +      if (result == NULL)
 +              goto fail1;
 +
 +      if (uris_init(&result->certs) != 0)
 +              goto fail2;
 +      result->crl_set = false;
 +      if (uris_init(&result->roas) != 0)
 +              goto fail3;
 +      if (uris_init(&result->ghostbusters) != 0)
 +              goto fail4;
 +
 +      return result;
 +
 +fail4:
 +      uris_cleanup(&result->roas, uri_cleanup);
 +fail3:
 +      uris_cleanup(&result->certs, uri_cleanup);
 +fail2:
 +      free(result);
 +fail1:
 +      return NULL;
 +}
 +
 +void
 +rpp_destroy(struct rpp *pp)
 +{
 +      uris_cleanup(&pp->certs, uri_cleanup);
 +      uri_cleanup(&pp->crl);
 +      uris_cleanup(&pp->roas, uri_cleanup);
 +      uris_cleanup(&pp->ghostbusters, uri_cleanup);
 +      free(pp);
 +}
 +
 +int
 +rpp_add_cert(struct rpp *pp, struct rpki_uri *uri)
 +{
 +      return uris_add(&pp->certs, uri);
 +}
 +
 +int
 +rpp_add_roa(struct rpp *pp, struct rpki_uri *uri)
 +{
 +      return uris_add(&pp->roas, uri);
 +}
 +
 +int
 +rpp_add_ghostbusters(struct rpp *pp, struct rpki_uri *uri)
 +{
 +      return uris_add(&pp->ghostbusters, uri);
 +}
 +
 +int
 +rpp_add_crl(struct rpp *pp, struct rpki_uri *uri)
 +{
 +      /* rfc6481#section-2.2 */
 +      if (pp->crl_set)
 +              return pr_err("Repository Publication Point has more than one CRL.");
 +
 +      pp->crl = *uri;
 +      pp->crl_set = true;
 +      return 0;
 +}
 +
 +static int
 +add_crl_to_stack(struct rpp *pp, STACK_OF(X509_CRL) *crls)
 +{
 +      X509_CRL *crl;
 +      int error;
 +      int idx;
 +
 +      fnstack_push_uri(&pp->crl);
 +
 +      error = crl_load(&pp->crl, &crl);
 +      if (error)
 +              goto end;
 +
 +      idx = sk_X509_CRL_push(crls, crl);
 +      if (idx <= 0) {
 +              error = crypto_err("Could not add CRL to a CRL stack");
 +              X509_CRL_free(crl);
 +              goto end;
 +      }
 +
 +end:
 +      fnstack_pop();
 +      return error;
 +}
 +
 +struct rpki_uri const *
 +rpp_get_crl(struct rpp const *pp)
 +{
 +      return pp->crl_set ? &pp->crl : NULL;
 +}
 +
 +int
 +rpp_traverse(struct rpp *pp)
 +{
 +      /*
 +       * TODO is the stack supposed to have only the CRLs of this layer,
 +       * or all of them?
 +       */
 +      STACK_OF(X509_CRL) *crls;
 +      struct rpki_uri *uri;
 +      int error;
 +
 +      crls = sk_X509_CRL_new_null();
 +      if (crls == NULL)
 +              return pr_enomem();
 +      error = add_crl_to_stack(pp, crls);
 +      if (error)
 +              goto end;
 +
 +      /* Use CRL stack to validate certificates, and also traverse them. */
 +      ARRAYLIST_FOREACH(&pp->certs, uri)
 +              certificate_traverse(pp, uri, crls, false);
 +
 +      /* Use valid address ranges to print ROAs that match them. */
 +      ARRAYLIST_FOREACH(&pp->roas, uri)
-               handle_ghostbusters(uri, pp, crls);
++              roa_traverse(uri, pp, crls);
 +
 +      ARRAYLIST_FOREACH(&pp->ghostbusters, uri)
++              ghostbusters_traverse(uri, pp, crls);
 +
 +end:
 +      sk_X509_CRL_pop_free(crls, X509_CRL_free);
 +      return error;
 +}
index 0000000000000000000000000000000000000000,cbea8177e0599278353a9953c0d47ad9227cb0c3..b8b4e3a823b311049bbadd596a7e383cfa6b97ff
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,71 +1,72 @@@
 -    char *message)
+ #include "err_pdu.h"
+ #include <err.h>
+ #include <unistd.h>
+ #include "pdu_sender.h"
++#include "log.h"
+ int
+ err_pdu_send(int fd, u_int8_t version, u_int16_t code, void *err_pdu_header,
 -      char *code_title;
++    char const *message)
+ {
+       int error;
+       error = send_error_report_pdu(fd, version, code, err_pdu_header,
+           message);
+       if (err_pdu_is_fatal(code)) {
+               warnx("Fatal error report PDU sent [code %u], closing socket.",
+                   code);
+               close(fd);
+       }
+       return error;
+ }
+ bool
+ err_pdu_is_fatal(u_int16_t code)
+ {
+       /* Only NO_DATA_AVAILABLE error isn't fatal */
+       return code != ERR_PDU_NO_DATA_AVAILABLE;
+ }
+ void
+ err_pdu_log(u_int16_t code, char *message)
+ {
 -      warnx("Error report PDU info: '%s', message '%s'.",
++      char const *code_title;
+       switch (code) {
+       case ERR_PDU_CORRUPT_DATA:
+               code_title = "Corrupt Data";
+               break;
+       case ERR_PDU_INTERNAL_ERROR:
+               code_title = "Internal Error";
+               break;
+       case ERR_PDU_NO_DATA_AVAILABLE:
+               code_title = "No Data Available";
+               break;
+       case ERR_PDU_INVALID_REQUEST:
+               code_title = "Invalid Request";
+               break;
+       case ERR_PDU_UNSUP_PROTO_VERSION:
+               code_title = "Unsupported Protocol Version";
+               break;
+       case ERR_PDU_UNSUP_PDU_TYPE:
+               code_title = "Unsupported PDU Type";
+               break;
+       case ERR_PDU_WITHDRAWAL_UNKNOWN:
+               code_title = "Withdrawal of Unknown Record";
+               break;
+       case ERR_PDU_DUPLICATE_ANNOUNCE:
+               code_title = "Duplicate Announcement Received";
+               break;
+       case ERR_PDU_UNEXPECTED_PROTO_VERSION:
+               code_title = "Unexpected Protocol Version";
+               break;
+       default:
+               code_title = "Unknown error code";
+               break;
+       }
++      pr_err("Error report PDU info: '%s', message '%s'.",
+           code_title, message == NULL ? "[empty]" : message);
+ }
index 0000000000000000000000000000000000000000,bdb0c62d27aacb76dea3539dabdfb73eb3ca9f4d..fa6f4ba9ae7c473fa8bf99545a44f3fa7061000c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,22 +1,22 @@@
 -int err_pdu_send(int, u_int8_t, u_int16_t, void *, char *);
+ #ifndef SRC_RTR_ERR_PDU_H_
+ #define SRC_RTR_ERR_PDU_H_
+ #include <stdbool.h>
+ #include <sys/types.h>
+ #define ERR_PDU_CORRUPT_DATA                          0
+ #define ERR_PDU_INTERNAL_ERROR                                1
+ #define ERR_PDU_NO_DATA_AVAILABLE                     2
+ #define ERR_PDU_INVALID_REQUEST                               3
+ #define ERR_PDU_UNSUP_PROTO_VERSION                   4
+ #define ERR_PDU_UNSUP_PDU_TYPE                                5
+ #define ERR_PDU_WITHDRAWAL_UNKNOWN                    6
+ #define ERR_PDU_DUPLICATE_ANNOUNCE                    7
+ #define ERR_PDU_UNEXPECTED_PROTO_VERSION              8
++int err_pdu_send(int, u_int8_t, u_int16_t, void *, char const *);
+ bool err_pdu_is_fatal(u_int16_t);
+ void err_pdu_log(u_int16_t, char *);
+ #endif /* SRC_RTR_ERR_PDU_H_ */
diff --cc src/rtr/pdu.c
index 0000000000000000000000000000000000000000,770e52a8be78da2458372d1b392467616c275bb9..d4f9d51dfd54bff40e1a0e8caa1c6abd62bef90d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,229 +1,227 @@@
 -          || read_int16(fd, &header->session_id)
+ #include "pdu.h"
+ #include <err.h>
+ #include <errno.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include "../common.h"
+ #include "pdu_handler.h"
+ static int    pdu_header_from_stream(int, struct pdu_header *);
+ static int    serial_notify_from_stream(struct pdu_header *, int, void *);
+ static int    serial_query_from_stream(struct pdu_header *, int, void *);
+ static int    reset_query_from_stream(struct pdu_header *, int, void *);
+ static int    cache_response_from_stream(struct pdu_header *, int, void *);
+ static int    ipv4_prefix_from_stream(struct pdu_header *, int, void *);
+ static int    ipv6_prefix_from_stream(struct pdu_header *, int, void *);
+ static int    end_of_data_from_stream(struct pdu_header *, int, void *);
+ static int    cache_reset_from_stream(struct pdu_header *, int, void *);
+ static int    error_report_from_stream(struct pdu_header *, int, void *);
+ static void   error_report_destroy(void *);
+ int
+ pdu_load(int fd, void **pdu, struct pdu_metadata const **metadata,
+     u_int8_t *rtr_version)
+ {
+       struct pdu_header header;
+       struct pdu_metadata const *meta;
+       int err;
+       err = pdu_header_from_stream(fd, &header);
+       if (err)
+               return err;
+       meta = pdu_get_metadata(header.pdu_type);
+       if (!meta)
+               return -ENOENT; /* TODO try to skip it anyway? */
+       *pdu = malloc(meta->length);
+       if (*pdu == NULL)
+               return -ENOMEM;
+       err = meta->from_stream(&header, fd, *pdu);
+       if (err) {
+               free(*pdu);
+               return err;
+       }
+       *rtr_version = header.protocol_version;
+       if (metadata)
+               *metadata = meta;
+       return 0;
+ }
+ static int
+ pdu_header_from_stream(int fd, struct pdu_header *header)
+ {
+       /* If the first read yields no bytes, the connection was terminated. */
+       return read_int8(fd, &header->protocol_version)
+           || read_int8(fd, &header->pdu_type)
 -      return (type < 0 || ARRAY_SIZE(pdu_metadatas) <= type)
 -          ? NULL
 -          : pdu_metadatas[type];
++          || read_int16(fd, &header->m.session_id)
+           || read_int32(fd, &header->length);
+ }
+ static int
+ serial_notify_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+       struct serial_notify_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int32(fd, &pdu->serial_number);
+ }
+ static int
+ serial_query_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+       struct serial_query_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int32(fd, &pdu->serial_number);
+ }
+ static int
+ reset_query_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+       struct reset_query_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return 0;
+ }
+ static int
+ cache_response_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+       struct cache_response_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return 0;
+ }
+ static int
+ ipv4_prefix_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+       struct ipv4_prefix_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int8(fd, &pdu->flags)
+           || read_int8(fd, &pdu->prefix_length)
+           || read_int8(fd, &pdu->max_length)
+           || read_int8(fd, &pdu->zero)
+           || read_in_addr(fd, &pdu->ipv4_prefix)
+           || read_int32(fd, &pdu->asn);
+ }
+ static int
+ ipv6_prefix_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+       struct ipv6_prefix_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int8(fd, &pdu->flags)
+           || read_int8(fd, &pdu->prefix_length)
+           || read_int8(fd, &pdu->max_length)
+           || read_int8(fd, &pdu->zero)
+           || read_in6_addr(fd, &pdu->ipv6_prefix)
+           || read_int32(fd, &pdu->asn);
+ }
+ static int
+ end_of_data_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+       struct end_of_data_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return read_int32(fd, &pdu->serial_number);
+ }
+ static int
+ cache_reset_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+       struct cache_reset_pdu *pdu = pdu_void;
+       memcpy(&pdu->header, header, sizeof(*header));
+       return 0;
+ }
+ static int
+ error_report_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+       struct error_report_pdu *pdu = pdu_void;
+       u_int32_t sub_pdu_len; /* TODO use this for something */
+       u_int8_t rtr_version;
+       int error;
+       memcpy(&pdu->header, header, sizeof(*header));
+       error = read_int32(fd, &sub_pdu_len);
+       if (error)
+               return error;
+       error = pdu_load(fd, &pdu->erroneous_pdu, NULL, &rtr_version);
+       if (error)
+               return -EINVAL;
+       error = read_string(fd, &pdu->error_message);
+       if (error) {
+               free(pdu->erroneous_pdu);
+               return error;
+       }
+       return 0;
+ }
+ static void
+ error_report_destroy(void *pdu_void)
+ {
+       struct error_report_pdu *pdu = pdu_void;
+       struct pdu_header *sub_hdr;
+       struct pdu_metadata const *sub_meta;
+       sub_hdr = pdu_get_header(pdu->erroneous_pdu);
+       sub_meta = pdu_get_metadata(sub_hdr->pdu_type);
+       if (sub_meta)
+               sub_meta->destructor(pdu->erroneous_pdu);
+       else
+               warnx("Unknown PDU type (%u).", sub_hdr->pdu_type);
+       free(pdu->error_message);
+       free(pdu_void);
+ }
+ #define DEFINE_METADATA(name, dtor)                                   \
+       static struct pdu_metadata const name ## _meta = {              \
+               .length = sizeof(struct name ## _pdu),                  \
+               .from_stream = name ## _from_stream,                    \
+               .handle = handle_ ## name ## _pdu,                      \
+               .destructor = dtor,                                     \
+       }
+ DEFINE_METADATA(serial_notify, free);
+ DEFINE_METADATA(serial_query, free);
+ DEFINE_METADATA(reset_query, free);
+ DEFINE_METADATA(cache_response, free);
+ DEFINE_METADATA(ipv4_prefix, free);
+ DEFINE_METADATA(ipv6_prefix, free);
+ DEFINE_METADATA(end_of_data, free);
+ DEFINE_METADATA(cache_reset, free);
+ DEFINE_METADATA(error_report, error_report_destroy);
+ struct pdu_metadata const *const pdu_metadatas[] = {
+       /* 0 */  &serial_notify_meta,
+       /* 1 */  &serial_query_meta,
+       /* 2 */  &reset_query_meta,
+       /* 3 */  &cache_response_meta,
+       /* 4 */  &ipv4_prefix_meta,
+       /* 5 */  NULL,
+       /* 6 */  &ipv6_prefix_meta,
+       /* 7 */  &end_of_data_meta,
+       /* 8 */  &cache_reset_meta,
+       /* 9 */  NULL,
+       /* 10 */ &error_report_meta,
+ };
+ struct pdu_metadata const *
+ pdu_get_metadata(u_int8_t type)
+ {
++      return (ARRAY_LEN(pdu_metadatas) <= type) ? NULL : pdu_metadatas[type];
+ }
+ struct pdu_header *
+ pdu_get_header(void *pdu)
+ {
+       /* The header is by definition the first field of every PDU. */
+       return pdu;
+ }
diff --cc src/rtr/pdu.h
index 0000000000000000000000000000000000000000,3fd761644024533d66d2c4f7e145c96be77fed53..8b441e1cc5c6d1aca45f6cc121b1217ae3f33b7f
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,102 +1,102 @@@
 -      };
+ #ifndef RTR_PDU_H_
+ #define RTR_PDU_H_
+ #include <netinet/in.h>
+ #include "../common.h"
+ #include "primitive_reader.h"
+ #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_ERROR_REPORT         10
+ struct pdu_header {
+       u_int8_t        protocol_version;
+       u_int8_t        pdu_type;
+       union {
+               u_int16_t       session_id;
+               u_int16_t       reserved;
+               u_int16_t       error_code;
++      } m; /* Note: "m" stands for "meh." I have no idea what to call this. */
+       u_int32_t       length;
+ };
+ struct serial_notify_pdu {
+       struct  pdu_header header;
+       u_int32_t       serial_number;
+ };
+ struct serial_query_pdu {
+       struct  pdu_header header;
+       u_int32_t       serial_number;
+ };
+ struct reset_query_pdu {
+       struct  pdu_header header;
+ };
+ struct cache_response_pdu {
+       struct pdu_header header;
+ };
+ struct ipv4_prefix_pdu {
+       struct  pdu_header header;
+       u_int8_t        flags;
+       u_int8_t        prefix_length;
+       u_int8_t        max_length;
+       u_int8_t        zero;
+       struct  in_addr ipv4_prefix;
+       u_int32_t       asn;
+ };
+ struct ipv6_prefix_pdu {
+       struct  pdu_header header;
+       u_int8_t        flags;
+       u_int8_t        prefix_length;
+       u_int8_t        max_length;
+       u_int8_t        zero;
+       struct  in6_addr ipv6_prefix;
+       u_int32_t       asn;
+ };
+ struct end_of_data_pdu {
+       struct  pdu_header header;
+       u_int32_t       serial_number;
+       u_int32_t       refresh_interval;
+       u_int32_t       retry_interval;
+       u_int32_t       expire_interval;
+ };
+ struct cache_reset_pdu {
+       struct  pdu_header header;
+ };
+ struct error_report_pdu {
+       struct  pdu_header header;
+       u_int32_t       error_pdu_length;
+       void            *erroneous_pdu;
+       u_int32_t       error_message_length;
+       rtr_char        *error_message;
+ };
+ struct pdu_metadata {
+       size_t  length;
+       int     (*from_stream)(struct pdu_header *, int, void *);
+       int     (*handle)(int, void *);
+       void    (*destructor)(void *);
+ };
+ __BEGIN_DECLS
+ int pdu_load(int, void **, struct pdu_metadata const **, u_int8_t *);
+ struct pdu_metadata const *pdu_get_metadata(u_int8_t);
+ struct pdu_header *pdu_get_header(void *);
+ __END_DECLS
+ #endif /* RTR_PDU_H_ */
index 0000000000000000000000000000000000000000,0ec9c4bf9d49b01d109de222017d3842f3b0fd63..ff87250ead81f0f167f9c1637d88a5e175a8db91
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,173 +1,173 @@@
 -warn_unexpected_pdu(int fd, void *pdu, char *pdu_name)
+ #include "pdu_handler.h"
+ #include <err.h>
+ #include <errno.h>
+ #include <stddef.h>
+ #include <unistd.h>
+ #include "err_pdu.h"
+ #include "pdu.h"
+ #include "pdu_sender.h"
+ #include "vrps.h"
+ static int
 -      if (received->header.session_id != session_id)
++warn_unexpected_pdu(int fd, void *pdu, char const *pdu_name)
+ {
+       struct pdu_header *pdu_header = pdu;
+       warnx("Unexpected %s PDU received", pdu_name);
+       err_pdu_send(fd, pdu_header->protocol_version, ERR_PDU_UNSUP_PDU_TYPE,
+           pdu_header, "Unexpected PDU received");
+       return -EINVAL;
+ }
+ int
+ 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 error;
+       /* Send Cache response PDU */
+       error = send_cache_response_pdu(common);
+       if (error)
+               return error;
+       /* Send Payload PDUs */
+       error = send_payload_pdus(common);
+       if (error)
+               return error;
+       /* Send End of data PDU */
+       return send_end_of_data_pdu(common);
+ }
+ int
+ handle_serial_query_pdu(int fd, void *pdu)
+ {
+       struct serial_query_pdu *received = pdu;
+       struct sender_common common;
+       int error, updates;
+       u_int32_t current_serial;
+       u_int16_t session_id;
+       u_int8_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 (err_pdu_is_fatal(received->header.error_code)) {
++      if (received->header.m.session_id != session_id)
+               return err_pdu_send(fd, version, ERR_PDU_CORRUPT_DATA,
+                   &received->header, NULL);
+       current_serial = get_last_serial_number();
+       init_sender_common(&common, fd, version, &session_id,
+           &received->serial_number, &current_serial);
+       updates = deltas_db_status(common.start_serial);
+       switch (updates) {
+       case 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 DIFF_UNDETERMINED:
+               /* https://tools.ietf.org/html/rfc8210#section-8.3 */
+               return send_cache_reset_pdu(&common);
+       case DIFF_AVAILABLE:
+               /* https://tools.ietf.org/html/rfc8210#section-8.2 */
+               return send_commmon_exchange(&common);
+       case NO_DIFF:
+               /* Typical exchange with no Payloads */
+               error = send_cache_response_pdu(&common);
+               if (error)
+                       return error;
+               return send_end_of_data_pdu(&common);
+       default:
+               warnx("Reached 'unreachable' code");
+               return -EINVAL;
+       }
+ }
+ int
+ handle_reset_query_pdu(int fd, void *pdu)
+ {
+       struct reset_query_pdu *received = pdu;
+       struct sender_common common;
+       u_int32_t current_serial;
+       u_int16_t session_id;
+       u_int8_t version;
+       int updates;
+       version = received->header.protocol_version;
+       session_id = get_current_session_id(version);
+       current_serial = get_last_serial_number();
+       init_sender_common(&common, fd, version, &session_id, NULL,
+           &current_serial);
+       updates = deltas_db_status(NULL);
+       switch (updates) {
+       case 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 DIFF_AVAILABLE:
+               /* https://tools.ietf.org/html/rfc8210#section-8.1 */
+               return send_commmon_exchange(&common);
+       default:
+               warnx("Reached 'unreachable' code");
+               return -EINVAL;
+       }
+ }
+ int
+ handle_cache_response_pdu(int fd, void *pdu)
+ {
+       return warn_unexpected_pdu(fd, pdu, "Cache Response");
+ }
+ int
+ handle_ipv4_prefix_pdu(int fd, void *pdu)
+ {
+       return warn_unexpected_pdu(fd, pdu, "IPv4 Prefix");
+ }
+ int
+ handle_ipv6_prefix_pdu(int fd, void *pdu)
+ {
+       return warn_unexpected_pdu(fd, pdu, "IPv6 Prefix");
+ }
+ int
+ handle_end_of_data_pdu(int fd, void *pdu)
+ {
+       return warn_unexpected_pdu(fd, pdu, "End of Data");
+ }
+ int
+ handle_cache_reset_pdu(int fd, void *pdu)
+ {
+       return warn_unexpected_pdu(fd, pdu, "Cache Reset");
+ }
+ int
+ handle_error_report_pdu(int fd, void *pdu)
+ {
+       struct error_report_pdu *received = pdu;
 -                  received->header.error_code);
++      if (err_pdu_is_fatal(received->header.m.error_code)) {
+               warnx("Fatal error report PDU received [code %u], closing socket.",
 -      err_pdu_log(received->header.error_code, received->error_message);
++                  received->header.m.error_code);
+               close(fd);
+       }
++      err_pdu_log(received->header.m.error_code, received->error_message);
+       return 0;
+ }
index 0000000000000000000000000000000000000000,a7bf2e1a8c6b9d54d2ce1178ff6eff0c5c1ea932..1148d8510ad0428a7914b3584de527b70d37aaab
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,291 +1,291 @@@
 -#include "../configuration.h"
 -#include "../vrps.h"
 -#include "pdu_serializer.h"
+ #include "pdu_sender.h"
+ #include <err.h>
+ #include <errno.h>
+ #include <stdbool.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
 -      header->reserved = reserved;
++#include "config.h"
++#include "vrps.h"
++#include "rtr/pdu_serializer.h"
+ /* Header length field is always 64 bits long */
+ #define HEADER_LENGTH         8
+ /* IPvN PDUs length without header */
+ #define IPV4_PREFIX_LENGTH    12
+ #define IPV6_PREFIX_LENGTH    24
+ void
+ init_sender_common(struct sender_common *common, int fd, u_int8_t version,
+     u_int16_t *session_id, u_int32_t *start_serial, u_int32_t *end_serial)
+ {
+       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;
+ }
+ /*
+  * Set all the header values, EXCEPT length field.
+  */
+ static void
+ set_header_values(struct pdu_header *header, u_int8_t version, u_int8_t type,
+     u_int16_t reserved)
+ {
+       header->protocol_version = version;
+       header->pdu_type = type;
 -      pdu.ipv4_prefix = vrp->ipv4_prefix;
++      header->m.reserved = reserved;
+ }
+ static u_int32_t
+ length_serial_notify_pdu(struct serial_notify_pdu *pdu)
+ {
+       return HEADER_LENGTH + sizeof(pdu->serial_number);
+ }
+ static u_int32_t
+ length_ipvx_prefix_pdu(bool isv4)
+ {
+       return HEADER_LENGTH +
+           (isv4 ? IPV4_PREFIX_LENGTH : IPV6_PREFIX_LENGTH);
+ }
+ static u_int32_t
+ length_end_of_data_pdu(struct end_of_data_pdu *pdu)
+ {
+       u_int32_t len;
+       len = HEADER_LENGTH;
+       len += sizeof(pdu->serial_number);
+       if (pdu->header.protocol_version == RTR_V1) {
+               len += sizeof(pdu->refresh_interval);
+               len += sizeof(pdu->retry_interval);
+               len += sizeof(pdu->expire_interval);
+       }
+       return len;
+ }
+ static u_int32_t
+ length_error_report_pdu(struct error_report_pdu *pdu)
+ {
+       return HEADER_LENGTH +
+           pdu->error_pdu_length + sizeof(pdu->error_pdu_length) +
+           pdu->error_message_length + sizeof(pdu->error_message_length);
+ }
+ static int
+ send_response(int fd, char *data, size_t data_len)
+ {
+       struct data_buffer buffer;
+       int error;
+       init_buffer(&buffer);
+       /* Check for buffer overflow */
+       if (data_len > buffer.capacity) {
+               warnx("Response buffer out of capacity");
+               return -EINVAL;
+       }
+       memcpy(buffer.data, data, data_len);
+       buffer.len = data_len;
+       error = write(fd, buffer.data, buffer.len);
+       free_buffer(&buffer);
+       if (error < 0) {
+               warnx("Error sending response");
+               return -EINVAL;
+       }
+       return 0;
+ }
+ int
+ send_serial_notify_pdu(struct sender_common *common)
+ {
+       struct serial_notify_pdu pdu;
+       char data[BUFFER_SIZE];
+       size_t len;
+       set_header_values(&pdu.header, common->version, PDU_TYPE_SERIAL_NOTIFY,
+           *common->session_id);
+       pdu.serial_number = *common->start_serial;
+       pdu.header.length = length_serial_notify_pdu(&pdu);
+       len = serialize_serial_notify_pdu(&pdu, data);
+       return send_response(common->fd, data, len);
+ }
+ int
+ send_cache_reset_pdu(struct sender_common *common)
+ {
+       struct cache_reset_pdu pdu;
+       char data[BUFFER_SIZE];
+       size_t len;
+       /* This PDU has only the header */
+       set_header_values(&pdu.header, common->version, PDU_TYPE_CACHE_RESET,
+           0);
+       pdu.header.length = HEADER_LENGTH;
+       len = serialize_cache_reset_pdu(&pdu, data);
+       return send_response(common->fd, data, len);
+ }
+ int
+ send_cache_response_pdu(struct sender_common *common)
+ {
+       struct cache_response_pdu pdu;
+       char data[BUFFER_SIZE];
+       size_t len;
+       /* This PDU has only the header */
+       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);
+       return send_response(common->fd, data, len);
+ }
+ static int
+ send_ipv4_prefix_pdu(struct sender_common *common, struct vrp *vrp)
+ {
+       struct ipv4_prefix_pdu pdu;
+       char data[BUFFER_SIZE];
+       size_t len;
+       set_header_values(&pdu.header, common->version, PDU_TYPE_IPV4_PREFIX,
+           0);
+       pdu.flags = vrp->flags;
+       pdu.prefix_length = vrp->prefix_length;
+       pdu.max_length = vrp->max_prefix_length;
+       pdu.zero = 0;
 -      pdu.ipv6_prefix = vrp->ipv6_prefix;
++      pdu.ipv4_prefix = vrp->prefix.ipv4;
+       pdu.asn = vrp->asn;
+       pdu.header.length = length_ipvx_prefix_pdu(true);
+       len = serialize_ipv4_prefix_pdu(&pdu, data);
+       return send_response(common->fd, data, len);
+ }
+ static int
+ send_ipv6_prefix_pdu(struct sender_common *common, struct vrp *vrp)
+ {
+       struct ipv6_prefix_pdu pdu;
+       char data[BUFFER_SIZE];
+       size_t len;
+       set_header_values(&pdu.header, common->version, PDU_TYPE_IPV6_PREFIX,
+           0);
+       pdu.flags = vrp->flags;
+       pdu.prefix_length = vrp->prefix_length;
+       pdu.max_length = vrp->max_prefix_length;
+       pdu.zero = 0;
 -struct pdu_header *err_pdu_header, char *message)
++      pdu.ipv6_prefix = vrp->prefix.ipv6;
+       pdu.asn = vrp->asn;
+       pdu.header.length = length_ipvx_prefix_pdu(false);
+       len = serialize_ipv6_prefix_pdu(&pdu, data);
+       return send_response(common->fd, data, len);
+ }
+ int
+ send_payload_pdus(struct sender_common *common)
+ {
+       struct vrp *vrps, *ptr;
+       unsigned int len;
+       int error;
+       vrps = malloc(sizeof(struct vrp));
+       if (vrps == NULL) {
+               warn("Couldn't allocate VRPs to send PDUs");
+               return -ENOMEM;
+       }
+       len = get_vrps_delta(common->start_serial, common->end_serial, &vrps);
+       if (len == 0)
+               goto end;
+       for (ptr = vrps; (ptr - vrps) < len; ptr++) {
+               if (ptr->addr_fam == AF_INET)
+                       error = send_ipv4_prefix_pdu(common, ptr);
+               else if (ptr->addr_fam == AF_INET6)
+                       error = send_ipv6_prefix_pdu(common, ptr);
+               else
+                       error = -EINVAL;
+               if (error) {
+                       free(vrps);
+                       return error;
+               }
+       }
+ end:
+       free(vrps);
+       return 0;
+ }
+ int
+ send_end_of_data_pdu(struct sender_common *common)
+ {
+       struct end_of_data_pdu pdu;
+       char data[BUFFER_SIZE];
+       size_t len;
+       set_header_values(&pdu.header, common->version, PDU_TYPE_END_OF_DATA,
+           *common->session_id);
+       pdu.serial_number = *common->end_serial;
+       if (common->version == RTR_V1) {
+               pdu.refresh_interval = config_get_refresh_interval();
+               pdu.retry_interval = config_get_retry_interval();
+               pdu.expire_interval = config_get_expire_interval();
+       }
+       pdu.header.length = length_end_of_data_pdu(&pdu);
+       len = serialize_end_of_data_pdu(&pdu, data);
+       return send_response(common->fd, data, len);
+ }
+ int
+ send_error_report_pdu(int fd, u_int8_t version, u_int16_t code,
++struct pdu_header *err_pdu_header, char const *message)
+ {
+       struct error_report_pdu pdu;
+       char data[BUFFER_SIZE];
+       size_t len;
+       set_header_values(&pdu.header, version, PDU_TYPE_ERROR_REPORT,
+           code);
+       pdu.error_pdu_length = 0;
+       pdu.erroneous_pdu = (void *)err_pdu_header;
+       if (err_pdu_header != NULL)
+               pdu.error_pdu_length = sizeof(err_pdu_header);
+       pdu.error_message_length = 0;
+       pdu.error_message = NULL;
+       if (message != NULL) {
+               pdu.error_message = malloc(strlen(message) + 1);
+               if (pdu.error_message == NULL)
+                       warn("Error message couldn't be allocated, removed from PDU");
+               else {
+                       pdu.error_message_length = strlen(message) + 1;
+                       strcpy(pdu.error_message, message);
+               }
+       }
+       /* Calculate lengths */
+       pdu.header.length = length_error_report_pdu(&pdu);
+       len = serialize_error_report_pdu(&pdu, data);
+       free(pdu.error_message);
+       return send_response(fd, data, len);
+ }
index 0000000000000000000000000000000000000000,db228c8d7eb16696be96abde8f35d0102aa9db8a..a4ff8f2c33111a2dea6221afd85ebe1f24c1bbf7
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,27 +1,27 @@@
 -    char *);
+ #ifndef SRC_RTR_PDU_SENDER_H_
+ #define SRC_RTR_PDU_SENDER_H_
+ #include <sys/types.h>
+ #include "pdu.h"
+ struct sender_common {
+       int fd;
+       u_int8_t version;
+       u_int16_t *session_id;
+       u_int32_t *start_serial;
+       u_int32_t *end_serial;
+ };
+ void init_sender_common(struct sender_common *, int, u_int8_t, u_int16_t *,
+     u_int32_t *, u_int32_t *);
+ int send_serial_notify_pdu(struct sender_common *);
+ int send_cache_reset_pdu(struct sender_common *);
+ int send_cache_response_pdu(struct sender_common *);
+ int send_payload_pdus(struct sender_common *);
+ int send_end_of_data_pdu(struct sender_common *);
+ int send_error_report_pdu(int, u_int8_t, u_int16_t, struct pdu_header *,
++    char const *);
+ #endif /* SRC_RTR_PDU_SENDER_H_ */
index 0000000000000000000000000000000000000000,3e6220d457730c215dba0cbd713080d27319860f..4d794694d33a41eba841b8df0c0eeeec13f781a5
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,152 +1,153 @@@
 -      head_size = serialize_pdu_header(&pdu->header, pdu->header.session_id,
+ #include "pdu_serializer.h"
+ #include <stdlib.h>
+ #include <string.h>
+ #include "primitive_writer.h"
+ void
+ init_buffer(struct data_buffer *buffer)
+ {
+       buffer->capacity = BUFFER_SIZE;
+       buffer->data = malloc(BUFFER_SIZE);
+ }
+ void
+ free_buffer(struct data_buffer *buffer)
+ {
+       free(buffer->data);
+ }
+ static size_t
+ serialize_pdu_header(struct pdu_header *header, u_int16_t union_value,
+     char *buf)
+ {
+       char *ptr;
+       ptr = buf;
+       ptr = write_int8(ptr, header->protocol_version);
+       ptr = write_int8(ptr, header->pdu_type);
+       ptr = write_int16(ptr, union_value);
+       ptr = write_int32(ptr, header->length);
+       return ptr - buf;
+ }
+ size_t
+ serialize_serial_notify_pdu(struct serial_notify_pdu *pdu, char *buf)
+ {
+       size_t head_size;
+       char *ptr;
 -      return serialize_pdu_header(&pdu->header, pdu->header.session_id, buf);
++      head_size = serialize_pdu_header(&pdu->header, pdu->header.m.session_id,
+           buf);
+       ptr = buf + head_size;
+       ptr = write_int32(ptr, pdu->serial_number);
+       return ptr - buf;
+ }
+ size_t
+ serialize_cache_response_pdu(struct cache_response_pdu *pdu, char *buf)
+ {
+       /* No payload to serialize */
 -      head_size = serialize_pdu_header(&pdu->header, pdu->header.reserved,
++      return serialize_pdu_header(&pdu->header, pdu->header.m.session_id,
++          buf);
+ }
+ size_t
+ serialize_ipv4_prefix_pdu(struct ipv4_prefix_pdu *pdu, char *buf)
+ {
+       size_t head_size;
+       char *ptr;
 -      head_size = serialize_pdu_header(&pdu->header, pdu->header.reserved,
++      head_size = serialize_pdu_header(&pdu->header, pdu->header.m.reserved,
+           buf);
+       ptr = buf + head_size;
+       ptr = write_int8(ptr, pdu->flags);
+       ptr = write_int8(ptr, pdu->prefix_length);
+       ptr = write_int8(ptr, pdu->max_length);
+       ptr = write_int8(ptr, pdu->zero);
+       ptr = write_in_addr(ptr, pdu->ipv4_prefix);
+       ptr = write_int32(ptr, pdu->asn);
+       return ptr - buf;
+ }
+ size_t
+ serialize_ipv6_prefix_pdu(struct ipv6_prefix_pdu *pdu, char *buf)
+ {
+       size_t head_size;
+       char *ptr;
 -      head_size = serialize_pdu_header(&pdu->header, pdu->header.session_id,
++      head_size = serialize_pdu_header(&pdu->header, pdu->header.m.reserved,
+           buf);
+       ptr = buf + head_size;
+       ptr = write_int8(ptr, pdu->flags);
+       ptr = write_int8(ptr, pdu->prefix_length);
+       ptr = write_int8(ptr, pdu->max_length);
+       ptr = write_int8(ptr, pdu->zero);
+       ptr = write_in6_addr(ptr, pdu->ipv6_prefix);
+       ptr = write_int32(ptr, pdu->asn);
+       return ptr - buf;
+ }
+ size_t
+ serialize_end_of_data_pdu(struct end_of_data_pdu *pdu, char *buf)
+ {
+       size_t head_size;
+       char *ptr;
 -      return serialize_pdu_header(&pdu->header, pdu->header.reserved, buf);
++      head_size = serialize_pdu_header(&pdu->header, pdu->header.m.session_id,
+           buf);
+       ptr = buf + head_size;
+       ptr = write_int32(ptr, pdu->serial_number);
+       if (pdu->header.protocol_version == RTR_V1) {
+               ptr = write_int32(ptr, pdu->refresh_interval);
+               ptr = write_int32(ptr, pdu->retry_interval);
+               ptr = write_int32(ptr, pdu->expire_interval);
+       }
+       return ptr - buf;
+ }
+ size_t
+ serialize_cache_reset_pdu(struct cache_reset_pdu *pdu, char *buf)
+ {
+       /* No payload to serialize */
 -      head_size = serialize_pdu_header(&pdu->header, pdu->header.error_code,
++      return serialize_pdu_header(&pdu->header, pdu->header.m.reserved, buf);
+ }
+ size_t
+ serialize_error_report_pdu(struct error_report_pdu *pdu, char *buf)
+ {
+       struct pdu_header *err_pdu_header;
+       size_t head_size;
+       char *ptr, *tmp_ptr;
+       int i;
 -                  err_pdu_header->reserved, ptr);
++      head_size = serialize_pdu_header(&pdu->header, pdu->header.m.error_code,
+           buf);
+       ptr = buf + head_size;
+       ptr = write_int32(ptr, pdu->error_pdu_length);
+       if (pdu->error_pdu_length > 0) {
+               /* Set only the header of err PDU */
+               err_pdu_header = (struct pdu_header *)pdu->erroneous_pdu;
+               head_size = serialize_pdu_header(err_pdu_header,
++                  err_pdu_header->m.reserved, ptr);
+               ptr = ptr + head_size;
+       }
+       ptr = write_int32(ptr, pdu->error_message_length);
+       tmp_ptr = pdu->error_message;
+       for (i = 0; i < pdu->error_message_length; i++)
+               ptr = write_int8(ptr, tmp_ptr[i]);
+       return ptr - buf;
+ }
index 0000000000000000000000000000000000000000,dd264d48c29917f3c1cb2e62dd051d34b3e05d5a..fac4f0ec45f56ad89a9abbb67e6e888baa98ce23
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,238 +1,250 @@@
 -      unsigned char trash[TLEN];
+ #include "primitive_reader.h"
+ #include <err.h>
+ #include <errno.h>
+ #include <stdlib.h>
++#include <stdint.h>
+ #include <unistd.h>
+ #include <netinet/in.h>
++#include "log.h"
++
+ static int read_exact(int, unsigned char *, size_t);
+ static int read_and_waste(int, unsigned char *, size_t, u_int32_t);
+ static int get_octets(unsigned char);
+ static void place_null_character(rtr_char *, size_t);
+ static int
+ read_exact(int fd, unsigned char *buffer, size_t buffer_len)
+ {
+       ssize_t read_result;
+       size_t offset;
+       for (offset = 0; offset < buffer_len; offset += read_result) {
+               read_result = read(fd, &buffer[offset], buffer_len - offset);
+               if (read_result == -1) {
+                       warn("Client socket read interrupted");
+                       return -errno;
+               }
+               if (read_result == 0) {
+                       warnx("Stream ended mid-PDU.");
+                       return -EPIPE;
+               }
+       }
+       return 0;
+ }
+ int
+ read_int8(int fd, u_int8_t *result)
+ {
+       return read_exact(fd, result, sizeof(u_int8_t));
+ }
+ /** Big Endian. */
+ int
+ read_int16(int fd, u_int16_t *result)
+ {
+       unsigned char buffer[2];
+       int err;
+       err = read_exact(fd, buffer, sizeof(buffer));
+       if (err)
+               return err;
+       *result = (((u_int16_t)buffer[0]) << 8) | ((u_int16_t)buffer[1]);
+       return 0;
+ }
+ /** Big Endian. */
+ int
+ read_int32(int fd, u_int32_t *result)
+ {
+       unsigned char buffer[4];
+       int err;
+       err = read_exact(fd, buffer, sizeof(buffer));
+       if (err)
+               return err;
+       *result = (((u_int32_t)buffer[0]) << 24)
+               | (((u_int32_t)buffer[1]) << 16)
+               | (((u_int32_t)buffer[2]) <<  8)
+               | (((u_int32_t)buffer[3])      );
+       return 0;
+ }
+ int
+ read_in_addr(int fd, struct in_addr *result)
+ {
+       return read_int32(fd, &result->s_addr);
+ }
+ int
+ read_in6_addr(int fd, struct in6_addr *result)
+ {
+       return read_int32(fd, &result->s6_addr32[0])
+           || read_int32(fd, &result->s6_addr32[1])
+           || read_int32(fd, &result->s6_addr32[2])
+           || read_int32(fd, &result->s6_addr32[3]);
+ }
+ /*
+  * Consumes precisely @total_len bytes from @fd.
+  * The first @str_len bytes are stored in @str.
+  *
+  * It is required that @str_len <= @total_len.
+  */
+ static int
+ read_and_waste(int fd, unsigned char *str, size_t str_len, u_int32_t total_len)
+ {
+ #define TLEN 1024 /* "Trash length" */
 -      for (offset = str_len; (offset + TLEN) < total_len; offset += TLEN) {
 -              err = read_exact(fd, trash, TLEN);
++      unsigned char *trash;
+       size_t offset;
+       int err;
+       err = read_exact(fd, str, str_len);
+       if (err)
+               return err;
 -                      return err;
++      if (str_len == total_len)
++              return 0;
++
++      trash = malloc(TLEN);
++      if (trash == NULL)
++              return pr_enomem();
++
++      for (offset = str_len; offset < total_len; offset += TLEN) {
++              err = read_exact(fd, trash,
++                  (total_len - offset >= TLEN) ? TLEN : (total_len - offset));
+               if (err)
 -      return read_exact(fd, trash, total_len - offset);
++                      break;
+       }
++      free(trash);
++      return err;
+ #undef TLEN
+ }
+ #define EINVALID_UTF8 -0xFFFF
+ /*
+  * Returns the length (in octets) of the UTF-8 code point that starts with
+  * octet @first_octet.
+  */
+ static int
+ get_octets(unsigned char first_octet)
+ {
+       if ((first_octet & 0x80) == 0)
+               return 1;
+       if ((first_octet >> 5) == 6) /* 0b110 */
+               return 2;
+       if ((first_octet >> 4) == 14) /* 0b1110 */
+               return 3;
+       if ((first_octet >> 3) == 30) /* 0b11110 */
+               return 4;
+       return EINVALID_UTF8;
+ }
+ /* This is just a cast. The barebones version is too cluttered. */
+ #define UCHAR(c) ((unsigned char *)c)
+ /*
+  * This also sanitizes the string, BTW.
+  * (Because it overrides the first invalid character with the null chara.
+  * The rest is silently ignored.)
+  */
+ static void
+ place_null_character(rtr_char *str, size_t len)
+ {
+       rtr_char *null_chara_pos;
+       rtr_char *cursor;
+       int octet;
+       int octets;
+       /*
+        * This could be optimized by noticing that all byte continuations in
+        * UTF-8 start with 0b10. This means that we could start from the end
+        * of the string and move left until we find a valid character.
+        * But if we do that, we'd lose the sanitization. So this is better
+        * methinks.
+        */
+       null_chara_pos = str;
+       cursor = str;
+       while (cursor < str + len - 1) {
+               octets = get_octets(*UCHAR(cursor));
+               if (octets == EINVALID_UTF8)
+                       break;
+               cursor++;
+               for (octet = 1; octet < octets; octet++) {
+                       /* Memory ends in the middle of this code point? */
+                       if (cursor >= str + len - 1)
+                               goto end;
+                       /* All continuation octets must begin with 0b10. */
+                       if ((*(UCHAR(cursor)) >> 6) != 2 /* 0b10 */)
+                               goto end;
+                       cursor++;
+               }
+               null_chara_pos = cursor;
+       }
+ end:
+       *null_chara_pos = '\0';
+ }
+ /*
+  * Reads an RTR string from the file descriptor @fd. Returns the string as a
+  * normal UTF-8 C string (NULL-terminated).
+  *
+  * Will consume the entire string from the stream, but @result can be
+  * truncated. This is because RTR strings are technically allowed to be 4 GBs
+  * long.
+  *
+  * The result is allocated in the heap. It will length 4096 characters at most.
+  * (Including the NULL chara.)
+  */
+ int
+ read_string(int fd, rtr_char **result)
+ {
+       /* Actual string length claimed by the PDU, in octets. */
+       u_int32_t full_length32; /* Excludes the null chara */
+       u_int64_t full_length64; /* Includes the null chara */
+       /*
+        * Actual length that we allocate. Octets.
+        * This exists because there might be value in truncating the string;
+        * full_length is a fucking 32-bit integer for some reason.
+        * Note that, because this is UTF-8 we're dealing with, this might not
+        * necessarily end up being the actual octet length of the final
+        * string; since our truncation can land in the middle of a code point,
+        * the null character might need to be shifted left slightly.
+        */
+       size_t alloc_length; /* Includes the null chara */
+       rtr_char *str;
+       int err;
+       err = read_int32(fd, &full_length32);
+       if (err)
+               return err;
+       full_length64 = ((u_int64_t) full_length32) + 1;
+       alloc_length = (full_length64 > 4096) ? 4096 : full_length64;
+       str = malloc(alloc_length);
+       if (!str)
+               return -ENOMEM;
+       err = read_and_waste(fd, UCHAR(str), alloc_length - 1, full_length32);
+       if (err) {
+               free(str);
+               return err;
+       }
+       place_null_character(str, alloc_length);
+       *result = str;
+       return 0;
+ }
diff --cc src/rtr/rtr.c
index 0000000000000000000000000000000000000000,863e4272191bedadd45ae41770ab2c04bd57d628..ed49b86c501e7cdb9bf973a5d69fb65ba0d48695
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,320 +1,359 @@@
 -#include "../updates_daemon.h"
+ #include "rtr.h"
+ #include <err.h>
+ #include <errno.h>
+ #include <netdb.h>
+ #include <pthread.h>
+ #include <signal.h>
+ #include <stdbool.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
+ #include <arpa/inet.h>
+ #include <sys/queue.h>
 -#include "configuration.h"
 -#include "err_pdu.h"
 -#include "pdu.h"
++#include "config.h"
+ #include "clients.h"
 -create_server_socket(void)
++#include "updates_daemon.h"
++#include "rtr/err_pdu.h"
++#include "rtr/pdu.h"
+ /* TODO Support both RTR v0 an v1 */
+ #define RTR_VERSION_SUPPORTED RTR_V0
+ volatile bool loop;
+ struct thread_node {
+       pthread_t tid;
+       SLIST_ENTRY(thread_node) next;
+ };
+ SLIST_HEAD(thread_list, thread_node) threads;
++static int
++init_addrinfo(struct addrinfo **result)
++{
++      char const *hostname;
++      char const *service;
++      struct addrinfo hints;
++      int error;
++
++      memset(&hints, 0 , sizeof(hints));
++      hints.ai_family = AF_UNSPEC;
++      /* hints.ai_socktype = SOCK_DGRAM; */
++      hints.ai_flags |= AI_PASSIVE;
++
++      hostname = config_get_server_address();
++      service = config_get_server_port();
++
++      error = getaddrinfo(hostname, service, &hints, result);
++      if (error) {
++              warnx("Could not infer a bindable address out of address '%s' and port '%s': %s",
++                  (hostname != NULL) ? hostname : "any", service,
++                  gai_strerror(error));
++              return error;
++      }
++
++      return 0;
++}
++
++
+ /*
+  * Creates the socket that will stay put and wait for new connections started
+  * from the clients.
+  */
+ static int
 -      struct addrinfo const *addr;
++create_server_socket(int *result)
+ {
 -      addr = config_get_server_addrinfo();
 -      for (; addr != NULL; addr = addr->ai_next) {
++      struct addrinfo *addrs;
++      struct addrinfo *addr;
+       int fd; /* "file descriptor" */
++      int error;
 -              return fd; /* Happy path */
++      error = init_addrinfo(&addrs);
++      if (error)
++              return error;
++
++      for (addr = addrs; addr != NULL; addr = addr->ai_next) {
+               printf(
+                   "Attempting to bind socket to address '%s', port '%s'.\n",
+                   (addr->ai_canonname != NULL) ? addr->ai_canonname : "any",
+                   config_get_server_port());
+               fd = socket(addr->ai_family, SOCK_STREAM, 0);
+               if (fd < 0) {
+                       warn("socket() failed");
+                       continue;
+               }
+               if (bind(fd, addr->ai_addr, addr->ai_addrlen) < 0) {
+                       warn("bind() failed");
+                       continue;
+               }
+               printf("Success.\n");
 -      server_fd = create_server_socket();
 -      if (server_fd < 0)
 -              return server_fd;
++              freeaddrinfo(addrs);
++              *result = fd;
++              return 0; /* Happy path */
+       }
+       warnx("None of the addrinfo candidates could be bound.");
++      freeaddrinfo(addrs);
+       return -EINVAL;
+ }
+ /*
+  * Arguments that the server socket thread will send to the client socket
+  * threads whenever it creates them.
+  */
+ struct thread_param {
+       int     client_fd;
+       struct sockaddr_storage client_addr;
+ };
+ enum verdict {
+       /* No errors; continue happily. */
+       VERDICT_SUCCESS,
+       /* A temporal error just happened. Try again. */
+       VERDICT_RETRY,
+       /* "Stop whatever you're doing and return." */
+       VERDICT_EXIT,
+ };
+ /*
+  * Converts an error code to a verdict.
+  * The error code is assumed to have been spewed by the `accept()` function.
+  */
+ static enum verdict
+ handle_accept_result(int client_fd, int err)
+ {
+       if (client_fd >= 0)
+               return VERDICT_SUCCESS;
+       /*
+        * Note: I can't just use a single nice switch because EAGAIN and
+        * EWOULDBLOCK are the same value in at least one supported system
+        * (Linux).
+        */
+       /*
+        * TODO this `if` is a Linux quirk and should probably not exist in the
+        * BSDs. See `man 2 accept`.
+        */
+       if (err == ENETDOWN || err == EPROTO || err == ENOPROTOOPT
+           || err == EHOSTDOWN || err == ENONET || err == EHOSTUNREACH
+           || err == EOPNOTSUPP || err == ENETUNREACH)
+               return VERDICT_RETRY;
++#pragma GCC diagnostic push
++#pragma GCC diagnostic ignored "-Wlogical-op"
+       if (err == EAGAIN || err == EWOULDBLOCK)
+               return VERDICT_RETRY;
++#pragma GCC diagnostic pop
+       errno = err;
+       warn("Connection acceptor thread interrupted");
+       return VERDICT_EXIT;
+ }
+ static void *
+ end_client(int client_fd, const struct pdu_metadata *meta, void *pdu)
+ {
+       if (meta != NULL && pdu != NULL)
+               meta->destructor(pdu);
+       clients_forget(client_fd);
+       return NULL;
+ }
+ /*
+  * The client socket threads' entry routine.
+  *
+  * Please remember that this function needs to always release @param_void
+  * before returning.
+  */
+ static void *
+ client_thread_cb(void *param_void)
+ {
+       struct thread_param param;
+       struct pdu_metadata const *meta;
+       void *pdu;
+       int err;
+       u_int8_t rtr_version;
+       memcpy(&param, param_void, sizeof(param));
+       while (loop) { /* For each PDU... */
+               err = pdu_load(param.client_fd, &pdu, &meta, &rtr_version);
+               if (err)
+                       return end_client(param.client_fd, NULL, NULL);
+               /* 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);
+                       return end_client(param.client_fd, meta, pdu);
+               }
+               /* RTR Version ready, now update client */
+               err = update_client(param.client_fd, &param.client_addr,
+                   rtr_version);
+               if (err) {
+                       if (err == -EINVAL) {
+                               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);
+                       }
+                       return end_client(param.client_fd, meta, pdu);
+               }
+               err = meta->handle(param.client_fd, pdu);
+               meta->destructor(pdu);
+               if (err)
+                       return end_client(param.client_fd, NULL, NULL);
+       }
+       return NULL; /* Unreachable. */
+ }
+ /*
+  * Waits for client connections and spawns threads to handle them.
+  */
+ static int
+ handle_client_connections(int server_fd)
+ {
+       struct sockaddr_storage client_addr;
+       struct thread_param arg;
+       struct thread_node *new_thread;
+       socklen_t sizeof_client_addr;
+       pthread_attr_t attr;
+       int client_fd;
+       listen(server_fd, config_get_server_queue());
+       sizeof_client_addr = sizeof(client_addr);
+       do {
+               client_fd = accept(server_fd, (struct sockaddr *)&client_addr,
+                   &sizeof_client_addr);
+               switch (handle_accept_result(client_fd, errno)) {
+               case VERDICT_SUCCESS:
+                       break;
+               case VERDICT_RETRY:
+                       continue;
+               case VERDICT_EXIT:
+                       return 0;
+               }
+               /*
+                * Note: My gut says that errors from now on (even the unknown
+                * ones) should be treated as temporary; maybe the next
+                * accept() will work.
+                * So don't interrupt the thread when this happens.
+                */
+               new_thread = malloc(sizeof(struct thread_node));
+               if (new_thread == NULL) {
+                       warnx("Couldn't create thread struct");
+                       close(client_fd);
+                       continue;
+               }
+               arg.client_fd = client_fd;
+               arg.client_addr = client_addr;
+               pthread_attr_init(&attr);
+               pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+               errno = pthread_create(&new_thread->tid, &attr,
+                   client_thread_cb, &arg);
+               pthread_attr_destroy(&attr);
+               if (errno) {
+                       warn("Could not spawn the client's thread");
+                       free(new_thread);
+                       close(client_fd);
+                       continue;
+               }
+               SLIST_INSERT_HEAD(&threads, new_thread, next);
+       } while (true);
+       return 0; /* Unreachable. */
+ }
+ static void
+ signal_handler(int signal, siginfo_t *info, void *param)
+ {
+       /* Empty handler */
+ }
+ static int
+ init_signal_handler(void)
+ {
+       struct sigaction act;
+       int error;
+       memset(&act, 0, sizeof act);
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_SIGINFO;
+       act.sa_sigaction = signal_handler;
+       error = sigaction(SIGINT, &act, NULL);
+       if (error) {
+               warn("Error initializing signal handler");
+               error = -errno;
+       }
+       return error;
+ }
+ /*
+  * Starts the server, using the current thread to listen for RTR client
+  * requests.
+  *
+  * This function blocks.
+  */
+ int
+ rtr_listen(void)
+ {
+       int server_fd; /* "file descriptor" */
+       int error;
++      error = create_server_socket(&server_fd);
++      if (error)
++              return error;
+       /* Server ready, start updates thread */
+       error = updates_daemon_start();
+       if (error)
+               return error;
+       /* Init global vars */
+       loop = true;
+       SLIST_INIT(&threads);
+       error = init_signal_handler();
+       if (error)
+               return error;
+       return handle_client_connections(server_fd);
+ }
+ void
+ rtr_cleanup(void)
+ {
+       struct thread_node *ptr;
+       updates_daemon_destroy();
+       /* Wait for threads to end gracefully */
+       loop = false;
+       while (!SLIST_EMPTY(&threads)) {
+               ptr = SLIST_FIRST(&threads);
+               SLIST_REMOVE_HEAD(&threads, next);
+               pthread_kill(ptr->tid, SIGINT);
+               pthread_join(ptr->tid, NULL);
+               free(ptr);
+       }
+ }
diff --cc src/str.h
index ec1db1c6da3915623b8e9658bc6be3a9cae0145c,0000000000000000000000000000000000000000..b8d00a384e96138462a99888d2abe37738f24a9c
mode 100644,000000..100644
--- /dev/null
+++ b/src/str.h
@@@ -1,34 -1,0 +1,37 @@@
 +#ifndef SRC_STR_H_
 +#define SRC_STR_H_
 +
 +#include <stdbool.h>
 +#include <stddef.h>
 +#include <openssl/asn1.h>
 +#include <openssl/x509.h>
 +
 +int ia5s2string(ASN1_IA5STRING *, char **);
 +int BN2string(BIGNUM *, char **);
 +
 +/* This file is named "str.h" because "string.h" collides with <string.h>. */
 +
 +/**
 + * Do not modify fields directly; this should be private.
++ *
++ * This is more or less like strtok(), except it doesn't modify the string at
++ * any point.
 + */
 +struct string_tokenizer {
 +      /** String we're tokenizing. */
 +      char const *str;
 +      size_t str_len;
 +      /** Token delimiter. */
 +      unsigned char separator;
 +      /** Offset of the first character of the current token. */
 +      size_t start;
 +      /** Offset of the last character of the current token + 1. */
 +      size_t end;
 +};
 +
 +void string_tokenizer_init(struct string_tokenizer *, char const *, size_t,
 +    unsigned char);
 +bool string_tokenizer_next(struct string_tokenizer *);
 +bool token_equals(struct string_tokenizer *, struct string_tokenizer *);
 +
 +#endif /* SRC_STR_H_ */
index 1d3b9839887f11043b974832b6ed079ff975cf78,0000000000000000000000000000000000000000..d907d85dac8cd91c1379efb80a385477803ccdd9
mode 100644,000000..100644
--- /dev/null
@@@ -1,258 -1,0 +1,260 @@@
- void
 +#include "thread_var.h"
 +
 +#include <errno.h>
 +#include <pthread.h>
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <string.h>
 +#include <arpa/inet.h>
 +#include <sys/socket.h>
 +
 +#include "config.h"
 +
 +static pthread_key_t state_key;
 +static pthread_key_t filenames_key;
 +
 +struct filename_stack {
 +      /* This can be NULL. Abort all operations if this is the case. */
 +      char const **filenames;
 +      unsigned int len;
 +      unsigned int size;
 +};
 +
 +static void
 +fnstack_discard(void *arg)
 +{
 +      struct filename_stack *files = arg;
 +      free(files->filenames);
 +      free(files);
 +}
 +
 +/** Initializes this entire module. Call once per runtime lifetime. */
-               exit(error);
++int
 +thvar_init(void)
 +{
 +      int error;
 +
 +      error = pthread_key_create(&state_key, NULL);
 +      if (error) {
 +              fprintf(stderr,
 +                  "Fatal: Errcode %d while initializing the validation state thread variable.\n",
 +                  error);
-               exit(error);
++              return error;
 +      }
 +
 +      /*
 +       * Hm. It's a little odd.
 +       * fnstack_discard() is not being called on program termination.
 +       * Not sure if this is an implementation quirk.
 +       * We'll just have to delete it manually.
 +       */
 +      error = pthread_key_create(&filenames_key, fnstack_discard);
 +      if (error) {
 +              fprintf(stderr,
 +                  "Fatal: Errcode %d while initializing the file name stack thread variable.\n",
 +                  error);
++              return error;
 +      }
++
++      return 0;
 +}
 +
 +/* Puts @state in the current thread's variable pool. Call once per thread. */
 +int
 +state_store(struct validation *state)
 +{
 +      int error;
 +
 +      error = pthread_setspecific(state_key, state);
 +      if (error)
 +              fprintf(stderr, "pthread_setspecific() returned %d.", error);
 +
 +      return error;
 +}
 +
 +/* Returns the current thread's validation state. */
 +struct validation *
 +state_retrieve(void)
 +{
 +      struct validation *state;
 +
 +      state = pthread_getspecific(state_key);
 +      if (state == NULL)
 +              fprintf(stderr, "Programming error: This thread lacks a validation state.\n");
 +
 +      return state;
 +}
 +
 +/** Initializes the current thread's fnstack. Call once per thread. */
 +void
 +fnstack_init(void)
 +{
 +      struct filename_stack *files;
 +      int error;
 +
 +      files = malloc(sizeof(struct filename_stack));
 +      if (files == NULL)
 +              return;
 +
 +      files->filenames = malloc(32 * sizeof(char *));
 +      if (files->filenames == NULL) {
 +              free(files);
 +              return;
 +      }
 +
 +      files->len = 0;
 +      files->size = 32;
 +
 +      error = pthread_setspecific(filenames_key, files);
 +      if (error)
 +              fprintf(stderr, "pthread_setspecific() returned %d.", error);
 +}
 +
 +void
 +fnstack_cleanup(void)
 +{
 +      struct filename_stack *files;
 +      int error;
 +
 +      files = pthread_getspecific(filenames_key);
 +      if (files == NULL)
 +              return;
 +
 +      fnstack_discard(files);
 +
 +      error = pthread_setspecific(filenames_key, NULL);
 +      if (error)
 +              fprintf(stderr, "pthread_setspecific() returned %d.", error);
 +}
 +
 +static struct filename_stack *
 +get_file_stack(void)
 +{
 +      struct filename_stack *files;
 +
 +      files = pthread_getspecific(filenames_key);
 +      if (files == NULL)
 +              fprintf(stderr, "This thread lacks a files stack.\n");
 +
 +      return files;
 +}
 +
 +/**
 + * Call this function every time you're about to start processing a new file.
 + * Any pr_err()s and friends will now include the new file name.
 + * Use fnstack_pop() to revert back to the previously stacked file name.
 + * @file is not cloned; it's expected to outlive the push/pop operation.
 + */
 +void
 +fnstack_push(char const *file)
 +{
 +      struct filename_stack *files;
 +      char const **tmp;
 +
 +      files = get_file_stack();
 +      if (files == NULL || files->filenames == NULL)
 +              return;
 +
 +      if (files->len >= files->size) {
 +              tmp = realloc(files->filenames, 2 * files->size * sizeof(char *));
 +              if (tmp == NULL) {
 +                      /* Oh noes */
 +                      free(files->filenames);
 +                      files->filenames = NULL;
 +                      return;
 +              }
 +
 +              files->filenames = tmp;
 +              files->size *= 2;
 +      }
 +
 +      files->filenames[files->len++] = file;
 +}
 +
 +/** See fnstack_push(). */
 +void
 +fnstack_push_uri(struct rpki_uri const *uri)
 +{
 +      fnstack_push(uri_get_printable(uri));
 +}
 +
 +/* Returns the file name on the top of the file name stack. */
 +char const *
 +fnstack_peek(void)
 +{
 +      struct filename_stack *files;
 +
 +      files = get_file_stack();
 +      if (files == NULL || files->filenames == NULL || files->len == 0)
 +              return NULL;
 +
 +      return files->filenames[files->len - 1];
 +}
 +
 +/* Reverts the last fnstack_push(). */
 +void
 +fnstack_pop(void)
 +{
 +      struct filename_stack *files;
 +
 +      files = get_file_stack();
 +      if (files == NULL || files->filenames == NULL || files->len == 0)
 +              return;
 +
 +      files->len--;
 +}
 +
 +static char const *
 +addr2str(int af, void *addr, char *(*buffer_cb)(struct validation *))
 +{
 +      struct validation *state;
 +
 +      state = state_retrieve();
 +      if (!state)
 +              return NULL;
 +
 +      return inet_ntop(af, addr, buffer_cb(state), INET6_ADDRSTRLEN);
 +}
 +
 +/**
 + * Returns @addr, converted to a printable string. Intended for minimal clutter
 + * address printing.
 + *
 + * The buffer the string is stored in was allocated in a thread variable, so it
 + * will be overridden the next time you call this function. Also, you should not
 + * free it.
 + *
 + * The buffer is the same as v6addr2str()'s, so don't mix them either.
 + */
 +char const *
 +v4addr2str(struct in_addr *addr)
 +{
 +      return addr2str(AF_INET, addr, validation_get_ip_buffer1);
 +}
 +
 +/**
 + * Same as v4addr2str(), except a different buffer is used.
 + */
 +char const *
 +v4addr2str2(struct in_addr *addr)
 +{
 +      return addr2str(AF_INET, addr, validation_get_ip_buffer2);
 +}
 +
 +/**
 + * See v4addr2str().
 + */
 +char const *
 +v6addr2str(struct in6_addr *addr)
 +{
 +      return addr2str(AF_INET6, addr, validation_get_ip_buffer1);
 +}
 +
 +/**
 + * See v4addr2str2().
 + */
 +char const *
 +v6addr2str2(struct in6_addr *addr)
 +{
 +      return addr2str(AF_INET6, addr, validation_get_ip_buffer2);
 +}
index 3d629533a8fac7094cacfece5e85aeb84f6b6526,0000000000000000000000000000000000000000..7dbe18e067f706c5367223163190dbc350fb67c2
mode 100644,000000..100644
--- /dev/null
@@@ -1,24 -1,0 +1,24 @@@
- void thvar_init(void);
 +#ifndef SRC_THREAD_VAR_H_
 +#define SRC_THREAD_VAR_H_
 +
 +#include "state.h"
 +
++int thvar_init(void); /* This function does not need cleanup. */
 +
 +int state_store(struct validation *);
 +struct validation *state_retrieve(void);
 +
 +void fnstack_init(void);
 +void fnstack_cleanup(void);
 +
 +void fnstack_push(char const *);
 +void fnstack_push_uri(struct rpki_uri const *);
 +char const *fnstack_peek(void);
 +void fnstack_pop(void);
 +
 +char const *v4addr2str(struct in_addr *addr);
 +char const *v4addr2str2(struct in_addr *addr);
 +char const *v6addr2str(struct in6_addr *addr);
 +char const *v6addr2str2(struct in6_addr *addr);
 +
 +#endif /* SRC_THREAD_VAR_H_ */
index 0000000000000000000000000000000000000000,3206bbafd0cd4b80d09052d4f461a7b88fc33943..cd7d3c8906fc0db8217f7eb2a5ab7c63dce09ebb
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,58 +1,61 @@@
 -#include "csv.h"
 -#include "configuration.h"
+ #include "updates_daemon.h"
+ #include <err.h>
+ #include <errno.h>
+ #include <pthread.h>
+ #include <stdbool.h>
+ #include <unistd.h>
 -pthread_t thread;
++#include "config.h"
+ #include "notify.h"
++#include "object/tal.h"
 -check_vrps_updates(void *param_void) {
++static pthread_t thread;
+ static void *
 -              error = csv_check_vrps_file(&updated);
++check_vrps_updates(void *param_void)
++{
+       int error;
+       bool updated;
++
+       do {
+               updated = false;
 -                      warnx("Error while searching CSV updates, sleeping..");
++              error = perform_standalone_validation(&updated);
+               if (error) {
++                      warnx("Error '%d' while searching CSV updates, sleeping...",
++                          error);
+                       goto sleep;
+               }
+               if (updated)
+                       notify_clients();
+ sleep:
+               sleep(config_get_vrps_check_interval());
+       } while (true);
+       return NULL;
+ }
+ int
+ updates_daemon_start(void)
+ {
+       pthread_attr_t attr;
+       pthread_attr_init(&attr);
+       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+       errno = pthread_create(&thread, NULL, check_vrps_updates, NULL);
+       pthread_attr_destroy(&attr);
+       if (errno) {
+               warn("Could not spawn the update daemon thread");
+               return -errno;
+       }
+       return 0;
+ }
+ void
+ updates_daemon_destroy(void)
+ {
+       void *ptr = NULL;
+       pthread_cancel(thread);
+       pthread_join(thread, &ptr);
+ }
diff --cc src/vrps.c
index 0000000000000000000000000000000000000000,ff3ae7ccfb6e68a7c04818be7ccffd5ff4e90399..2222431d8f60ef3c6958d2f63099e39dabfa3ed6
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,466 +1,461 @@@
 -      if (error){
 -              warnx("Delta base DB couldn't be initialized");
+ #include "vrps.h"
+ #include <stdbool.h>
+ #include <string.h>
+ #include "array_list.h"
+ #include "common.h"
+ /*
+  * Storage of VRPs (term taken from RFC 6811 "Validated ROA Payload") and
+  * Serials that contain such VRPs
+  */
+ #define FLAG_WITHDRAWAL               0
+ #define FLAG_ANNOUNCEMENT     1
+ #define START_SERIAL          0
+ ARRAY_LIST(vrps, struct vrp)
+ struct delta {
+       u_int32_t serial;
+       struct vrps vrps;
+ };
+ ARRAY_LIST(deltasdb, struct delta)
+ struct state {
+       /** The current valid ROAs, freshly loaded from the file */
+       struct delta base_db;
+       /** ROA changes over time */
+       struct deltasdb deltas_db;
+       u_int32_t current_serial;
+       u_int16_t v0_session_id;
+       u_int16_t v1_session_id;
+       time_t last_modified_date;
+ } state;
+ /* Read and Write locks */
+ sem_t rlock, wlock;
+ /* Readers counter */
+ unsigned int rcounter;
+ static int
+ delta_init(struct delta *delta)
+ {
+       return vrps_init(&delta->vrps);
+ }
+ static void
+ vrp_destroy(struct vrp *vrp)
+ {
+       /* Didn't allocate something, so do nothing */
+ }
+ static void
+ delta_destroy(struct delta *delta)
+ {
+       vrps_cleanup(&delta->vrps, vrp_destroy);
+ }
+ int
+ deltas_db_init(void)
+ {
+       int error, shift;
+       error = delta_init(&state.base_db);
 -      }
++      if (error)
+               return error;
 -              warnx("Deltas DB couldn't be initialized");
+       error = deltasdb_init(&state.deltas_db);
+       if (error) {
 -      result.ipv4_prefix = ipv4_prefix;
+               delta_destroy(&state.base_db);
+               return error;
+       }
+       /*
+        * Use the same start serial, the session ID will avoid
+        * "desynchronization" (more at RFC 6810 'Glossary' and
+        * 'Fields of a PDU')
+        */
+       state.current_serial = START_SERIAL;
+       /* Get the bits that'll fit in session_id */
+       shift = sizeof(time_t) - sizeof(state.v0_session_id);
+       state.v0_session_id = (u_int16_t)((time(NULL) << shift) >> shift);
+       /* Minus 1 to prevent same ID */
+       state.v1_session_id = state.v0_session_id - 1;
+       sem_init(&rlock, 0, 1);
+       sem_init(&wlock, 0, 1);
+       rcounter = 0;
+       return 0;
+ }
+ static void
+ init_vrp (struct vrp *vrp, u_int32_t asn, u_int8_t prefix_length,
+     u_int8_t max_prefix_length)
+ {
+       vrp->asn = asn;
+       vrp->prefix_length = prefix_length;
+       vrp->max_prefix_length = max_prefix_length;
+       /* Set as ANNOUNCEMENT by default */
+       vrp->flags = FLAG_ANNOUNCEMENT;
+ }
+ struct vrp
+ create_vrp4(u_int32_t asn, struct in_addr ipv4_prefix, u_int8_t prefix_length,
+     u_int8_t max_prefix_length)
+ {
+       struct vrp result;
+       init_vrp(&result, asn, prefix_length, max_prefix_length);
 -      result.ipv6_prefix = ipv6_prefix;
++      result.prefix.ipv4 = ipv4_prefix;
+       result.addr_fam = AF_INET;
+       return result;
+ }
+ struct vrp
+ create_vrp6(u_int32_t asn, struct in6_addr ipv6_prefix, u_int8_t prefix_length,
+     u_int8_t max_prefix_length)
+ {
+       struct vrp result;
+       init_vrp(&result, asn, prefix_length, max_prefix_length);
 -              && left->ipv4_prefix.s_addr == right->ipv4_prefix.s_addr)
++      result.prefix.ipv6 = ipv6_prefix;
+       result.addr_fam = AF_INET6;
+       return result;
+ }
+ static bool
+ vrp_equal(struct vrp *left, struct vrp *right)
+ {
+       return left->asn == right->asn
+           && left->addr_fam == right->addr_fam
+           && left->prefix_length == right->prefix_length
+           && left->max_prefix_length == right->max_prefix_length
+           && ((left->addr_fam == AF_INET
 -          && IN6_ARE_ADDR_EQUAL(left->ipv6_prefix.s6_addr32,
 -              right->ipv6_prefix.s6_addr32)));
++              && left->prefix.ipv4.s_addr == right->prefix.ipv4.s_addr)
+           || (left->addr_fam == AF_INET6
 -                              warnx("Couldn't add announcement to summary");
++          && IN6_ARE_ADDR_EQUAL(left->prefix.ipv6.s6_addr32,
++              right->prefix.ipv6.s6_addr32)));
+ }
+ static struct vrp *
+ vrp_locate(struct vrps *base, struct vrp *vrp)
+ {
+       struct vrp *cursor;
+       ARRAYLIST_FOREACH(base, cursor)
+               if (vrp_equal(cursor, vrp))
+                       return cursor;
+       return NULL;
+ }
+ static bool
+ vrp_is_new(struct vrps *base, struct vrp *vrp)
+ {
+       return vrp_locate(base, vrp) == NULL;
+ }
+ static int
+ delta_add_vrp(struct delta *delta, struct vrp *vrp)
+ {
+       return vrps_add(&delta->vrps, vrp);
+ }
+ static int
+ delta_summary(struct delta *base_delta, struct delta *result)
+ {
+       struct vrps *base, *search_list;
+       struct vrp *cursor;
+       int error;
+       /*
+        * Note: Don't fix this function yet.
+        * I realize why you implemented it this way, and I'm trying to come up
+        * with a more efficient algorithm.
+        */
+       error = delta_init(result);
+       if (error)
+               return error;
+       result->serial = base_delta->serial;
+       read_lock(&rlock, &wlock, &rcounter);
+       /* First check for announcements */
+       base = &base_delta->vrps;
+       search_list = &state.base_db.vrps;
+       ARRAYLIST_FOREACH(base, cursor)
+               if (vrp_is_new(search_list, cursor)) {
+                       cursor->flags = FLAG_ANNOUNCEMENT;
+                       error = delta_add_vrp(result, cursor);
+                       if (error) {
 -                              warnx("Couldn't add withdrawal to summary");
+                               read_unlock(&rlock, &wlock, &rcounter);
+                               return error;
+                       }
+               }
+       /* Now for withdrawals */
+       base = &state.base_db.vrps;
+       search_list = &base_delta->vrps;
+       ARRAYLIST_FOREACH(base, cursor)
+               if (vrp_is_new(search_list, cursor)) {
+                       cursor->flags = FLAG_WITHDRAWAL;
+                       error = delta_add_vrp(result, cursor);
+                       if (error) {
 -                      warnx("Error summarizing new delta");
+                               read_unlock(&rlock, &wlock, &rcounter);
+                               return error;
+                       }
+               }
+       read_unlock(&rlock, &wlock, &rcounter);
+       return 0;
+ }
+ static int
+ deltas_db_add_delta(struct delta delta)
+ {
+       struct delta summary;
+       int result;
+       result = 0;
+       read_lock(&rlock, &wlock, &rcounter);
+       delta.serial = state.current_serial;
+       read_unlock(&rlock, &wlock, &rcounter);
+       /* Store only updates */
+       if (delta.serial != START_SERIAL) {
+               result = delta_summary(&delta, &summary);
+               if (result != 0) {
 -              warnx("Error persisting new delta");
++                      pr_err("Error summarizing new delta");
+                       return result;
+               }
+               sem_wait(&wlock);
+               result = deltasdb_add(&state.deltas_db, &summary);
+               sem_post(&wlock);
+       }
+       /* Don't set the base in case of error */
+       if (result != 0) {
 -              warn("Couldn't copy VRPs");
++              pr_err("Error persisting new delta");
+               return result;
+       }
+       sem_wait(&wlock);
+       free(state.base_db.vrps.array);
+       state.base_db = delta;
+       state.current_serial++;
+       sem_post(&wlock);
+       return result;
+ }
+ static void
+ copy_vrps(struct vrp **dst, struct vrp *src, unsigned int len)
+ {
+       struct vrp *tmp;
+       tmp = realloc(*dst, len * sizeof(struct vrp));
+       if (tmp == NULL) {
 -              warnx("New Delta couldn't be initialized");
++              pr_enomem();
+               return;
+       }
+       *dst = tmp;
+       memcpy(*dst, src, len * sizeof(struct vrp));
+ }
+ int
+ deltas_db_create_delta(struct vrp *array, unsigned int len)
+ {
+       struct delta new_delta;
+       int error;
+       error = delta_init(&new_delta);
+       if (error) {
++              pr_err("New Delta couldn't be initialized");
+               return error;
+       }
+       copy_vrps(&new_delta.vrps.array, array, len);
+       new_delta.vrps.len = len;
+       new_delta.vrps.capacity = len * sizeof(struct vrp);
+       error = deltas_db_add_delta(new_delta);
+       if (error)
+               return error;
+       return 0;
+ }
+ void
+ deltas_db_destroy(void)
+ {
+       sem_wait(&wlock);
+       delta_destroy(&state.base_db);
+       deltasdb_cleanup(&state.deltas_db, delta_destroy);
+       sem_post(&wlock);
+       sem_destroy(&wlock);
+       sem_destroy(&rlock);
+ }
+ /*
+  * 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.
+  *
+  * The possible return values are:
+  *  NO_DATA_AVAILABLE -> There's no data at the DB
+  *  DIFF_UNDETERMINED -> The diff can't be determined
+  *  NO_DIFF -> There's no difference
+  *  DIFF_AVAILABLE -> There are diffs between SERIAL and the last DB serial
+  */
+ int
+ deltas_db_status(u_int32_t *serial)
+ {
+       struct delta *delta;
+       int result;
+       read_lock(&rlock, &wlock, &rcounter);
+       if (state.base_db.vrps.len == 0) {
+               result = NO_DATA_AVAILABLE;
+               goto end;
+       }
+       /* No serial to match, and there's data at DB */
+       if (serial == NULL) {
+               result = DIFF_AVAILABLE;
+               goto end;
+       }
+       /* Is the last version? */
+       if (*serial == state.base_db.serial) {
+               result = NO_DIFF;
+               goto end;
+       }
+       /* Get the delta corresponding to the serial */
+       ARRAYLIST_FOREACH(&state.deltas_db, delta)
+               if (delta->serial == *serial) {
+                       result = DIFF_AVAILABLE;
+                       goto end;
+               }
+       /* No match yet, release lock */
+       read_unlock(&rlock, &wlock, &rcounter);
+       /* The first serial isn't at deltas */
+       if (*serial == START_SERIAL)
+               return DIFF_AVAILABLE;
+       /* Reached end, diff can't be determined */
+       return DIFF_UNDETERMINED;
+ end:
+       read_unlock(&rlock, &wlock, &rcounter);
+       return result;
+ }
+ static void
+ add_vrps_filtered(struct vrps *dst, struct vrps *src)
+ {
+       struct vrp *ptr;
+       for (ptr = src->array; (ptr - src->array) < src->len; ptr++)
+               if (vrp_is_new(dst, ptr))
+                       vrps_add(dst, ptr);
+ }
+ /*
+  * Get the number of updates from serial START_SERIAL to END_SERIAL, set them
+  * at RESULT.
+  *
+  * Return 0 if no updates are available or couldn't be calculated with the
+  * received values.
+  */
+ unsigned int
+ get_vrps_delta(u_int32_t *start_serial, u_int32_t *end_serial,
+     struct vrp **result)
+ {
+       struct delta *delta1;
+       struct vrps summary;
+       unsigned int vrps_len;
+       read_lock(&rlock, &wlock, &rcounter);
+       /* No data */
+       if (state.base_db.vrps.len == 0) {
+               read_unlock(&rlock, &wlock, &rcounter);
+               return 0;
+       }
+       /* NULL start? Send the last version, there's no need to iterate DB */
+       if (start_serial == NULL) {
+               copy_vrps(result, state.base_db.vrps.array,
+                   state.base_db.vrps.len);
+               vrps_len = state.base_db.vrps.len;
+               read_unlock(&rlock, &wlock, &rcounter);
+               return vrps_len;
+       }
+       /* Apparently nothing to return */
+       if (*start_serial >= *end_serial) {
+               read_unlock(&rlock, &wlock, &rcounter);
+               return 0;
+       }
+       /* Get the delta corresponding to the serials */
+       vrps_init(&summary);
+       ARRAYLIST_FOREACH(&state.deltas_db, delta1) {
+               if (delta1->serial > *start_serial)
+                       add_vrps_filtered(&summary, &delta1->vrps);
+               if (delta1->serial == *end_serial)
+                       break;
+       }
+       read_unlock(&rlock, &wlock, &rcounter);
+       copy_vrps(result, summary.array, summary.len);
+       vrps_cleanup(&summary, vrp_destroy);
+       return summary.len;
+ }
+ void
+ set_vrps_last_modified_date(time_t new_date)
+ {
+       sem_wait(&wlock);
+       state.last_modified_date = new_date;
+       sem_post(&wlock);
+ }
+ u_int32_t
+ get_last_serial_number(void)
+ {
+       u_int32_t serial;
+       read_lock(&rlock, &wlock, &rcounter);
+       serial = state.current_serial - 1;
+       read_unlock(&rlock, &wlock, &rcounter);
+       return serial;
+ }
+ u_int16_t
+ get_current_session_id(u_int8_t rtr_version)
+ {
+       /* Semaphore isn't needed since this value is set at initialization */
+       if (rtr_version == 1)
+               return state.v1_session_id;
+       return state.v0_session_id;
+ }
+ time_t
+ get_vrps_last_modified_date(void)
+ {
+       time_t date;
+       read_lock(&rlock, &wlock, &rcounter);
+       date = state.last_modified_date;
+       read_unlock(&rlock, &wlock, &rcounter);
+       return date;
+ }
diff --cc src/vrps.h
index 0000000000000000000000000000000000000000,f6d4c208f5ef152c77b9a039dd85a069dd6fc02a..6939e08e472bab23c97f8cf3132cc68e28b328fa
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,41 +1,41 @@@
 -              struct  in_addr ipv4_prefix;
 -              struct  in6_addr ipv6_prefix;
 -      };
+ #ifndef SRC_VRPS_H_
+ #define SRC_VRPS_H_
+ #include <time.h>
+ #include <netinet/ip.h>
+ #define NO_DATA_AVAILABLE     -2
+ #define DIFF_UNDETERMINED     -1
+ #define NO_DIFF                       0
+ #define DIFF_AVAILABLE                1
+ struct vrp {
+       u_int32_t       asn;
+       union {
++              struct  in_addr ipv4;
++              struct  in6_addr ipv6;
++      } prefix;
+       u_int8_t        prefix_length;
+       u_int8_t        max_prefix_length;
+       u_int8_t        addr_fam;
+       u_int8_t        flags;
+ };
+ int deltas_db_init(void);
+ struct vrp create_vrp4(u_int32_t, struct in_addr, u_int8_t, u_int8_t);
+ struct vrp create_vrp6(u_int32_t, struct in6_addr, u_int8_t, u_int8_t);
+ int deltas_db_create_delta(struct vrp *, unsigned int);
+ int deltas_db_status(u_int32_t *);
+ unsigned int get_vrps_delta(u_int32_t *, u_int32_t *, struct vrp **);
+ void deltas_db_destroy(void);
+ void set_vrps_last_modified_date(time_t);
+ u_int32_t get_last_serial_number(void);
+ u_int16_t get_current_session_id(u_int8_t);
+ time_t get_vrps_last_modified_date(void);
+ #endif /* SRC_VRPS_H_ */
index 75168b9e2650d03c556182ab9732da1ac79ea6f8,fdacf08948547601ee10939ff859f56012441494..b4d456636f32ef9eb53aaae478e9fd59d54b0ac3
@@@ -1,51 -1,30 +1,73 @@@
 -#     mumble_mumble_CFLAGS = $(AM_CFLAGS) flag1 flag2 flag3 ...
 -AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src @CHECK_CFLAGS@
 -
 +# Reminder: `make check`
 +
 +# If you want to only run one test, do `make check TESTS=<test>`.
 +# Example: `make check TESTS=address`
 +
 +# Once compiled, you can also just run the test directly: `./address.test`
 +# You can easily see standard output and error this way.
 +
+ # Reminder: Automake will automatically add this to any targets where
+ # <mumble>_CFLAGS is not defined.
+ # Otherwise it must be included manually:
 -MY_LDADD = $(CHECK_LIBS)
++#     mumble_mumble_CFLAGS = ${AM_CFLAGS} flag1 flag2 flag3 ...
 +AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src -DUNIT_TESTING ${CHECK_CFLAGS}
+ # Reminder: As opposed to AM_CFLAGS, "AM_LDADD" is not idiomatic automake, and
+ # autotools will even reprehend us if we declare it. Therefore, I came up with
+ # "my" own "ldadd". Unlike AM_CFLAGS, it needs to be manually added to every
+ # target.
 +MY_LDADD = ${CHECK_LIBS}
 +BASIC_MODULES  = ../src/log.c ../src/log.h
 +BASIC_MODULES += impersonator.c
  
 -check_PROGRAMS = rtr/primitive_reader.test rtr/pdu.test address.test
 -TESTS = $(check_PROGRAMS)
 +check_PROGRAMS  = address.test
 +check_PROGRAMS += vcard.test
 +check_PROGRAMS += line_file.test
 +check_PROGRAMS += rsync.test
 +check_PROGRAMS += tal.test
++check_PROGRAMS += rtr/primitive_reader.test
++check_PROGRAMS += rtr/pdu.test
 +TESTS = ${check_PROGRAMS}
  
 -rtr_primitive_reader_test_SOURCES = \
 -      rtr/primitive_reader_test.c \
 -      rtr/stream.c
 -rtr_primitive_reader_test_LDADD = $(MY_LDADD)
 +address_test_SOURCES  = ${BASIC_MODULES}
 +address_test_SOURCES += ../src/address.h
 +address_test_SOURCES += address_test.c
 +address_test_LDADD = ${MY_LDADD}
  
- vcard_test_SOURCES  = ${BASIC_MODULES}
- vcard_test_SOURCES += vcard_test.c
- vcard_test_LDADD = ${MY_LDADD}
 -rtr_pdu_test_SOURCES = \
 -      rtr/pdu_test.c \
 -      rtr/stream.c \
 -      $(top_builddir)/src/rtr/primitive_reader.c \
 -      $(top_builddir)/src/rtr/pdu_handler.c
 -rtr_pdu_test_LDADD = $(MY_LDADD)
 +line_file_test_SOURCES  = ${BASIC_MODULES}
 +line_file_test_SOURCES += ../src/file.c ../src/file.h
 +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}
 +
 +rsync_test_SOURCES  = ${BASIC_MODULES}
 +rsync_test_SOURCES += ../src/str.c ../src/str.h
 +rsync_test_SOURCES += ../src/uri.c ../src/uri.h
 +rsync_test_SOURCES += rsync_test.c
 +rsync_test_LDADD = ${MY_LDADD}
 +
++rtr_primitive_reader_test_SOURCES  = ${BASIC_MODULES}
++rtr_primitive_reader_test_SOURCES += rtr/primitive_reader_test.c
++rtr_primitive_reader_test_SOURCES += rtr/stream.c
++rtr_primitive_reader_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
++rtr_pdu_test_SOURCES += $(top_builddir)/src/rtr/primitive_reader.c
++rtr_pdu_test_SOURCES += $(top_builddir)/src/rtr/pdu_handler.c
++rtr_pdu_test_LDADD = ${MY_LDADD}
++
 +tal_test_SOURCES  = ${BASIC_MODULES}
 +tal_test_SOURCES += ../src/file.c ../src/file.h
 +tal_test_SOURCES += ../src/crypto/base64.c ../src/crypto/base64.h
 +tal_test_SOURCES += ../src/random.c ../src/random.h
 +tal_test_SOURCES += ../src/str.c ../src/str.h
 +tal_test_SOURCES += ../src/uri.c ../src/uri.h
 +tal_test_SOURCES += ../src/line_file.c ../src/line_file.h
 +tal_test_SOURCES += tal_test.c
- tal_test_CFLAGS = ${AM_CFLAGS}
 +tal_test_LDADD = ${MY_LDADD}
++
++vcard_test_SOURCES  = ${BASIC_MODULES}
++vcard_test_SOURCES += vcard_test.c
++vcard_test_LDADD = ${MY_LDADD}
 -address_test_SOURCES = ../src/address.h
 -address_test_SOURCES += address_test.c
 -address_test_LDADD = $(MY_LDADD)
index c31e8d7c0983a41823435f5e84730f0db9dc18c2,bb23a855265c9c1457f34c28132fcf3d621d7fe0..d829b08ae027467545bb8a174fb21485a03a1215
@@@ -28,145 -11,48 +28,176 @@@ START_TEST(check_encoding4_test
  }
  END_TEST
  
 +static void
 +test_range6(uint32_t a1a, uint32_t a1b, uint32_t a1c, uint32_t a1d,
 +    uint32_t a2a, uint32_t a2b, uint32_t a2c, uint32_t a2d,
 +    bool valid)
 +{
 +      struct ipv6_range range;
 +
 +      addr6_set_quadrant(&range.min, 0, a1a);
 +      addr6_set_quadrant(&range.min, 1, a1b);
 +      addr6_set_quadrant(&range.min, 2, a1c);
 +      addr6_set_quadrant(&range.min, 3, a1d);
 +      addr6_set_quadrant(&range.max, 0, a2a);
 +      addr6_set_quadrant(&range.max, 1, a2b);
 +      addr6_set_quadrant(&range.max, 2, a2c);
 +      addr6_set_quadrant(&range.max, 3, a2d);
 +
 +      ck_assert_int_eq(valid ? 0 : -EINVAL, check_encoding6(&range));
 +}
 +
 +START_TEST(check_encoding6_test)
 +{
 +      test_range6(0x00000000, 0x00000000, 0x00000000, 0x00000000,
 +                  0x00000000, 0x00000000, 0x00000000, 0x00000000,
 +                  false);
 +      test_range6(0x12345678, 0x12345678, 0x12345678, 0x12345678,
 +                  0x12345678, 0x12345678, 0x12345678, 0x12345678,
 +                  false);
 +      test_range6(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  false);
 +
 +      test_range6(0x00000000, 0x00000000, 0x00000000, 0x00000000,
 +                  0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  false);
 +      test_range6(0x00000000, 0x00000000, 0x00000000, 0x00000000,
 +                  0x00000000, 0x00000000, 0x00000000, 0x00000001,
 +                  false);
 +
 +      /* Matching most significant bits stop on the first quadrant */
 +      test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
 +                  0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  false);
 +
 +      test_range6(0x00001010, 0x00000000, 0x00000000, 0x00000000,
 +                  0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
 +                  0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00000000, 0x00001000, 0x00000000,
 +                  0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00000000, 0x00000000, 0x00001000,
 +                  0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +
 +      test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
 +                  0x00001F0F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
 +                  0x00001FFF, 0xFF0FFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
 +                  0x00001FFF, 0xFFFFFFFF, 0xFFFFF0FF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
 +                  0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF0FF,
 +                  true);
 +
 +      /* Matching most significant bits stop on the second quadrant */
 +      test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
 +                  0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  false);
 +
 +      test_range6(0x00001000, 0x00001010, 0x00000000, 0x00000000,
 +                  0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000,
 +                  0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00001000, 0x00000000, 0x00001000,
 +                  0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +
 +      test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
 +                  0x00001000, 0x00001F0F, 0xFFFFFFFF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
 +                  0x00001000, 0x00001FFF, 0xFFFFF0FF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
 +                  0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFF0FF,
 +                  true);
 +
 +      /* Matching most significant bits stop on the third quadrant */
 +      test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000,
 +                  0x00001000, 0x00001000, 0x00001FFF, 0xFFFFFFFF,
 +                  false);
 +
 +      test_range6(0x00001000, 0x00001000, 0x00001010, 0x00000000,
 +                  0x00001000, 0x00001000, 0x00001FFF, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001000,
 +                  0x00001000, 0x00001000, 0x00001FFF, 0xFFFFFFFF,
 +                  true);
 +
 +      test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000,
 +                  0x00001000, 0x00001000, 0x00001F0F, 0xFFFFFFFF,
 +                  true);
 +      test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000,
 +                  0x00001000, 0x00001000, 0x00001FFF, 0xFFFFF0FF,
 +                  true);
 +
 +      /* Matching most significant bits stop on the fourth quadrant */
 +      test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001000,
 +                  0x00001000, 0x00001000, 0x00001000, 0x00001FFF,
 +                  false);
 +
 +      test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001010,
 +                  0x00001000, 0x00001000, 0x00001000, 0x00001FFF,
 +                  true);
 +
 +      test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001000,
 +                  0x00001000, 0x00001000, 0x00001000, 0x00001F0F,
 +                  true);
 +}
 +END_TEST
 +
+ static void
+ test_get_address_from_string(char *text_prefix)
+ {
+       struct ipv4_prefix prefix;
+       const char *result;
+       int error;
+       char buffer[INET_ADDRSTRLEN];
+       error = prefix4_decode(text_prefix, &prefix);
+       if (error)
+               return;
+       result = addr2str4(&prefix.addr, buffer);
+       ck_assert_str_eq(text_prefix, result);
+ }
+ START_TEST(address_test_get_addr)
+ {
+       char *text;
+       text = "198.248.146.0";
+       test_get_address_from_string(text);
+ }
+ END_TEST
  Suite *address_load_suite(void)
  {
        Suite *suite;
 -      TCase *core, *test_get_address;
 +      TCase *core;
++      TCase *string;
  
        core = tcase_create("Core");
 -      tcase_add_test(core, load_normal);
 +      tcase_add_test(core, check_encoding4_test);
 +      tcase_add_test(core, check_encoding6_test);
  
 -      test_get_address = tcase_create("test_get_address");
 -      tcase_add_test(test_get_address, address_test_get_addr);
++      string = tcase_create("Strings");
++      tcase_add_test(string, address_test_get_addr);
 -      suite = suite_create("address_test()");
 +      suite = suite_create("Encoding checking");
        suite_add_tcase(suite, core);
 -      suite_add_tcase(suite, test_get_address);
 -
        return suite;
  }
  
index 0000000000000000000000000000000000000000,0902dc6293cfc2b217f8867a0b8b99097a776536..7526aa88d413c6f267f0f1cf6827962eed1fe239
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,62 +1,62 @@@
 -              return -abs(err);
+ #include "stream.h"
+ #include <err.h>
+ #include <errno.h>
+ #include <stdlib.h>
+ #include <unistd.h>
+ #include "common.h"
+ /*
+  * Writes exactly @length bytes from @buffer to the file descriptor @fd.
+  * All or nothing.
+  *
+  * The result is zero on success, nonzero on failure.
+  */
+ int
+ write_exact(int fd, unsigned char *buffer, size_t length)
+ {
+       size_t written;
+       int written_now;
+       for (written = 0; written < length; written += written_now) {
+               written_now = write(fd, buffer + written, length - written);
+               if (written_now == -1)
+                       return errno;
+       }
+       return 0;
+ }
+ /*
+  * "Converts" the buffer @buffer (sized @size) to a file descriptor (FD).
+  * You will get @buffer if you `read()` the FD.
+  *
+  * If the result is not negative, then you're receiving the resulting FD.
+  * If the result is negative, it's an error code.
+  *
+  * Note that you need to close the FD when you're done reading it.
+  */
+ int
+ buffer2fd(unsigned char *buffer, size_t size)
+ {
+       int fd[2];
+       int err;
+       if (pipe(fd) == -1) {
+               err = errno;
+               warn("Pipe creation failed");
 -              return -abs(err);
++              return ENSURE_NEGATIVE(err);
+       }
+       err = write_exact(fd[1], buffer, size);
+       close(fd[1]);
+       if (err) {
+               errno = err;
+               warn("Pipe write failed");
+               close(fd[0]);
++              return ENSURE_NEGATIVE(err);
+       }
+       return fd[0];
+ }