summaryrefslogtreecommitdiffstats
path: root/src/libknot/packet
diff options
context:
space:
mode:
Diffstat (limited to 'src/libknot/packet')
-rw-r--r--src/libknot/packet/compr.h101
-rw-r--r--src/libknot/packet/pkt.c837
-rw-r--r--src/libknot/packet/pkt.h416
-rw-r--r--src/libknot/packet/rrset-wire.c727
-rw-r--r--src/libknot/packet/rrset-wire.h69
-rw-r--r--src/libknot/packet/wire.h1045
6 files changed, 3195 insertions, 0 deletions
diff --git a/src/libknot/packet/compr.h b/src/libknot/packet/compr.h
new file mode 100644
index 0000000..ea14d64
--- /dev/null
+++ b/src/libknot/packet/compr.h
@@ -0,0 +1,101 @@
+/* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Name compression API.
+ *
+ * \addtogroup pkt
+ * @{
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include "libknot/packet/wire.h"
+
+/*! \brief Compression hint type. */
+enum knot_compr_hint {
+ KNOT_COMPR_HINT_NONE = 0, /* No hint. */
+ KNOT_COMPR_HINT_NOCOMP = 1, /* Don't compress. */
+ KNOT_COMPR_HINT_QNAME = KNOT_WIRE_HEADER_SIZE /* Name is QNAME. */
+};
+
+/*! \brief Compression hint array offsets. */
+enum knot_compr_offset {
+ KNOT_COMPR_HINT_OWNER = 0, /* First element in the array is RR owner. */
+ KNOT_COMPR_HINT_RDATA = 1, /* First name in RDATA is at offset 1. */
+ KNOT_COMPR_HINT_COUNT = 16 /* Maximum number of stored hints per-RR. */
+};
+
+/*
+ * \note A little bit about how compression hints work.
+ *
+ * We're storing a RRSet say 'abcd. CNAME [0]net. [1]com.' (owner=abcd. 2 RRs).
+ * The owner 'abcd.' is same for both RRs, we put it at the offset 0 in rrinfo.compress_ptr
+ * The names 'net.' and 'com.' are in the RDATA, therefore go to offsets 1 and 2.
+ * Now this is useful when solving additionals for example, because we can scan
+ * rrinfo for this RRSet and we know that 'net.' name is at the hint 1 and that leads
+ * to packet position N. With that, we just put the pointer in without any calculation.
+ * This is also useful for positive answers, where we know the RRSet owner is always QNAME.
+ * All in all, we just remember the positions of written domain names.
+ */
+
+/*! \brief Additional information about RRSet position and compression hints. */
+typedef struct {
+ uint16_t pos; /* RRSet position in the packet. */
+ uint16_t flags; /* RRSet flags. */
+ uint16_t compress_ptr[KNOT_COMPR_HINT_COUNT]; /* Array of compr. ptr hints. */
+} knot_rrinfo_t;
+
+/*!
+ * \brief Name compression context.
+ */
+typedef struct knot_compr {
+ uint8_t *wire; /* Packet wireformat. */
+ knot_rrinfo_t *rrinfo; /* Hints for current RRSet. */
+ struct {
+ uint16_t pos; /* Position of current suffix. */
+ uint8_t labels; /* Label count of the suffix. */
+ } suffix;
+} knot_compr_t;
+
+/*!
+ * \brief Retrieve compression hint from given offset.
+ */
+static inline uint16_t knot_compr_hint(const knot_rrinfo_t *info, uint16_t hint_id)
+{
+ if (hint_id < KNOT_COMPR_HINT_COUNT) {
+ return info->compress_ptr[hint_id];
+ } else {
+ return KNOT_COMPR_HINT_NONE;
+ }
+}
+
+/*!
+ * \brief Store compression hint for given offset.
+ */
+static inline void knot_compr_hint_set(knot_rrinfo_t *info, uint16_t hint_id,
+ uint16_t val, uint16_t len)
+{
+ if ((hint_id < KNOT_COMPR_HINT_COUNT) && (val + len < KNOT_WIRE_PTR_MAX)) {
+ info->compress_ptr[hint_id] = val;
+ }
+}
+
+/*! @} */
diff --git a/src/libknot/packet/pkt.c b/src/libknot/packet/pkt.c
new file mode 100644
index 0000000..728bb3e
--- /dev/null
+++ b/src/libknot/packet/pkt.c
@@ -0,0 +1,837 @@
+/* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "libknot/attribute.h"
+#include "libknot/packet/pkt.h"
+#include "libknot/codes.h"
+#include "libknot/descriptor.h"
+#include "libknot/errcode.h"
+#include "libknot/rrtype/tsig.h"
+#include "libknot/tsig-op.h"
+#include "libknot/packet/wire.h"
+#include "libknot/packet/rrset-wire.h"
+#include "libknot/wire.h"
+#include "contrib/mempattern.h"
+#include "contrib/wire_ctx.h"
+
+/*! \brief Packet RR array growth step. */
+#define NEXT_RR_ALIGN 16
+#define NEXT_RR_COUNT(count) (((count) / NEXT_RR_ALIGN + 1) * NEXT_RR_ALIGN)
+
+/*! \brief Scan packet for RRSet existence. */
+static bool pkt_contains(const knot_pkt_t *packet, const knot_rrset_t *rrset)
+{
+ assert(packet);
+ assert(rrset);
+
+ for (int i = 0; i < packet->rrset_count; ++i) {
+ const uint16_t type = packet->rr[i].type;
+ const knot_rdata_t *data = packet->rr[i].rrs.rdata;
+ if (type == rrset->type && data == rrset->rrs.rdata) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*! \brief Free all RRSets and reset RRSet count. */
+static void pkt_free_data(knot_pkt_t *pkt)
+{
+ assert(pkt);
+
+ /* Free RRSets if applicable. */
+ for (uint16_t i = 0; i < pkt->rrset_count; ++i) {
+ if (pkt->rr_info[i].flags & KNOT_PF_FREE) {
+ knot_rrset_clear(&pkt->rr[i], &pkt->mm);
+ }
+ }
+ pkt->rrset_count = 0;
+
+ /* Free EDNS option positions. */
+ mm_free(&pkt->mm, pkt->edns_opts);
+ pkt->edns_opts = 0;
+}
+
+/*! \brief Allocate new wireformat of given length, assuming *pkt is zeroed. */
+static int pkt_wire_alloc(knot_pkt_t *pkt, uint16_t len)
+{
+ assert(pkt);
+
+ if (len < KNOT_WIRE_HEADER_SIZE) {
+ return KNOT_ERANGE;
+ }
+
+ pkt->wire = mm_alloc(&pkt->mm, len);
+ if (pkt->wire == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ pkt->flags |= KNOT_PF_FREE;
+ pkt->max_size = len;
+
+ /* Reset to header size. */
+ pkt->size = KNOT_WIRE_HEADER_SIZE;
+ memset(pkt->wire, 0, pkt->size);
+
+ return KNOT_EOK;
+}
+
+/*! \brief Set packet wireformat to an existing memory. */
+static void pkt_wire_set(knot_pkt_t *pkt, void *wire, uint16_t len)
+{
+ assert(pkt);
+
+ pkt->wire = wire;
+ pkt->size = pkt->max_size = len;
+ pkt->parsed = 0;
+}
+
+/*! \brief Calculate remaining size in the packet. */
+static uint16_t pkt_remaining(knot_pkt_t *pkt)
+{
+ assert(pkt);
+
+ return pkt->max_size - pkt->size - pkt->reserved;
+}
+
+/*! \brief Return RR count for given section (from wire xxCOUNT in header). */
+static uint16_t pkt_rr_wirecount(knot_pkt_t *pkt, knot_section_t section_id)
+{
+ assert(pkt);
+ switch (section_id) {
+ case KNOT_ANSWER: return knot_wire_get_ancount(pkt->wire);
+ case KNOT_AUTHORITY: return knot_wire_get_nscount(pkt->wire);
+ case KNOT_ADDITIONAL: return knot_wire_get_arcount(pkt->wire);
+ default: assert(0); return 0;
+ }
+}
+
+/*! \brief Update RR count for given section (wire xxCOUNT in header). */
+static void pkt_rr_wirecount_add(knot_pkt_t *pkt, knot_section_t section_id,
+ int16_t val)
+{
+ assert(pkt);
+ switch (section_id) {
+ case KNOT_ANSWER: knot_wire_add_ancount(pkt->wire, val); break;
+ case KNOT_AUTHORITY: knot_wire_add_nscount(pkt->wire, val); break;
+ case KNOT_ADDITIONAL: knot_wire_add_arcount(pkt->wire, val); break;
+ }
+}
+
+/*! \brief Reserve enough space in the RR arrays. */
+static int pkt_rr_array_alloc(knot_pkt_t *pkt, uint16_t count)
+{
+ /* Enough space. */
+ if (pkt->rrset_allocd >= count) {
+ return KNOT_EOK;
+ }
+
+ /* Allocate rr_info and rr fields to next size. */
+ size_t next_size = NEXT_RR_COUNT(count);
+ knot_rrinfo_t *rr_info = mm_alloc(&pkt->mm, sizeof(knot_rrinfo_t) * next_size);
+ if (rr_info == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ knot_rrset_t *rr = mm_alloc(&pkt->mm, sizeof(knot_rrset_t) * next_size);
+ if (rr == NULL) {
+ mm_free(&pkt->mm, rr_info);
+ return KNOT_ENOMEM;
+ }
+
+ /* Copy and free the old data, if any. */
+ if (pkt->rrset_allocd > 0) {
+ memcpy(rr_info, pkt->rr_info, pkt->rrset_allocd * sizeof(knot_rrinfo_t));
+ memcpy(rr, pkt->rr, pkt->rrset_allocd * sizeof(knot_rrset_t));
+ mm_free(&pkt->mm, pkt->rr);
+ mm_free(&pkt->mm, pkt->rr_info);
+ }
+ pkt->rr = rr;
+ pkt->rr_info = rr_info;
+ pkt->rrset_allocd = next_size;
+
+ return KNOT_EOK;
+}
+
+static void compr_clear(knot_compr_t *compr)
+{
+ compr->rrinfo = NULL;
+ compr->suffix.pos = 0;
+ compr->suffix.labels = 0;
+}
+
+/*! \brief Clear the packet and switch wireformat pointers (possibly allocate new). */
+static int pkt_init(knot_pkt_t *pkt, void *wire, uint16_t len, knot_mm_t *mm)
+{
+ assert(pkt);
+
+ memset(pkt, 0, offsetof(knot_pkt_t, lower_qname));
+ pkt->lower_qname[0] = '\0';
+
+ /* No data to free, set memory context. */
+ memcpy(&pkt->mm, mm, sizeof(knot_mm_t));
+
+ /* Initialize wire. */
+ int ret = KNOT_EOK;
+ if (wire == NULL) {
+ ret = pkt_wire_alloc(pkt, len);
+ } else {
+ pkt_wire_set(pkt, wire, len);
+ }
+
+ /* Initialize compression context (zeroed above). */
+ pkt->compr.wire = pkt->wire;
+
+ return ret;
+}
+
+/*! \brief Reset packet parse state. */
+static void sections_reset(knot_pkt_t *pkt)
+{
+ pkt->current = KNOT_ANSWER;
+ memset(pkt->sections, 0, sizeof(pkt->sections));
+ (void)knot_pkt_begin(pkt, KNOT_ANSWER);
+}
+
+/*! \brief Allocate new packet using memory context. */
+static knot_pkt_t *pkt_new_mm(void *wire, uint16_t len, knot_mm_t *mm)
+{
+ assert(mm);
+
+ knot_pkt_t *pkt = mm_alloc(mm, sizeof(knot_pkt_t));
+ if (pkt == NULL) {
+ return NULL;
+ }
+
+ if (pkt_init(pkt, wire, len, mm) != KNOT_EOK) {
+ mm_free(mm, pkt);
+ return NULL;
+ }
+
+ return pkt;
+}
+
+_public_
+knot_pkt_t *knot_pkt_new(void *wire, uint16_t len, knot_mm_t *mm)
+{
+ /* Default memory allocator if NULL. */
+ knot_mm_t _mm;
+ if (mm == NULL) {
+ mm_ctx_init(&_mm);
+ mm = &_mm;
+ }
+
+ return pkt_new_mm(wire, len, mm);
+}
+
+static int append_tsig(knot_pkt_t *dst, const knot_pkt_t *src)
+{
+ /* Check if a wire TSIG is available. */
+ if (src->tsig_wire.pos != NULL) {
+ if (dst->max_size < src->size + src->tsig_wire.len) {
+ return KNOT_ESPACE;
+ }
+ memcpy(dst->wire + dst->size, src->tsig_wire.pos,
+ src->tsig_wire.len);
+ dst->size += src->tsig_wire.len;
+
+ /* Increment arcount. */
+ knot_wire_set_arcount(dst->wire,
+ knot_wire_get_arcount(dst->wire) + 1);
+ } else {
+ return knot_tsig_append(dst->wire, &dst->size, dst->max_size,
+ src->tsig_rr);
+ }
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_pkt_copy(knot_pkt_t *dst, const knot_pkt_t *src)
+{
+ if (dst == NULL || src == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (dst->max_size < src->size) {
+ return KNOT_ESPACE;
+ }
+ memcpy(dst->wire, src->wire, src->size);
+ dst->size = src->size;
+
+ /* Append TSIG record. */
+ if (src->tsig_rr) {
+ int ret = append_tsig(dst, src);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ /* Invalidate arrays. */
+ dst->rr = NULL;
+ dst->rr_info = NULL;
+ dst->rrset_count = 0;
+ dst->rrset_allocd = 0;
+
+ /* @note This could be done more effectively if needed. */
+ return knot_pkt_parse(dst, 0);
+}
+
+static void payload_clear(knot_pkt_t *pkt)
+{
+ assert(pkt);
+
+ /* Keep question. */
+ pkt->parsed = 0;
+ pkt->reserved = 0;
+
+ /* Free RRSets if applicable. */
+ pkt_free_data(pkt);
+
+ /* Reset sections. */
+ sections_reset(pkt);
+
+ /* Reset special types. */
+ pkt->opt_rr = NULL;
+ pkt->tsig_rr = NULL;
+
+ /* Reset TSIG wire reference. */
+ pkt->tsig_wire.pos = NULL;
+ pkt->tsig_wire.len = 0;
+}
+
+_public_
+int knot_pkt_init_response(knot_pkt_t *pkt, const knot_pkt_t *query)
+{
+ if (pkt == NULL || query == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Header + question size. */
+ size_t base_size = KNOT_WIRE_HEADER_SIZE + knot_pkt_question_size(query);
+ if (base_size > pkt->max_size) {
+ return KNOT_ESPACE;
+ }
+
+ pkt->size = base_size;
+ memcpy(pkt->wire, query->wire, base_size);
+
+ /* Copy lowercased QNAME. */
+ pkt->qname_size = query->qname_size;
+ if (query->qname_size == 0) {
+ /* Reset question count if malformed. */
+ knot_wire_set_qdcount(pkt->wire, 0);
+ }
+ memcpy(pkt->lower_qname, query->lower_qname, pkt->qname_size);
+
+ /* Update flags and section counters. */
+ knot_wire_set_ancount(pkt->wire, 0);
+ knot_wire_set_nscount(pkt->wire, 0);
+ knot_wire_set_arcount(pkt->wire, 0);
+
+ knot_wire_set_qr(pkt->wire);
+ knot_wire_clear_tc(pkt->wire);
+ knot_wire_clear_ad(pkt->wire);
+ knot_wire_clear_ra(pkt->wire);
+ knot_wire_clear_aa(pkt->wire);
+ knot_wire_clear_z(pkt->wire);
+
+ /* Clear payload. */
+ payload_clear(pkt);
+
+ /* Clear compression context. */
+ compr_clear(&pkt->compr);
+
+ return KNOT_EOK;
+}
+
+_public_
+void knot_pkt_clear(knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return;
+ }
+
+ /* Reset to header size. */
+ pkt->size = KNOT_WIRE_HEADER_SIZE;
+ memset(pkt->wire, 0, pkt->size);
+
+ /* Clear payload. */
+ payload_clear(pkt);
+
+ /* Clear compression context. */
+ compr_clear(&pkt->compr);
+
+ /* Initialize lowercased QNAME. */
+ pkt->lower_qname[0] = '\0';
+}
+
+_public_
+void knot_pkt_free(knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return;
+ }
+
+ /* Free temporary RRSets. */
+ pkt_free_data(pkt);
+
+ /* Free RR/RR info arrays. */
+ mm_free(&pkt->mm, pkt->rr);
+ mm_free(&pkt->mm, pkt->rr_info);
+
+ /* Free the space for wireformat. */
+ if (pkt->flags & KNOT_PF_FREE) {
+ mm_free(&pkt->mm, pkt->wire);
+ }
+
+ mm_free(&pkt->mm, pkt);
+}
+
+_public_
+int knot_pkt_reserve(knot_pkt_t *pkt, uint16_t size)
+{
+ if (pkt == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Reserve extra space (if possible). */
+ if (pkt_remaining(pkt) >= size) {
+ pkt->reserved += size;
+ return KNOT_EOK;
+ } else {
+ return KNOT_ERANGE;
+ }
+}
+
+_public_
+int knot_pkt_reclaim(knot_pkt_t *pkt, uint16_t size)
+{
+ if (pkt == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (pkt->reserved >= size) {
+ pkt->reserved -= size;
+ return KNOT_EOK;
+ } else {
+ return KNOT_ERANGE;
+ }
+}
+
+_public_
+int knot_pkt_begin(knot_pkt_t *pkt, knot_section_t section_id)
+{
+ if (pkt == NULL || section_id < pkt->current) {
+ return KNOT_EINVAL;
+ }
+
+ /* Remember watermark but not on repeated calls. */
+ pkt->sections[section_id].pkt = pkt;
+ if (section_id > pkt->current) {
+ pkt->sections[section_id].pos = pkt->rrset_count;
+ }
+
+ pkt->current = section_id;
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_pkt_put_question(knot_pkt_t *pkt, const knot_dname_t *qname, uint16_t qclass, uint16_t qtype)
+{
+ if (pkt == NULL || qname == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ assert(pkt->size == KNOT_WIRE_HEADER_SIZE);
+ assert(pkt->rrset_count == 0);
+
+ /* Copy name into wire format buffer. */
+ wire_ctx_t wire = wire_ctx_init(pkt->wire, pkt->max_size);
+ wire_ctx_set_offset(&wire, KNOT_WIRE_HEADER_SIZE);
+
+ int qname_len = knot_dname_to_wire(wire.position,
+ qname, wire_ctx_available(&wire));
+ if (qname_len < 0) {
+ return qname_len;
+ }
+ wire_ctx_skip(&wire, qname_len);
+
+ /* Copy QNAME and canonicalize to lowercase. */
+ knot_dname_copy_lower(pkt->lower_qname, qname);
+
+ /* Copy QTYPE & QCLASS */
+ wire_ctx_write_u16(&wire, qtype);
+ wire_ctx_write_u16(&wire, qclass);
+
+ /* Check errors. */
+ if (wire.error != KNOT_EOK) {
+ return wire.error;
+ }
+
+ /* Update question count and sizes. */
+ knot_wire_set_qdcount(pkt->wire, 1);
+ pkt->size = wire_ctx_offset(&wire);
+ pkt->qname_size = qname_len;
+
+ /* Start writing ANSWER. */
+ return knot_pkt_begin(pkt, KNOT_ANSWER);
+}
+
+_public_
+int knot_pkt_put_rotate(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr,
+ uint16_t rotate, uint16_t flags)
+{
+ if (pkt == NULL || rr == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Reserve memory for RR descriptors. */
+ int ret = pkt_rr_array_alloc(pkt, pkt->rrset_count + 1);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Check for double insertion. */
+ if ((flags & KNOT_PF_CHECKDUP) && pkt_contains(pkt, rr)) {
+ return KNOT_EOK;
+ }
+
+ knot_rrinfo_t *rrinfo = &pkt->rr_info[pkt->rrset_count];
+ memset(rrinfo, 0, sizeof(knot_rrinfo_t));
+ rrinfo->pos = pkt->size;
+ rrinfo->flags = flags;
+ rrinfo->compress_ptr[0] = compr_hint;
+ memcpy(pkt->rr + pkt->rrset_count, rr, sizeof(knot_rrset_t));
+
+ /* Disable compression if no QNAME is available. */
+ knot_compr_t *compr = NULL;
+ if (knot_pkt_qname(pkt) != NULL) {
+ /* Initialize compression context if it did not happen yet. */
+ pkt->compr.rrinfo = rrinfo;
+ if (pkt->compr.suffix.pos == 0) {
+ pkt->compr.suffix.pos = KNOT_WIRE_HEADER_SIZE;
+ pkt->compr.suffix.labels =
+ knot_dname_labels(pkt->compr.wire + pkt->compr.suffix.pos,
+ pkt->compr.wire);
+ }
+
+ compr = &pkt->compr;
+ }
+
+ uint8_t *pos = pkt->wire + pkt->size;
+ size_t maxlen = pkt_remaining(pkt);
+
+ /* Write RRSet to wireformat. */
+ ret = knot_rrset_to_wire_extra(rr, pos, maxlen, rotate, compr, flags);
+ if (ret < 0) {
+ /* Truncate packet if required. */
+ if (ret == KNOT_ESPACE && !(flags & KNOT_PF_NOTRUNC)) {
+ knot_wire_set_tc(pkt->wire);
+ }
+ return ret;
+ }
+
+ size_t len = ret;
+ uint16_t rr_added = rr->rrs.count;
+
+ /* Keep reference to special types. */
+ if (rr->type == KNOT_RRTYPE_OPT) {
+ pkt->opt_rr = &pkt->rr[pkt->rrset_count];
+ }
+
+ if (rr_added > 0) {
+ pkt->rrset_count += 1;
+ pkt->sections[pkt->current].count += 1;
+ pkt->size += len;
+ pkt_rr_wirecount_add(pkt, pkt->current, rr_added);
+ }
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_pkt_parse_question(knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Check at least header size. */
+ if (pkt->size < KNOT_WIRE_HEADER_SIZE) {
+ return KNOT_EMALF;
+ }
+
+ /* We have at least some DNS header. */
+ pkt->parsed = KNOT_WIRE_HEADER_SIZE;
+
+ /* Check QD count. */
+ uint16_t qd = knot_wire_get_qdcount(pkt->wire);
+ if (qd > 1) {
+ return KNOT_EMALF;
+ }
+
+ /* No question. */
+ if (qd == 0) {
+ pkt->qname_size = 0;
+ return KNOT_EOK;
+ }
+
+ /* Process question. */
+ int len = knot_dname_wire_check(pkt->wire + pkt->parsed,
+ pkt->wire + pkt->size,
+ NULL /* No compression in QNAME. */);
+ if (len <= 0) {
+ return KNOT_EMALF;
+ }
+
+ /* Check QCLASS/QTYPE size. */
+ uint16_t question_size = len + 2 * sizeof(uint16_t); /* QCLASS + QTYPE */
+ if (pkt->parsed + question_size > pkt->size) {
+ return KNOT_EMALF;
+ }
+
+ pkt->parsed += question_size;
+ pkt->qname_size = len;
+
+ /* Copy QNAME and canonicalize to lowercase. */
+ knot_dname_copy_lower(pkt->lower_qname, pkt->wire + KNOT_WIRE_HEADER_SIZE);
+
+ return KNOT_EOK;
+}
+
+/*! \brief Check constraints (position, uniqueness, validity) for special types
+ * (TSIG, OPT).
+ */
+static int check_rr_constraints(knot_pkt_t *pkt, knot_rrset_t *rr, size_t rr_size,
+ unsigned flags)
+{
+ switch (rr->type) {
+ case KNOT_RRTYPE_TSIG:
+ if (pkt->current != KNOT_ADDITIONAL || pkt->tsig_rr != NULL ||
+ !knot_tsig_rdata_is_ok(rr)) {
+ return KNOT_EMALF;
+ }
+
+ /* Strip TSIG RR from wireformat and decrease ARCOUNT. */
+ if (!(flags & KNOT_PF_KEEPWIRE)) {
+ pkt->parsed -= rr_size;
+ pkt->size -= rr_size;
+ pkt->tsig_wire.pos = pkt->wire + pkt->parsed;
+ pkt->tsig_wire.len = rr_size;
+ knot_wire_set_arcount(pkt->wire, knot_wire_get_arcount(pkt->wire) - 1);
+ }
+
+ pkt->tsig_rr = rr;
+ break;
+ case KNOT_RRTYPE_OPT:
+ if (pkt->current != KNOT_ADDITIONAL || pkt->opt_rr != NULL ||
+ knot_edns_get_options(rr, &pkt->edns_opts, &pkt->mm) != KNOT_EOK) {
+ return KNOT_EMALF;
+ }
+
+ pkt->opt_rr = rr;
+ break;
+ default:
+ break;
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_rr(knot_pkt_t *pkt, unsigned flags)
+{
+ assert(pkt);
+
+ if (pkt->parsed >= pkt->size) {
+ return KNOT_EFEWDATA;
+ }
+
+ /* Reserve memory for RR descriptors. */
+ int ret = pkt_rr_array_alloc(pkt, pkt->rrset_count + 1);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Initialize RR info. */
+ memset(&pkt->rr_info[pkt->rrset_count], 0, sizeof(knot_rrinfo_t));
+ pkt->rr_info[pkt->rrset_count].pos = pkt->parsed;
+ pkt->rr_info[pkt->rrset_count].flags = KNOT_PF_FREE;
+
+ /* Parse wire format. */
+ size_t rr_size = pkt->parsed;
+ knot_rrset_t *rr = &pkt->rr[pkt->rrset_count];
+ ret = knot_rrset_rr_from_wire(pkt->wire, &pkt->parsed, pkt->size,
+ rr, &pkt->mm, !(flags & KNOT_PF_NOCANON));
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Calculate parsed RR size from before/after parsing. */
+ rr_size = (pkt->parsed - rr_size);
+
+ /* Update packet RRSet count. */
+ ++pkt->rrset_count;
+ ++pkt->sections[pkt->current].count;
+
+ /* Check special RRs (OPT and TSIG). */
+ return check_rr_constraints(pkt, rr, rr_size, flags);
+}
+
+static int parse_section(knot_pkt_t *pkt, unsigned flags)
+{
+ assert(pkt);
+
+ uint16_t rr_parsed = 0;
+ uint16_t rr_count = pkt_rr_wirecount(pkt, pkt->current);
+
+ /* Parse all RRs belonging to the section. */
+ for (rr_parsed = 0; rr_parsed < rr_count; ++rr_parsed) {
+ int ret = parse_rr(pkt, flags);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int parse_payload(knot_pkt_t *pkt, unsigned flags)
+{
+ assert(pkt);
+ assert(pkt->wire);
+ assert(pkt->size > 0);
+
+ /* Reserve memory in advance to avoid resizing. */
+ size_t rr_count = knot_wire_get_ancount(pkt->wire) +
+ knot_wire_get_nscount(pkt->wire) +
+ knot_wire_get_arcount(pkt->wire);
+
+ if (rr_count > pkt->size / KNOT_WIRE_RR_MIN_SIZE) {
+ return KNOT_EMALF;
+ }
+
+ int ret = pkt_rr_array_alloc(pkt, rr_count);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) {
+ ret = knot_pkt_begin(pkt, i);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ ret = parse_section(pkt, flags);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ /* TSIG must be last record of AR if present. */
+ const knot_pktsection_t *ar = knot_pkt_section(pkt, KNOT_ADDITIONAL);
+ if (pkt->tsig_rr != NULL) {
+ const knot_rrset_t *last_rr = knot_pkt_rr(ar, ar->count - 1);
+ if (ar->count > 0 && pkt->tsig_rr->rrs.rdata != last_rr->rrs.rdata) {
+ return KNOT_EMALF;
+ }
+ }
+
+ /* Check for trailing garbage. */
+ if (pkt->parsed < pkt->size) {
+ return KNOT_ETRAIL;
+ }
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_pkt_parse(knot_pkt_t *pkt, unsigned flags)
+{
+ if (pkt == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Reset parse state. */
+ sections_reset(pkt);
+
+ int ret = knot_pkt_parse_question(pkt);
+ if (ret == KNOT_EOK) {
+ ret = parse_payload(pkt, flags);
+ }
+
+ return ret;
+}
+
+_public_
+uint16_t knot_pkt_ext_rcode(const knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return 0;
+ }
+
+ /* Get header RCODE. */
+ uint16_t rcode = knot_wire_get_rcode(pkt->wire);
+
+ /* Update to extended RCODE if EDNS is available. */
+ if (pkt->opt_rr != NULL) {
+ uint8_t opt_rcode = knot_edns_get_ext_rcode(pkt->opt_rr);
+ rcode = knot_edns_whole_rcode(opt_rcode, rcode);
+ }
+
+ /* Return if not NOTAUTH. */
+ if (rcode != KNOT_RCODE_NOTAUTH) {
+ return rcode;
+ }
+
+ /* Get TSIG RCODE. */
+ uint16_t tsig_rcode = KNOT_RCODE_NOERROR;
+ if (pkt->tsig_rr != NULL) {
+ tsig_rcode = knot_tsig_rdata_error(pkt->tsig_rr);
+ }
+
+ /* Return proper RCODE. */
+ if (tsig_rcode != KNOT_RCODE_NOERROR) {
+ return tsig_rcode;
+ } else {
+ return rcode;
+ }
+}
+
+_public_
+const char *knot_pkt_ext_rcode_name(const knot_pkt_t *pkt)
+{
+ if (pkt == NULL) {
+ return "";
+ }
+
+ uint16_t rcode = knot_pkt_ext_rcode(pkt);
+
+ const knot_lookup_t *item = NULL;
+ if (pkt->tsig_rr != NULL) {
+ item = knot_lookup_by_id(knot_tsig_rcode_names, rcode);
+ }
+ if (item == NULL) {
+ item = knot_lookup_by_id(knot_rcode_names, rcode);
+ }
+
+ return (item != NULL) ? item->name : "";
+}
diff --git a/src/libknot/packet/pkt.h b/src/libknot/packet/pkt.h
new file mode 100644
index 0000000..f5e218f
--- /dev/null
+++ b/src/libknot/packet/pkt.h
@@ -0,0 +1,416 @@
+/* Copyright (C) 2020 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Structure for holding DNS packet data and metadata.
+ *
+ * \addtogroup pkt
+ * @{
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <stdint.h>
+
+#include "libknot/consts.h"
+#include "libknot/dname.h"
+#include "libknot/mm_ctx.h"
+#include "libknot/rrset.h"
+#include "libknot/rrtype/opt.h"
+#include "libknot/packet/wire.h"
+#include "libknot/packet/compr.h"
+#include "libknot/wire.h"
+
+/* Number of packet sections (ANSWER, AUTHORITY, ADDITIONAL). */
+#define KNOT_PKT_SECTIONS 3
+
+/*!
+ * \brief Packet flags.
+ */
+enum {
+ KNOT_PF_NULL = 0 << 0, /*!< No flags. */
+ KNOT_PF_FREE = 1 << 1, /*!< Free with packet. */
+ KNOT_PF_NOTRUNC = 1 << 2, /*!< Don't truncate. */
+ KNOT_PF_CHECKDUP = 1 << 3, /*!< Check for duplicates. */
+ KNOT_PF_KEEPWIRE = 1 << 4, /*!< Keep wireformat untouched when parsing. */
+ KNOT_PF_NOCANON = 1 << 5, /*!< Don't canonicalize rrsets during parsing. */
+ KNOT_PF_ORIGTTL = 1 << 6, /*!< Write RRSIGs with their original TTL. */
+ KNOT_PF_SOAMINTTL = 1 << 7, /*!< Write SOA with its minimum-ttl as TTL. */
+};
+
+typedef struct knot_pkt knot_pkt_t;
+
+/*!
+ * \brief Packet section.
+ * Points to RRSet and RRSet info arrays in the packet.
+ * This structure is required for random access to packet sections.
+ */
+typedef struct {
+ knot_pkt_t *pkt; /*!< Owner. */
+ uint16_t pos; /*!< Position in the rr/rrinfo fields in packet. */
+ uint16_t count; /*!< Number of RRSets in this section. */
+} knot_pktsection_t;
+
+/*!
+ * \brief Structure representing a DNS packet.
+ */
+struct knot_pkt {
+ uint8_t *wire; /*!< Wire format of the packet. */
+ size_t size; /*!< Current wire size of the packet. */
+ size_t max_size; /*!< Maximum allowed size of the packet. */
+ size_t parsed; /*!< Parsed size. */
+ uint16_t reserved; /*!< Reserved space. */
+ uint16_t qname_size; /*!< QNAME size. */
+ uint16_t rrset_count; /*!< Packet RRSet count. */
+ uint16_t flags; /*!< Packet flags. */
+
+ knot_rrset_t *opt_rr; /*!< OPT RR included in the packet. */
+ knot_rrset_t *tsig_rr; /*!< TSIG RR stored in the packet. */
+
+ /*! EDNS option positions in the wire (if parsed from wire). */
+ knot_edns_options_t *edns_opts;
+
+ /*! TSIG RR position in the wire (if parsed from wire). */
+ struct {
+ uint8_t *pos;
+ size_t len;
+ } tsig_wire;
+
+ /* Packet sections. */
+ knot_section_t current;
+ knot_pktsection_t sections[KNOT_PKT_SECTIONS];
+
+ /* Packet RRSet (meta)data. */
+ size_t rrset_allocd;
+ knot_rrinfo_t *rr_info;
+ knot_rrset_t *rr;
+
+ knot_mm_t mm; /*!< Memory allocation context. */
+
+ knot_compr_t compr; /*!< Compression context. */
+
+ /*! Lowercased QNAME. MUST BE LAST ITEM! */
+ knot_dname_storage_t lower_qname;
+};
+
+/*!
+ * \brief Create new packet over existing memory, or allocate new from memory context.
+ *
+ * \note Packet is allocated from given memory context.
+ *
+ * \param wire If NULL, memory of 'len' size shall be allocated.
+ * Otherwise pointer is used for the wire format of the packet.
+ * \param len Wire format length.
+ * \param mm Memory context (NULL for default).
+ * \return New packet or NULL.
+ */
+knot_pkt_t *knot_pkt_new(void *wire, uint16_t len, knot_mm_t *mm);
+
+/*!
+ * \brief Copy packet.
+ *
+ * \note Current implementation is not very efficient, as it re-parses the wire.
+ *
+ * \param dst Target packet.
+ * \param src Source packet.
+ *
+ * \return new packet or NULL
+ */
+int knot_pkt_copy(knot_pkt_t *dst, const knot_pkt_t *src);
+
+/*!
+ * \brief Initialized response from query packet.
+ *
+ * \note Question is not checked, it is expected to be checked already.
+ *
+ * \param pkt Given packet.
+ * \param query Query.
+ * \return KNOT_EOK, KNOT_EINVAL, KNOT_ESPACE
+ */
+int knot_pkt_init_response(knot_pkt_t *pkt, const knot_pkt_t *query);
+
+/*! \brief Reinitialize packet for another use. */
+void knot_pkt_clear(knot_pkt_t *pkt);
+
+/*! \brief Begone you foul creature of the underworld. */
+void knot_pkt_free(knot_pkt_t *pkt);
+
+/*!
+ * \brief Reserve an arbitrary amount of space in the packet.
+ *
+ * \return KNOT_EOK
+ * \return KNOT_ERANGE if size can't be reserved
+ */
+int knot_pkt_reserve(knot_pkt_t *pkt, uint16_t size);
+
+/*!
+ * \brief Reclaim reserved size.
+ *
+ * \return KNOT_EOK
+ * \return KNOT_ERANGE if size can't be reclaimed
+ */
+int knot_pkt_reclaim(knot_pkt_t *pkt, uint16_t size);
+
+/*
+ * Packet QUESTION accessors.
+ */
+static inline uint16_t knot_pkt_question_size(const knot_pkt_t *pkt)
+{
+ if (pkt == NULL || pkt->qname_size == 0) {
+ return 0;
+ }
+
+ return pkt->qname_size + 2 * sizeof(uint16_t);
+}
+
+static inline const knot_dname_t *knot_pkt_qname(const knot_pkt_t *pkt)
+{
+ if (pkt == NULL || pkt->qname_size == 0) {
+ return NULL;
+ }
+
+ return pkt->lower_qname;
+}
+
+static inline const knot_dname_t *knot_pkt_wire_qname(const knot_pkt_t *pkt)
+{
+ if (pkt == NULL || pkt->qname_size == 0) {
+ return NULL;
+ }
+
+ return pkt->wire + KNOT_WIRE_HEADER_SIZE;
+}
+
+static inline uint16_t knot_pkt_qtype(const knot_pkt_t *pkt)
+{
+ if (pkt == NULL || pkt->qname_size == 0) {
+ return 0;
+ }
+
+ unsigned off = KNOT_WIRE_HEADER_SIZE + pkt->qname_size;
+ return knot_wire_read_u16(pkt->wire + off);
+}
+
+static inline uint16_t knot_pkt_qclass(const knot_pkt_t *pkt)
+{
+ if (pkt == NULL || pkt->qname_size == 0) {
+ return 0;
+ }
+
+ unsigned off = KNOT_WIRE_HEADER_SIZE + pkt->qname_size + sizeof(uint16_t);
+ return knot_wire_read_u16(pkt->wire + off);
+}
+
+/*
+ * Packet writing API.
+ */
+
+/*!
+ * \brief Begin reading/writing packet section.
+ *
+ * \note You must proceed in the natural order (ANSWER, AUTHORITY, ADDITIONAL).
+ *
+ * \param pkt
+ * \param section_id
+ * \return KNOT_EOK or KNOT_EINVAL
+ */
+int knot_pkt_begin(knot_pkt_t *pkt, knot_section_t section_id);
+
+/*!
+ * \brief Put QUESTION in the packet.
+ *
+ * \note Since we support QD=1 only, QUESTION is a special type of packet section.
+ * \note Must not be used after putting RRsets into the packet.
+ *
+ * \param pkt
+ * \param qname
+ * \param qclass
+ * \param qtype
+ * \return KNOT_EOK or various errors
+ */
+int knot_pkt_put_question(knot_pkt_t *pkt, const knot_dname_t *qname,
+ uint16_t qclass, uint16_t qtype);
+
+/*!
+ * \brief Put RRSet into packet.
+ *
+ * \note See compr.h for description on how compression hints work.
+ * \note Available flags: PF_FREE, KNOT_PF_CHECKDUP, KNOT_PF_NOTRUNC
+ *
+ * \param pkt
+ * \param compr_hint Compression hint, see enum knot_compr_hint or absolute
+ * position.
+ * \param rr Given RRSet.
+ * \param rotate Rotate the RRSet order by this count.
+ * \param flags RRSet flags (set PF_FREE if you want RRSet to be freed
+ * with the packet).
+ *
+ * \return KNOT_EOK, KNOT_ESPACE, various errors
+ */
+int knot_pkt_put_rotate(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr,
+ uint16_t rotate, uint16_t flags);
+
+/*! \brief Same as knot_pkt_put_rotate but without rrset rotation. */
+static inline int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint,
+ const knot_rrset_t *rr, uint16_t flags)
+{
+ return knot_pkt_put_rotate(pkt, compr_hint, rr, 0, flags);
+}
+
+/*! \brief Get description of the given packet section. */
+static inline const knot_pktsection_t *knot_pkt_section(const knot_pkt_t *pkt,
+ knot_section_t section_id)
+{
+ assert(pkt);
+ return &pkt->sections[section_id];
+}
+
+/*! \brief Get RRSet from the packet section. */
+static inline const knot_rrset_t *knot_pkt_rr(const knot_pktsection_t *section,
+ uint16_t i)
+{
+ assert(section);
+ return section->pkt->rr + section->pos + i;
+}
+
+/*! \brief Get RRSet offset in the packet wire. */
+static inline uint16_t knot_pkt_rr_offset(const knot_pktsection_t *section,
+ uint16_t i)
+{
+ assert(section);
+ return section->pkt->rr_info[section->pos + i].pos;
+}
+
+/*
+ * Packet parsing API.
+ */
+
+/*!
+ * \brief Parse both packet question and payload.
+ *
+ * Parses both QUESTION and all packet sections,
+ * includes semantic checks over specific RRs (TSIG, OPT).
+ *
+ * \note If KNOT_PF_KEEPWIRE is set, TSIG RR is not stripped from the wire
+ * and is processed as any other RR.
+ *
+ * \param pkt Given packet.
+ * \param flags Parsing flags (allowed KNOT_PF_KEEPWIRE)
+ *
+ * \retval KNOT_EOK if success.
+ * \retval KNOT_ETRAIL if success but with some trailing data.
+ * \retval KNOT_EMALF and other errors.
+ */
+int knot_pkt_parse(knot_pkt_t *pkt, unsigned flags);
+
+/*!
+ * \brief Parse packet header and a QUESTION section.
+ */
+int knot_pkt_parse_question(knot_pkt_t *pkt);
+
+/*!
+ * \brief Get packet extended RCODE.
+ *
+ * Extended RCODE is created by considering TSIG RCODE, EDNS RCODE and
+ * DNS Header RCODE. (See RFC 6895, Section 2.3).
+ *
+ * \param pkt Packet to get the response code from.
+ *
+ * \return Whole extended RCODE (0 if pkt == NULL).
+ */
+uint16_t knot_pkt_ext_rcode(const knot_pkt_t *pkt);
+
+/*!
+ * \brief Get packet extended RCODE name.
+ *
+ * The packet parameter is important as the name depends on TSIG.
+ *
+ * \param pkt Packet to get the response code from.
+ *
+ * \return RCODE name (or empty string if not known).
+ */
+const char *knot_pkt_ext_rcode_name(const knot_pkt_t *pkt);
+
+/*!
+ * \brief Checks if there is an OPT RR in the packet.
+ */
+static inline bool knot_pkt_has_edns(const knot_pkt_t *pkt)
+{
+ assert(pkt);
+ return pkt->opt_rr != NULL;
+}
+
+/*!
+ * \brief Checks if TSIG is present.
+ */
+static inline bool knot_pkt_has_tsig(const knot_pkt_t *pkt)
+{
+ assert(pkt);
+ return pkt->tsig_rr != NULL;
+}
+
+/*!
+ * \brief Checks if DO bit is set in the packet's OPT RR.
+ */
+static inline bool knot_pkt_has_dnssec(const knot_pkt_t *pkt)
+{
+ assert(pkt);
+ return knot_pkt_has_edns(pkt) && knot_edns_do(pkt->opt_rr);
+}
+
+/*!
+ * \brief Get specific EDNS option from a parsed packet.
+ */
+static inline uint8_t *knot_pkt_edns_option(const knot_pkt_t *pkt, uint16_t code)
+{
+ assert(pkt);
+ if (pkt->edns_opts != NULL && code <= KNOT_EDNS_MAX_OPTION_CODE) {
+ return pkt->edns_opts->ptr[code];
+ } else {
+ return NULL;
+ }
+}
+
+/*!
+ * \brief Computes a reasonable Padding data length for a given packet and opt RR.
+ *
+ * \param pkt DNS Packet prepared and otherwise ready to go, no OPT yet added.
+ * \param opt_rr OPT RR, not yet including padding.
+ *
+ * \return Required padding length or -1 if padding not required.
+ */
+static inline int knot_pkt_default_padding_size(const knot_pkt_t *pkt,
+ const knot_rrset_t *opt_rr)
+{
+ if (knot_wire_get_qr(pkt->wire)) {
+ return knot_edns_alignment_size(pkt->size, knot_rrset_size(opt_rr),
+ KNOT_EDNS_ALIGNMENT_RESPONSE_DEFAULT);
+ } else {
+ return knot_edns_alignment_size(pkt->size, knot_rrset_size(opt_rr),
+ KNOT_EDNS_ALIGNMENT_QUERY_DEFAULT);
+ }
+}
+
+static inline size_t knot_pkt_size(const knot_pkt_t *pkt)
+{
+ assert(pkt);
+ return pkt->size + (knot_pkt_has_tsig(pkt) ? pkt->tsig_wire.len : 0);
+}
+
+/*! @} */
diff --git a/src/libknot/packet/rrset-wire.c b/src/libknot/packet/rrset-wire.c
new file mode 100644
index 0000000..2b02969
--- /dev/null
+++ b/src/libknot/packet/rrset-wire.c
@@ -0,0 +1,727 @@
+/* Copyright (C) 2021 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+
+#include "libknot/attribute.h"
+#include "libknot/consts.h"
+#include "libknot/descriptor.h"
+#include "libknot/packet/pkt.h"
+#include "libknot/packet/rrset-wire.h"
+#include "libknot/rrtype/naptr.h"
+#include "libknot/rrtype/rrsig.h"
+#include "libknot/rrtype/soa.h"
+#include "contrib/macros.h"
+#include "contrib/mempattern.h"
+#include "contrib/tolower.h"
+#include "contrib/wire_ctx.h"
+
+/*!
+ * \brief Get maximal size of a domain name in a wire with given capacity.
+ */
+static uint16_t dname_max(size_t wire_avail)
+{
+ return MIN(wire_avail, KNOT_DNAME_MAXLEN);
+}
+
+/*!
+ * \brief Compares two domain name labels.
+ *
+ * \param label1 First label.
+ * \param label2 Second label (may be in upper-case).
+ *
+ * \retval true if the labels are identical
+ * \retval false if the labels are NOT identical
+ */
+static bool label_is_equal(const uint8_t *label1, const uint8_t *label2)
+{
+ assert(label1 && label2);
+
+ if (*label1 != *label2) {
+ return false;
+ }
+
+ uint8_t len = *label1;
+ for (uint8_t i = 1; i <= len; i++) {
+ if (label1[i] != knot_tolower(label2[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*!
+ * Case insensitive comparison of two dnames in wire format.
+ * The second name may be compressed in a supplied wire.
+ */
+static bool dname_equal_wire(const knot_dname_t *d1, const knot_dname_t *d2,
+ const uint8_t *wire)
+{
+ assert(d1);
+ assert(d2);
+
+ d2 = knot_wire_seek_label(d2, wire);
+
+ while (*d1 != '\0' || *d2 != '\0') {
+ if (!label_is_equal(d1, d2)) {
+ return false;
+ }
+ d1 = knot_wire_next_label(d1, NULL);
+ d2 = knot_wire_next_label(d2, wire);
+ }
+
+ return true;
+}
+
+static uint16_t compr_get_ptr(knot_compr_t *compr, uint16_t hint)
+{
+ if (compr == NULL) {
+ return 0;
+ }
+
+ return knot_compr_hint(compr->rrinfo, hint);
+}
+
+static void compr_set_ptr(knot_compr_t *compr, uint16_t hint,
+ const uint8_t *written_at, uint16_t written_size)
+{
+ if (compr == NULL) {
+ return;
+ }
+
+ assert(written_at >= compr->wire);
+
+ uint16_t offset = written_at - compr->wire;
+
+ if (knot_wire_is_pointer(written_at)) {
+ offset = knot_wire_get_pointer(written_at);
+ }
+
+ knot_compr_hint_set(compr->rrinfo, hint, offset, written_size);
+}
+
+static int write_rdata_fixed(const uint8_t **src, size_t *src_avail,
+ uint8_t **dst, size_t *dst_avail, size_t size)
+{
+ assert(src && *src);
+ assert(src_avail);
+ assert(dst && *dst);
+ assert(dst_avail);
+
+ // Check input/output buffer boundaries.
+ if (size > *src_avail) {
+ return KNOT_EMALF;
+ }
+
+ if (size > *dst_avail) {
+ return KNOT_ESPACE;
+ }
+
+ // Data binary copy.
+ memcpy(*dst, *src, size);
+
+ // Update buffers.
+ *src += size;
+ *src_avail -= size;
+
+ *dst += size;
+ *dst_avail -= size;
+
+ return KNOT_EOK;
+}
+
+static int write_rdata_naptr_header(const uint8_t **src, size_t *src_avail,
+ uint8_t **dst, size_t *dst_avail)
+{
+ assert(src && *src);
+ assert(src_avail);
+ assert(dst && *dst);
+ assert(dst_avail);
+
+ int ret = knot_naptr_header_size(*src, *src + *src_avail);
+ if (ret < 0) {
+ return ret;
+ }
+
+ // Copy the data.
+ return write_rdata_fixed(src, src_avail, dst, dst_avail, ret);
+}
+
+/*! \brief Helper for \ref compr_put_dname, writes label(s) with size checks. */
+#define WRITE_LABEL(dst, written, label, max, len) \
+ if ((written) + (len) > (max)) { \
+ return KNOT_ESPACE; \
+ } else { \
+ memcpy((dst) + (written), (label), (len)); \
+ written += (len); \
+ }
+
+/*!
+ * \brief Write compressed domain name to the destination wire.
+ *
+ * \param dname Name to be written.
+ * \param dst Destination wire.
+ * \param max Maximum number of bytes available.
+ * \param compr Compression context (NULL for no compression)
+ * \return Number of written bytes or an error.
+ */
+static int compr_put_dname(const knot_dname_t *dname, uint8_t *dst, uint16_t max,
+ knot_compr_t *compr)
+{
+ assert(dname && dst);
+
+ // Write uncompressible names directly (zero label dname).
+ if (compr == NULL || *dname == '\0') {
+ return knot_dname_to_wire(dst, dname, max);
+ }
+
+ // Get number of labels (should not be a zero label dname).
+ size_t name_labels = knot_dname_labels(dname, NULL);
+ assert(name_labels > 0);
+
+ // Suffix must not be longer than whole name.
+ const knot_dname_t *suffix = compr->wire + compr->suffix.pos;
+ int suffix_labels = compr->suffix.labels;
+ while (suffix_labels > name_labels) {
+ suffix = knot_wire_next_label(suffix, compr->wire);
+ --suffix_labels;
+ }
+
+ // Suffix is shorter than name, write labels until aligned.
+ uint8_t orig_labels = name_labels;
+ uint16_t written = 0;
+ while (name_labels > suffix_labels) {
+ WRITE_LABEL(dst, written, dname, max, (*dname + 1));
+ dname = knot_wire_next_label(dname, NULL);
+ --name_labels;
+ }
+
+ // Label count is now equal.
+ assert(name_labels == suffix_labels);
+ const knot_dname_t *match_begin = dname;
+ const knot_dname_t *compr_ptr = suffix;
+ while (dname[0] != '\0') {
+ // Next labels.
+ const knot_dname_t *next_dname = knot_wire_next_label(dname, NULL);
+ const knot_dname_t *next_suffix = knot_wire_next_label(suffix, compr->wire);
+
+ // Two labels match, extend suffix length.
+ if (!label_is_equal(dname, suffix)) {
+ // If they don't match, write unmatched labels.
+ uint16_t mismatch_len = (dname - match_begin) + (*dname + 1);
+ WRITE_LABEL(dst, written, match_begin, max, mismatch_len);
+ // Start new potential match.
+ match_begin = next_dname;
+ compr_ptr = next_suffix;
+ }
+
+ // Jump to next labels.
+ dname = next_dname;
+ suffix = next_suffix;
+ }
+
+ // If match begins at the end of the name, write '\0' label.
+ if (match_begin == dname) {
+ WRITE_LABEL(dst, written, dname, max, 1);
+ } else {
+ // Match covers >0 labels, write out compression pointer.
+ if (written + sizeof(uint16_t) > max) {
+ return KNOT_ESPACE;
+ }
+ knot_wire_put_pointer(dst + written, compr_ptr - compr->wire);
+ written += sizeof(uint16_t);
+ }
+
+ assert(dst >= compr->wire);
+ size_t wire_pos = dst - compr->wire;
+ assert(wire_pos < KNOT_WIRE_MAX_PKTSIZE);
+
+ // Heuristics - expect similar names are grouped together.
+ if (written > sizeof(uint16_t) && wire_pos + written < KNOT_WIRE_PTR_MAX) {
+ compr->suffix.pos = wire_pos;
+ compr->suffix.labels = orig_labels;
+ }
+
+ return written;
+}
+
+#define WRITE_OWNER_CHECK(size, dst_avail) \
+ if ((size) > *(dst_avail)) { \
+ return KNOT_ESPACE; \
+ }
+
+#define WRITE_OWNER_INCR(dst, dst_avail, size) \
+ *(dst) += (size); \
+ *(dst_avail) -= (size);
+
+static int write_owner(const knot_rrset_t *rrset, uint8_t **dst, size_t *dst_avail,
+ knot_compr_t *compr)
+{
+ assert(rrset);
+ assert(dst && *dst);
+ assert(dst_avail);
+
+ // Check for zero label owner (don't compress).
+ uint16_t owner_pointer = 0;
+ if (*rrset->owner != '\0') {
+ owner_pointer = compr_get_ptr(compr, KNOT_COMPR_HINT_OWNER);
+ }
+
+ // Write result.
+ if (owner_pointer > 0) {
+ WRITE_OWNER_CHECK(sizeof(uint16_t), dst_avail);
+ knot_wire_put_pointer(*dst, owner_pointer);
+ WRITE_OWNER_INCR(dst, dst_avail, sizeof(uint16_t));
+ // Check for coincidence with previous RR set.
+ } else if (compr != NULL && compr->suffix.pos != 0 && *rrset->owner != '\0' &&
+ dname_equal_wire(rrset->owner, compr->wire + compr->suffix.pos,
+ compr->wire)) {
+ WRITE_OWNER_CHECK(sizeof(uint16_t), dst_avail);
+ knot_wire_put_pointer(*dst, compr->suffix.pos);
+ compr_set_ptr(compr, KNOT_COMPR_HINT_OWNER,
+ compr->wire + compr->suffix.pos,
+ knot_dname_size(rrset->owner));
+ WRITE_OWNER_INCR(dst, dst_avail, sizeof(uint16_t));
+ } else {
+ if (compr != NULL) {
+ compr->suffix.pos = KNOT_WIRE_HEADER_SIZE;
+ compr->suffix.labels =
+ knot_dname_labels(compr->wire + compr->suffix.pos,
+ compr->wire);
+ }
+ // WRITE_OWNER_CHECK not needed, compr_put_dname has a check.
+ int written = compr_put_dname(rrset->owner, *dst,
+ dname_max(*dst_avail), compr);
+ if (written < 0) {
+ return written;
+ }
+
+ compr_set_ptr(compr, KNOT_COMPR_HINT_OWNER, *dst, written);
+ WRITE_OWNER_INCR(dst, dst_avail, written);
+ }
+
+ return KNOT_EOK;
+}
+
+static int write_fixed_header(const knot_rrset_t *rrset, uint16_t rrset_index,
+ uint8_t **dst, size_t *dst_avail, uint16_t flags)
+{
+ assert(rrset);
+ assert(rrset_index < rrset->rrs.count);
+ assert(dst && *dst);
+ assert(dst_avail);
+
+ // Write header.
+ wire_ctx_t write = wire_ctx_init(*dst, *dst_avail);
+
+ wire_ctx_write_u16(&write, rrset->type);
+ wire_ctx_write_u16(&write, rrset->rclass);
+
+ if ((flags & KNOT_PF_ORIGTTL) && rrset->type == KNOT_RRTYPE_RRSIG) {
+ const knot_rdata_t *rdata = knot_rdataset_at(&rrset->rrs, rrset_index);
+ wire_ctx_write_u32(&write, knot_rrsig_original_ttl(rdata));
+ } else if ((flags & KNOT_PF_SOAMINTTL) && rrset->type == KNOT_RRTYPE_SOA) {
+ const knot_rdata_t *rdata = knot_rdataset_at(&rrset->rrs, rrset_index);
+ wire_ctx_write_u32(&write, MIN(knot_soa_minimum(rdata), rrset->ttl));
+ } else {
+ wire_ctx_write_u32(&write, rrset->ttl);
+ }
+
+ // Check write.
+ if (write.error != KNOT_EOK) {
+ return write.error;
+ }
+
+ // Update buffer.
+ *dst = write.position;
+ *dst_avail = wire_ctx_available(&write);
+
+ return KNOT_EOK;
+}
+
+static int compress_rdata_dname(const uint8_t **src, size_t *src_avail,
+ uint8_t **dst, size_t *dst_avail,
+ knot_compr_t *put_compr, knot_compr_t *compr,
+ uint16_t hint)
+{
+ assert(src && *src);
+ assert(src_avail);
+ assert(dst && *dst);
+ assert(dst_avail);
+
+ // Source domain name.
+ const knot_dname_t *dname = *src;
+ size_t dname_size = knot_dname_size(dname);
+
+ // Output domain name.
+ int written = compr_put_dname(dname, *dst, dname_max(*dst_avail), put_compr);
+ if (written < 0) {
+ return written;
+ }
+
+ // Update compression hints.
+ if (compr_get_ptr(compr, hint) == 0) {
+ compr_set_ptr(compr, hint, *dst, written);
+ }
+
+ // Update buffers.
+ *dst += written;
+ *dst_avail -= written;
+
+ *src += dname_size;
+ *src_avail -= dname_size;
+
+ return KNOT_EOK;
+}
+
+static int rdata_traverse_write(const uint8_t **src, size_t *src_avail,
+ uint8_t **dst, size_t *dst_avail,
+ const knot_rdata_descriptor_t *desc,
+ knot_compr_t *compr, uint16_t hint)
+{
+ for (const int *type = desc->block_types; *type != KNOT_RDATA_WF_END; type++) {
+ int ret;
+ knot_compr_t *put_compr = NULL;
+ switch (*type) {
+ case KNOT_RDATA_WF_COMPRESSIBLE_DNAME:
+ put_compr = compr;
+ // FALLTHROUGH
+ case KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME:
+ case KNOT_RDATA_WF_FIXED_DNAME:
+ ret = compress_rdata_dname(src, src_avail, dst, dst_avail,
+ put_compr, compr, hint);
+ break;
+ case KNOT_RDATA_WF_NAPTR_HEADER:
+ ret = write_rdata_naptr_header(src, src_avail, dst, dst_avail);
+ break;
+ case KNOT_RDATA_WF_REMAINDER:
+ ret = write_rdata_fixed(src, src_avail, dst, dst_avail, *src_avail);
+ break;
+ default:
+ // Fixed size block.
+ assert(*type > 0);
+ ret = write_rdata_fixed(src, src_avail, dst, dst_avail, *type);
+ break;
+ }
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int write_rdata(const knot_rrset_t *rrset, uint16_t rrset_index,
+ uint8_t **dst, size_t *dst_avail, knot_compr_t *compr)
+{
+ assert(rrset);
+ assert(rrset_index < rrset->rrs.count);
+ assert(dst && *dst);
+ assert(dst_avail);
+
+ const knot_rdata_t *rdata = knot_rdataset_at(&rrset->rrs, rrset_index);
+
+ // Reserve space for RDLENGTH.
+ if (sizeof(uint16_t) > *dst_avail) {
+ return KNOT_ESPACE;
+ }
+
+ uint8_t *wire_rdlength = *dst;
+ *dst += sizeof(uint16_t);
+ *dst_avail -= sizeof(uint16_t);
+ uint8_t *wire_rdata_begin = *dst;
+
+ // Write RDATA.
+ const uint8_t *src = rdata->data;
+ size_t src_avail = rdata->len;
+ if (src_avail > 0) {
+ // Only write non-empty data.
+ const knot_rdata_descriptor_t *desc =
+ knot_get_rdata_descriptor(rrset->type);
+ int ret = rdata_traverse_write(&src, &src_avail, dst, dst_avail,
+ desc, compr, KNOT_COMPR_HINT_RDATA + rrset_index);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ // Check for trailing data in the message.
+ if (src_avail > 0) {
+ return KNOT_EMALF;
+ }
+
+ // Write final RDLENGTH.
+ size_t rdlength = *dst - wire_rdata_begin;
+ knot_wire_write_u16(wire_rdlength, rdlength);
+
+ return KNOT_EOK;
+}
+
+static int write_rr(const knot_rrset_t *rrset, uint16_t rrset_index, uint8_t **dst,
+ size_t *dst_avail, knot_compr_t *compr, uint16_t flags)
+{
+ int ret = write_owner(rrset, dst, dst_avail, compr);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ ret = write_fixed_header(rrset, rrset_index, dst, dst_avail, flags);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ return write_rdata(rrset, rrset_index, dst, dst_avail, compr);
+}
+
+_public_
+int knot_rrset_to_wire_extra(const knot_rrset_t *rrset, uint8_t *wire,
+ uint16_t max_size, uint16_t rotate,
+ knot_compr_t *compr, uint16_t flags)
+{
+ if (rrset == NULL || wire == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (rrset->rrs.count == 0) {
+ return 0;
+ }
+ if (rotate != 0) {
+ rotate %= rrset->rrs.count;
+ }
+
+ uint8_t *write = wire;
+ size_t capacity = max_size;
+
+ uint16_t count = rrset->rrs.count;
+ for (int i = rotate; i < count + rotate; i++) {
+ uint16_t pos = (i < count) ? i : (i - count);
+ int ret = write_rr(rrset, pos, &write, &capacity, compr, flags);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return write - wire;
+}
+
+static int parse_header(const uint8_t *wire, size_t *pos, size_t pkt_size,
+ knot_mm_t *mm, knot_rrset_t *rrset, uint16_t *rdlen)
+{
+ assert(wire);
+ assert(pos);
+ assert(rrset);
+ assert(rdlen);
+
+ wire_ctx_t src = wire_ctx_init_const(wire, pkt_size);
+ wire_ctx_set_offset(&src, *pos);
+
+ int compr_size = knot_dname_wire_check(src.position, wire + pkt_size, wire);
+ if (compr_size <= 0) {
+ return KNOT_EMALF;
+ }
+
+ knot_dname_storage_t buff;
+ int decompr_size = knot_dname_unpack(buff, src.position, sizeof(buff), wire);
+ if (decompr_size <= 0) {
+ return KNOT_EMALF;
+ }
+
+ knot_dname_t *owner = mm_alloc(mm, decompr_size);
+ if (owner == NULL) {
+ return KNOT_ENOMEM;
+ }
+ memcpy(owner, buff, decompr_size);
+ wire_ctx_skip(&src, compr_size);
+
+ uint16_t type = wire_ctx_read_u16(&src);
+ uint16_t rclass = wire_ctx_read_u16(&src);
+ uint32_t ttl = wire_ctx_read_u32(&src);
+ *rdlen = wire_ctx_read_u16(&src);
+
+ if (src.error != KNOT_EOK) {
+ knot_dname_free(owner, mm);
+ return KNOT_EMALF;
+ }
+
+ if (wire_ctx_available(&src) < *rdlen) {
+ knot_dname_free(owner, mm);
+ return KNOT_EMALF;
+ }
+
+ *pos = wire_ctx_offset(&src);
+
+ knot_rrset_init(rrset, owner, type, rclass, ttl);
+
+ return KNOT_EOK;
+}
+
+static int decompress_rdata_dname(const uint8_t **src, size_t *src_avail,
+ uint8_t **dst, size_t *dst_avail,
+ const uint8_t *pkt_wire)
+{
+ assert(src && *src);
+ assert(src_avail);
+ assert(dst && *dst);
+ assert(dst_avail);
+
+ int compr_size = knot_dname_wire_check(*src, *src + *src_avail, pkt_wire);
+ if (compr_size <= 0) {
+ return compr_size;
+ }
+
+ int decompr_size = knot_dname_unpack(*dst, *src, *dst_avail, pkt_wire);
+ if (decompr_size <= 0) {
+ return decompr_size;
+ }
+
+ // Update buffers.
+ *dst += decompr_size;
+ *dst_avail -= decompr_size;
+
+ *src += compr_size;
+ *src_avail -= compr_size;
+
+ return KNOT_EOK;
+}
+
+static int rdata_traverse_parse(const uint8_t **src, size_t *src_avail,
+ uint8_t **dst, size_t *dst_avail,
+ const knot_rdata_descriptor_t *desc,
+ const uint8_t *pkt_wire)
+{
+ for (const int *type = desc->block_types; *type != KNOT_RDATA_WF_END; type++) {
+ int ret;
+ switch (*type) {
+ case KNOT_RDATA_WF_COMPRESSIBLE_DNAME:
+ case KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME:
+ case KNOT_RDATA_WF_FIXED_DNAME:
+ ret = decompress_rdata_dname(src, src_avail, dst, dst_avail,
+ pkt_wire);
+ break;
+ case KNOT_RDATA_WF_NAPTR_HEADER:
+ ret = write_rdata_naptr_header(src, src_avail, dst, dst_avail);
+ break;
+ case KNOT_RDATA_WF_REMAINDER:
+ ret = write_rdata_fixed(src, src_avail, dst, dst_avail, *src_avail);
+ break;
+ default:
+ /* Fixed size block */
+ assert(*type > 0);
+ ret = write_rdata_fixed(src, src_avail, dst, dst_avail, *type);
+ break;
+ }
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static bool allow_zero_rdata(const knot_rrset_t *rr,
+ const knot_rdata_descriptor_t *desc)
+{
+ return rr->rclass != KNOT_CLASS_IN || // NONE and ANY for DDNS
+ rr->type == KNOT_RRTYPE_APL || // APL RR type
+ desc->type_name == NULL; // Unknown RR type
+}
+
+static int parse_rdata(const uint8_t *pkt_wire, size_t *pos, size_t pkt_size,
+ knot_mm_t *mm, uint16_t rdlength, knot_rrset_t *rrset)
+{
+ assert(pkt_wire);
+ assert(pos);
+ assert(rrset);
+
+ const knot_rdata_descriptor_t *desc = knot_get_rdata_descriptor(rrset->type);
+ if (desc->type_name == NULL) {
+ desc = knot_get_obsolete_rdata_descriptor(rrset->type);
+ }
+
+ if (rdlength == 0) {
+ if (allow_zero_rdata(rrset, desc)) {
+ return knot_rrset_add_rdata(rrset, NULL, 0, mm);
+ } else {
+ return KNOT_EMALF;
+ }
+ } else if (pkt_size - *pos < rdlength) {
+ return KNOT_EMALF;
+ }
+
+ // Buffer for parsed rdata (decompression extends rdata length).
+ const size_t max_rdata_len = UINT16_MAX;
+ uint8_t buf[knot_rdata_size(max_rdata_len)];
+ knot_rdata_t *rdata = (knot_rdata_t *)buf;
+
+ const uint8_t *src = pkt_wire + *pos;
+ size_t src_avail = rdlength;
+ uint8_t *dst = rdata->data;
+ size_t dst_avail = max_rdata_len;
+
+ // Parse RDATA.
+ int ret = rdata_traverse_parse(&src, &src_avail, &dst, &dst_avail, desc, pkt_wire);
+ if (ret != KNOT_EOK) {
+ return KNOT_EMALF;
+ }
+
+ // Check for trailing data.
+ size_t real_len = max_rdata_len - dst_avail;
+ if (real_len < rdlength) {
+ return KNOT_EMALF;
+ }
+ rdata->len = real_len;
+
+ ret = knot_rdataset_add(&rrset->rrs, rdata, mm);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Update position pointer.
+ *pos += rdlength;
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_rrset_rr_from_wire(const uint8_t *wire, size_t *pos, size_t max_size,
+ knot_rrset_t *rrset, knot_mm_t *mm, bool canonical)
+{
+ if (wire == NULL || pos == NULL || *pos > max_size || rrset == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ uint16_t rdlen = 0;
+ int ret = parse_header(wire, pos, max_size, mm, rrset, &rdlen);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ ret = parse_rdata(wire, pos, max_size, mm, rdlen, rrset);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(rrset, mm);
+ return ret;
+ }
+
+ // Convert RR to the canonical format.
+ if (canonical) {
+ ret = knot_rrset_rr_to_canonical(rrset);
+ if (ret != KNOT_EOK) {
+ knot_rrset_clear(rrset, mm);
+ }
+ }
+
+ return KNOT_EOK;
+}
diff --git a/src/libknot/packet/rrset-wire.h b/src/libknot/packet/rrset-wire.h
new file mode 100644
index 0000000..3be0cba
--- /dev/null
+++ b/src/libknot/packet/rrset-wire.h
@@ -0,0 +1,69 @@
+/* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief RRSet from/to wire conversion functions.
+ *
+ * \addtogroup wire
+ * @{
+ */
+
+#pragma once
+
+#include "libknot/rrset.h"
+#include "libknot/packet/compr.h"
+
+/*!
+ * \brief Write RR Set content to a wire.
+ *
+ * \param rrset RRSet to be converted.
+ * \param wire Output wire buffer.
+ * \param max_size Capacity of wire buffer.
+ * \param rotate Rotate the RR order by this count.
+ * \param compr Compression context.
+ * \param flags Flags; currently only KNOT_PF_TTL_ORIG is accepted.
+ *
+ * \return Output size, negative number on error (KNOT_E*).
+ */
+int knot_rrset_to_wire_extra(const knot_rrset_t *rrset, uint8_t *wire,
+ uint16_t max_size, uint16_t rotate,
+ knot_compr_t *compr, uint16_t flags);
+
+/*! \brief Same as knot_rrset_to_wire_extra but without rrset rotation and flags. */
+static inline int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire,
+ uint16_t max_size, knot_compr_t *compr)
+{
+ return knot_rrset_to_wire_extra(rrset, wire, max_size, 0, compr, 0);
+}
+
+/*!
+* \brief Creates one RR from wire, stores it into \a rrset.
+*
+* \param wire Source wire (the whole packet).
+* \param pos Position in \a wire where to start parsing.
+* \param max_size Total size of data in \a wire (size of the packet).
+* \param rrset Destination RRSet.
+* \param mm Memory context.
+* \param canonical Convert rrset to canonical format indication.
+*
+* \return KNOT_E*
+*/
+int knot_rrset_rr_from_wire(const uint8_t *wire, size_t *pos, size_t max_size,
+ knot_rrset_t *rrset, knot_mm_t *mm, bool canonical);
+
+/*! @} */
diff --git a/src/libknot/packet/wire.h b/src/libknot/packet/wire.h
new file mode 100644
index 0000000..698ac3d
--- /dev/null
+++ b/src/libknot/packet/wire.h
@@ -0,0 +1,1045 @@
+/* Copyright (C) 2019 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Functions for manipulating and parsing raw data in DNS packets.
+ *
+ * \addtogroup wire
+ * @{
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <stdint.h>
+
+#include "libknot/attribute.h"
+#include "libknot/wire.h"
+
+/*! \brief Offset of DNS header fields in wireformat. */
+enum knot_wire_offsets {
+ KNOT_WIRE_OFFSET_ID = 0,
+ KNOT_WIRE_OFFSET_FLAGS1 = 2,
+ KNOT_WIRE_OFFSET_FLAGS2 = 3,
+ KNOT_WIRE_OFFSET_QDCOUNT = 4,
+ KNOT_WIRE_OFFSET_ANCOUNT = 6,
+ KNOT_WIRE_OFFSET_NSCOUNT = 8,
+ KNOT_WIRE_OFFSET_ARCOUNT = 10
+};
+
+/*! \brief Minimum size for some parts of the DNS packet. */
+enum knot_wire_sizes {
+ KNOT_WIRE_HEADER_SIZE = 12,
+ KNOT_WIRE_QUESTION_MIN_SIZE = 5,
+ KNOT_WIRE_RR_MIN_SIZE = 11,
+ KNOT_WIRE_MIN_PKTSIZE = 512,
+ KNOT_WIRE_MAX_PKTSIZE = 65535,
+ KNOT_WIRE_MAX_PAYLOAD = KNOT_WIRE_MAX_PKTSIZE
+ - KNOT_WIRE_HEADER_SIZE
+ - KNOT_WIRE_QUESTION_MIN_SIZE
+};
+
+/*
+ * Packet header manipulation functions.
+ */
+
+/*!
+ * \brief Returns the ID from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return DNS packet ID.
+ */
+static inline uint16_t knot_wire_get_id(const uint8_t *packet)
+{
+ assert(packet);
+ return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ID);
+}
+
+/*!
+ * \brief Sets the ID to the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ * \param id DNS packet ID.
+ */
+static inline void knot_wire_set_id(uint8_t *packet, uint16_t id)
+{
+ assert(packet);
+ knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ID, id);
+}
+
+/*!
+ * \brief Returns the first byte of flags from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return First byte of DNS flags.
+ */
+static inline uint8_t knot_wire_get_flags1(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS1);
+}
+
+/*!
+ * \brief Sets the first byte of flags to the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ * \param flags1 First byte of the DNS flags.
+ */
+static inline uint8_t knot_wire_set_flags1(uint8_t *packet, uint8_t flags1)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS1) = flags1;
+}
+
+/*!
+ * \brief Returns the second byte of flags from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return Second byte of DNS flags.
+ */
+static inline uint8_t knot_wire_get_flags2(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS2);
+}
+
+/*!
+ * \brief Sets the second byte of flags to the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ * \param flags2 Second byte of the DNS flags.
+ */
+static inline uint8_t knot_wire_set_flags2(uint8_t *packet, uint8_t flags2)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS2) = flags2;
+}
+
+/*!
+ * \brief Returns the QDCOUNT (count of Question entries) from wire format of
+ * the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return QDCOUNT (count of Question entries in the packet).
+ */
+static inline uint16_t knot_wire_get_qdcount(const uint8_t *packet)
+{
+ assert(packet);
+ return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT);
+}
+
+/*!
+ * \brief Sets the QDCOUNT (count of Question entries) to wire format of the
+ * packet.
+ *
+ * \param packet Wire format of the packet.
+ * \param qdcount QDCOUNT (count of Question entries in the packet).
+ */
+static inline void knot_wire_set_qdcount(uint8_t *packet, uint16_t qdcount)
+{
+ assert(packet);
+ knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT, qdcount);
+}
+
+/*!
+ * \brief Adds to QDCOUNT.
+ */
+static inline void knot_wire_add_qdcount(uint8_t *packet, int16_t n)
+{
+ assert(packet);
+ knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT,
+ knot_wire_get_qdcount(packet) + n);
+}
+
+/*!
+ * \brief Returns the ANCOUNT (count of Answer entries) from wire format of
+ * the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return ANCOUNT (count of Answer entries in the packet).
+ */
+static inline uint16_t knot_wire_get_ancount(const uint8_t *packet)
+{
+ assert(packet);
+ return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT);
+}
+
+/*!
+ * \brief Sets the ANCOUNT (count of Answer entries) to wire format of the
+ * packet.
+ *
+ * \param packet Wire format of the packet.
+ * \param ancount ANCOUNT (count of Answer entries in the packet).
+ */
+static inline void knot_wire_set_ancount(uint8_t *packet, uint16_t ancount)
+{
+ assert(packet);
+ knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT, ancount);
+}
+
+/*!
+ * \brief Adds to ANCOUNT.
+ */
+static inline void knot_wire_add_ancount(uint8_t *packet, int16_t n)
+{
+ assert(packet);
+ knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT,
+ knot_wire_get_ancount(packet) + n);
+}
+
+/*!
+ * \brief Returns the NSCOUNT (count of Authority entries) from wire format of
+ * the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return NSCOUNT (count of Authority entries in the packet).
+ */
+static inline uint16_t knot_wire_get_nscount(const uint8_t *packet)
+{
+ assert(packet);
+ return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT);
+}
+
+/*!
+ * \brief Sets the NSCOUNT (count of Authority entries) to wire format of the
+ * packet.
+ *
+ * \param packet Wire format of the packet.
+ * \param nscount NSCOUNT (count of Authority entries in the packet).
+ */
+static inline void knot_wire_set_nscount(uint8_t *packet, uint16_t nscount)
+{
+ assert(packet);
+ knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT, nscount);
+}
+
+/*!
+ * \brief Adds to NSCOUNT.
+ */
+static inline void knot_wire_add_nscount(uint8_t *packet, int16_t n)
+{
+ assert(packet);
+ knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT,
+ knot_wire_get_nscount(packet) + n);
+}
+
+/*!
+ * \brief Returns the ARCOUNT (count of Additional entries) from wire format of
+ * the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return ARCOUNT (count of Additional entries in the packet).
+ */
+static inline uint16_t knot_wire_get_arcount(const uint8_t *packet)
+{
+ assert(packet);
+ return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT);
+}
+
+/*!
+ * \brief Sets the ARCOUNT (count of Additional entries) to wire format of the
+ * packet.
+ *
+ * \param packet Wire format of the packet.
+ * \param arcount ARCOUNT (count of Additional entries in the packet).
+ */
+static inline void knot_wire_set_arcount(uint8_t *packet, uint16_t arcount)
+{
+ assert(packet);
+ knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT, arcount);
+}
+
+/*!
+ * \brief Adds to ARCOUNT.
+ */
+static inline void knot_wire_add_arcount(uint8_t *packet, int16_t n)
+{
+ assert(packet);
+ knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT,
+ knot_wire_get_arcount(packet) + n);
+}
+
+/*
+ * Packet header flags manipulation functions.
+ */
+/*! \brief Constants for DNS header flags in the first flags byte. */
+enum knot_wire_flags1_consts {
+ KNOT_WIRE_RD_MASK = (uint8_t)0x01U, /*!< RD bit mask. */
+ KNOT_WIRE_RD_SHIFT = 0, /*!< RD bit shift. */
+ KNOT_WIRE_TC_MASK = (uint8_t)0x02U, /*!< TC bit mask. */
+ KNOT_WIRE_TC_SHIFT = 1, /*!< TC bit shift. */
+ KNOT_WIRE_AA_MASK = (uint8_t)0x04U, /*!< AA bit mask. */
+ KNOT_WIRE_AA_SHIFT = 2, /*!< AA bit shift. */
+ KNOT_WIRE_OPCODE_MASK = (uint8_t)0x78U, /*!< OPCODE mask. */
+ KNOT_WIRE_OPCODE_SHIFT = 3, /*!< OPCODE shift. */
+ KNOT_WIRE_QR_MASK = (uint8_t)0x80U, /*!< QR bit mask. */
+ KNOT_WIRE_QR_SHIFT = 7 /*!< QR bit shift. */
+};
+
+/*! \brief Constants for DNS header flags in the second flags byte. */
+enum knot_wire_flags2_consts {
+ KNOT_WIRE_RCODE_MASK = (uint8_t)0x0fU, /*!< RCODE mask. */
+ KNOT_WIRE_RCODE_SHIFT = 0, /*!< RCODE shift. */
+ KNOT_WIRE_CD_MASK = (uint8_t)0x10U, /*!< CD bit mask. */
+ KNOT_WIRE_CD_SHIFT = 4, /*!< CD bit shift. */
+ KNOT_WIRE_AD_MASK = (uint8_t)0x20U, /*!< AD bit mask. */
+ KNOT_WIRE_AD_SHIFT = 5, /*!< AD bit shift. */
+ KNOT_WIRE_Z_MASK = (uint8_t)0x40U, /*!< Zero bit mask. */
+ KNOT_WIRE_Z_SHIFT = 6, /*!< Zero bit shift. */
+ KNOT_WIRE_RA_MASK = (uint8_t)0x80U, /*!< RA bit mask. */
+ KNOT_WIRE_RA_SHIFT = 7 /*!< RA bit shift. */
+};
+
+/*
+ * Functions for getting / setting / clearing flags and codes directly in packet
+ */
+
+/*!
+ * \brief Returns the RD bit from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return Flags with only the RD bit according to its setting in the packet.
+ */
+static inline uint8_t knot_wire_get_rd(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_RD_MASK;
+}
+
+/*!
+ * \brief Sets the RD bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_set_rd(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_RD_MASK;
+}
+
+/*!
+ * \brief Clears the RD bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_clear_rd(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_RD_MASK;
+}
+
+/*!
+ * \brief Returns the TC bit from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return Flags with only the TC bit according to its setting in the packet.
+ */
+static inline uint8_t knot_wire_get_tc(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_TC_MASK;
+}
+
+/*!
+ * \brief Sets the TC bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_set_tc(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_TC_MASK;
+}
+
+/*!
+ * \brief Clears the TC bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_clear_tc(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_TC_MASK;
+}
+
+/*!
+ * \brief Returns the AA bit from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return Flags with only the AA bit according to its setting in the packet.
+ */
+static inline uint8_t knot_wire_get_aa(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_AA_MASK;
+}
+
+/*!
+ * \brief Sets the AA bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_set_aa(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_AA_MASK;
+}
+
+/*!
+ * \brief Clears the AA bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_clear_aa(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_AA_MASK;
+}
+
+/*!
+ * \brief Returns the OPCODE from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return OPCODE of the packet.
+ */
+static inline uint8_t knot_wire_get_opcode(const uint8_t *packet)
+{
+ assert(packet);
+ return (*(packet + KNOT_WIRE_OFFSET_FLAGS1)
+ & KNOT_WIRE_OPCODE_MASK) >> KNOT_WIRE_OPCODE_SHIFT;
+}
+
+/*!
+ * \brief Sets the OPCODE in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ * \param opcode OPCODE to set.
+ */
+static inline void knot_wire_set_opcode(uint8_t *packet, short opcode)
+{
+ assert(packet);
+ uint8_t *flags1 = packet + KNOT_WIRE_OFFSET_FLAGS1;
+ *flags1 = (*flags1 & ~KNOT_WIRE_OPCODE_MASK)
+ | ((opcode) << KNOT_WIRE_OPCODE_SHIFT);
+}
+
+/*!
+ * \brief Returns the QR bit from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return Nonzero for responses and zero for queries.
+ */
+static inline uint8_t knot_wire_get_qr(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_QR_MASK;
+}
+
+/*!
+ * \brief Sets the QR bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_set_qr(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_QR_MASK;
+}
+
+/*!
+ * \brief Clears the QR bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_clear_qr(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_QR_MASK;
+}
+
+/*!
+ * \brief Returns the RCODE from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return RCODE of the packet.
+ */
+static inline uint8_t knot_wire_get_rcode(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS2)
+ & KNOT_WIRE_RCODE_MASK;
+}
+
+/*!
+ * \brief Sets the RCODE in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ * \param rcode RCODE to set.
+ */
+static inline void knot_wire_set_rcode(uint8_t *packet, short rcode)
+{
+ assert(packet);
+ uint8_t *flags2 = packet + KNOT_WIRE_OFFSET_FLAGS2;
+ *flags2 = (*flags2 & ~KNOT_WIRE_RCODE_MASK) | (rcode);
+}
+
+/*!
+ * \brief Returns the CD bit from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return Flags with only the CD bit according to its setting in the packet.
+ */
+static inline uint8_t knot_wire_get_cd(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_CD_MASK;
+}
+
+/*!
+ * \brief Sets the CD bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_set_cd(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_CD_MASK;
+}
+
+/*!
+ * \brief Clears the CD bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_clear_cd(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_CD_MASK;
+}
+
+/*!
+ * \brief Returns the AD bit from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return Flags with only the AD bit according to its setting in the packet.
+ */
+static inline uint8_t knot_wire_get_ad(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_AD_MASK;
+}
+
+/*!
+ * \brief Sets the AD bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_set_ad(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_AD_MASK;
+}
+
+/*!
+ * \brief Clears the AD bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_clear_ad(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_AD_MASK;
+}
+
+/*!
+ * \brief Returns the Zero bit from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return Flags with only the Zero bit according to its setting in the packet.
+ */
+static inline uint8_t knot_wire_get_z(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_Z_MASK;
+}
+
+/*!
+ * \brief Sets the Zero bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_set_z(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_Z_MASK;
+}
+
+/*!
+ * \brief Clears the Zero bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_clear_z(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_Z_MASK;
+}
+
+/*!
+ * \brief Returns the RA bit from wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ *
+ * \return Flags with only the RA bit according to its setting in the packet.
+ */
+static inline uint8_t knot_wire_get_ra(const uint8_t *packet)
+{
+ assert(packet);
+ return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_RA_MASK;
+}
+
+/*!
+ * \brief Sets the RA bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_set_ra(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_RA_MASK;
+}
+
+/*!
+ * \brief Clears the RA bit in the wire format of the packet.
+ *
+ * \param packet Wire format of the packet.
+ */
+static inline void knot_wire_clear_ra(uint8_t *packet)
+{
+ assert(packet);
+ *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_RA_MASK;
+}
+
+/*
+ * Functions for getting / setting / clearing flags in flags variable
+ */
+
+/*!
+ * \brief Returns the RD bit from the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ *
+ * \return Flags byte with only the RD bit according to its setting in
+ * \a flags1.
+ */
+static inline uint8_t knot_wire_flags_get_rd(uint8_t flags1)
+{
+ return flags1 & KNOT_WIRE_RD_MASK;
+}
+
+/*!
+ * \brief Sets the RD bit in the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ */
+static inline void knot_wire_flags_set_rd(uint8_t *flags1)
+{
+ assert(flags1);
+ *flags1 |= KNOT_WIRE_RD_MASK;
+}
+
+/*!
+ * \brief Clears the RD bit in the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ */
+static inline void knot_wire_flags_clear_rd(uint8_t *flags1)
+{
+ assert(flags1);
+ *flags1 &= ~KNOT_WIRE_RD_MASK;
+}
+
+/*!
+ * \brief Returns the TC bit from the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ *
+ * \return Flags byte with only the TC bit according to its setting in
+ * \a flags1.
+ */
+static inline uint8_t knot_wire_flags_get_tc(uint8_t flags1)
+{
+ return flags1 & KNOT_WIRE_TC_MASK;
+}
+
+/*!
+ * \brief Sets the TC bit in the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ */
+static inline void knot_wire_flags_set_tc(uint8_t *flags1)
+{
+ assert(flags1);
+ *flags1 |= KNOT_WIRE_TC_MASK;
+}
+
+/*!
+ * \brief Clears the TC bit in the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ */
+static inline void knot_wire_flags_clear_tc(uint8_t *flags1)
+{
+ assert(flags1);
+ *flags1 &= ~KNOT_WIRE_TC_MASK;
+}
+
+/*!
+ * \brief Returns the AA bit from the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ *
+ * \return Flags byte with only the AA bit according to its setting in
+ * \a flags1.
+ */
+static inline uint8_t knot_wire_flags_get_aa(uint8_t flags1)
+{
+ return flags1 & KNOT_WIRE_AA_MASK;
+}
+
+/*!
+ * \brief Sets the AA bit in the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ */
+static inline void knot_wire_flags_set_aa(uint8_t *flags1)
+{
+ assert(flags1);
+ *flags1 |= KNOT_WIRE_AA_MASK;
+}
+
+/*!
+ * \brief Clears the AA bit in the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ */
+static inline void knot_wire_flags_clear_aa(uint8_t *flags1)
+{
+ assert(flags1);
+ *flags1 &= ~KNOT_WIRE_AA_MASK;
+}
+
+/*!
+ * \brief Returns the OPCODE from the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ *
+ * \return OPCODE
+ */
+static inline uint8_t knot_wire_flags_get_opcode(uint8_t flags1)
+{
+ return (flags1 & KNOT_WIRE_OPCODE_MASK)
+ >> KNOT_WIRE_OPCODE_SHIFT;
+}
+
+/*!
+ * \brief Sets the OPCODE in the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ * \param opcode OPCODE to set.
+ */
+static inline void knot_wire_flags_set_opcode(uint8_t *flags1, short opcode)
+{
+ assert(flags1);
+ *flags1 = (*flags1 & ~KNOT_WIRE_OPCODE_MASK)
+ | ((opcode) << KNOT_WIRE_OPCODE_SHIFT);
+}
+
+/*!
+ * \brief Returns the QR bit from the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ *
+ * \return Flags byte with only the QR bit according to its setting in
+ * \a flags1.
+ */
+static inline uint8_t knot_wire_flags_get_qr(uint8_t flags1)
+{
+ return flags1 & KNOT_WIRE_QR_MASK;
+}
+
+/*!
+ * \brief Sets the QR bit in the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ */
+static inline void knot_wire_flags_set_qr(uint8_t *flags1)
+{
+ assert(flags1);
+ *flags1 |= KNOT_WIRE_QR_MASK;
+}
+
+/*!
+ * \brief Clears the QR bit in the first byte of flags.
+ *
+ * \param flags1 First byte of DNS header flags.
+ */
+static inline void knot_wire_flags_clear_qr(uint8_t *flags1)
+{
+ assert(flags1);
+ *flags1 &= ~KNOT_WIRE_QR_MASK;
+}
+
+/*!
+ * \brief Returns the RCODE from the second byte of flags.
+ *
+ * \param flags2 First byte of DNS header flags.
+ *
+ * \return RCODE
+ */
+static inline uint8_t knot_wire_flags_get_rcode(uint8_t flags2)
+{
+ return flags2 & KNOT_WIRE_RCODE_MASK;
+}
+
+/*!
+ * \brief Sets the RCODE in the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ * \param rcode RCODE to set.
+ */
+static inline void knot_wire_flags_set_rcode(uint8_t *flags2, short rcode)
+{
+ assert(flags2);
+ *flags2 = (*flags2 & ~KNOT_WIRE_RCODE_MASK) | (rcode);
+}
+
+/*!
+ * \brief Returns the CD bit from the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ *
+ * \return Flags byte with only the CD bit according to its setting in
+ * \a flags2.
+ */
+static inline uint8_t knot_wire_flags_get_cd(uint8_t flags2)
+{
+ return flags2 & KNOT_WIRE_CD_MASK;
+}
+
+/*!
+ * \brief Sets the CD bit in the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ */
+static inline void knot_wire_flags_set_cd(uint8_t *flags2)
+{
+ assert(flags2);
+ *flags2 |= KNOT_WIRE_CD_MASK;
+}
+
+/*!
+ * \brief Clears the CD bit in the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ */
+static inline void knot_wire_flags_clear_cd(uint8_t *flags2)
+{
+ assert(flags2);
+ *flags2 &= ~KNOT_WIRE_CD_MASK;
+}
+
+/*!
+ * \brief Returns the AD bit from the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ *
+ * \return Flags byte with only the AD bit according to its setting in
+ * \a flags2.
+ */
+static inline uint8_t knot_wire_flags_get_ad(uint8_t flags2)
+{
+ return flags2 & KNOT_WIRE_AD_MASK;
+}
+
+/*!
+ * \brief Sets the AD bit in the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ */
+static inline void knot_wire_flags_set_ad(uint8_t *flags2)
+{
+ assert(flags2);
+ *flags2 |= KNOT_WIRE_AD_MASK;
+}
+
+/*!
+ * \brief Clears the AD bit in the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ */
+static inline void knot_wire_flags_clear_ad(uint8_t *flags2)
+{
+ assert(flags2);
+ *flags2 &= ~KNOT_WIRE_AD_MASK;
+}
+
+/*!
+ * \brief Returns the Zero bit from the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ *
+ * \return Flags byte with only the Zero bit according to its setting in
+ * \a flags2.
+ */
+static inline uint8_t knot_wire_flags_get_z(uint8_t flags2)
+{
+ return flags2 & KNOT_WIRE_Z_MASK;
+}
+
+/*!
+ * \brief Sets the Zero bit in the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ */
+static inline void knot_wire_flags_set_z(uint8_t *flags2)
+{
+ assert(flags2);
+ *flags2 |= KNOT_WIRE_Z_MASK;
+}
+
+/*!
+ * \brief Clears the Zero bit in the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ */
+static inline void knot_wire_flags_clear_z(uint8_t *flags2)
+{
+ assert(flags2);
+ *flags2 &= ~KNOT_WIRE_Z_MASK;
+}
+
+/*!
+ * \brief Returns the RA bit from the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ *
+ * \return Flags byte with only the RA bit according to its setting in
+ * \a flags2.
+ */
+static inline uint8_t knot_wire_flags_get_ra(uint8_t flags2)
+{
+ return flags2 & KNOT_WIRE_RA_MASK;
+}
+
+/*!
+ * \brief Sets the RA bit in the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ */
+static inline void knot_wire_flags_set_ra(uint8_t *flags2)
+{
+ assert(flags2);
+ *flags2 |= KNOT_WIRE_RA_MASK;
+}
+
+/*!
+ * \brief Clears the RA bit in the second byte of flags.
+ *
+ * \param flags2 Second byte of DNS header flags.
+ */
+static inline void knot_wire_flags_clear_ra(uint8_t *flags2)
+{
+ assert(flags2);
+ *flags2 &= ~KNOT_WIRE_RA_MASK;
+}
+
+/*
+ * Pointer manipulation
+ */
+
+enum knot_wire_pointer_consts {
+ /*! \brief DNS packet pointer designation (first two bits set to 1). */
+ KNOT_WIRE_PTR = (uint8_t)0xC0,
+ /*! \brief DNS packet minimal pointer (KNOT_WIRE_PTR + 1 zero byte). */
+ KNOT_WIRE_PTR_BASE = (uint16_t)0xC000,
+ /*! \brief DNS packet maximal offset (KNOT_WIRE_BASE complement). */
+ KNOT_WIRE_PTR_MAX = (uint16_t)0x3FFF
+};
+
+static inline int knot_wire_is_pointer(const uint8_t *pos)
+{
+ return pos && ((pos[0] & KNOT_WIRE_PTR) == KNOT_WIRE_PTR);
+}
+
+/*!
+ * \brief Creates a DNS packet pointer and stores it in wire format.
+ *
+ * \param pos Position where tu put the pointer.
+ * \param ptr Relative position of the item to which the pointer should point in
+ * the wire format of the packet.
+ */
+static inline void knot_wire_put_pointer(uint8_t *pos, uint16_t ptr)
+{
+ knot_wire_write_u16(pos, ptr); // Write pointer offset.
+ assert((pos[0] & KNOT_WIRE_PTR) == 0); // Check for maximal offset.
+ pos[0] |= KNOT_WIRE_PTR; // Add pointer mark.
+}
+
+static inline uint16_t knot_wire_get_pointer(const uint8_t *pos)
+{
+ assert(knot_wire_is_pointer(pos)); // Check pointer.
+ return (knot_wire_read_u16(pos) - KNOT_WIRE_PTR_BASE); // Return offset.
+}
+
+_pure_ _mustcheck_
+static inline const uint8_t *knot_wire_seek_label(const uint8_t *lp, const uint8_t *wire)
+{
+ while (knot_wire_is_pointer(lp)) {
+ if (!wire)
+ return NULL;
+ lp = wire + knot_wire_get_pointer(lp);
+ }
+ return lp;
+}
+
+_pure_ _mustcheck_
+static inline const uint8_t *knot_wire_next_label(const uint8_t *lp, const uint8_t *wire)
+{
+ if (!lp || !lp[0]) /* No label after final label. */
+ return NULL;
+ return knot_wire_seek_label(lp + (lp[0] + sizeof(uint8_t)), wire);
+}
+
+/*! @} */