Commit b72f858b76 for bind

commit b72f858b76946ed6ae9728d76f4e712e52681bee
Author: Alessio Podda <alessio@isc.org>
Date:   Mon Aug 31 12:12:33 2026 +0200

    Add dns_fixedname_fromnsec3hash

    Add a function that creates a fixedname directly from the origin
    and the SHA1 hash, saving intermediate copies.

diff --git a/lib/dns/fixedname.c b/lib/dns/fixedname.c
index c9c3f28d36..f8d9a49480 100644
--- a/lib/dns/fixedname.c
+++ b/lib/dns/fixedname.c
@@ -13,6 +13,9 @@

 /*! \file */

+#include <isc/base32.h>
+#include <isc/util.h>
+
 #include <dns/fixedname.h>

 void
@@ -37,3 +40,37 @@ dns_fixedname_initname(dns_fixedname_t *fixed) {
 	dns_fixedname_init(fixed);
 	return dns_fixedname_name(fixed);
 }
+
+isc_result_t
+dns_fixedname_fromnsec3hash(dns_fixedname_t *fixed, const unsigned char *hash,
+			    size_t hash_length, const dns_name_t *origin) {
+	isc_region_t origin_region;
+	isc_region_t source = {
+		.base = UNCONST(hash),
+		.length = (unsigned int)hash_length,
+	};
+
+	REQUIRE(fixed != NULL);
+	REQUIRE(hash != NULL);
+	REQUIRE(DNS_NAME_VALID(origin));
+
+	isc_buffer_clear(&fixed->buffer);
+	isc_buffer_putuint8(&fixed->buffer,
+			    (uint8_t)((hash_length * 8U + 4U) / 5U));
+	RETERR(isc_base32hexnp_totext(&source, -1, "", &fixed->buffer));
+
+	dns_name_toregion(origin, &origin_region);
+	RETERR(isc_buffer_copyregion(&fixed->buffer, &origin_region));
+
+	fixed->name = (dns_name_t){
+		.magic = DNS_NAME_MAGIC,
+		.attributes = { .absolute = true },
+		.ndata = fixed->data,
+		.length = (uint8_t)isc_buffer_usedlength(&fixed->buffer),
+		.buffer = &fixed->buffer,
+		.link = ISC_LINK_INITIALIZER,
+		.list = ISC_LIST_INITIALIZER,
+	};
+
+	return ISC_R_SUCCESS;
+}
diff --git a/lib/dns/include/dns/fixedname.h b/lib/dns/include/dns/fixedname.h
index ce3e735177..2b5d7da3e8 100644
--- a/lib/dns/include/dns/fixedname.h
+++ b/lib/dns/include/dns/fixedname.h
@@ -74,3 +74,19 @@ dns_fixedname_name(dns_fixedname_t *fixed);

 dns_name_t *
 dns_fixedname_initname(dns_fixedname_t *fixed);
