Commit 1e79f9b2d2 for bind
commit 1e79f9b2d2d09dd0546657763a321b8a1fcd1100
Author: Alessio Podda <alessio@isc.org>
Date: Mon Aug 17 17:31:01 2026 +0200
Linear rdatavec merge
Since rdatavecs are always sorted, we can merge them in linear time
with a zip merge. The previous algorithm was quadratic.
diff --git a/lib/dns/rdatavec.c b/lib/dns/rdatavec.c
index 335737520d..81b7a8c341 100644
--- a/lib/dns/rdatavec.c
+++ b/lib/dns/rdatavec.c
@@ -454,6 +454,7 @@ typedef struct vecinfo {
unsigned char *pos;
dns_rdata_t rdata;
bool dup;
+ uint32_t length;
} vecinfo_t;
isc_result_t
@@ -464,7 +465,7 @@ dns_rdatavec_merge(dns_vecheader_t *oheader, dns_vecheader_t *nheader,
isc_result_t result = ISC_R_SUCCESS;
unsigned char *ocurrent = NULL, *ncurrent = NULL, *tcurrent = NULL;
unsigned int ocount, ncount, tcount = 0;
- uint32_t tlength;
+ size_t rlength = 0, tlength = 0;
vecinfo_t *oinfo = NULL, *ninfo = NULL;
size_t o = 0, n = 0;
@@ -483,12 +484,6 @@ dns_rdatavec_merge(dns_vecheader_t *oheader, dns_vecheader_t *nheader,
return DNS_R_TOOMANYRECORDS;
}
- /*
- * Figure out the target length. Start with the header,
- * plus 2 octets for the count.
- */
- tlength = header_size(oheader) + 2;
-
/*
* Allocate both info arrays up front so the cleanup path is
* always safe to call regardless of where we exit.
@@ -497,67 +492,59 @@ dns_rdatavec_merge(dns_vecheader_t *oheader, dns_vecheader_t *nheader,
ninfo = isc_mem_cget(mctx, ncount, sizeof(struct vecinfo));
/*
- * Gather the rdatas in the old vec and add their lengths to
- * the larget length.
+ * Gather the rdatas in the old vec.
*/
for (size_t i = 0; i < ocount; i++) {
oinfo[i].pos = ocurrent;
dns_rdata_init(&oinfo[i].rdata);
rdata_from_vecitem(&ocurrent, rdclass, type, &oinfo[i].rdata);
- tlength += (uint32_t)(ocurrent - oinfo[i].pos);
- if (tlength - header_size(oheader) - 2 > DNS_RDATA_MAXLENGTH) {
- CLEANUP(ISC_R_NOSPACE);
- }
+ oinfo[i].length = (uint32_t)(ocurrent - oinfo[i].pos);
}
/*
- * Then add the length of rdatas in the new vec that aren't
- * duplicated in the old vec.
+ * Gather the rdatas in the new vec.
*/
for (size_t i = 0; i < ncount; i++) {
ninfo[i].pos = ncurrent;
dns_rdata_init(&ninfo[i].rdata);
rdata_from_vecitem(&ncurrent, rdclass, type, &ninfo[i].rdata);
+ ninfo[i].length = (uint32_t)(ncurrent - ninfo[i].pos);
+ }
- for (size_t j = 0; j < ocount; j++) {
- if (oinfo[j].dup) {
- /*
- * This was already found to be
- * duplicated; no need to compare
- * it again.
- */
- continue;
- }
+ /*
+ * The old and new vecs are both sorted in DNSSEC order, so
+ * duplicates can be found with a linear walk.
+ */
+ for (size_t oi = 0, ni = 0; oi < ocount && ni < ncount;) {
+ int cmp = dns_rdata_compare(&oinfo[oi].rdata,
+ &ninfo[ni].rdata);
+ bool is_dup = cmp == 0;
+
+ oinfo[oi].dup = ninfo[ni].dup = is_dup;
+ oi += cmp <= 0;
+ ni += cmp >= 0;
+ }
- if (dns_rdata_compare(&oinfo[j].rdata,
- &ninfo[i].rdata) == 0)
- {
- /*
- * Found a dup. Mark the old copy as a
- * duplicate so we don't check it again;
- * mark the new copy as a duplicate so we
- * don't copy it to the target.
- */
- oinfo[j].dup = ninfo[i].dup = true;
- break;
- }
- }
+ /*
+ * Compute the target length and count. All old rdatas are copied;
+ * only new rdatas that are not duplicated in the old vec are copied.
+ */
+ for (size_t i = 0; i < ocount; i++) {
+ rlength += oinfo[i].length;
+ }
- if (ninfo[i].dup) {
- continue;
- }
+ for (size_t i = 0; i < ncount; i++) {
+ bool not_dup = !ninfo[i].dup;
+ rlength += not_dup * ninfo[i].length;
+ tcount += not_dup;
+ }
- /*
- * We will be copying this item to the target, so
- * add its length to tlength and increment tcount.
- */
- tlength += (uint32_t)(ncurrent - ninfo[i].pos);
- if (tlength - header_size(oheader) - 2 > DNS_RDATA_MAXLENGTH) {
- CLEANUP(ISC_R_NOSPACE);
- }
- tcount++;
+ if (rlength > DNS_RDATA_MAXLENGTH) {
+ CLEANUP(ISC_R_NOSPACE);
}
+ tlength = header_size(oheader) + 2 + rlength;
+
/*
* If the EXACT flag is set, there can't be any rdata in
* the new vec that was also in the old. If tcount is less