Commit 6a80b94b04 for bind
commit 6a80b94b041a92a213db1a4275df94ff86f6d9ca
Author: Alessio Podda <alessio@isc.org>
Date: Fri Sep 25 12:55:43 2026 +0200
Add hypothesis tests
Hypothesis tests for vecheader operations.
diff --git a/tests/hypothesis/meson.build b/tests/hypothesis/meson.build
index d37982152e..d02b7b703e 100644
--- a/tests/hypothesis/meson.build
+++ b/tests/hypothesis/meson.build
@@ -55,3 +55,51 @@ test(
timeout: 300,
workdir: meson.current_build_dir(),
)
+
+vec_proto_c = custom_target(
+ 'vecheader-protobuf-c',
+ input: 'vecheader.proto',
+ output: ['vecheader.pb-c.c', 'vecheader.pb-c.h'],
+ command: [
+ hypothesis_protoc_c,
+ '--proto_path=@CURRENT_SOURCE_DIR@',
+ '--c_out=@OUTDIR@',
+ '@INPUT@',
+ ],
+)
+vec_proto_py = custom_target(
+ 'vecheader-protobuf-python',
+ input: 'vecheader.proto',
+ output: 'vecheader_pb2.py',
+ command: [
+ hypothesis_protoc,
+ '--proto_path=@CURRENT_SOURCE_DIR@',
+ '--python_out=@OUTDIR@',
+ '@INPUT@',
+ ],
+)
+vec_server = executable(
+ 'vecheader_server',
+ 'vecheader_server.c',
+ vec_proto_c,
+ default_sanitize_options,
+ include_directories: test_inc,
+ dependencies: [libdns_dep, libisc_dep, hypothesis_proto_dep],
+ build_by_default: false,
+ install: false,
+)
+vec_env = environment(test_env_vars)
+vec_env.set('VECHEADER_SERVER', vec_server.full_path())
+vec_env.set('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', 'python')
+vec_env.prepend('PYTHONPATH', meson.current_build_dir())
+vec_env.append('PYTHONPATH', meson.project_source_root() / 'bin/tests/system')
+test(
+ 'vecheader-hypothesis',
+ python,
+ args: files('vecheader.py'),
+ depends: [vec_server, vec_proto_py],
+ env: vec_env,
+ suite: ['dns', 'hypothesis'],
+ timeout: 300,
+ workdir: meson.current_build_dir(),
+)
diff --git a/tests/hypothesis/vecheader.proto b/tests/hypothesis/vecheader.proto
new file mode 100644
index 0000000000..ef61302f46
--- /dev/null
+++ b/tests/hypothesis/vecheader.proto
@@ -0,0 +1,76 @@
+// Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+// SPDX-License-Identifier: MPL-2.0
+syntax = "proto2";
+package vec;
+
+message Header {
+ repeated bytes records = 1;
+ repeated bool offline = 2;
+ optional bytes owner = 3;
+ optional uint32 ttl = 4 [default = 300];
+ optional uint32 trust = 5 [default = 0];
+ optional uint32 serial = 6 [default = 0];
+ optional bool resign = 7 [default = false];
+ optional int64 resign_time = 8 [default = 0];
+}
+message Construct {
+ required uint32 type = 1;
+ required Header header = 2;
+ optional uint32 limit = 3 [default = 0];
+}
+message Merge {
+ required uint32 type = 1;
+ required Header left = 2;
+ required Header right = 3;
+ optional uint32 flags = 4 [default = 0];
+ optional uint32 limit = 5 [default = 0];
+}
+message Subtract {
+ required uint32 type = 1;
+ required Header left = 2;
+ required Header right = 3;
+ optional uint32 flags = 4 [default = 0];
+}
+message Copy {
+ required uint32 type = 1;
+ required Header header = 2;
+}
+message SetOwnerCase {
+ required uint32 type = 1;
+ required Header header = 2;
+ required bytes owner = 3;
+}
+message Request {
+ oneof command {
+ Construct construct = 1;
+ Merge merge = 2;
+ Subtract subtract = 3;
+ Copy copy = 4;
+ SetOwnerCase set_owner_case = 5;
+ }
+}
+enum Result {
+ SUCCESS = 0;
+ TOO_MANY_RECORDS = 1;
+ NOT_EXACT = 2;
+ UNCHANGED = 3;
+ SINGLETON = 4;
+ NXRRSET = 5;
+ NO_SPACE = 6;
+ FAILURE = 7;
+}
+
+message Response {
+ required Result result = 1;
+ optional Header header = 2;
+ optional uint32 count = 3;
+ optional uint32 raw_length = 4;
+ optional uint32 size = 5;
+ optional uint32 header_size = 6;
+ optional uint32 type = 7;
+ optional bool case_set = 8;
+ optional bool case_lower = 9;
+ optional bytes bitmap = 10;
+ optional bytes applied_owner = 11;
+ optional uint32 covers = 12;
+}
diff --git a/tests/hypothesis/vecheader.py b/tests/hypothesis/vecheader.py
new file mode 100644
index 0000000000..b290b30187
--- /dev/null
+++ b/tests/hypothesis/vecheader.py
@@ -0,0 +1,378 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+"""
+Model tests for the real C vecheader implementation.
+
+Run through Meson: meson test -C builddir --suite hypothesis --print-errorlogs
+Each protobuf request is independent; only the adapter process is reused.
+"""
+
+import os
+import struct
+import sys
+
+from isc_hypothesis import RestartingProtobufServer
+
+try:
+ from hypothesis import example, given, settings
+ from hypothesis import strategies as st
+
+ import dns.rdatatype
+
+ from isctest.hypothesis.strategies import dns_names
+except ModuleNotFoundError as error:
+ print(f"skipping vecheader hypothesis tests: {error}", file=sys.stderr)
+ sys.exit(77)
+
+import vecheader_pb2 as proto # pylint: disable=import-error,wrong-import-position
+
+OPAQUE_TYPE = dns.rdatatype.RdataType(65280)
+
+settings.register_profile(
+ "vecheader-ci", max_examples=300, deadline=None, derandomize=True
+)
+settings.register_profile("extended", max_examples=3000, deadline=None)
+
+settings.load_profile(os.environ.get("HYPOTHESIS_PROFILE", "vecheader-ci"))
+
+server = None
+
+
+@st.composite
+def owners(draw):
+ if draw(st.booleans()):
+ return None
+ name = draw(dns_names()).to_wire()
+ return name.lower() if draw(st.booleans()) else name
+
+
+@st.composite
+def headers(draw, records):
+ values = draw(st.lists(st.sampled_from(records), max_size=15))
+ header = proto.Header(
+ records=values,
+ # A record's flag is deterministic across duplicates within an input;
+ # differing flags across operands still exercise left-wins merging.
+ offline=[False] * len(values),
+ ttl=draw(st.integers(0, 2**32 - 1)),
+ trust=draw(st.integers(0, 10)),
+ serial=draw(st.integers(0, 2**32 - 1)),
+ resign=draw(st.booleans()),
+ resign_time=draw(st.integers(0, 2**40)),
+ )
+ owner = draw(owners())
+ if owner is not None:
+ header.owner = owner
+ return header
+
+
+@st.composite
+def cases(draw):
+ rrtype = draw(
+ st.sampled_from(
+ [
+ dns.rdatatype.A,
+ dns.rdatatype.AAAA,
+ dns.rdatatype.TXT,
+ dns.rdatatype.RRSIG,
+ dns.rdatatype.CNAME,
+ OPAQUE_TYPE,
+ ]
+ )
+ )
+ tokens = draw(st.lists(st.integers(0, 15), min_size=1, max_size=8, unique=True))
+
+ def wire(token):
+ if rrtype == dns.rdatatype.A:
+ return bytes([192, 0, 2, token])
+ if rrtype == dns.rdatatype.AAAA:
+ return bytes(15) + bytes([token])
+ if rrtype == dns.rdatatype.TXT:
+ return bytes([token]) + bytes([token]) * token
+ if rrtype == dns.rdatatype.CNAME:
+ return b"\x01" + bytes([97 + token]) + b"\x00"
+ if rrtype == dns.rdatatype.RRSIG:
+ # A/algorithm/labels/TTL/expiration/inception/key tag/root signer.
+ return (
+ struct.pack("!HBBIIIH", dns.rdatatype.A, 8, 0, 300, 1000, 0, token)
+ + b"\x00sig"
+ )
+ return bytes([token]) * token
+
+ pool = [wire(token) for token in tokens]
+ left = draw(headers(pool))
+ right = draw(headers(pool))
+ if rrtype == dns.rdatatype.CNAME:
+ # Valid singleton operands; union can still violate singleton rules.
+ del left.records[1:]
+ del left.offline[1:]
+ del right.records[1:]
+ del right.offline[1:]
+ if rrtype == dns.rdatatype.RRSIG:
+ for header in (left, right):
+ flag = draw(st.booleans())
+ header.offline[:] = [flag] * len(header.records)
+ count = len(set(left.records) | set(right.records))
+ limit = draw(st.sampled_from(sorted({0, 1, max(1, count - 1), count, count + 1})))
+ return rrtype, left, right, limit
+
+
+def contents(header):
+ return dict(zip(header.records, header.offline))
+
+
+def check_header(response, expected, metadata, owner, rrtype):
+ assert response.HasField("header")
+ actual = response.header
+ # For these generated types, canonical DNSSEC order is wire byte order.
+ assert list(actual.records) == sorted(expected)
+ assert list(actual.offline) == [expected[key] for key in sorted(expected)]
+ assert response.count == len(expected)
+ assert response.raw_length == sum(
+ 2 + len(key) + (rrtype == dns.rdatatype.RRSIG) for key in expected
+ )
+ assert response.size == response.header_size + response.raw_length
+ assert response.type == rrtype
+ assert response.covers == (
+ dns.rdatatype.A if rrtype == dns.rdatatype.RRSIG else dns.rdatatype.NONE
+ )
+ for field in ("ttl", "trust", "serial", "resign", "resign_time"):
+ assert getattr(actual, field) == getattr(metadata, field), field
+ name = owner.owner if owner.HasField("owner") else None
+ bitmap = bytearray(32)
+ if name is not None:
+ for index, byte in enumerate(name):
+ if 65 <= byte <= 90:
+ bitmap[index // 8] |= 1 << (index % 8)
+ assert response.case_set == (name is not None)
+ assert response.case_lower == (name is not None and name == name.lower())
+ assert response.bitmap == bytes(bitmap)
+ if name is not None:
+ assert response.applied_owner == name
+
+
+@given(cases(), st.integers(0, 3))
+def test_merge(case, flags):
+ rrtype, left, right, limit = case
+ a, b = contents(left), contents(right)
+ expected = b | a # Old RDATA wins on equality, including offline metadata.
+ if limit and len(expected) > limit:
+ result = proto.TOO_MANY_RECORDS
+ elif flags & 2 and a.keys() & b.keys():
+ result = proto.NOT_EXACT
+ elif b.keys() <= a.keys() and not flags & 1:
+ result = proto.UNCHANGED
+ elif rrtype == dns.rdatatype.CNAME and len(expected) > 1:
+ result = proto.SINGLETON
+ else:
+ result = proto.SUCCESS
+ response = server.command(
+ proto.Request(
+ merge=proto.Merge(
+ type=rrtype, left=left, right=right, flags=flags, limit=limit
+ )
+ )
+ )
+ assert response.result == result
+ if result == proto.SUCCESS:
+ check_header(response, expected, right, left, rrtype)
+ else:
+ assert not response.HasField("header")
+
+
+@given(cases(), st.booleans())
+def test_subtract(case, exact):
+ rrtype, left, right, _ = case
+ a, b = contents(left), contents(right)
+ expected = {key: flag for key, flag in a.items() if key not in b}
+ if exact and not b.keys() <= a.keys():
+ result = proto.NOT_EXACT
+ elif not expected:
+ result = proto.NXRRSET
+ elif expected == a:
+ result = proto.UNCHANGED
+ else:
+ result = proto.SUCCESS
+ response = server.command(
+ proto.Request(
+ subtract=proto.Subtract(
+ type=rrtype, left=left, right=right, flags=2 if exact else 0
+ )
+ )
+ )
+ assert response.result == result
+ if result == proto.SUCCESS:
+ check_header(response, expected, left, left, rrtype)
+ else:
+ assert not response.HasField("header")
+
+
+@given(cases())
+def test_construction(case):
+ rrtype, left, _, _ = case
+ response = server.command(
+ proto.Request(construct=proto.Construct(type=rrtype, header=left))
+ )
+ assert response.result == proto.SUCCESS
+ check_header(response, contents(left), left, left, rrtype)
+
+
+@given(st.integers(65507, 65515))
+@example(65510)
+@example(65511)
+def test_size_boundary(length):
+ # Opaque RDATA avoids type-specific parsing; two length bytes count too.
+ header = proto.Header(records=[bytes(length)], offline=[False])
+ response = server.command(
+ proto.Request(construct=proto.Construct(type=OPAQUE_TYPE, header=header))
+ )
+ expected = proto.SUCCESS if length + 2 <= 65512 else proto.NO_SPACE
+ assert response.result == expected
+ if expected == proto.SUCCESS:
+ check_header(response, contents(header), header, header, OPAQUE_TYPE)
+
+
+@given(st.integers(32750, 32760))
+def test_merge_size_boundary(length):
+ left = proto.Header(records=[b"a" * length], offline=[False])
+ right = proto.Header(records=[b"b" * length], offline=[False])
+ response = server.command(
+ proto.Request(merge=proto.Merge(type=OPAQUE_TYPE, left=left, right=right))
+ )
+ expected = proto.SUCCESS if 2 * (length + 2) <= 65512 else proto.NO_SPACE
+ assert response.result == expected
+ if expected == proto.SUCCESS:
+ check_header(
+ response, contents(left) | contents(right), right, left, OPAQUE_TYPE
+ )
+
+
+@given(st.integers(2, 30), st.booleans())
+def test_overlapping_limit(count, force):
+ records = [bytes([192, 0, 2, i]) for i in range(count)]
+ left = proto.Header(records=records[:-1], offline=[False] * (count - 1))
+ right = proto.Header(records=records[-2:], offline=[False] * 2)
+ response = server.command(
+ proto.Request(
+ merge=proto.Merge(
+ type=dns.rdatatype.A,
+ left=left,
+ right=right,
+ limit=count,
+ flags=1 if force else 0,
+ )
+ )
+ )
+ assert response.result == proto.SUCCESS
+ check_header(
+ response, contents(left) | contents(right), right, left, dns.rdatatype.A
+ )
+ response = server.command(
+ proto.Request(
+ merge=proto.Merge(
+ type=dns.rdatatype.A,
+ left=right,
+ right=right,
+ limit=2,
+ flags=1 if force else 0,
+ )
+ )
+ )
+ assert response.result == (proto.SUCCESS if force else proto.UNCHANGED)
+ if force:
+ check_header(response, contents(right), right, right, dns.rdatatype.A)
+
+
+@given(owners())
+@example(b"\x07ExAmPlE\x03com\x00")
+@example(b"\x07example\x03com\x00")
+def test_subtract_owner_case(owner):
+ left = proto.Header(
+ records=[b"\xc0\x00\x02\x01", b"\xc0\x00\x02\x02"], offline=[False, False]
+ )
+ if owner is not None:
+ left.owner = owner
+ right = proto.Header(records=[left.records[0]], offline=[False])
+ response = server.command(
+ proto.Request(
+ subtract=proto.Subtract(type=dns.rdatatype.A, left=left, right=right)
+ )
+ )
+ assert response.result == proto.SUCCESS
+ check_header(response, {left.records[1]: False}, left, left, dns.rdatatype.A)
+
+
+@given(st.integers(1, 20), st.integers(0, 21))
+def test_construction_limit(count, limit):
+ # Construction currently checks input count, before deduplication.
+ header = proto.Header(
+ records=[b"\xc0\x00\x02\x01"] * count, offline=[False] * count
+ )
+ response = server.command(
+ proto.Request(
+ construct=proto.Construct(type=dns.rdatatype.A, header=header, limit=limit)
+ )
+ )
+ expected = proto.TOO_MANY_RECORDS if limit and count > limit else proto.SUCCESS
+ assert response.result == expected
+ if expected == proto.SUCCESS:
+ check_header(response, contents(header), header, header, dns.rdatatype.A)
+
+
+@given(cases())
+def test_copy(case):
+ rrtype, left, _, _ = case
+ response = server.command(proto.Request(copy=proto.Copy(type=rrtype, header=left)))
+ assert response.result == proto.SUCCESS
+ # The copy constructor initializes a fresh header from rdataset fields;
+ # serial, case and resign metadata are deliberately not copied.
+ fresh = proto.Header(ttl=left.ttl, trust=left.trust)
+ check_header(response, contents(left), fresh, fresh, rrtype)
+ if left.HasField("owner"):
+ assert response.applied_owner == left.owner.lower()
+
+
+@given(cases(), dns_names())
+def test_set_owner_case(case, name):
+ rrtype, left, _, _ = case
+ header = proto.Header()
+ header.CopyFrom(left)
+ header.ClearField("owner")
+ owner = name.to_wire()
+ response = server.command(
+ proto.Request(
+ set_owner_case=proto.SetOwnerCase(type=rrtype, header=header, owner=owner)
+ )
+ )
+ assert response.result == proto.SUCCESS
+ header.owner = owner
+ check_header(response, contents(header), header, header, rrtype)
+
+
+if __name__ == "__main__":
+ with RestartingProtobufServer(
+ os.environ["VECHEADER_SERVER"], proto.Response
+ ) as server:
+ for test in (
+ test_construction,
+ test_merge,
+ test_subtract,
+ test_size_boundary,
+ test_merge_size_boundary,
+ test_overlapping_limit,
+ test_subtract_owner_case,
+ test_construction_limit,
+ test_copy,
+ test_set_owner_case,
+ ):
+ test()
+ print(f"{test.__name__}: passed", flush=True)
diff --git a/tests/hypothesis/vecheader_server.c b/tests/hypothesis/vecheader_server.c
new file mode 100644
index 0000000000..7dfd774881
--- /dev/null
+++ b/tests/hypothesis/vecheader_server.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/lib.h>
+#include <isc/mem.h>
+#include <isc/region.h>
+#include <isc/util.h>
+
+#include <dns/lib.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatatype.h>
+#include <dns/rdatavec.h>
+
+#include "rdatavec_p.h"
+#include "vecheader.pb-c.h"
+
+static void
+set_owner_case(dns_vecheader_t *header, const ProtobufCBinaryData *owner) {
+ dns_name_t name = DNS_NAME_INITEMPTY;
+ isc_region_t region = {
+ .base = owner->data,
+ .length = owner->len,
+ };
+ dns_name_fromregion(&name, ®ion);
+ dns_vecheader_setownercase(header, &name);
+}
+
+/* All input bytes are logical RDATA, never a serialized C header. */
+static isc_result_t
+make_header(isc_mem_t *mctx, const Vec__Header *input, dns_rdatatype_t type,
+ uint32_t limit, dns_vecheader_t **output) {
+ dns_rdatalist_t list = {
+ .rdata = ISC_LIST_INITIALIZER,
+ .link = ISC_LINK_INITIALIZER,
+ .type = type,
+ .covers = type == dns_rdatatype_rrsig ? dns_rdatatype_a : 0,
+ .rdclass = dns_rdataclass_in,
+ .ttl = input->ttl,
+ };
+ dns_rdataset_t set = DNS_RDATASET_INIT;
+ dns_rdata_t *records = calloc(input->n_records + 1, sizeof(*records));
+ isc_region_t region;
+ isc_result_t result = ISC_R_SUCCESS;
+ RUNTIME_CHECK(records != NULL);
+ RUNTIME_CHECK(input->n_offline == input->n_records);
+ memset(list.upper, 0xeb, sizeof(list.upper));
+ list.upper[0] &= ~0x01;
+ for (size_t i = 0; i < input->n_records; i++) {
+ /* protobuf-c uses NULL for empty bytes; comparators need a
+ * base. */
+ static unsigned char empty;
+ region = (isc_region_t){
+ .base = input->records[i].len == 0
+ ? &empty
+ : input->records[i].data,
+ .length = input->records[i].len,
+ };
+ dns_rdata_init(&records[i]);
+ dns_rdata_fromregion(&records[i], list.rdclass, type, ®ion);
+ if (input->offline[i]) {
+ records[i].flags |= DNS_RDATA_OFFLINE;
+ }
+ ISC_LIST_APPEND(list.rdata, &records[i], link);
+ }
+ dns_rdatalist_tordataset(&list, &set);
+ set.trust = input->trust;
+ if (input->n_records == 0) {
+ /* Empty headers represent tombstones, not empty rdatasets. */
+ *output = dns_vecheader_new(mctx);
+ (*output)->typepair = DNS_TYPEPAIR_VALUE(type, list.covers);
+ (*output)->ttl = input->ttl;
+ atomic_store(&(*output)->trust, input->trust);
+ } else {
+ result = dns_rdatavec_fromrdataset(&set, mctx, ®ion, limit);
+ if (result == ISC_R_SUCCESS) {
+ *output = (dns_vecheader_t *)region.base;
+ }
+ }
+ dns_rdataset_disassociate(&set);
+ free(records);
+ if (result != ISC_R_SUCCESS) {
+ return result;
+ }
+ (*output)->serial = input->serial;
+ (*output)->resign = input->resign_time;
+ if (input->resign) {
+ DNS_VECHEADER_SETATTR(*output, DNS_VECHEADERATTR_RESIGN);
+ }
+ if (input->has_owner) {
+ set_owner_case(*output, &input->owner);
+ }
+ return result;
+}
+
+static void
+write_response(Vec__Response *response) {
+ size_t length = vec__response__get_packed_size(response);
+ unsigned char *data = malloc(length);
+ uint32_t wire_length = htonl(length);
+ RUNTIME_CHECK(data != NULL);
+ vec__response__pack(response, data);
+ RUNTIME_CHECK(fwrite(&wire_length, 1, 4, stdout) == 4);
+ RUNTIME_CHECK(fwrite(data, 1, length, stdout) == length);
+ RUNTIME_CHECK(fflush(stdout) == 0);
+ free(data);
+}
+
+static Vec__Result
+result_to_proto(isc_result_t result) {
+ switch (result) {
+ case ISC_R_SUCCESS:
+ return VEC__RESULT__SUCCESS;
+ case DNS_R_TOOMANYRECORDS:
+ return VEC__RESULT__TOO_MANY_RECORDS;
+ case DNS_R_NOTEXACT:
+ return VEC__RESULT__NOT_EXACT;
+ case DNS_R_UNCHANGED:
+ return VEC__RESULT__UNCHANGED;
+ case DNS_R_SINGLETON:
+ return VEC__RESULT__SINGLETON;
+ case DNS_R_NXRRSET:
+ return VEC__RESULT__NXRRSET;
+ case ISC_R_NOSPACE:
+ return VEC__RESULT__NO_SPACE;
+ case ISC_R_FAILURE:
+ return VEC__RESULT__FAILURE;
+ default:
+ fprintf(stderr, "unexpected vecheader result: %s\n",
+ isc_result_toid(result));
+ abort();
+ }
+}
+
+static void
+reply(isc_result_t result, dns_vecheader_t *header, const Vec__Header *owner) {
+ Vec__Response response = {
+ .base = PROTOBUF_C_MESSAGE_INIT(&vec__response__descriptor),
+ .result = result_to_proto(result),
+ };
+ unsigned char namebytes[255];
+ if (header == NULL) {
+ write_response(&response);
+ return;
+ }
+ Vec__Header value = {
+ .base = PROTOBUF_C_MESSAGE_INIT(&vec__header__descriptor),
+ .has_ttl = true,
+ .ttl = header->ttl,
+ .has_trust = true,
+ .trust = atomic_load(&header->trust),
+ .has_serial = true,
+ .serial = header->serial,
+ .has_resign = true,
+ .resign = RESIGN(header),
+ .has_resign_time = true,
+ .resign_time = header->resign,
+ .records = calloc(header->count + 1, sizeof(*value.records)),
+ .offline = calloc(header->count + 1, sizeof(*value.offline)),
+ };
+ response = (Vec__Response){
+ .base = PROTOBUF_C_MESSAGE_INIT(&vec__response__descriptor),
+ .result = result_to_proto(result),
+ .header = &value,
+ .has_count = true,
+ .count = dns_rdatavec_count(header),
+ .has_raw_length = true,
+ .raw_length = header->raw_length,
+ .has_size = true,
+ .size = dns_rdatavec_size(header),
+ .has_header_size = true,
+ .header_size = sizeof(*header),
+ .has_type = true,
+ .type = DNS_TYPEPAIR_TYPE(header->typepair),
+ .has_covers = true,
+ .covers = DNS_TYPEPAIR_COVERS(header->typepair),
+ .has_case_set = true,
+ .case_set = CASESET(header),
+ .has_case_lower = true,
+ .case_lower = CASEFULLYLOWER(header),
+ .has_bitmap = true,
+ .bitmap = {
+ .len = sizeof(header->upper),
+ .data = header->upper,
+ },
+ };
+ RUNTIME_CHECK(value.records != NULL && value.offline != NULL);
+ rdatavec_iter_t iter;
+ DNS_VECHEADER_FOREACH(&iter, header, dns_rdataclass_in) {
+ dns_rdata_t data = DNS_RDATA_INIT;
+ vecheader_current(&iter, &data);
+ dns_rdata_t again = DNS_RDATA_INIT;
+ vecheader_current(&iter, &again);
+ RUNTIME_CHECK(data.data == again.data &&
+ data.length == again.length &&
+ data.flags == again.flags);
+ RUNTIME_CHECK(value.n_records < header->count);
+ value.records[value.n_records++] = (ProtobufCBinaryData){
+ .len = data.length,
+ .data = data.data,
+ };
+ value.offline[value.n_offline++] = (data.flags &
+ DNS_RDATA_OFFLINE) != 0;
+ }
+ RUNTIME_CHECK(value.n_records == header->count);
+ RUNTIME_CHECK(vecheader_next(&iter) == ISC_R_NOMORE);
+ RUNTIME_CHECK(vecheader_next(&iter) == ISC_R_NOMORE);
+ if (owner->has_owner) {
+ RUNTIME_CHECK(owner->owner.len <= sizeof(namebytes));
+ memmove(namebytes, owner->owner.data, owner->owner.len);
+ isc_region_t region = {
+ .base = namebytes,
+ .length = owner->owner.len,
+ };
+ dns_name_t name = DNS_NAME_INITEMPTY;
+ dns_name_fromregion(&name, ®ion);
+ RUNTIME_CHECK(dns_name_downcase(&name, &name) == ISC_R_SUCCESS);
+ dns_rdataset_t set = {
+ .magic = DNS_RDATASET_MAGIC,
+ .link = ISC_LINK_INITIALIZER,
+ .methods = &dns_rdatavec_rdatasetmethods,
+ .vec.header = dns_vecheader_ref(header),
+ };
+ dns_rdataset_getownercase(&set, &name);
+ dns_rdataset_disassociate(&set);
+ response.has_applied_owner = true;
+ response.applied_owner = (ProtobufCBinaryData){
+ .len = name.length,
+ .data = name.ndata,
+ };
+ }
+ write_response(&response);
+ free(value.records);
+ free(value.offline);
+}
+
+static void
+execute(const Vec__Request *request) {
+ isc_mem_t *mctx = NULL;
+ dns_vecheader_t *left = NULL, *right = NULL, *output = NULL;
+ const Vec__Header *owner = NULL;
+ Vec__Header owner_input;
+ isc_result_t result = ISC_R_FAILURE;
+ isc_mem_create("vecheader-test", &mctx);
+ switch (request->command_case) {
+ case VEC__REQUEST__COMMAND_CONSTRUCT: {
+ const Vec__Construct *command = request->construct;
+ owner = command->header;
+ result = make_header(mctx, command->header, command->type,
+ command->limit, &output);
+ break;
+ }
+ case VEC__REQUEST__COMMAND_COPY: {
+ const Vec__Copy *command = request->copy;
+ owner = command->header;
+ result = make_header(mctx, command->header, command->type, 0,
+ &left);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ dns_rdataset_t set = {
+ .magic = DNS_RDATASET_MAGIC,
+ .link = ISC_LINK_INITIALIZER,
+ .methods = &dns_rdatavec_rdatasetmethods,
+ .vec.header = dns_vecheader_ref(left),
+ .type = command->type,
+ .covers = DNS_TYPEPAIR_COVERS(left->typepair),
+ .rdclass = dns_rdataclass_in,
+ .ttl = left->ttl,
+ .trust = atomic_load(&left->trust),
+ };
+ isc_region_t region;
+ result = dns_rdatavec_fromrdataset(&set, mctx, ®ion, 0);
+ if (result == ISC_R_SUCCESS) {
+ output = (dns_vecheader_t *)region.base;
+ }
+ dns_rdataset_disassociate(&set);
+ break;
+ }
+ case VEC__REQUEST__COMMAND_MERGE: {
+ const Vec__Merge *command = request->merge;
+ owner = command->left;
+ result = make_header(mctx, command->left, command->type, 0,
+ &left);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ result = make_header(mctx, command->right, command->type, 0,
+ &right);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ result = dns_rdatavec_merge(
+ left, right, mctx, dns_rdataclass_in, command->type,
+ command->flags, command->limit, &output);
+ break;
+ }
+ case VEC__REQUEST__COMMAND_SUBTRACT: {
+ const Vec__Subtract *command = request->subtract;
+ owner = command->left;
+ result = make_header(mctx, command->left, command->type, 0,
+ &left);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ result = make_header(mctx, command->right, command->type, 0,
+ &right);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ result = dns_rdatavec_subtract(left, right, mctx,
+ dns_rdataclass_in, command->type,
+ command->flags, &output);
+ break;
+ }
+ case VEC__REQUEST__COMMAND_SET_OWNER_CASE: {
+ const Vec__SetOwnerCase *command = request->set_owner_case;
+ RUNTIME_CHECK(!command->header->has_owner);
+ owner_input = *command->header;
+ result = make_header(mctx, &owner_input, command->type, 0,
+ &output);
+ owner_input.has_owner = true;
+ owner_input.owner = command->owner;
+ owner = &owner_input;
+ if (result == ISC_R_SUCCESS) {
+ set_owner_case(output, &command->owner);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ reply(result, output, owner);
+ if (output != NULL) {
+ dns_vecheader_unref(output);
+ }
+ if (right != NULL) {
+ dns_vecheader_unref(right);
+ }
+ if (left != NULL) {
+ dns_vecheader_unref(left);
+ }
+ RUNTIME_CHECK(isc_mem_inuse(mctx) == 0);
+ RUNTIME_CHECK(isc_mem_references(mctx) == 1);
+ isc_mem_detach(&mctx);
+}
+
+int
+main(void) {
+ uint32_t wire_length;
+ while (true) {
+ size_t n = fread(&wire_length, 1, 4, stdin);
+ if (n == 0 && feof(stdin)) {
+ break;
+ }
+ RUNTIME_CHECK(n == 4);
+ size_t length = ntohl(wire_length);
+ RUNTIME_CHECK(length > 0 && length <= 4 * 1024 * 1024);
+ unsigned char *data = malloc(length);
+ RUNTIME_CHECK(data != NULL);
+ RUNTIME_CHECK(fread(data, 1, length, stdin) == length);
+ Vec__Request *request = vec__request__unpack(NULL, length,
+ data);
+ RUNTIME_CHECK(request != NULL);
+ execute(request);
+ vec__request__free_unpacked(request, NULL);
+ free(data);
+ }
+ return 0;
+}