From 6618a7d3265639bf4f4edcc0cfd50243df020c42 Mon Sep 17 00:00:00 2001 From: Austin Foxley Date: Tue, 4 Jun 2024 03:32:47 +0000 Subject: [PATCH] pw_chrono: Introduce SystemClock backed link time wrappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate the existing build time backed wrappers under pw_chrono Change-Id: I8fe10f7eacbec3b84c2e8358f3f5a7f58827883b Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/213551 Reviewed-by: Armando Montanez Lint: Lint 🤖 Commit-Queue: Austin Foxley --- pw_chrono/BUILD.bazel | 31 +++++++++ pw_chrono/BUILD.gn | 35 ++++++++++ pw_chrono/CMakeLists.txt | 42 ++++++++++++ pw_chrono/docs.rst | 32 ++++++++++ .../generate_build_time_header.py | 0 .../wrap_time_build_time.cc | 18 ++---- pw_chrono/wrap_time_system_clock.cc | 64 +++++++++++++++++++ pw_tls_client/BUILD.bazel | 13 ---- pw_tls_client/BUILD.gn | 31 --------- pw_tls_client/CMakeLists.txt | 43 ------------- pw_tls_client/backend.cmake | 2 +- pw_tls_client/configs.gni | 2 +- pw_tls_client/docs.rst | 2 +- 13 files changed, 214 insertions(+), 101 deletions(-) rename {pw_tls_client => pw_chrono}/generate_build_time_header.py (100%) rename pw_tls_client/build_time.cc => pw_chrono/wrap_time_build_time.cc (86%) create mode 100644 pw_chrono/wrap_time_system_clock.cc diff --git a/pw_chrono/BUILD.bazel b/pw_chrono/BUILD.bazel index f57eb330db..b82f9d2d2a 100644 --- a/pw_chrono/BUILD.bazel +++ b/pw_chrono/BUILD.bazel @@ -158,3 +158,34 @@ pw_cc_test( "//pw_unit_test", ], ) + +py_binary( + name = "generate_build_time_header", + srcs = ["generate_build_time_header.py"], +) + +genrule( + name = "build_time_header", + outs = ["build_time.h"], + cmd = "$(location :generate_build_time_header) $@", + tools = [":generate_build_time_header"], +) + +cc_library( + name = "wrap_time_build_time", + srcs = [ + "wrap_time_build_time.cc", + ":build_time_header", + ], +) + +cc_library( + name = "wrap_time_system_clock", + srcs = ["wrap_time_system_clock.cc"], + linkopts = [ + "-Wl,--wrap=gettimeofday", + "-Wl,--wrap=time", + ], + deps = [":system_clock"], + alwayslink = 1, +) diff --git a/pw_chrono/BUILD.gn b/pw_chrono/BUILD.gn index c9c193ad7e..7a050cb980 100644 --- a/pw_chrono/BUILD.gn +++ b/pw_chrono/BUILD.gn @@ -101,6 +101,41 @@ pw_test("system_timer_facade_test") { ] } +# The ":time" target wraps the time() and gettimeofday(), which are +# commonly used by TLS libraries for expiration check. +config("time_wrap") { + # Link options that wrap C time calls. + ldflags = [ + "-Wl,--wrap=time", + "-Wl,--wrap=gettimeofday", + ] + visibility = [ ":*" ] +} + +# The build time is obtained with a python script and put in a generated header +# file. The header file is included in build_time.cc +pw_python_action("generate_build_time_header") { + header_output = "$target_gen_dir/pw_chrono/build_time.h" + script = "generate_build_time_header.py" + outputs = [ header_output ] + args = [ rebase_path(header_output) ] +} + +# The target provides a backend to :time that returns build time. +pw_source_set("wrap_time_build_time") { + all_dependent_configs = [ ":time_wrap" ] + include_dirs = + [ get_label_info(":generate_build_time_header", "target_gen_dir") ] + sources = [ "wrap_time_build_time.cc" ] + deps = [ ":generate_build_time_header" ] +} + +pw_source_set("wrap_time_system_clock") { + all_dependent_configs = [ ":time_wrap" ] + sources = [ "wrap_time_system_clock.cc" ] + deps = [ ":system_clock" ] +} + pw_proto_library("protos") { sources = [ "chrono.proto" ] prefix = "pw_chrono_protos" diff --git a/pw_chrono/CMakeLists.txt b/pw_chrono/CMakeLists.txt index 5d865e4963..251895b21c 100644 --- a/pw_chrono/CMakeLists.txt +++ b/pw_chrono/CMakeLists.txt @@ -108,3 +108,45 @@ if(NOT "${pw_chrono.system_timer_BACKEND}" STREQUAL "") pw_chrono ) endif() + +function(generate_build_time_header NAME) + pw_parse_arguments( + NUM_POSITIONAL_ARGS + 1 + ) + + set(out_dir "${CMAKE_CURRENT_BINARY_DIR}/${NAME}") + set(output "${out_dir}/pw_chrono/build_time.h") + get_filename_component(output_include_path "${output}" DIRECTORY) + + set(gen_header "$ENV{PW_ROOT}/pw_chrono/generate_build_time_header.py") + LIST(APPEND gen_header_cmd python3 "${gen_header}" ${output}) + + add_custom_command( + COMMAND + ${gen_header_cmd} + DEPENDS + ${gen_header} + OUTPUT + ${output}) + + add_custom_target("${NAME}._generate" DEPENDS ${output}) + + pw_add_library_generic("${NAME}" INTERFACE + PUBLIC_INCLUDES + ${out_dir} + ) + add_dependencies("${NAME}" "${NAME}._generate") +endfunction() + +generate_build_time_header(pw_chrono.build_time_header) + +pw_add_library(pw_chrono.wrap_time_build_time STATIC + SOURCES + wrap_time_build_time.cc + PUBLIC_DEPS + pw_chrono.build_time_header + PUBLIC_LINK_OPTIONS + -Wl,--wrap=time + -Wl,--wrap=gettimeofday +) diff --git a/pw_chrono/docs.rst b/pw_chrono/docs.rst index fd5ee20a02..b0c6f5c406 100644 --- a/pw_chrono/docs.rst +++ b/pw_chrono/docs.rst @@ -557,3 +557,35 @@ Example in C++ :maxdepth: 1 Backends + +------------------ +Libc Time Wrappers +------------------ +The `gettimeofday ` +and `time ` +POSIX functions are defined to return the current time since the Epoch. +The default ``pw_toolchain/arg_gcc:newlib_os_interface_stubs`` stub for +``gettimeofday`` will cause a linker error if any code tried to use this +function, but it's common for software not written for embedded systems to +depend on this function being defined and returning something that increments +like a clock. In addition, some software depends on having ``gettimeofday`` +return something much closer to the actual time so it can compare against well +known time points inside TLS certificates for instance. + +For compatibility with such software, ``pw_toolchain`` provides two different +options to wrap libc time functions. Both of these are not recommended for +general time querying and are only intended to provide compatibility. + +``pw_chrono:wrap_time_build_time`` +================================== +Wrap ``gettimeofday`` and ``time`` with an implementation that returns a static +time value at which the library was built. Use this option if you need these +functions to return a known value greater than some point in the past. + +``pw_chrono:wrap_time_system_clock`` +==================================== +Wrap ``gettimeofday`` and ``time`` with an implementation that uses +``pw::chrono::SystemClock`` to return the current time. Note the epoch is +determined by the SystemClock backend epoch, which on most embedded systems will +be time since boot. Use this option if you don't care about the time returned +being close to actual time, but do care that it increments like a real clock. diff --git a/pw_tls_client/generate_build_time_header.py b/pw_chrono/generate_build_time_header.py similarity index 100% rename from pw_tls_client/generate_build_time_header.py rename to pw_chrono/generate_build_time_header.py diff --git a/pw_tls_client/build_time.cc b/pw_chrono/wrap_time_build_time.cc similarity index 86% rename from pw_tls_client/build_time.cc rename to pw_chrono/wrap_time_build_time.cc index d1d9d06cff..fb61f61fd9 100644 --- a/pw_tls_client/build_time.cc +++ b/pw_chrono/wrap_time_build_time.cc @@ -12,22 +12,20 @@ // License for the specific language governing permissions and limitations under // the License. -#include "build_time.h" - #include + +#include "pw_chrono/build_time.h" + #if __has_include() #include #endif // __has_include() +namespace pw::chrono { namespace { constexpr uint64_t kMicrosecondsPerSecond = 1'000'000; } -#if __cplusplus -extern "C" { -#endif - -time_t __wrap_time(time_t* t) { +extern "C" time_t __wrap_time(time_t* t) { time_t ret = static_cast(kBuildTimeMicrosecondsUTC / kMicrosecondsPerSecond); if (t) { @@ -38,7 +36,7 @@ time_t __wrap_time(time_t* t) { #if __has_include() -int __wrap_gettimeofday(struct timeval* tv, void* tz) { +extern "C" int __wrap_gettimeofday(struct timeval* tv, void* tz) { // The use of the timezone structure is obsolete (see docs "man // gettimeofday"). Thus we don't consider it. (void)tz; @@ -49,6 +47,4 @@ int __wrap_gettimeofday(struct timeval* tv, void* tz) { #endif // __has_include() -#if __cplusplus -} -#endif +} // namespace pw::chrono diff --git a/pw_chrono/wrap_time_system_clock.cc b/pw_chrono/wrap_time_system_clock.cc new file mode 100644 index 0000000000..bc7ff16058 --- /dev/null +++ b/pw_chrono/wrap_time_system_clock.cc @@ -0,0 +1,64 @@ +// Copyright 2024 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// This file provides an implementation of time and gettimeofday that can be +// used with ld's --wrap option. + +#include + +#if __has_include() +#include +#endif // __has_include() + +#include + +#include + +#include "pw_chrono/system_clock.h" + +namespace pw::chrono { + +extern "C" time_t __wrap_time(time_t* t) { + const pw::chrono::SystemClock::time_point now = + pw::chrono::SystemClock::now(); + const time_t ret = + std::chrono::duration_cast(now.time_since_epoch()) + .count(); + if (t) { + *t = ret; + } + return ret; +} + +#if __has_include() + +extern "C" int __wrap_gettimeofday(struct timeval* tv, void* tz) { + using namespace std::chrono_literals; + // The use of the timezone structure is obsolete (see docs "man + // gettimeofday"). Thus we don't consider it. + (void)tz; + // Get time since epoch from system clock. + const pw::chrono::SystemClock::time_point now = + pw::chrono::SystemClock::now(); + const auto usec_since_epoch = + std::chrono::duration_cast( + now.time_since_epoch()); + tv->tv_sec = (usec_since_epoch / 1s); + tv->tv_usec = (usec_since_epoch % 1s).count(); + return 0; +} + +#endif // __has_include() + +} // namespace pw::chrono diff --git a/pw_tls_client/BUILD.bazel b/pw_tls_client/BUILD.bazel index 2d9981941e..a83cbbf88c 100644 --- a/pw_tls_client/BUILD.bazel +++ b/pw_tls_client/BUILD.bazel @@ -82,19 +82,6 @@ cc_library( ], ) -# TODO(zyecheng): The target requires a build_time.h header that defines a -# 'constexpr size_t kBuildTimeMicrosecondsUTC' variable for storing the build time. -# In gn build, this is generated by a python action target. Need to figure out a -# solution in bazel build. -cc_library( - name = "build_time", - srcs = [ - "build_time.cc", - ], - # TODO: b/257527057 - Get this to build. - tags = ["manual"], -) - cc_library( name = "crlset", hdrs = ["public/pw_tls_client/crlset.h"], diff --git a/pw_tls_client/BUILD.gn b/pw_tls_client/BUILD.gn index 5f37a4f0cd..022460028e 100644 --- a/pw_tls_client/BUILD.gn +++ b/pw_tls_client/BUILD.gn @@ -60,19 +60,8 @@ pw_source_set("fake_entropy") { deps = [ "$dir_pw_log" ] } -# The ":time" target wraps the time() and gettimeofday(), which are -# commonly used by TLS libraries for expiration check. -config("time_wrap") { - # Link options that wrap C time calls. - ldflags = [ - "-Wl,--wrap=time", - "-Wl,--wrap=gettimeofday", - ] -} - pw_facade("time") { backend = pw_tls_client_TIME_BACKEND - public_configs = [ ":time_wrap" ] public = [] # The target should only be used by TLS libraries to obtain date time @@ -84,26 +73,6 @@ pw_facade("time") { ] } -# The build time is obtained with a python script and put in a generated header -# file. The header file is included in build_time.cc -pw_python_action("generate_build_time_header") { - header_output = "$target_gen_dir/$target_name/build_time.h" - script = "generate_build_time_header.py" - outputs = [ header_output ] - args = [ rebase_path(header_output) ] -} - -# The target provides a backend to :time that returns build time. -pw_source_set("build_time") { - time_injection_outputs = get_target_outputs(":generate_build_time_header") - include_dirs = [ get_path_info(time_injection_outputs[0], "dir") ] - sources = [ "build_time.cc" ] - deps = [ - ":generate_build_time_header", - ":time.facade", - ] -} - # TODO: b/235290724 - Add a python target to generate source file from the # specified CRLSet file in `pw_tls_client_CRLSET_FILE` diff --git a/pw_tls_client/CMakeLists.txt b/pw_tls_client/CMakeLists.txt index 751db5403c..add0569952 100644 --- a/pw_tls_client/CMakeLists.txt +++ b/pw_tls_client/CMakeLists.txt @@ -36,47 +36,4 @@ pw_add_facade(pw_tls_client.pw_tls_client INTERFACE pw_add_facade(pw_tls_client.time INTERFACE BACKEND pw_tls_client.time_BACKEND - PUBLIC_LINK_OPTIONS - -Wl,--wrap=time - -Wl,--wrap=gettimeofday -) - -function(generate_build_time_header NAME) - pw_parse_arguments( - NUM_POSITIONAL_ARGS - 1 - ) - - set(out_dir "${CMAKE_CURRENT_BINARY_DIR}/${NAME}") - set(output "${out_dir}/build_time.h") - get_filename_component(output_include_path "${output}" DIRECTORY) - - set(gen_header "$ENV{PW_ROOT}/pw_tls_client/generate_build_time_header.py") - LIST(APPEND gen_header_cmd python3 "${gen_header}" ${output}) - - add_custom_command( - COMMAND - ${gen_header_cmd} - DEPENDS - ${gen_header} - OUTPUT - ${output}) - - add_custom_target("${NAME}._generate" DEPENDS ${output}) - - pw_add_library_generic("${NAME}" INTERFACE - PUBLIC_INCLUDES - ${output_include_path} - ) - add_dependencies("${NAME}" "${NAME}._generate") -endfunction() - -generate_build_time_header(pw_tls_client.build_time_header) - -pw_add_library(pw_tls_client.build_time STATIC - SOURCES - build_time.cc - PUBLIC_DEPS - pw_tls_client.build_time_header - pw_tls_client.time.facade ) diff --git a/pw_tls_client/backend.cmake b/pw_tls_client/backend.cmake index 167ebc25de..147507c4ee 100644 --- a/pw_tls_client/backend.cmake +++ b/pw_tls_client/backend.cmake @@ -20,5 +20,5 @@ include($ENV{PW_ROOT}/pw_build/pigweed.cmake) pw_add_backend_variable(pw_tls_client.pw_tls_client_BACKEND) pw_add_backend_variable(pw_tls_client.time_BACKEND DEFAULT_BACKEND - pw_tls_client.build_time + pw_chrono.wrap_time_build_time ) diff --git a/pw_tls_client/configs.gni b/pw_tls_client/configs.gni index 70e04e5c02..faaf7baee6 100644 --- a/pw_tls_client/configs.gni +++ b/pw_tls_client/configs.gni @@ -22,7 +22,7 @@ declare_args() { pw_tls_client_ENTROPY_BACKEND = "$dir_pw_tls_client:fake_entropy" # Backend for pw_tls_client:time - pw_tls_client_TIME_BACKEND = "$dir_pw_tls_client:build_time" + pw_tls_client_TIME_BACKEND = "$dir_pw_chrono:wrap_time_build_time" # The path to the CRLSet file. pw_tls_client_CRLSET_FILE = "" diff --git a/pw_tls_client/docs.rst b/pw_tls_client/docs.rst index 93201a65f6..ee2ad07282 100644 --- a/pw_tls_client/docs.rst +++ b/pw_tls_client/docs.rst @@ -64,7 +64,7 @@ and MbedTLS, support the use of C APIs ``time()`` and ``getimtofday()`` for obtaining date time. To accomodate the use of these libraries, a facade target ``pw_tls_client:time`` is added that wraps these APIs. For GN builds, specify the backend target with variable ``pw_tls_client_TIME_BACKEND``. -``pw_tls_client_TIME_BACKEND`` defaults to the ``pw_tls_client::build_time`` +``pw_tls_client_TIME_BACKEND`` defaults to the ``pw_chrono::wrap_time_build_time`` backend that returns build time. If downstream project chooses to use other TLS libraires that handle time source