Skip to content

Commit bccee56

Browse files
author
Frederik Rothenberger
committed
Add feature to support custom now_utc implementations
This PR adds a feature to `identity_core` to allow specifying a custom function to get the current time (`Timestamp::now_utc`). The feature is disabled by default. Closes #1391.
1 parent c704062 commit bccee56

File tree

5 files changed

+126
-3
lines changed

5 files changed

+126
-3
lines changed

identity_core/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,12 @@ rustdoc-args = ["--cfg", "docsrs"]
3838

3939
[lints]
4040
workspace = true
41+
42+
[features]
43+
# Enables a macro to provide a custom time (Timestamp::now_utc) implementation, see src/custom_time.rs
44+
custom_time = []
45+
46+
47+
[[test]]
48+
name = "custom_time"
49+
required-features = ["custom_time"]

identity_core/src/common/timestamp.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl Timestamp {
4242
/// fractional seconds truncated.
4343
///
4444
/// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production).
45-
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
45+
#[cfg(all(not(all(target_arch = "wasm32", not(target_os = "wasi"))), not(feature = "custom_time")))]
4646
pub fn now_utc() -> Self {
4747
Self(truncate_fractional_seconds(OffsetDateTime::now_utc()))
4848
}
@@ -51,14 +51,23 @@ impl Timestamp {
5151
/// fractional seconds truncated.
5252
///
5353
/// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production).
54-
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
54+
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(feature = "custom_time")))]
5555
pub fn now_utc() -> Self {
5656
let milliseconds_since_unix_epoch: i64 = js_sys::Date::now() as i64;
5757
let seconds: i64 = milliseconds_since_unix_epoch / 1000;
5858
// expect is okay, we assume the current time is between 0AD and 9999AD
5959
Self::from_unix(seconds).expect("Timestamp failed to convert system datetime")
6060
}
6161

62+
/// Creates a new `Timestamp` with the current date and time, normalized to UTC+00:00 with
63+
/// fractional seconds truncated.
64+
///
65+
/// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production).
66+
#[cfg(feature = "custom_time")]
67+
pub fn now_utc() -> Self {
68+
crate::custom_time::now_utc_custom()
69+
}
70+
6271
/// Returns the `Timestamp` as an [RFC 3339](https://tools.ietf.org/html/rfc3339) `String`.
6372
pub fn to_rfc3339(&self) -> String {
6473
// expect is okay, constructors ensure RFC 3339 compatible timestamps.

identity_core/src/custom_time.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//! An implementation of `now_utc` which calls out to an externally defined function.
2+
use crate::common::Timestamp;
3+
4+
/// Register a function to be invoked by `identity_core` in order to get a [Timestamp] representing
5+
/// "now".
6+
///
7+
/// ## Writing a custom `now_utc` implementation
8+
///
9+
/// The function to register must have the same signature as
10+
/// [`Timestamp::now_utc`](Timestamp::now_utc). The function can be defined
11+
/// wherever you want, either in root crate or a dependent crate.
12+
///
13+
/// For example, if we wanted a `static_now_utc` crate containing an
14+
/// implementation that always returns the same timestamp, we would first depend on `identity_core`
15+
/// (for the [`Timestamp`] type) in `static_now_utc/Cargo.toml`:
16+
/// ```toml
17+
/// [dependencies]
18+
/// identity_core = "1"
19+
/// ```
20+
/// Note that the crate containing this function does **not** need to enable the
21+
/// `"custom_time"` Cargo feature.
22+
///
23+
/// Next, in `static_now_utc/src/lib.rs`, we define our function:
24+
/// ```rust
25+
/// use identity_core::common::Timestamp;
26+
///
27+
/// // Some fixed timestamp
28+
/// const MY_FIXED_TIMESTAMP: i64 = 1724402964;
29+
/// pub fn static_now_utc() -> Timestamp {
30+
/// Timestamp::from_unix(MY_FIXED_TIMESTAMP).unwrap()
31+
/// }
32+
/// ```
33+
///
34+
/// ## Registering a custom `now_utc` implementation
35+
///
36+
/// Functions can only be registered in the root binary crate. Attempting to
37+
/// register a function in a non-root crate will result in a linker error.
38+
/// This is similar to
39+
/// [`#[panic_handler]`](https://doc.rust-lang.org/nomicon/panic-handler.html) or
40+
/// [`#[global_allocator]`](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html),
41+
/// where helper crates define handlers/allocators but only the binary crate
42+
/// actually _uses_ the functionality.
43+
///
44+
/// To register the function, we first depend on `static_now_utc` _and_
45+
/// `identity_core` in `Cargo.toml`:
46+
/// ```toml
47+
/// [dependencies]
48+
/// static_now_utc = "0.1"
49+
/// identity_core = { version = "1", features = ["custom_time"] }
50+
/// ```
51+
///
52+
/// Then, we register the function in `src/main.rs`:
53+
/// ```rust
54+
/// # mod static_now_utc { pub fn static_now_utc() -> Timestamp { unimplemented!() } }
55+
///
56+
/// use static_now_utc::static_now_utc;
57+
/// use identity_core::register_custom_now_utc;
58+
///
59+
/// register_custom_now_utc!(static_now_utc);
60+
/// ```
61+
///
62+
/// Now any user of `now_utc` (direct or indirect) on this target will use the
63+
/// registered function.
64+
#[macro_export]
65+
macro_rules! register_custom_now_utc {
66+
($path:path) => {
67+
const __GET_TIME_INTERNAL: () = {
68+
// We use Rust ABI to be safe against potential panics in the passed function.
69+
#[no_mangle]
70+
unsafe fn __now_utc_custom() -> Timestamp {
71+
// Make sure the passed function has the type of getrandom::getrandom
72+
type F = fn() -> Timestamp;
73+
let f: F = $path;
74+
f()
75+
}
76+
};
77+
};
78+
}
79+
80+
pub(crate) fn now_utc_custom() -> Timestamp {
81+
extern "Rust" {
82+
fn __now_utc_custom() -> Timestamp;
83+
}
84+
unsafe { __now_utc_custom() }
85+
}

identity_core/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright 2020-2021 IOTA Stiftung
22
// SPDX-License-Identifier: Apache-2.0
33

4-
#![forbid(unsafe_code)]
54
#![doc = include_str!("./../README.md")]
65
#![allow(clippy::upper_case_acronyms)]
76
#![warn(
@@ -19,9 +18,15 @@
1918
#[doc(inline)]
2019
pub use serde_json::json;
2120

21+
#[forbid(unsafe_code)]
2222
pub mod common;
23+
#[forbid(unsafe_code)]
2324
pub mod convert;
25+
#[forbid(unsafe_code)]
2426
pub mod error;
2527

28+
#[cfg(feature = "custom_time")]
29+
pub mod custom_time;
30+
2631
pub use self::error::Error;
2732
pub use self::error::Result;

identity_core/tests/custom_time.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use identity_core::common::Timestamp;
2+
use identity_core::register_custom_now_utc;
3+
4+
const STATIC_TIME: i64 = 1724402964; // 2024-08-23T11:33:30+00:00
5+
pub fn static_now_utc() -> Timestamp {
6+
Timestamp::from_unix(STATIC_TIME).unwrap()
7+
}
8+
9+
register_custom_now_utc!(static_now_utc);
10+
11+
#[test]
12+
fn should_use_registered_static_time() {
13+
let timestamp = Timestamp::now_utc();
14+
assert_eq!(timestamp.to_unix(), STATIC_TIME)
15+
}

0 commit comments

Comments
 (0)