Kea 2.5.8
time_utils.cc
Go to the documentation of this file.
1// Copyright (C) 2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <dns/time_utils.h>
11
12#include <cstdint>
13#include <cstdio>
14#include <ctime>
15#include <iomanip>
16#include <iostream>
17#include <sstream>
18#include <string>
19#include <sys/time.h>
20
21using namespace std;
22
23namespace {
24int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
25
26bool
27isLeap(const int y) {
28 return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0);
29}
30
31unsigned int
32yearSecs(const int year) {
33 return ((isLeap(year) ? 366 : 365) * 86400);
34}
35
36unsigned int
37monthSecs(const int month, const int year) {
38 return ((days[month] + ((month == 1 && isLeap(year)) ? 1 : 0)) * 86400);
39}
40} // anonymous namespace
41
42namespace isc {
43namespace util {
44namespace {
45constexpr size_t DATE_LEN = 14; // YYYYMMDDHHmmSS
46
47uint64_t
48ull(const int c) {
49 return (static_cast<uint64_t>(c));
50}
51
52void
53checkRange(const unsigned min, const unsigned max, const unsigned value, const string& valname) {
54 if ((value >= min) && (value <= max)) {
55 return;
56 }
57
58 isc_throw(InvalidTime, "Invalid " << valname << " value: " << value);
59}
60} // anonymous namespace
61
62namespace detail {
63// timeToText32() below uses the current system time. To test it with
64// unusual current time values we introduce the following function pointer;
65// when it's non NULL, we call it to get the (normally faked) current time.
66// Otherwise we use the standard gettimeofday(2). This hook is specifically
67// intended for testing purposes, so, even if it's visible outside of this
68// library, it's not even declared in a header file.
69int64_t (*getTimeFunction)() = 0;
70
71int64_t
73 if (getTimeFunction != 0) {
74 return (getTimeFunction());
75 }
76
77 struct timeval now{};
78 gettimeofday(&now, 0);
79
80 return (static_cast<int64_t>(now.tv_sec));
81}
82} // namespace detail
83
84string
85timeToText64(uint64_t value) {
86 struct tm tm{};
87 unsigned int secs;
88
89 // We cannot rely on gmtime() because time_t may not be of 64 bit
90 // integer. The following conversion logic is borrowed from BIND 9.
91 tm.tm_year = 70;
92 while ((secs = yearSecs(tm.tm_year + 1900)) <= value) {
93 value -= secs;
94 ++tm.tm_year;
95 if (tm.tm_year + 1900 > 9999) {
96 isc_throw(InvalidTime, "Time value out of range (year > 9999): " << tm.tm_year + 1900);
97 }
98 }
99
100 tm.tm_mon = 0;
101 while ((secs = monthSecs(tm.tm_mon, tm.tm_year + 1900)) <= value) {
102 value -= secs;
103 tm.tm_mon++;
104 }
105
106 tm.tm_mday = 1;
107 while (86400 <= value) {
108 value -= 86400;
109 ++tm.tm_mday;
110 }
111
112 tm.tm_hour = 0;
113 while (3600 <= value) {
114 value -= 3600;
115 ++tm.tm_hour;
116 }
117
118 tm.tm_min = 0;
119 while (60 <= value) {
120 value -= 60;
121 ++tm.tm_min;
122 }
123
124 tm.tm_sec = value; // now t < 60, so this substitution is safe.
125
126 ostringstream oss;
127 oss << setfill('0') << setw(4) << tm.tm_year + 1900 << setw(2) << tm.tm_mon + 1 << setw(2)
128 << tm.tm_mday << setw(2) << tm.tm_hour << setw(2) << tm.tm_min << setw(2) << tm.tm_sec;
129 return (oss.str());
130}
131
132string
133timeToText32(const uint32_t value) {
134 // We first adjust the time to the closest epoch based on the current time.
135 // Note that the following variables must be signed in order to handle
136 // time until year 2038 correctly.
137 const int64_t start = detail::getTimeWrapper() - 0x7fffffff;
138 int64_t base = 0;
139 int64_t t;
140 while ((t = (base + value)) < start) {
141 base += 0x100000000LL;
142 }
143
144 // Then convert it to text.
145 return (timeToText64(t));
146}
147
148uint64_t
149timeFromText64(const string& time_txt) {
150 // Confirm the source only consists digits. sscanf() allows some
151 // minor exceptions.
152 for (string::size_type i = 0; i < time_txt.length(); ++i) {
153 if (!isdigit(time_txt.at(i))) {
154 isc_throw(InvalidTime, "Couldn't convert non-numeric time value: " << time_txt);
155 }
156 }
157
158 unsigned year, month, day, hour, minute, second;
159 if (time_txt.length() != DATE_LEN || sscanf(time_txt.c_str(), "%4u%2u%2u%2u%2u%2u", &year,
160 &month, &day, &hour, &minute, &second) != 6) {
161 isc_throw(InvalidTime, "Couldn't convert time value: " << time_txt);
162 }
163
164 checkRange(1970, 9999, year, "year");
165 checkRange(1, 12, month, "month");
166 checkRange(1, days[month - 1] + ((month == 2 && isLeap(year)) ? 1 : 0), day, "day");
167 checkRange(0, 23, hour, "hour");
168 checkRange(0, 59, minute, "minute");
169 checkRange(0, 60, second, "second"); // 60 == leap second.
170
171 uint64_t timeval = second + (ull(60) * minute) + (ull(3600) * hour) + ((day - 1) * ull(86400));
172 for (unsigned m = 0; m < (month - 1); ++m) {
173 timeval += days[m] * ull(86400);
174 }
175
176 if (isLeap(year) && month > 2) {
177 timeval += ull(86400);
178 }
179
180 for (unsigned y = 1970; y < year; ++y) {
181 timeval += ((isLeap(y) ? 366 : 365) * ull(86400));
182 }
183
184 return (timeval);
185}
186
187uint32_t
188timeFromText32(const string& time_txt) {
189 // The implicit conversion from uint64_t to uint32_t should just work here,
190 // because we only need to drop higher 32 bits.
191 return (timeFromText64(time_txt));
192}
193
194} // namespace util
195} // namespace isc
A standard DNS (or ISC) module exception that is thrown if a time conversion function encounters bad ...
Definition: time_utils.h:21
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
int64_t(* getTimeFunction)()=0
Definition: time_utils.cc:69
int64_t getTimeWrapper()
Return the current time in seconds.
Definition: time_utils.cc:72
uint32_t timeFromText32(const string &time_txt)
Convert textual DNSSEC time to integer, 32-bit version.
Definition: time_utils.cc:188
string timeToText64(uint64_t value)
Convert integral DNSSEC time to textual form, 64-bit version.
Definition: time_utils.cc:85
uint64_t timeFromText64(const string &time_txt)
Convert textual DNSSEC time to integer, 64-bit version.
Definition: time_utils.cc:149
string timeToText32(const uint32_t value)
Convert integral DNSSEC time to textual form, 32-bit version.
Definition: time_utils.cc:133
Defines the logger used by the top-level component of kea-lfc.