Add timestamp helper function
Adds a helper function to convert chrono time points into a UEFI CPER
record header timestamp specification.
Tested: Unit test
Google-Bug-Id: 322561358
Change-Id: I75cbce7db4c6f348d96d028598f00360f4154e20
Signed-off-by: Aryk Ledet <arykledet@google.com>
diff --git a/include/cper_encoder.hpp b/include/cper_encoder.hpp
new file mode 100644
index 0000000..d1b5ae5
--- /dev/null
+++ b/include/cper_encoder.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "cper.hpp"
+
+#include <chrono>
+
+namespace uefi::cper
+{
+
+/**
+ * @brief Helper function to create a UTC UEFI CPER timestamp from a
+ * std::chrono time point.
+ * https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html#record-header
+ *
+ * @param[in] timeOfCollection - A std::chrono time_point representing the time
+ * the fault was detected by the system software.
+ * @param[in] isPrecise - Bool which indicates if the time is precise.
+ *
+ * @param[out] A uefi cper timestamp as uint64_t.
+ */
+uint64_t createCperTimestamp(
+ const std::chrono::time_point<std::chrono::system_clock> timeOfCollection,
+ const bool isPrecise = true)
+{
+ // Number of years in a century.
+ constexpr int kCentury = 100;
+
+ // The tm_year counts the number of years since 1900.
+ // https://en.cppreference.com/w/c/chrono/tm
+ constexpr int kTmYearOffset = 1900;
+
+ // UEFI expects the isPrecise byte bits [1:7] to be zero.
+ const uint8_t isPreciseBit = 0 | (isPrecise & 0x1);
+
+ const time_t tt = std::chrono::system_clock::to_time_t(timeOfCollection);
+ const tm utcTime = *gmtime(&tt);
+
+ // The uefi timestamp breaks the calendar year into two bytes
+ // representing the number of centuries and number of years.
+ const int calendarYear = utcTime.tm_year + kTmYearOffset;
+ const uint8_t year = calendarYear % kCentury;
+ const uint8_t century = calendarYear / kCentury;
+
+ const Timestamp timestamp(utcTime.tm_sec, utcTime.tm_min, utcTime.tm_hour,
+ isPreciseBit, utcTime.tm_mday, utcTime.tm_mon,
+ year, century);
+
+ uint64_t ret;
+ memcpy(&ret, ×tamp, sizeof(ret));
+ return ret;
+}
+} // namespace uefi::cper
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..1003aba
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,32 @@
+project('cper_lib', ['cpp'],
+ version: '0.1', meson_version: '>=1.1.1',
+ default_options: [
+ 'cpp_std=c++23',
+ 'werror=true',
+ 'warning_level=3']
+)
+
+cpp_compiler = meson.get_compiler('cpp')
+
+cper_lib_headers = files(
+ 'include/cper_encoder.hpp',
+ 'include/cper.hpp',
+ 'include/guid.hpp'
+)
+
+headers_inc = include_directories('include')
+
+install_headers(
+ cper_lib_headers,
+ install_dir: get_option('includedir') / 'cper_lib',
+)
+
+pkg = import('pkgconfig')
+pkg.generate(
+ name: 'cper_lib',
+ description: 'Utilities for easily creating CPER logs'
+)
+
+if get_option('tests').allowed()
+ subdir('test')
+endif
diff --git a/meson.options b/meson.options
new file mode 100644
index 0000000..b6cb722
--- /dev/null
+++ b/meson.options
@@ -0,0 +1 @@
+option('tests', type: 'feature', value: 'auto', description: 'Build tests')
diff --git a/test/cper_encoder_test.cpp b/test/cper_encoder_test.cpp
index 3053d84..2903936 100644
--- a/test/cper_encoder_test.cpp
+++ b/test/cper_encoder_test.cpp
@@ -1,4 +1,6 @@
-#include "test/mock_cper_encoder.hpp"
+#include "cper_encoder.hpp"
+
+#include <chrono>
#include "gtest/gtest.h"
#include <gmock/gmock.h>
@@ -10,8 +12,40 @@
public:
void SetUp() override
{}
+
+ void expectEqTimestamp(
+ const uint64_t cperTimestamp,
+ const std::chrono::time_point<std::chrono::system_clock> ref,
+ const bool isPrecise = true)
+ {
+ Timestamp structTimestamp;
+ memcpy(&structTimestamp, &cperTimestamp, sizeof(structTimestamp));
+
+ time_t tt = std::chrono::system_clock::to_time_t(ref);
+ tm utcTime = *gmtime(&tt);
+
+ EXPECT_EQ(structTimestamp.seconds, utcTime.tm_sec);
+ EXPECT_EQ(structTimestamp.minutes, utcTime.tm_min);
+ EXPECT_EQ(structTimestamp.hours, utcTime.tm_hour);
+ EXPECT_EQ(structTimestamp.isPrecise, (isPrecise & 0x1));
+ EXPECT_EQ(structTimestamp.day, utcTime.tm_mday);
+ EXPECT_EQ(structTimestamp.month, utcTime.tm_mon);
+ EXPECT_EQ(structTimestamp.year, (utcTime.tm_year + 1900) % 100);
+ EXPECT_EQ(structTimestamp.century, (utcTime.tm_year + 1900) / 100);
+ }
};
+TEST_F(CperEncoderTest, CreateCperTimestamp)
+{
+ const auto now = std::chrono::system_clock::now();
+
+ const uint64_t preciseTimestamp = createCperTimestamp(now);
+ expectEqTimestamp(preciseTimestamp, now);
+
+ const uint64_t impreciseTimestamp = createCperTimestamp(now, false);
+ expectEqTimestamp(impreciseTimestamp, now, false);
+}
+
TEST_F(CperEncoderTest, SmokeTest)
{
RecordHeader recordHeader;
diff --git a/test/meson.build b/test/meson.build
index 9fdd874..1fdc537 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -27,7 +27,7 @@
test(t,
executable(t, [t + '.cpp', ],
dependencies: default_test_deps,
- include_directories : top_inc,
+ include_directories : headers_inc,
cpp_args: test_cpp_args
)
)