summaryrefslogtreecommitdiffstats
path: root/src/ClockTAI.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ClockTAI.cpp')
-rw-r--r--src/ClockTAI.cpp281
1 files changed, 123 insertions, 158 deletions
diff --git a/src/ClockTAI.cpp b/src/ClockTAI.cpp
index 6d05370..bdef901 100644
--- a/src/ClockTAI.cpp
+++ b/src/ClockTAI.cpp
@@ -25,9 +25,8 @@
along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
*/
-/* This file downloads the TAI-UTC bulletins from the USNO servers and/or
- * from IETF and parses them so that correct time can be communicated in EDI
- * timestamps.
+/* This file downloads the TAI-UTC bulletins from the from IETF and parses them
+ * so that correct time can be communicated in EDI timestamps.
*
* This file contains self-test code that can be executed by running
* g++ -g -Wall -DTEST -DHAVE_CURL -std=c++11 -lcurl -lboost_regex -pthread \
@@ -60,83 +59,67 @@
static bool wait_longer = true;
#endif
+// Offset between NTP time and POSIX time:
+// timestamp_unix = timestamp_ntp - ntp_unix_offset
+const int64_t ntp_unix_offset = 2208988800L;
+
// leap seconds insertion bulletin is available at
static const char* tai_ietf_url =
"http://www.ietf.org/timezones/data/leap-seconds.list";
// and in the tz distribution
static const char* tai_tz_url =
"https://raw.githubusercontent.com/eggert/tz/master/leap-seconds.list";
-// leap-seconds.list has a different format from the USNO one
-
-static const char* tai_usno_url1 =
- "http://maia.usno.navy.mil/ser7/tai-utc.dat";
-static const char* tai_usno_url2 =
- "http://toshi.nofs.navy.mil/ser7/tai-utc.dat";
-/* Last two lines from USNO bulletin, example:
- 2015 JUL 1 =JD 2457204.5 TAI-UTC= 36.0 S + (MJD - 41317.) X 0.0 S
- 2017 JAN 1 =JD 2457754.5 TAI-UTC= 37.0 S + (MJD - 41317.) X 0.0
- */
-int ClockTAI::download_offset_task()
+static const char* tai_ietf_cache_file = "/tmp/odr-dabmux-leap-seconds.cache";
+
+int ClockTAI::get_valid_offset()
{
int offset = 0;
bool offset_valid = false;
- // Clear the bulletin
- m_bulletin.str("");
- m_bulletin.clear();
-
- try {
- download_tai_utc_bulletin(tai_ietf_url);
+ if (bulletin_is_valid()) {
+#if TEST
+ etiLog.level(info) << "Bulletin already valid";
+#endif
offset = parse_ietf_bulletin();
offset_valid = true;
}
- catch (std::runtime_error& e)
- {
- etiLog.level(warn) <<
- "TAI-UTC offset from IETF could not be retrieved: " <<
- e.what();
- }
+ else {
+ load_bulletin_from_file(tai_ietf_cache_file);
- if (not offset_valid) {
- try {
- download_tai_utc_bulletin(tai_tz_url);
+ if (bulletin_is_valid()) {
+#if TEST
+ etiLog.level(info) << "Bulletin from file valid";
+#endif
offset = parse_ietf_bulletin();
offset_valid = true;
}
- catch (std::runtime_error& e)
- {
- etiLog.level(warn) <<
- "TAI-UTC offset from TZ distribution could not be retrieved: " <<
- e.what();
- }
- }
-
- if (not offset_valid) {
- try {
- download_tai_utc_bulletin(tai_usno_url1);
- offset = parse_usno_bulletin();
- offset_valid = true;
- }
- catch (std::runtime_error& e)
- {
- etiLog.level(warn) <<
- "TAI-UTC offset from USNO server #1 could not be retrieved: " <<
- e.what();
- }
- }
-
- if (not offset_valid) {
- try {
- download_tai_utc_bulletin(tai_usno_url2);
- offset = parse_usno_bulletin();
- offset_valid = true;
- }
- catch (std::runtime_error& e)
- {
- etiLog.level(warn) <<
- "TAI-UTC offset from USNO server #2 could not be retrieved: " <<
- e.what();
+ else {
+ for (const auto url : {tai_ietf_url, tai_tz_url}) {
+ try {
+ download_tai_utc_bulletin(tai_ietf_url);
+#if TEST
+ etiLog.level(info) << "Load bulletin from " << url;
+#endif
+ if (bulletin_is_valid()) {
+#if TEST
+ etiLog.level(info) << "Bulletin from " << url << " valid";
+#endif
+ offset = parse_ietf_bulletin();
+ offset_valid = true;
+ }
+ }
+ catch (std::runtime_error& e) {
+ etiLog.level(warn) <<
+ "TAI-UTC offset could not be retrieved from " <<
+ url << " : " << e.what();
+ }
+
+ if (offset_valid) {
+ update_cache(tai_ietf_cache_file);
+ break;
+ }
+ }
}
}
@@ -175,7 +158,7 @@ int ClockTAI::get_offset()
#else
// First time we run we must block until we know
// the offset
- m_offset = download_offset_task();
+ m_offset = get_valid_offset();
m_offset_valid = true;
m_bulletin_download_time = std::chrono::system_clock::now();
#endif
@@ -217,7 +200,7 @@ int ClockTAI::get_offset()
etiLog.level(debug) << " Launch async";
#endif
m_offset_future = std::async(std::launch::async,
- &ClockTAI::download_offset_task, this);
+ &ClockTAI::get_valid_offset, this);
}
}
@@ -252,8 +235,6 @@ int ClockTAI::parse_ietf_bulletin()
// The difference between NTP timestamps and unix epoch is 70
// years i.e. 2208988800 seconds
- const int64_t ntp_unix_offset = 2208988800L;
-
boost::regex regex_bulletin(R"(([0-9]+)\s+([0-9]+)\s+#.*)");
time_t now = time(nullptr);
@@ -262,6 +243,9 @@ int ClockTAI::parse_ietf_bulletin()
int tai_utc_offset_valid = false;
+ m_bulletin.clear();
+ m_bulletin.seekg(0);
+
/* We cannot just take the last line, because it might
* be in the future, announcing an upcoming leap second.
*
@@ -285,10 +269,6 @@ int ClockTAI::parse_ietf_bulletin()
std::atol(bulletin_ntp_timestamp.c_str()) - ntp_unix_offset;
const int offset = std::atoi(bulletin_offset.c_str());
-#if TEST
- std::cerr << "Match for line " << line << std::endl;
- std::cerr << " " << timestamp_unix << " < " << now << std::endl;
-#endif
// Ignore entries announcing leap seconds in the future
if (timestamp_unix < now) {
tai_utc_offset = offset;
@@ -311,114 +291,97 @@ int ClockTAI::parse_ietf_bulletin()
return tai_utc_offset;
}
-int ClockTAI::parse_usno_bulletin()
+size_t ClockTAI::fill_bulletin(char *ptr, size_t size, size_t nmemb)
{
- boost::regex regex_bulletin(
- "([0-9]{4}) ([A-Z]{3}) +([0-9]+) =JD +[0-9.]+ +TAI-UTC= *([0-9.]+)");
- /* regex groups:
- * Year Month Day Julian date Offset
- */
+ size_t len = size * nmemb;
+ for (size_t i = 0; i < len; i++) {
+ m_bulletin << ptr[i];
+ }
+ return len;
+}
- const std::array<std::string, 12> months{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
+size_t ClockTAI::fill_bulletin_cb(
+ char *ptr, size_t size, size_t nmemb, void *ctx)
+{
+ return ((ClockTAI*)ctx)->fill_bulletin(ptr, size, nmemb);
+}
- /* I'm not certain about the format they would use if the day is a
- * two-digit number. Will they keep two spaces after the month? The regex
- * should be resilient enough in that case.
- */
+void ClockTAI::load_bulletin_from_file(const char* cache_filename)
+{
+ // Clear the bulletin
+ m_bulletin.str("");
+ m_bulletin.clear();
- time_t now = time(nullptr);
- struct tm *utc_time_now = gmtime(&now);
+ std::ifstream f(cache_filename);
+ if (not f.good()) {
+ return;
+ }
- const int year = utc_time_now->tm_year + 1900;
- const int month = utc_time_now->tm_mon; // January is 0
- if (month < 0 or month > 11) {
- throw std::runtime_error("Invalid value for month");
+ m_bulletin << f.rdbuf();
+ f.close();
+}
+
+void ClockTAI::update_cache(const char* cache_filename)
+{
+ std::ofstream f(cache_filename);
+ if (not f.good()) {
+ throw std::runtime_error("TAI-UTC bulletin open cache for writing");
}
- const int day = utc_time_now->tm_mday;
+ m_bulletin.clear();
+ m_bulletin.seekg(0);
+#if TEST
+ etiLog.level(info) << "Update cache, state:" <<
+ (m_bulletin.eof() ? " eof" : "") <<
+ (m_bulletin.fail() ? " fail" : "") <<
+ (m_bulletin.bad() ? " bad" : "");
+#endif
- int tai_utc_offset = 0;
- bool tai_utc_offset_valid = false;
+ f << m_bulletin.rdbuf();
+ f.close();
+}
- /* We cannot just take the last line, because it might
- * be in the future, announcing an upcoming leap second.
- *
- * So we need to look at the current date, and compare it
- * with the date of the leap second.
- */
- for (std::string line; std::getline(m_bulletin, line); ) {
+bool ClockTAI::bulletin_is_valid()
+{
+ // The bulletin contains one line that specifies an expiration date
+ // in NTP time. If that point in time is in the future, we consider
+ // the bulletin valid.
+ //
+ // The entry looks like this:
+ //#@ 3707596800
+
+ boost::regex regex_expiration(R"(#@\s+([0-9]+))");
+ time_t now = time(nullptr);
+
+ m_bulletin.clear();
+ m_bulletin.seekg(0);
+
+ for (std::string line; std::getline(m_bulletin, line); ) {
boost::smatch bulletin_entry;
- bool is_match = boost::regex_search(line, bulletin_entry, regex_bulletin);
+ bool is_match = boost::regex_search(line, bulletin_entry, regex_expiration);
if (is_match) {
-
- if (bulletin_entry.size() != 5) {
+ if (bulletin_entry.size() != 2) {
throw std::runtime_error(
- "Incorrect number of matched USNO TAI bulletin entries");
- }
- const std::string bulletin_year(bulletin_entry[1]);
- const std::string bulletin_month_name(bulletin_entry[2]);
- const std::string bulletin_day(bulletin_entry[3]);
- const std::string bulletin_offset(bulletin_entry[4]);
-
- const auto match = std::find(months.begin(), months.end(), bulletin_month_name);
- if (match == months.end()) {
- std::stringstream s;
- s << "Month " << bulletin_month_name << " not in array!";
- std::runtime_error(s.str());
+ "Incorrect number of matched TAI IETF bulletin expiration");
}
+ const std::string expiry_data_str(bulletin_entry[1]);
+ const int64_t expiry_unix =
+ std::atol(expiry_data_str.c_str()) - ntp_unix_offset;
- if ( (std::atoi(bulletin_year.c_str()) < year) or
-
- (std::atoi(bulletin_year.c_str()) == year and
- std::distance(months.begin(), match) < month) or
-
- (std::atoi(bulletin_year.c_str()) == year and
- std::distance(months.begin(), match) == month and
- std::atoi(bulletin_day.c_str()) < day) ) {
- tai_utc_offset = std::atoi(bulletin_offset.c_str());
- tai_utc_offset_valid = true;
- }
-#if TEST
- else {
- std::cerr << "Ignoring offset " << bulletin_offset << " at date "
- << bulletin_year << " " << bulletin_month_name << " " <<
- bulletin_day << " in the future" << std::endl;
- }
-#endif
- }
-#if TEST
- else {
- std::cerr << "No match for line " << line << std::endl;
+ return expiry_unix > now;
}
-#endif
- }
-
- if (not tai_utc_offset_valid) {
- throw std::runtime_error("No data in TAI bulletin");
- }
-
- return tai_utc_offset;
-}
-
-size_t ClockTAI::fill_bulletin(char *ptr, size_t size, size_t nmemb)
-{
- size_t len = size * nmemb;
- for (size_t i = 0; i < len; i++) {
- m_bulletin << ptr[i];
}
- return len;
-}
-
-size_t ClockTAI::fill_bulletin_cb(
- char *ptr, size_t size, size_t nmemb, void *ctx)
-{
- return ((ClockTAI*)ctx)->fill_bulletin(ptr, size, nmemb);
+ return false;
}
void ClockTAI::download_tai_utc_bulletin(const char* url)
{
+ // Clear the bulletin
+ m_bulletin.str("");
+ m_bulletin.clear();
+
#ifdef HAVE_CURL
CURL *curl;
CURLcode res;
@@ -487,10 +450,12 @@ int main(int argc, char **argv)
while (wait_longer) {
try {
- cerr << "Offset is " << tai.get_offset() << endl;
+ etiLog.level(info) <<
+ "Offset is " << tai.get_offset();
}
catch (std::exception &e) {
- cerr << "Exception " << e.what() << endl;
+ etiLog.level(error) <<
+ "Exception " << e.what();
}
std::this_thread::sleep_for(std::chrono::seconds(2));