+Validate and parse an Update Notification file whenever is found at a certificate. Currently this does nothing else, is merely to download and validate the file; rsync is still utilized to fetch the repository data.
+Add libxml2 dependency, utilized to validate XML files using relaxNG schema.
+Initialize and cleanup XML parser on main thread.
+Currently libxml2 doesn't seems to support Relax NG compact form, but due to its license, it's the best option to use. The RRDP schema was transformed to Relax NG schema with the tool rnc2rng.
+Add basic unit test to parse an RRDP XML file.
# typical obscure bullshit autotools reasoning) I have no idea why.
PKG_CHECK_MODULES([JANSSON], [jansson])
PKG_CHECK_MODULES([CURL], [libcurl])
+PKG_CHECK_MODULES([XML2], [libxml-2.0])
PKG_CHECK_MODULES([CHECK], [check], [usetests=yes], [usetests=no])
AM_CONDITIONAL([USE_TESTS], [test "x$usetests" = "xyes"])
fort_SOURCES += resource/ip6.h resource/ip6.c
fort_SOURCES += resource/asn.h resource/asn.c
+fort_SOURCES += rrdp/rrdp_objects.h rrdp/rrdp_objects.c
+fort_SOURCES += rrdp/rrdp_parser.h rrdp/rrdp_parser.c
+
fort_SOURCES += rsync/rsync.h rsync/rsync.c
fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h
fort_SOURCES += slurm/slurm_loader.c slurm/slurm_loader.h
fort_SOURCES += slurm/slurm_parser.c slurm/slurm_parser.h
+fort_SOURCES += xml/relax_ng.c xml/relax_ng.h
+
# I'm placing these at the end because they rarely change, and I want warnings
# to appear as soon as possible.
include asn1/asn1c/Makefile.include
fort_CFLAGS = -Wall -Wno-cpp
# Feel free to temporarily remove this one if you're not using gcc 7.3.0.
#fort_CFLAGS += $(GCC_WARNS)
-fort_CFLAGS += -std=gnu11 -O2 -g $(FORT_FLAGS)
+fort_CFLAGS += -std=gnu11 -O2 -g $(FORT_FLAGS) ${XML2_CFLAGS}
fort_LDFLAGS = $(LDFLAGS_DEBUG)
-fort_LDADD = ${JANSSON_LIBS} ${CURL_LIBS}
+fort_LDADD = ${JANSSON_LIBS} ${CURL_LIBS} ${XML2_LIBS}
# I'm tired of scrolling up, but feel free to comment this out.
GCC_WARNS = -fmax-errors=1
#include <stddef.h>
#include "uri.h"
+/* Init on the main process */
int http_init(void);
void http_cleanup(void);
#include "http/http.h"
#include "rtr/rtr.h"
#include "rtr/db/vrps.h"
+#include "xml/relax_ng.h"
static int
start_rtr_server(void)
if (error)
goto revert_nid;
+ error = relax_ng_init();
+ if (error)
+ goto revert_http;
+
error = start_rtr_server();
+ relax_ng_cleanup();
+revert_http:
http_cleanup();
revert_nid:
nid_destroy();
#include "object/name.h"
#include "object/manifest.h"
#include "object/signed_object.h"
+#include "rrdp/rrdp_parser.h"
#include "rsync/rsync.h"
/* Just to prevent some line breaking. */
goto revert_refs;
}
-
/* Currently there's support for 2 access methods */
/* FIXME (now) Download files according to the preferred method */
if (sia_uris.rpkiNotify.uri != NULL &&
- sia_uris.caRepository.position > sia_uris.rpkiNotify.position)
+ sia_uris.caRepository.position > sia_uris.rpkiNotify.position) {
pr_info("Preferred Access Method: RRDP, but doing rsync anyways");
- else
+ rrdp_parse_notification(sia_uris.rpkiNotify.uri);
+ } else
pr_info("Preferred Access Method: RSYNC [found RRDP? %s]",
(sia_uris.rpkiNotify.uri == NULL ? "NO" : "YES"));
--- /dev/null
+#include "rrdp_objects.h"
+
+#include "log.h"
+
+struct xml_source {
+ xmlDoc *doc;
+};
+
+int
+global_data_init(struct global_data *data)
+{
+ data->session_id = NULL;
+ return 0;
+}
+
+void
+global_data_cleanup(struct global_data *data)
+{
+ free(data->session_id);
+}
+
+int
+doc_data_init(struct doc_data *data)
+{
+ data->hash = NULL;
+ data->uri = NULL;
+ return 0;
+}
+
+void
+doc_data_cleanup(struct doc_data *data)
+{
+ free(data->hash);
+ free(data->uri);
+}
+
+int
+xml_source_create(struct xml_source **src)
+{
+ struct xml_source *tmp;
+
+ tmp = malloc(sizeof(struct xml_source));
+ if (tmp == NULL)
+ return pr_enomem();
+
+ *src = tmp;
+ return 0;
+}
+
+void
+xml_source_destroy(struct xml_source *src){
+ if (src != NULL) {
+ xmlFreeDoc(src->doc);
+ free(src);
+ }
+}
+
+
+int
+xml_source_set(struct xml_source *src, xmlDoc *orig)
+{
+ xmlDoc *cpy;
+
+ cpy = xmlCopyDoc(orig, 1);
+ if (cpy == NULL)
+ return pr_enomem();
+
+ src->doc = cpy;
+ return 0;
+}
+
+int
+update_notification_create(struct update_notification **file)
+{
+ struct update_notification *tmp;
+
+ tmp = malloc(sizeof(struct update_notification));
+ if (tmp == NULL)
+ return pr_enomem();
+
+ global_data_init(&tmp->gdata);
+ doc_data_init(&tmp->snapshot);
+
+ SLIST_INIT(&tmp->deltas_list);
+
+ *file = tmp;
+ return 0;
+}
+
+void
+update_notification_destroy(struct update_notification *file)
+{
+ struct deltas_head *list;
+ struct delta_head *head;
+
+ list = &file->deltas_list;
+ while (!SLIST_EMPTY(list)) {
+ head = list->slh_first;
+ SLIST_REMOVE_HEAD(list, next);
+ doc_data_cleanup(&head->doc_data);
+ free(head);
+ }
+ doc_data_cleanup(&file->snapshot);
+ global_data_cleanup(&file->gdata);
+
+ free(file);
+}
+
+/* URI and HASH must already be allocated */
+int
+update_notification_deltas_add(struct deltas_head *deltas, unsigned long serial,
+ char **uri, unsigned char **hash, size_t hash_len)
+{
+ struct delta_head *elem;
+
+ elem = malloc(sizeof(struct delta_head));
+ if (elem == NULL)
+ return pr_enomem();
+
+ elem->serial = serial;
+ elem->doc_data.uri = *uri;
+ elem->doc_data.hash = *hash;
+ elem->doc_data.hash_len = hash_len;
+ SLIST_INSERT_HEAD(deltas, elem, next);
+
+ return 0;
+}
--- /dev/null
+#ifndef SRC_RRDP_RRDP_OBJECTS_H_
+#define SRC_RRDP_RRDP_OBJECTS_H_
+
+#include <libxml/tree.h>
+#include <sys/queue.h>
+#include <stddef.h>
+
+/* Common structures */
+struct xml_source;
+
+struct global_data {
+ char *session_id;
+ unsigned long serial;
+};
+
+struct doc_data {
+ char *uri;
+ unsigned char *hash;
+ size_t hash_len;
+};
+
+struct publish {
+ struct doc_data doc_data;
+ unsigned char *content;
+ SLIST_ENTRY(publish) next;
+};
+
+struct withdraw {
+ struct doc_data doc_data;
+ SLIST_ENTRY(withdraw) next;
+};
+
+/* Delta file structs */
+SLIST_HEAD(publish_list, publish);
+SLIST_HEAD(withdrawn_list, withdraw);
+
+struct delta {
+ struct global_data global_data;
+ struct publish_list publish_list;
+ struct withdrawn_list withdraw_list;
+ struct xml_source *source;
+};
+
+/* Snapshot file structs */
+struct snapshot {
+ struct global_data global_data;
+ struct publish_list publish_list;
+ struct xml_source *source;
+};
+
+/* Update notification file structs */
+struct delta_head {
+ unsigned long serial;
+ struct doc_data doc_data;
+ SLIST_ENTRY(delta_head) next;
+};
+
+SLIST_HEAD(deltas_head, delta_head);
+
+struct update_notification {
+ struct global_data gdata;
+ struct doc_data snapshot;
+ struct deltas_head deltas_list;
+};
+
+int global_data_init(struct global_data *);
+void global_data_cleanup(struct global_data *);
+
+int doc_data_init(struct doc_data *);
+void doc_data_cleanup(struct doc_data *);
+
+int xml_source_create(struct xml_source **);
+void xml_source_destroy(struct xml_source *);
+int xml_source_set(struct xml_source *, xmlDoc *);
+
+int update_notification_create(struct update_notification **);
+void update_notification_destroy(struct update_notification *);
+
+int update_notification_deltas_add(struct deltas_head *, unsigned long, char **,
+ unsigned char **, size_t);
+
+#endif /* SRC_RRDP_RRDP_OBJECTS_H_ */
--- /dev/null
+#include "rrdp_parser.h"
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "http/http.h"
+#include "xml/relax_ng.h"
+#include "log.h"
+
+/* XML Elements */
+#define RRDP_ELEM_NOTIFICATION "notification"
+#define RRDP_ELEM_SNAPSHOT "snapshot"
+#define RRDP_ELEM_DELTA "delta"
+
+/* XML Attributes */
+#define RRDP_ATTR_VERSION "version"
+#define RRDP_ATTR_SESSION_ID "session_id"
+#define RRDP_ATTR_SERIAL "serial"
+#define RRDP_ATTR_URI "uri"
+#define RRDP_ATTR_HASH "hash"
+
+
+static int
+get_root_element(xmlDoc *doc, xmlNode **result)
+{
+ xmlNode *tmp;
+
+ tmp = xmlDocGetRootElement(doc);
+ if (tmp == NULL)
+ return pr_err("XML file doesn't have a root element");
+
+ *result = tmp;
+ return 0;
+}
+
+static int
+parse_string(xmlNode *root, char const *attr, char **result)
+{
+ xmlChar *xml_value;
+ char *tmp;
+
+ xml_value = xmlGetProp(root, BAD_CAST attr);
+ if (xml_value == NULL)
+ return pr_err("RRDP file: Couldn't find xml attribute %s",
+ attr);
+
+ tmp = malloc(xmlStrlen(xml_value) + 1);
+ if (tmp == NULL) {
+ xmlFree(xml_value);
+ return pr_enomem();
+ }
+
+ memcpy(tmp, xml_value, xmlStrlen(xml_value));
+ tmp[xmlStrlen(xml_value)] = '\0';
+ xmlFree(xml_value);
+
+ *result = tmp;
+ return 0;
+}
+
+static int
+parse_long(xmlNode *root, char const *attr, unsigned long *result)
+{
+ xmlChar *xml_value;
+ unsigned long tmp;
+
+ xml_value = xmlGetProp(root, BAD_CAST attr);
+ if (xml_value == NULL)
+ return pr_err("RRDP file: Couldn't find xml attribute %s",
+ attr);
+
+ errno = 0;
+ tmp = strtoul((char *) xml_value, NULL, 10);
+ if (errno) {
+ xmlFree(xml_value);
+ pr_errno(errno, "RRDP file: Invalid long value '%s'",
+ xml_value);
+ return -EINVAL;
+ }
+ xmlFree(xml_value);
+
+ (*result) = tmp;
+ return 0;
+}
+
+static int
+parse_hex_string(xmlNode *root, char const *attr, unsigned char **result,
+ size_t *result_len)
+{
+ xmlChar *xml_value;
+ unsigned char *tmp, *ptr;
+ char *xml_cur;
+ char buf[2];
+ size_t tmp_len;
+
+ xml_value = xmlGetProp(root, BAD_CAST attr);
+ if (xml_value == NULL)
+ return pr_err("RRDP file: Couldn't find xml attribute %s",
+ attr);
+
+ /* The rest of the checks are done at the schema */
+ if (xmlStrlen(xml_value) % 2 != 0) {
+ xmlFree(xml_value);
+ return pr_err("RRDP file: Attribute %s isn't a valid hash",
+ attr);
+ }
+
+ tmp_len = xmlStrlen(xml_value) / 2;
+ tmp = malloc(tmp_len);
+ if (tmp == NULL) {
+ xmlFree(xml_value);
+ return pr_enomem();
+ }
+ memset(tmp, 0, tmp_len);
+
+ ptr = tmp;
+ xml_cur = (char *) xml_value;
+ while (ptr - tmp < tmp_len) {
+ memcpy(buf, xml_cur, 2);
+ *ptr = strtol(buf, NULL, 16);
+ xml_cur+=2;
+ ptr++;
+ }
+ xmlFree(xml_value);
+
+ *result = tmp;
+ (*result_len) = tmp_len;
+ return 0;
+}
+
+/* @gdata elements are allocated */
+static int
+parse_global_data(xmlNode *root, struct global_data *gdata)
+{
+ int error;
+
+ error = parse_string(root, RRDP_ATTR_SESSION_ID, &gdata->session_id);
+ if (error)
+ return error;
+
+ error = parse_long(root, RRDP_ATTR_SERIAL, &gdata->serial);
+ if (error) {
+ free(gdata->session_id);
+ return error;
+ }
+
+ return 0;
+}
+
+/* @data elements are allocated */
+static int
+parse_doc_data(xmlNode *root, bool parse_hash, struct doc_data *data)
+{
+ int error;
+
+ error = parse_string(root, RRDP_ATTR_URI, &data->uri);
+ if (error)
+ return error;
+
+ if (!parse_hash)
+ return 0;
+
+ error = parse_hex_string(root, RRDP_ATTR_HASH, &data->hash,
+ &data->hash_len);
+ if (error) {
+ free(data->uri);
+ return error;
+ }
+
+ return 0;
+}
+
+static int
+parse_notification_deltas(xmlNode *root, struct deltas_head *deltas)
+{
+ struct delta_head delta;
+ int error;
+
+ error = parse_long(root, RRDP_ATTR_SERIAL, &delta.serial);
+ if (error)
+ return error;
+
+ error = parse_doc_data(root, true, &delta.doc_data);
+ if (error)
+ return error;
+
+ error = update_notification_deltas_add(deltas, delta.serial,
+ &delta.doc_data.uri, &delta.doc_data.hash, delta.doc_data.hash_len);
+ if (error) {
+ doc_data_cleanup(&delta.doc_data);
+ return error;
+ }
+
+ return 0;
+}
+
+static int
+parse_notification_data(xmlNode *root, struct update_notification *file)
+{
+ xmlNode *cur_node;
+ int error;
+
+ for (cur_node = root->children; cur_node; cur_node = cur_node->next) {
+ if (xmlStrEqual(cur_node->name, BAD_CAST RRDP_ELEM_DELTA))
+ error = parse_notification_deltas(cur_node,
+ &file->deltas_list);
+ else if (xmlStrEqual(cur_node->name,
+ BAD_CAST RRDP_ELEM_SNAPSHOT))
+ error = parse_doc_data(cur_node, true, &file->snapshot);
+
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static int
+parse_notification(const char *path,
+ struct update_notification **file)
+{
+ xmlDoc *doc;
+ xmlNode *root;
+ struct update_notification *tmp;
+ int error;
+
+ root = NULL;
+
+ error = relax_ng_validate(path, &doc);
+ if (error)
+ return error;
+
+ error = update_notification_create(&tmp);
+ if (error)
+ goto release_doc;
+
+ error = get_root_element(doc, &root);
+ if (error)
+ return error;
+
+ /* FIXME (now) validate version, namespace, etc. */
+ error = parse_global_data(root, &tmp->gdata);
+ if (error)
+ goto release_update;
+
+ error = parse_notification_data(root, tmp);
+ if (error)
+ goto release_update;
+
+ *file = tmp;
+ /* Error 0 is ok */
+ goto release_doc;
+
+release_update:
+ update_notification_destroy(tmp);
+release_doc:
+ xmlFreeDoc(doc);
+ return error;
+}
+
+static size_t
+write_local(unsigned char *content, size_t size, size_t nmemb, void *arg)
+{
+ FILE *fd = arg;
+ size_t read = size * nmemb;
+ size_t written;
+
+ written = fwrite(content, size, nmemb, fd);
+ if (written != nmemb)
+ return -EINVAL;
+
+ return read;
+}
+
+/* FIXME (now) Receive an **update_notification? */
+int
+rrdp_parse_notification(struct rpki_uri *uri)
+{
+ struct update_notification *tmp;
+ int error;
+
+ if (uri == NULL || uri_is_rsync(uri))
+ pr_crit("Wrong call, trying to parse a non HTTPS URI");
+
+ error = http_download_file(uri, write_local);
+ if (error)
+ return error;
+
+ error = parse_notification(uri_get_local(uri), &tmp);
+ if (error)
+ return error;
+
+ /* FIXME (now) This is just a test, must be removed */
+ update_notification_destroy(tmp);
+
+ /* result = tmp; */
+ return 0;
+}
--- /dev/null
+#ifndef SRC_RRDP_RRDP_PARSER_H_
+#define SRC_RRDP_RRDP_PARSER_H_
+
+#include "rrdp/rrdp_objects.h"
+#include "uri.h"
+
+int rrdp_parse_notification(struct rpki_uri *);
+
+#endif /* SRC_RRDP_RRDP_PARSER_H_ */
--- /dev/null
+#include "relax_ng.h"
+
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+#include <libxml/relaxng.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "log.h"
+
+xmlRelaxNGPtr schema;
+xmlRelaxNGValidCtxtPtr validctxt;
+xmlRelaxNGParserCtxtPtr rngparser;
+
+/* Initialize global schema to parse RRDP files */
+int
+relax_ng_init(void)
+{
+ int error;
+
+ xmlInitParser();
+
+ rngparser = xmlRelaxNGNewMemParserCtxt(RRDP_V1_RNG, RRDP_V1_RNG_SIZE);
+ if (rngparser == NULL) {
+ error = pr_err("xmlRelaxNGNewMemParserCtxt() returned NULL");
+ goto cleanup_parser;
+ }
+
+ schema = xmlRelaxNGParse(rngparser);
+ if (schema == NULL) {
+ error = pr_err("xmlRelaxNGParse() returned NULL");
+ goto free_parser_ctx;
+ }
+
+ validctxt = xmlRelaxNGNewValidCtxt(schema);
+ if (validctxt == NULL) {
+ error = pr_err("xmlRelaxNGNewValidCtxt() returned NULL");
+ goto free_schema;
+ }
+
+ /*
+ * FIXME (now) Use xmlRelaxNGValidityErrorFunc and
+ * xmlRelaxNGValidityWarningFunc?
+ */
+ return 0;
+free_schema:
+ xmlRelaxNGFree(schema);
+free_parser_ctx:
+ xmlRelaxNGFreeParserCtxt(rngparser);
+cleanup_parser:
+ xmlCleanupParser();
+ return error;
+}
+
+/*
+ * Validate file at @path against globally loaded schema. If the file is valid,
+ * the result is set at @doc, returns error otherwise
+ */
+int
+relax_ng_validate(const char *path, xmlDoc **doc)
+{
+ xmlDoc *tmp;
+ int error;
+
+ tmp = xmlParseFile(path);
+ error = xmlRelaxNGValidateDoc(validctxt, tmp);
+ if (error) {
+ xmlFreeDoc(tmp);
+ return -EINVAL;
+ }
+
+ *doc = tmp;
+ return 0;
+}
+
+void
+relax_ng_cleanup(void)
+{
+ xmlRelaxNGFreeValidCtxt(validctxt);
+ xmlRelaxNGFree(schema);
+ xmlRelaxNGFreeParserCtxt(rngparser);
+ xmlCleanupParser();
+}
--- /dev/null
+#ifndef SRC_XML_RELAX_NG_H_
+#define SRC_XML_RELAX_NG_H_
+
+#include <libxml/tree.h>
+#include <string.h>
+
+/*
+ * Schema obtained from RFC 8182, converted using the tool rnc2rng
+ * (https://github.com/djc/rnc2rng)
+ */
+#define RRDP_V1_RNG \
+"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" \
+"<grammar xmlns=\"http://relaxng.org/ns/structure/1.0\"" \
+" ns=\"http://www.ripe.net/rpki/rrdp\"" \
+" datatypeLibrary=\"http://www.w3.org/2001/XMLSchema-datatypes\">"\
+" <define name=\"version\">" \
+" <data type=\"positiveInteger\">" \
+" <param name=\"maxInclusive\">1</param>" \
+" </data>" \
+" </define>" \
+" <define name=\"serial\">" \
+" <data type=\"positiveInteger\"/>" \
+" </define>" \
+" <define name=\"uri\">" \
+" <data type=\"anyURI\"/>" \
+" </define>" \
+" <define name=\"uuid\">" \
+" <data type=\"string\">" \
+" <param name=\"pattern\">[\\-0-9a-fA-F]+</param>" \
+" </data>" \
+" </define>" \
+" <define name=\"hash\">" \
+" <data type=\"string\">" \
+" <param name=\"pattern\">[0-9a-fA-F]+</param>" \
+" </data>" \
+" </define>" \
+" <define name=\"base64\">" \
+" <data type=\"base64Binary\"/>" \
+" </define>" \
+" <start combine=\"choice\">" \
+" <element>" \
+" <name ns=\"http://www.ripe.net/rpki/rrdp\">notification</name>" \
+" <attribute>" \
+" <name ns=\"\">version</name>" \
+" <ref name=\"version\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">session_id</name>" \
+" <ref name=\"uuid\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">serial</name>" \
+" <ref name=\"serial\"/>" \
+" </attribute>" \
+" <element>" \
+" <name ns=\"http://www.ripe.net/rpki/rrdp\">snapshot</name>" \
+" <attribute>" \
+" <name ns=\"\">uri</name>" \
+" <ref name=\"uri\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">hash</name>" \
+" <ref name=\"hash\"/>" \
+" </attribute>" \
+" </element>" \
+" <zeroOrMore>" \
+" <element>" \
+" <name ns=\"http://www.ripe.net/rpki/rrdp\">delta</name>" \
+" <attribute>" \
+" <name ns=\"\">serial</name>" \
+" <ref name=\"serial\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">uri</name>" \
+" <ref name=\"uri\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">hash</name>" \
+" <ref name=\"hash\"/>" \
+" </attribute>" \
+" </element>" \
+" </zeroOrMore>" \
+" </element>" \
+" </start>" \
+" <start combine=\"choice\">" \
+" <element>" \
+" <name ns=\"http://www.ripe.net/rpki/rrdp\">snapshot</name>" \
+" <attribute>" \
+" <name ns=\"\">version</name>" \
+" <ref name=\"version\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">session_id</name>" \
+" <ref name=\"uuid\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">serial</name>" \
+" <ref name=\"serial\"/>" \
+" </attribute>" \
+" <zeroOrMore>" \
+" <element>" \
+" <name ns=\"http://www.ripe.net/rpki/rrdp\">publish</name>" \
+" <attribute>" \
+" <name ns=\"\">uri</name>" \
+" <ref name=\"uri\"/>" \
+" </attribute>" \
+" <ref name=\"base64\"/>" \
+" </element>" \
+" </zeroOrMore>" \
+" </element>" \
+" </start>" \
+" <start combine=\"choice\">" \
+" <element>" \
+" <name ns=\"http://www.ripe.net/rpki/rrdp\">delta</name>" \
+" <attribute>" \
+" <name ns=\"\">version</name>" \
+" <ref name=\"version\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">session_id</name>" \
+" <ref name=\"uuid\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">serial</name>" \
+" <ref name=\"serial\"/>" \
+" </attribute>" \
+" <oneOrMore>" \
+" <ref name=\"delta_element\"/>" \
+" </oneOrMore>" \
+" </element>" \
+" </start>" \
+" <define name=\"delta_element\" combine=\"choice\">" \
+" <element>" \
+" <name ns=\"http://www.ripe.net/rpki/rrdp\">publish</name>" \
+" <attribute>" \
+" <name ns=\"\">uri</name>" \
+" <ref name=\"uri\"/>" \
+" </attribute>" \
+" <optional>" \
+" <attribute>" \
+" <name ns=\"\">hash</name>" \
+" <ref name=\"hash\"/>" \
+" </attribute>" \
+" </optional>" \
+" <ref name=\"base64\"/>" \
+" </element>" \
+" </define>" \
+" <define name=\"delta_element\" combine=\"choice\">" \
+" <element>" \
+" <name ns=\"http://www.ripe.net/rpki/rrdp\">withdraw</name>" \
+" <attribute>" \
+" <name ns=\"\">uri</name>" \
+" <ref name=\"uri\"/>" \
+" </attribute>" \
+" <attribute>" \
+" <name ns=\"\">hash</name>" \
+" <ref name=\"hash\"/>" \
+" </attribute>" \
+" </element>" \
+" </define>" \
+"</grammar>"
+
+#define RRDP_V1_RNG_SIZE strlen(RRDP_V1_RNG)
+
+
+int relax_ng_init(void);
+int relax_ng_validate(const char *, xmlDoc **);
+void relax_ng_cleanup(void);
+
+#endif /* SRC_XML_RELAX_NG_H_ */
# <mumble>_CFLAGS is not defined.
# Otherwise it must be included manually:
# mumble_mumble_CFLAGS = ${AM_CFLAGS} flag1 flag2 flag3 ...
-AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src -DUNIT_TESTING ${CHECK_CFLAGS}
+AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src -DUNIT_TESTING ${CHECK_CFLAGS} ${XML2_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
check_PROGRAMS += tal.test
check_PROGRAMS += vcard.test
check_PROGRAMS += vrps.test
+check_PROGRAMS += xml.test
check_PROGRAMS += rtr/pdu.test
check_PROGRAMS += rtr/primitive_reader.test
TESTS = ${check_PROGRAMS}
vrps_test_SOURCES = rtr/db/vrps_test.c
vrps_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS}
+xml_test_SOURCES = xml_test.c
+xml_test_LDADD = ${MY_LDADD} ${XML2_LIBS}
+
rtr_pdu_test_SOURCES = rtr/pdu_test.c
rtr_pdu_test_LDADD = ${MY_LDADD}
--- /dev/null
+<notification version="1" session_id="b912d0fc-14d3-4a51-bc6b-ceb9e7384222" serial="1510" xmlns="http://www.ripe.net/rpki/rrdp">
+ <snapshot uri="https://rrdp.ripe.net/b912d0fc-14d3-4a51-bc6b-ceb9e7384222/1510/snapshot.xml" hash="5C6B311E6F5D8132C189308B2E274E5FC62C533E013BB9FACF02872F6D6887A0"/>
+ <delta serial="1510" uri="https://rrdp.ripe.net/b912d0fc-14d3-4a51-bc6b-ceb9e7384222/1510/delta.xml" hash="31B9EBB3B89B9A9719AD3B6E50AADE04188D8075BFEA2B34119FF0DD398F6E1A"/>
+ <delta serial="1509" uri="https://rrdp.ripe.net/b912d0fc-14d3-4a51-bc6b-ceb9e7384222/1509/delta.xml" hash="24EC92A1653860482EE8712776F3CD8E1AC7C7E680CA7101F340617FB1932652"/>
+ <delta serial="1508" uri="https://rrdp.ripe.net/b912d0fc-14d3-4a51-bc6b-ceb9e7384222/1508/delta.xml" hash="FF0EA743A560463EAF9BF8638BD9A81A29B44E10057C288D10F665BFD664594F"/>
+ <delta serial="1507" uri="https://rrdp.ripe.net/b912d0fc-14d3-4a51-bc6b-ceb9e7384222/1507/delta.xml" hash="CDF7A919F49321C4CBC909DBAB789F7178B52ECBB4285F31A01140AAEF8B6B7C"/>
+ <delta serial="1506" uri="https://rrdp.ripe.net/b912d0fc-14d3-4a51-bc6b-ceb9e7384222/1506/delta.xml" hash="B7AB1EBB1BBA30CDEEE2AA21F95AE907625C2FED65C940446CDB08C3D5E973C9"/>
+</notification>
\ No newline at end of file
--- /dev/null
+#include <check.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <libxml/tree.h>
+#include "impersonator.c"
+#include "log.c"
+#include "xml/relax_ng.c"
+
+START_TEST(relax_ng_valid)
+{
+ char const *url = "xml/notification.xml";
+ xmlDoc *doc;
+
+ relax_ng_init();
+ ck_assert_int_eq(relax_ng_validate(url, &doc), 0);
+ xmlFreeDoc(doc);
+ relax_ng_cleanup();
+}
+END_TEST
+
+Suite *xml_load_suite(void)
+{
+ Suite *suite;
+ TCase *validate;
+
+ validate = tcase_create("Validate");
+ tcase_add_test(validate, relax_ng_valid);
+
+ suite = suite_create("xml_test()");
+ suite_add_tcase(suite, validate);
+
+ return suite;
+}
+
+int main(void)
+{
+ Suite *suite;
+ SRunner *runner;
+ int tests_failed;
+
+ suite = xml_load_suite();
+
+ runner = srunner_create(suite);
+ srunner_run_all(runner, CK_NORMAL);
+ tests_failed = srunner_ntests_failed(runner);
+ srunner_free(runner);
+
+ return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}