Commit 19d4402788 for bind
commit 19d4402788098dee623b30822c52fa94e18f99b8
Author: Alessio Podda <alessio@isc.org>
Date: Mon Aug 31 15:25:04 2026 +0200
Timestamp parsing optimization
Add a more optimized timestamp parsing algorithm as it is a bottleneck
for rrsig text parsing.
diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c
index 7ce316b187..439d7cd610 100644
--- a/lib/dns/dst_api.c
+++ b/lib/dns/dst_api.c
@@ -1704,7 +1704,8 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp) {
BADTOKEN();
}
- CHECK(dns_time32_fromtext(DST_AS_STR(token), &when));
+ CHECK(dns_time32_fromregion(token.value.as_textregion,
+ &when));
dst_key_settime(*keyp, tag, when);
goto next;
diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c
index 8fbb00fa61..6f649d691a 100644
--- a/lib/dns/dst_parse.c
+++ b/lib/dns/dst_parse.c
@@ -533,7 +533,8 @@ dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex,
goto cleanup;
}
- CHECK(dns_time32_fromtext(DST_AS_STR(token), &when));
+ CHECK(dns_time32_fromregion(token.value.as_textregion,
+ &when));
dst_key_settime(key, tag, when);
diff --git a/lib/dns/include/dns/time.h b/lib/dns/include/dns/time.h
index 866d3fcebd..9f8f422fd8 100644
--- a/lib/dns/include/dns/time.h
+++ b/lib/dns/include/dns/time.h
@@ -22,6 +22,7 @@
#include <inttypes.h>
#include <isc/buffer.h>
+#include <isc/region.h>
/***
*** Functions
@@ -35,6 +36,13 @@ dns_time64_fromtext(const char *source, int64_t *target);
* Store the count at 'target'.
*/
+isc_result_t
+dns_time64_fromregion(isc_textregion_t source, int64_t *target);
+/*%<
+ * Like dns_time64_fromtext, but read the text from the length-delimited
+ * region 'source'.
+ */
+
isc_result_t
dns_time32_fromtext(const char *source, uint32_t *target);
/*%<
@@ -42,6 +50,13 @@ dns_time32_fromtext(const char *source, uint32_t *target);
* as per RFC2535.
*/
+isc_result_t
+dns_time32_fromregion(isc_textregion_t source, uint32_t *target);
+/*%<
+ * Like dns_time64_fromregion, but returns the second count modulo 2^32 as per
+ * RFC2535.
+ */
+
isc_result_t
dns_time64_totext(int64_t value, isc_buffer_t *target);
/*%<
diff --git a/lib/dns/master.c b/lib/dns/master.c
index 0b8db4f181..cc8b0d5a9c 100644
--- a/lib/dns/master.c
+++ b/lib/dns/master.c
@@ -1206,8 +1206,9 @@ load_text(dns_loadctx_t *lctx) {
isc_stdtime_t dump_time;
isc_stdtime_t current_time = isc_stdtime_now();
GETTOKEN(lctx->lex, 0, &token, false);
- result = dns_time64_fromtext(DNS_AS_STR(token),
- &dump_time64);
+ result = dns_time64_fromregion(
+ token.value.as_textregion,
+ &dump_time64);
if (MANYERRS(lctx, result)) {
SETRESULT(lctx, result);
LOGIT(result);
diff --git a/lib/dns/rdata/generic/keydata_65533.c b/lib/dns/rdata/generic/keydata_65533.c
index ee8cf0307a..cffe24de60 100644
--- a/lib/dns/rdata/generic/keydata_65533.c
+++ b/lib/dns/rdata/generic/keydata_65533.c
@@ -40,19 +40,19 @@ fromtext_keydata(ARGS_FROMTEXT) {
/* refresh timer */
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &refresh));
+ RETTOK(dns_time32_fromregion(token.value.as_textregion, &refresh));
RETERR(uint32_tobuffer(refresh, target));
/* add hold-down */
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &addhd));
+ RETTOK(dns_time32_fromregion(token.value.as_textregion, &addhd));
RETERR(uint32_tobuffer(addhd, target));
/* remove hold-down */
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &removehd));
+ RETTOK(dns_time32_fromregion(token.value.as_textregion, &removehd));
RETERR(uint32_tobuffer(removehd, target));
/* flags */
diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c
index a4f9b4916b..11067ae672 100644
--- a/lib/dns/rdata/generic/rrsig_46.c
+++ b/lib/dns/rdata/generic/rrsig_46.c
@@ -89,8 +89,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
*/
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- if (strlen(DNS_AS_STR(token)) <= 10U && *DNS_AS_STR(token) != '-' &&
- *DNS_AS_STR(token) != '+')
+ if (token.value.as_textregion.length <= 10U &&
+ *DNS_AS_STR(token) != '-' && *DNS_AS_STR(token) != '+')
{
char *end;
unsigned long u;
@@ -105,7 +105,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
}
time_expire = u;
} else {
- RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_expire));
+ RETTOK(dns_time32_fromregion(token.value.as_textregion,
+ &time_expire));
}
RETERR(uint32_tobuffer(time_expire, target));
@@ -114,8 +115,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
*/
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- if (strlen(DNS_AS_STR(token)) <= 10U && *DNS_AS_STR(token) != '-' &&
- *DNS_AS_STR(token) != '+')
+ if (token.value.as_textregion.length <= 10U &&
+ *DNS_AS_STR(token) != '-' && *DNS_AS_STR(token) != '+')
{
char *end;
unsigned long u;
@@ -130,7 +131,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
}
time_signed = u;
} else {
- RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_signed));
+ RETTOK(dns_time32_fromregion(token.value.as_textregion,
+ &time_signed));
}
RETERR(uint32_tobuffer(time_signed, target));
diff --git a/lib/dns/rdata/generic/sig_24.c b/lib/dns/rdata/generic/sig_24.c
index 35cede14d8..71cde4686b 100644
--- a/lib/dns/rdata/generic/sig_24.c
+++ b/lib/dns/rdata/generic/sig_24.c
@@ -85,7 +85,7 @@ fromtext_sig(ARGS_FROMTEXT) {
*/
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_expire));
+ RETTOK(dns_time32_fromregion(token.value.as_textregion, &time_expire));
RETERR(uint32_tobuffer(time_expire, target));
/*
@@ -93,7 +93,7 @@ fromtext_sig(ARGS_FROMTEXT) {
*/
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_signed));
+ RETTOK(dns_time32_fromregion(token.value.as_textregion, &time_signed));
RETERR(uint32_tobuffer(time_signed, target));
/*
diff --git a/lib/dns/skr.c b/lib/dns/skr.c
index 77b2bed723..cf057d27cb 100644
--- a/lib/dns/skr.c
+++ b/lib/dns/skr.c
@@ -286,7 +286,8 @@ dns_skr_read(isc_mem_t *mctx, const char *filename, dns_name_t *origin,
}
/* Create new bundle */
- CHECK(dns_time32_fromtext(STR(token), &bundle_id));
+ CHECK(dns_time32_fromregion(token.value.as_textregion,
+ &bundle_id));
bundle = NULL;
skrbundle_create(mctx, (isc_stdtime_t)bundle_id,
&bundle);
diff --git a/lib/dns/time.c b/lib/dns/time.c
index 5520ae9df8..574f137d12 100644
--- a/lib/dns/time.c
+++ b/lib/dns/time.c
@@ -13,7 +13,6 @@
/*! \file */
-#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
#include <time.h>
@@ -29,6 +28,31 @@
static const int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+static int64_t
+date_to_days(uint32_t year, uint32_t month, uint32_t day) {
+ /*
+ * This is the Neri-Schneider calendar algorithm from "Euclidean affine
+ * functions and their application to calendar algorithms".
+ *
+ * Map January and February to the end of the preceding year. Shift
+ * the calendar by one 400-year cycle so that dates in year zero can
+ * still be calculated using unsigned arithmetic.
+ */
+ const uint32_t jan_feb = month <= 2U;
+ const uint32_t y = year + 400U - jan_feb;
+ const uint32_t m = month + 12U * jan_feb;
+ const uint32_t century = y / 100U;
+ const uint32_t days_to_year = 1461U * y / 4U - century + century / 4U;
+ const uint32_t days_to_month = (979U * m - 2919U) / 32U;
+ const uint32_t shifted_days = days_to_year + days_to_month + day - 1U;
+
+ /*
+ * 719468 selects the Unix epoch; 146097 compensates for the 400-year
+ * shift above.
+ */
+ return (int64_t)shifted_days - (719468LL + 146097LL);
+}
+
isc_result_t
dns_time64_totext(int64_t t, isc_buffer_t *target) {
struct tm tm;
@@ -127,70 +151,70 @@ dns_time32_totext(uint32_t value, isc_buffer_t *target) {
return dns_time64_totext(dns_time64_from32(value), target);
}
-isc_result_t
-dns_time64_fromtext(const char *source, int64_t *target) {
- int year, month, day, hour, minute, second;
- int64_t value;
- int secs;
- int i;
-
-#define RANGE(min, max, value) \
- do { \
- if (value < (min) || value > (max)) \
- return ((ISC_R_RANGE)); \
- } while (0)
+static isc_result_t
+time64_fromtext(const char *source, size_t length, int64_t *target) {
+ uint32_t digits[14];
+ uint32_t year, month, day, hour, minute, second;
+ uint32_t month_days;
+ unsigned int i;
- if (strlen(source) != 14U) {
+ if (length != ARRAY_SIZE(digits)) {
return DNS_R_SYNTAX;
}
- /*
- * Confirm the source only consists digits. sscanf() allows some
- * minor exceptions.
- */
- for (i = 0; i < 14; i++) {
- if (!isdigit((unsigned char)source[i])) {
+ for (i = 0; i < ARRAY_SIZE(digits); i++) {
+ digits[i] = (unsigned char)source[i] - (unsigned int)'0';
+ if (digits[i] > 9U) {
return DNS_R_SYNTAX;
}
}
- if (sscanf(source, "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour,
- &minute, &second) != 6)
+
+ /* "YYYYxxxxxxxxxx" */
+ year = digits[0] * 1000U + digits[1] * 100U + digits[2] * 10U +
+ digits[3];
+ /* "xxxxMMxxxxxxxx" */
+ month = digits[4] * 10U + digits[5];
+ /* "xxxxxxDDxxxxxx" */
+ day = digits[6] * 10U + digits[7];
+ /* "xxxxxxxxHHxxxx" */
+ hour = digits[8] * 10U + digits[9];
+ /* "xxxxxxxxxxMMxx" */
+ minute = digits[10] * 10U + digits[11];
+ /* "xxxxxxxxxxxxSS" */
+ second = digits[12] * 10U + digits[13];
+
+ if (month - 1U >= ARRAY_SIZE(days)) {
+ return ISC_R_RANGE;
+ }
+ month_days = days[month - 1U] +
+ ((month == 2U && is_leap(year)) ? 1U : 0U);
+ if (day - 1U >= month_days || hour > 23U || minute > 59U ||
+ second > 60U) /* 60 == leap second. */
{
- return DNS_R_SYNTAX;
+ return ISC_R_RANGE;
}
- RANGE(0, 9999, year);
- RANGE(1, 12, month);
- RANGE(1, days[month - 1] + ((month == 2 && is_leap(year)) ? 1 : 0),
- day);
+ *target = date_to_days(year, month, day) * 86400LL + hour * 3600U +
+ minute * 60U + second;
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+dns_time64_fromregion(isc_textregion_t source, int64_t *target) {
+ return time64_fromtext(source.base, source.length, target);
+}
- RANGE(0, 23, hour);
- RANGE(0, 59, minute);
- RANGE(0, 60, second); /* 60 == leap second. */
+isc_result_t
+dns_time64_fromtext(const char *source, int64_t *target) {
+ return time64_fromtext(source, strlen(source), target);
+}
- /*
- * Calculate seconds from epoch.
- * Note: this uses a idealized calendar.
- */
- value = second + (60 * minute) + (3600 * hour) + ((day - 1) * 86400);
- for (i = 0; i < (month - 1); i++) {
- value += days[i] * 86400;
- }
- if (is_leap(year) && month > 2) {
- value += 86400;
- }
- if (year < 1970) {
- for (i = 1969; i >= year; i--) {
- secs = (is_leap(i) ? 366 : 365) * 86400;
- value -= secs;
- }
- } else {
- for (i = 1970; i < year; i++) {
- secs = (is_leap(i) ? 366 : 365) * 86400;
- value += secs;
- }
- }
+isc_result_t
+dns_time32_fromregion(isc_textregion_t source, uint32_t *target) {
+ int64_t value64;
+
+ RETERR(dns_time64_fromregion(source, &value64));
+ *target = (uint32_t)value64;
- *target = value;
return ISC_R_SUCCESS;
}
diff --git a/lib/dns/view.c b/lib/dns/view.c
index c82d3358be..22c28f00ff 100644
--- a/lib/dns/view.c
+++ b/lib/dns/view.c
@@ -1706,7 +1706,7 @@ dns_view_loadnta(dns_view_t *view) {
for (;;) {
int options = (ISC_LEXOPT_EOL | ISC_LEXOPT_EOF);
- char *name, *type, *timestamp;
+ char *name, *type;
size_t len;
dns_fixedname_t fn;
const dns_name_t *ntaname;
@@ -1753,8 +1753,7 @@ dns_view_loadnta(dns_view_t *view) {
if (token.type != isc_tokentype_string) {
CLEANUP(ISC_R_UNEXPECTEDTOKEN);
}
- timestamp = TSTR(token);
- CHECK(dns_time32_fromtext(timestamp, &t));
+ CHECK(dns_time32_fromregion(token.value.as_textregion, &t));
CHECK(isc_lex_gettoken(lex, options, &token));
if (token.type != isc_tokentype_eol &&
diff --git a/tests/dns/time_test.c b/tests/dns/time_test.c
index 8002c0ed21..2a383e01a8 100644
--- a/tests/dns/time_test.c
+++ b/tests/dns/time_test.c
@@ -159,6 +159,65 @@ ISC_RUN_TEST_IMPL(some_ago) {
assert_int_equal(when, test_time);
}
+ISC_RUN_TEST_IMPL(fromregion) {
+ char text[] = "x19700101000000y";
+ isc_textregion_t source = {
+ .base = &text[1],
+ .length = 14,
+ };
+ int64_t when;
+
+ UNUSED(state);
+
+ assert_int_equal(dns_time64_fromregion(source, &when), ISC_R_SUCCESS);
+ assert_int_equal(when, 0);
+}
+
+ISC_RUN_TEST_IMPL(fromtext_boundaries) {
+ static const struct {
+ const char *text;
+ int64_t when;
+ } tests[] = {
+ { "00000101000000", -62167219200LL },
+ { "19691231235959", -1 },
+ { "19700101000000", 0 },
+ { "99991231235959", 253402300799LL },
+ };
+ int64_t when;
+
+ UNUSED(state);
+
+ for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
+ assert_int_equal(dns_time64_fromtext(tests[i].text, &when),
+ ISC_R_SUCCESS);
+ assert_int_equal(when, tests[i].when);
+ }
+}
+
+ISC_RUN_TEST_IMPL(fromtext_invalid) {
+ static const char *syntax[] = {
+ "1970010100000",
+ "197001010000000",
+ "1970010100000x",
+ };
+ static const char *range[] = {
+ "19700001000000", "19701301000000", "19700229000000",
+ "19700101240000", "19700101006000", "19700101000061",
+ };
+ int64_t when;
+
+ UNUSED(state);
+
+ for (size_t i = 0; i < ARRAY_SIZE(syntax); i++) {
+ assert_int_equal(dns_time64_fromtext(syntax[i], &when),
+ DNS_R_SYNTAX);
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(range); i++) {
+ assert_int_equal(dns_time64_fromtext(range[i], &when),
+ ISC_R_RANGE);
+ }
+}
+
ISC_TEST_LIST_START
ISC_TEST_ENTRY(epoch_minus_one)
ISC_TEST_ENTRY(epoch)
@@ -166,6 +225,9 @@ ISC_TEST_ENTRY(half_maxint)
ISC_TEST_ENTRY(half_plus_one)
ISC_TEST_ENTRY(fifty_before)
ISC_TEST_ENTRY(some_ago)
+ISC_TEST_ENTRY(fromregion)
+ISC_TEST_ENTRY(fromtext_boundaries)
+ISC_TEST_ENTRY(fromtext_invalid)
ISC_TEST_LIST_END
ISC_TEST_MAIN