Commit 04e564dd authored by Jan Včelák's avatar Jan Včelák 🚀

events: put handlers in separate files

parent 7b2638d6
......@@ -293,6 +293,18 @@ libknotd_la_SOURCES = \
knot/dnssec/zone-nsec.h \
knot/dnssec/zone-sign.c \
knot/dnssec/zone-sign.h \
knot/events/events.c \
knot/events/events.h \
knot/events/handlers.h \
knot/events/handlers/dnssec.c \
knot/events/handlers/expire.c \
knot/events/handlers/flush.c \
knot/events/handlers/load.c \
knot/events/handlers/notify.c \
knot/events/handlers/refresh.c \
knot/events/handlers/update.c \
knot/events/replan.c \
knot/events/replan.h \
knot/modules/dnsproxy.c \
knot/modules/dnsproxy.h \
knot/modules/online_sign/module.c \
......@@ -325,6 +337,8 @@ libknotd_la_SOURCES = \
knot/nameserver/tsig_ctx.h \
knot/nameserver/update.c \
knot/nameserver/update.h \
knot/query/query.c \
knot/query/query.h \
knot/common/evsched.c \
knot/common/evsched.h \
knot/common/fdset.c \
......@@ -365,12 +379,6 @@ libknotd_la_SOURCES = \
knot/worker/queue.h \
knot/zone/contents.c \
knot/zone/contents.h \
knot/zone/events/events.c \
knot/zone/events/events.h \
knot/zone/events/handlers.c \
knot/zone/events/handlers.h \
knot/zone/events/replan.c \
knot/zone/events/replan.h \
knot/zone/node.c \
knot/zone/node.h \
knot/zone/semantic-check.c \
......
......@@ -20,9 +20,9 @@
#include "libknot/libknot.h"
#include "knot/common/log.h"
#include "knot/zone/events/events.h"
#include "knot/zone/events/handlers.h"
#include "knot/zone/events/replan.h"
#include "knot/events/events.h"
#include "knot/events/handlers.h"
#include "knot/events/replan.h"
#include "knot/zone/zone.h"
#define ZONE_EVENT_IMMEDIATE 1 /* Fast-track to worker queue. */
......
/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -12,13 +12,17 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
*/
#pragma once
// XXX: Consider forward declaration of conf_t a zone_t.
#include "knot/conf/conf.h"
#include "knot/zone/zone.h"
// XXX: logging workaround
#include "knot/nameserver/log.h"
/*! \brief Loads or reloads potentially changed zone. */
int event_load(conf_t *conf, zone_t *zone);
/*! \brief Sends a SOA query to master. */
......@@ -33,8 +37,5 @@ int event_expire(conf_t *conf, zone_t *zone);
int event_flush(conf_t *conf, zone_t *zone);
/*! \brief Sends notify to slaves. */
int event_notify(conf_t *conf, zone_t *zone);
/*! \brief (re)Signs the zone using its DNSSEC keys. */
/*! \brief Signs the zone using its DNSSEC keys. */
int event_dnssec(conf_t *conf, zone_t *zone);
/*! \brief Progressive bootstrap retry timer. */
uint32_t bootstrap_next(uint32_t timer);
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <urcu.h>
#include "knot/common/log.h"
#include "knot/conf/conf.h"
#include "knot/dnssec/zone-events.h"
#include "knot/updates/apply.h"
#include "knot/zone/zone.h"
#include "libknot/errcode.h"
/*!
* \todo Separate signing from zone loading and drop this function.
*
* DNSSEC signing is planned from two places - after zone loading and after
* successful resign. This function just logs the message and reschedules the
* DNSSEC timer.
*
* I would rather see the invocation of the signing from event_dnssec()
* function. This would require to split refresh event to zone load and zone
* publishing.
*/
static void schedule_dnssec(zone_t *zone, time_t refresh_at)
{
// log a message
char time_str[64] = { 0 };
struct tm time_gm = { 0 };
localtime_r(&refresh_at, &time_gm);
strftime(time_str, sizeof(time_str), KNOT_LOG_TIME_FORMAT, &time_gm);
log_zone_info(zone->name, "DNSSEC, next signing on %s", time_str);
// schedule
zone_events_schedule_at(zone, ZONE_EVENT_DNSSEC, refresh_at);
}
int event_dnssec(conf_t *conf, zone_t *zone)
{
assert(zone);
changeset_t ch;
int ret = changeset_init(&ch, zone->name);
if (ret != KNOT_EOK) {
goto done;
}
uint32_t refresh_at = time(NULL);
int sign_flags = 0;
if (zone->flags & ZONE_FORCE_RESIGN) {
log_zone_info(zone->name, "DNSSEC, dropping previous "
"signatures, resigning zone");
zone->flags &= ~ZONE_FORCE_RESIGN;
sign_flags = ZONE_SIGN_DROP_SIGNATURES;
} else {
log_zone_info(zone->name, "DNSSEC, signing zone");
sign_flags = 0;
}
ret = knot_dnssec_zone_sign(zone->contents, &ch, sign_flags, &refresh_at);
if (ret != KNOT_EOK) {
goto done;
}
bool zone_changed = !changeset_empty(&ch);
if (zone_changed) {
/* Apply change. */
apply_ctx_t a_ctx = { { 0 } };
apply_init_ctx(&a_ctx);
zone_contents_t *new_contents = NULL;
int ret = apply_changeset(&a_ctx, zone, &ch, &new_contents);
if (ret != KNOT_EOK) {
log_zone_error(zone->name, "DNSSEC, failed to sign zone (%s)",
knot_strerror(ret));
goto done;
}
/* Write change to journal. */
ret = zone_change_store(conf, zone, &ch);
if (ret != KNOT_EOK) {
log_zone_error(zone->name, "DNSSEC, failed to sign zone (%s)",
knot_strerror(ret));
update_rollback(&a_ctx);
update_free_zone(&new_contents);
goto done;
}
/* Switch zone contents. */
zone_contents_t *old_contents = zone_switch_contents(zone, new_contents);
zone->flags &= ~ZONE_EXPIRED;
synchronize_rcu();
update_free_zone(&old_contents);
update_cleanup(&a_ctx);
}
// Schedule dependent events.
schedule_dnssec(zone, refresh_at);
if (zone_changed) {
zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW);
conf_val_t val = conf_zone_get(conf, C_ZONEFILE_SYNC, zone->name);
if (conf_int(&val) == 0) {
zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
}
}
done:
changeset_clear(&ch);
return ret;
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <urcu.h>
#include "contrib/trim.h"
#include "knot/common/log.h"
#include "knot/conf/conf.h"
#include "knot/events/handlers.h"
#include "knot/zone/contents.h"
#include "knot/zone/zone.h"
int event_expire(conf_t *conf, zone_t *zone)
{
assert(zone);
zone_contents_t *expired = zone_switch_contents(zone, NULL);
synchronize_rcu();
/* Expire zonefile information. */
zone->zonefile.exists = false;
zone->flags |= ZONE_EXPIRED;
zone_contents_deep_free(&expired);
log_zone_info(zone->name, "zone expired");
mem_trim();
return KNOT_EOK;
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include "knot/conf/conf.h"
#include "knot/zone/zone.h"
int event_flush(conf_t *conf, zone_t *zone)
{
assert(zone);
/* Reschedule. */
conf_val_t val = conf_zone_get(conf, C_ZONEFILE_SYNC, zone->name);
int64_t sync_timeout = conf_int(&val);
if (sync_timeout > 0) {
zone_events_schedule(zone, ZONE_EVENT_FLUSH, sync_timeout);
}
/* Check zone contents. */
if (zone_contents_is_empty(zone->contents)) {
return KNOT_EOK;
}
return zone_flush_journal(conf, zone);
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <urcu.h>
#include "knot/common/log.h"
#include "knot/conf/conf.h"
#include "knot/zone/zone-load.h"
#include "knot/zone/zone.h"
#include "knot/zone/zonefile.h"
int event_load(conf_t *conf, zone_t *zone)
{
assert(zone);
/* Take zone file mtime and load it. */
time_t mtime;
char *filename = conf_zonefile(conf, zone->name);
int ret = zonefile_exists(filename, &mtime);
free(filename);
if (ret != KNOT_EOK) {
goto fail;
}
uint32_t dnssec_refresh = time(NULL);
zone_contents_t *contents = NULL;
ret = zone_load_contents(conf, zone->name, &contents);
if (ret != KNOT_EOK) {
goto fail;
}
/* Store the zonefile SOA serial. */
zone->zonefile.serial = zone_contents_serial(contents);
/* Apply journal if first load or reload with original zonefile. */
if (zone->contents == NULL ||
(zone->zonefile.exists && zone->zonefile.mtime == mtime)) {
ret = zone_load_journal(conf, zone, contents);
if (ret != KNOT_EOK) {
goto fail;
}
}
/* Store the zonefile mtime. */
zone->zonefile.mtime = mtime;
/* Post load actions - calculate delta, sign with DNSSEC... */
/*! \todo issue #242 dnssec signing should occur in the special event */
ret = zone_load_post(conf, zone, contents, &dnssec_refresh);
if (ret != KNOT_EOK) {
if (ret == KNOT_ESPACE) {
log_zone_error(zone->name, "journal size is too small "
"to fit the changes");
} else {
log_zone_error(zone->name, "failed to store changes into "
"journal (%s)", knot_strerror(ret));
}
goto fail;
}
/* Check zone contents consistency. */
ret = zone_load_check(conf, contents);
if (ret != KNOT_EOK) {
goto fail;
}
/* Everything went alright, switch the contents. */
zone->flags &= ~ZONE_EXPIRED;
zone->zonefile.exists = true;
zone_contents_t *old = zone_switch_contents(zone, contents);
bool old_contents = (old != NULL);
uint32_t old_serial = zone_contents_serial(old);
if (old != NULL) {
synchronize_rcu();
zone_contents_deep_free(&old);
}
/* Schedule notify and refresh after load. */
if (zone_is_slave(conf, zone)) {
zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
}
if (!zone_contents_is_empty(contents)) {
zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW);
zone->bootstrap_retry = ZONE_EVENT_NOW;
}
/* Schedule zone resign. */
conf_val_t val = conf_zone_get(conf, C_DNSSEC_SIGNING, zone->name);
if (conf_bool(&val)) {
//schedule_dnssec(zone, dnssec_refresh);
}
/* Periodic execution. */
val = conf_zone_get(conf, C_ZONEFILE_SYNC, zone->name);
int64_t sync_timeout = conf_int(&val);
if (sync_timeout >= 0) {
zone_events_schedule(zone, ZONE_EVENT_FLUSH, sync_timeout);
}
uint32_t current_serial = zone_contents_serial(zone->contents);
if (old_contents) {
log_zone_info(zone->name, "loaded, serial %u -> %u",
old_serial, current_serial);
} else {
log_zone_info(zone->name, "loaded, serial %u", current_serial);
}
return KNOT_EOK;
fail:
zone->zonefile.exists = false;
zone_contents_deep_free(&contents);
/* Try to bootstrap the zone if local error. */
if (zone_is_slave(conf, zone) && !zone_events_is_scheduled(zone, ZONE_EVENT_XFER)) {
zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW);
}
return ret;
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include "knot/common/log.h"
#include "knot/conf/conf.h"
#include "knot/query/query.h"
#include "knot/zone/zone.h"
#include "libknot/errcode.h"
#define NOTIFY_LOG(priority, zone, remote, msg...) \
ZONE_QUERY_LOG(priority, zone, remote, "NOTIFY, outgoing", msg);
int event_notify(conf_t *conf, zone_t *zone)
{
assert(zone);
/* Check zone contents. */
if (zone_contents_is_empty(zone->contents)) {
return KNOT_EOK;
}
/* Walk through configured remotes and send messages. */
conf_val_t notify = conf_zone_get(conf, C_NOTIFY, zone->name);
while (notify.code == KNOT_EOK) {
conf_val_t addr = conf_id_get(conf, C_RMT, C_ADDR, &notify);
size_t addr_count = conf_val_count(&addr);
for (int i = 0; i < addr_count; i++) {
conf_remote_t slave = conf_remote(conf, &notify, i);
int ret = zone_query_execute(conf, zone, KNOT_QUERY_NOTIFY, &slave);
if (ret == KNOT_EOK) {
NOTIFY_LOG(LOG_INFO, zone, &slave, "serial %u", zone_contents_serial(zone->contents));
break;
} else {
NOTIFY_LOG(LOG_WARNING, zone, &slave, "failed (%s)", knot_strerror(ret));
}
}
conf_val_next(&notify);
}
return KNOT_EOK;
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdint.h>
#include "contrib/trim.h"
#include "dnssec/random.h"
#include "knot/common/log.h"
#include "knot/conf/conf.h"
#include "knot/query/query.h"
#include "knot/zone/zone.h"
#include "libknot/errcode.h"
#define BOOTSTRAP_RETRY (30) /*!< Interval between AXFR bootstrap retries. */
#define BOOTSTRAP_MAXTIME (24*60*60) /*!< Maximum AXFR retry cap of 24 hours. */
#include "knot/nameserver/log.h"
// XXX: duplicate message
/* @note Module specific, expects some variables set. */
#define LOG_TRANSFER(severity, pkt_type, msg, ...) \
if (pkt_type == KNOT_QUERY_AXFR) { \
ZONE_QUERY_LOG(severity, zone, master, "AXFR, incoming", msg, ##__VA_ARGS__); \
} else { \
ZONE_QUERY_LOG(severity, zone, master, "IXFR, incoming", msg, ##__VA_ARGS__); \
}
/*! \brief Progressive bootstrap retry timer. */
static uint32_t bootstrap_next(uint32_t timer)
{
timer *= 2;
timer += dnssec_random_uint32_t() % BOOTSTRAP_RETRY;
if (timer > BOOTSTRAP_MAXTIME) {
timer = BOOTSTRAP_MAXTIME;
}
return timer;
}
/*! \brief Get SOA from zone. */
static const knot_rdataset_t *zone_soa(zone_t *zone)
{
return node_rdataset(zone->contents->apex, KNOT_RRTYPE_SOA);
}
static int try_refresh(conf_t *conf, zone_t *zone, const conf_remote_t *master, void *ctx)
{
assert(zone);
assert(master);
int ret = zone_query_execute(conf, zone, KNOT_QUERY_NORMAL, master);
if (ret != KNOT_EOK && ret != KNOT_LAYER_ERROR) {
ZONE_QUERY_LOG(LOG_WARNING, zone, master, "refresh, outgoing",
"failed (%s)", knot_strerror(ret));
}
return ret;
}
/*! \brief Schedule expire event, unless it is already scheduled. */
static void start_expire_timer(conf_t *conf, zone_t *zone, const knot_rdataset_t *soa)
{
if (zone_events_is_scheduled(zone, ZONE_EVENT_EXPIRE)) {
return;
}
zone_events_schedule(zone, ZONE_EVENT_EXPIRE, knot_soa_expire(soa));
}
int event_refresh(conf_t *conf, zone_t *zone)
{
assert(zone);
/* Ignore if not slave zone. */
if (!zone_is_slave(conf, zone)) {
return KNOT_EOK;
}
if (zone_contents_is_empty(zone->contents)) {
/* No contents, schedule retransfer now. */
zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW);
return KNOT_EOK;
}
int ret = zone_master_try(conf, zone, try_refresh, NULL, "refresh");
const knot_rdataset_t *soa = zone_soa(zone);
if (ret != KNOT_EOK) {
log_zone_error(zone->name, "refresh, failed (%s)",
knot_strerror(ret));
/* Schedule next retry. */
zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_retry(soa));
start_expire_timer(conf, zone, soa);
} else {
/* SOA query answered, reschedule refresh timer. */
zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
}
return KNOT_EOK;
}
/*! \brief Execute zone transfer request. */
static int zone_query_transfer(conf_t *conf, zone_t *zone, const conf_remote_t *master,
uint16_t pkt_type)
{
assert(zone);
assert(master);
int ret = zone_query_execute(conf, zone, pkt_type, master);
if (ret != KNOT_EOK) {
/* IXFR failed, revert to AXFR. */
if (pkt_type == KNOT_QUERY_IXFR) {
LOG_TRANSFER(LOG_NOTICE, pkt_type, "fallback to AXFR");
return zone_query_transfer(conf, zone, master, KNOT_QUERY_AXFR);
}
/* Log connection errors. */
LOG_TRANSFER(LOG_WARNING, pkt_type, "failed (%s)", knot_strerror(ret));
}
return ret;
}
struct transfer_data {
uint16_t pkt_type;
};
static int try_xfer(conf_t *conf, zone_t *zone, const conf_remote_t *master, void *_data)
{
assert(zone);
assert(master);
assert(_data);
struct transfer_data *data = _data;
return zone_query_transfer(conf, zone, master, data->pkt_type);
}
int event_xfer(conf_t *conf, zone_t *zone)
{
assert(zone);
/* Ignore if not slave zone. */
if (!zone_is_slave(conf, zone)) {
return KNOT_EOK;
}
struct transfer_data data = { 0 };
const char *err_str = "";
/* Determine transfer type. */
bool is_bootstrap = zone_contents_is_empty(zone->contents);
if (is_bootstrap || zone->flags & ZONE_FORCE_AXFR) {
data.pkt_type = KNOT_QUERY_AXFR;
err_str = "AXFR, incoming";
} else {
data.pkt_type = KNOT_QUERY_IXFR;
err_str = "IXFR, incoming";
}
/* Execute zone transfer. */
int ret = zone_master_try(conf, zone, try_xfer, &data, err_str);
zone_clear_preferred_master(zone);
if (ret != KNOT_EOK) {
log_zone_error(zone->name, "%s, failed (%s)", err_str,
knot_strerror(ret));
if (is_bootstrap) {
zone->bootstrap_retry = bootstrap_next(zone->bootstrap_retry);
zone_events_schedule(zone, ZONE_EVENT_XFER, zone->bootstrap_retry);
} else {
const knot_rdataset_t *soa = zone_soa(zone);
zone_events_schedule(zone, ZONE_EVENT_XFER, knot_soa_retry(soa));
start_expire_timer(conf, zone, soa);
}
return KNOT_EOK;
}
assert(!zone_contents_is_empty(zone->contents));
const knot_rdataset_t *soa = zone_soa(zone);
/* Rechedule events. */
zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW);
zone_events_cancel(zone, ZONE_EVENT_EXPIRE);
conf_val_t val = conf_zone_get(conf, C_ZONEFILE_SYNC, zone->name);
int64_t sync_timeout = conf_int(&val);
if (sync_timeout == 0) {
zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
} else if (sync_timeout > 0 &&
!zone_events_is_scheduled(zone, ZONE_EVENT_FLUSH)) {
zone_events_schedule(zone, ZONE_EVENT_FLUSH, sync_timeout);
}
/* Transfer cleanup. */
zone->bootstrap_retry = ZONE_EVENT_NOW;
zone->flags &= ~ZONE_FORCE_AXFR;
/* Trim extra heap. */
if (!is_bootstrap) {
mem_trim();
}
return KNOT_EOK;
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdint.h>