+
+isc_result_t
+dns_fixedname_fromnsec3hash(dns_fixedname_t *fixed, const unsigned char *hash,
+			    size_t hash_length, const dns_name_t *origin);
+/*%<
+ * Initialize 'fixed' with an NSEC3 owner name made from the base32hex
+ * encoding of 'hash', followed by 'origin'.
+ *
+ * Requires:
+ *\li	'fixed' is initialized.
+ *\li	'hash' is non-NULL.
+ *\li	'origin' is a valid absolute name.
+ *
+ * Returns:
+ *\li	#ISC_R_NOSPACE when the resulting name does not fit in 'fixed'.
+ */
diff --git a/lib/dns/include/dns/nsec3.h b/lib/dns/include/dns/nsec3.h
index df8810caee..5751fd36d7 100644
--- a/lib/dns/include/dns/nsec3.h
+++ b/lib/dns/include/dns/nsec3.h
@@ -94,6 +94,11 @@ dns_nsec3_hashname(dns_fixedname_t *result,
 /*%<
  * Make a hashed domain name from an unhashed one. If rethash is not NULL
  * the raw hash is stored there.
+ *
+ * Requires:
+ *\li	'result' is initialized.
+ *\li	'name' is a valid name.
+ *\li	'origin' is a valid absolute name.
  */

 unsigned int
diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c
index 0906c7dd3d..9211026387 100644
--- a/lib/dns/nsec3.c
+++ b/lib/dns/nsec3.c
@@ -232,11 +232,8 @@ dns_nsec3_hashname(dns_fixedname_t *result,
 		   unsigned int iterations, const unsigned char *salt,
 		   size_t saltlength) {
 	unsigned char hash[NSEC3_MAX_HASH_LENGTH];
-	unsigned char nametext[DNS_NAME_FORMATSIZE];
 	dns_fixedname_t fixed;
 	dns_name_t *downcased;
-	isc_buffer_t namebuffer;
-	isc_region_t region;
 	size_t len;

 	if (rethash == NULL) {
@@ -258,16 +255,7 @@ dns_nsec3_hashname(dns_fixedname_t *result,

 	SET_IF_NOT_NULL(hash_length, len);

-	/* convert the hash to base32hex non-padded */
-	region.base = rethash;
-	region.length = (unsigned int)len;
-	isc_buffer_init(&namebuffer, nametext, sizeof nametext);
-	isc_base32hexnp_totext(&region, 1, "", &namebuffer);
-
-	/* convert the hex to a domain name */
-	dns_fixedname_init(result);
-	return dns_name_fromtext(dns_fixedname_name(result), &namebuffer,
-				 origin, 0);
+	return dns_fixedname_fromnsec3hash(result, rethash, len, origin);
 }

 unsigned int
diff --git a/tests/dns/nsec3_test.c b/tests/dns/nsec3_test.c
index 87002c973e..319dda0f59 100644
--- a/tests/dns/nsec3_test.c
+++ b/tests/dns/nsec3_test.c
@@ -24,6 +24,7 @@
 #include <cmocka.h>

 #include <isc/lib.h>
+#include <isc/md.h>
 #include <isc/string.h>
 #include <isc/util.h>

@@ -254,10 +255,40 @@ ISC_RUN_TEST_IMPL(nsec3param_salttotext) {
 	}
 }

+/* Check NSEC3 hash and owner-name generation against RFC 5155 Appendix A. */
+ISC_RUN_TEST_IMPL(hashname) {
+	static const unsigned char salt[] = { 0xaa, 0xbb, 0xcc, 0xdd };
+	unsigned char hash[NSEC3_MAX_HASH_LENGTH];
+	dns_fixedname_t expected;
+	dns_fixedname_t name;
+	dns_fixedname_t origin;
+	dns_fixedname_t result;
+	size_t hash_length = 0;
+
+	UNUSED(state);
+
+	dns_test_namefromstring("example.", &name);
+	dns_test_namefromstring("example.", &origin);
+	dns_test_namefromstring("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM.example.",
+				&expected);
+	dns_test_namefromstring("stale.example.", &result);
+
+	assert_int_equal(dns_nsec3_hashname(&result, hash, &hash_length,
+					    dns_fixedname_name(&name),
+					    dns_fixedname_name(&origin),
+					    dns_hash_sha1, 12, salt,
+					    sizeof(salt)),
+			 ISC_R_SUCCESS);
+	assert_int_equal(hash_length, ISC_SHA1_DIGESTLENGTH);
+	assert_true(dns_name_equal(dns_fixedname_name(&result),
+				   dns_fixedname_name(&expected)));
+}
+
 ISC_TEST_LIST_START
 ISC_TEST_ENTRY(activex)
 ISC_TEST_ENTRY(max_iterations)
 ISC_TEST_ENTRY(nsec3param_salttotext)
+ISC_TEST_ENTRY(hashname)
 ISC_TEST_LIST_END

 ISC_TEST_MAIN