1use crate::{
4 config::schema::{RandomnessConfig, RandomnessSource},
5 log::Topic,
6 ops::{
7 config::ConfigOps,
8 ic::{mgmt, timer::TimerOps},
9 },
10};
11use canic_utils::rand as rand_utils;
12use sha2::{Digest, Sha256};
13use std::{cell::RefCell, time::Duration};
14
15thread_local! {
16 static SEED_TIMER: RefCell<Option<crate::ops::ic::timer::TimerId>> =
17 const { RefCell::new(None) };
18}
19
20pub struct RandomOps;
26
27impl RandomOps {
28 pub fn start() {
30 let cfg = Self::randomness_config();
31 if !cfg.enabled {
32 crate::log!(Topic::Init, Info, "randomness seeding disabled by config");
33 return;
34 }
35
36 let interval_secs = cfg.reseed_interval_secs;
37 if interval_secs == 0 {
38 crate::log!(
39 Topic::Init,
40 Warn,
41 "randomness reseed_interval_secs is 0; seeding disabled"
42 );
43 return;
44 }
45
46 let interval = Duration::from_secs(interval_secs);
47 let source = cfg.source;
48 let _ = TimerOps::set_guarded_interval(
49 &SEED_TIMER,
50 Duration::ZERO,
51 "random:seed:init",
52 move || async move {
53 Self::seed_once(source).await;
54 },
55 interval,
56 "random:seed:interval",
57 move || async move {
58 Self::seed_once(source).await;
59 },
60 );
61 }
62
63 async fn seed_once(source: RandomnessSource) {
64 match source {
65 RandomnessSource::Ic => match mgmt::raw_rand().await {
66 Ok(seed) => rand_utils::seed_from(seed),
67 Err(err) => {
68 crate::log!(Topic::Init, Warn, "raw_rand reseed failed: {err}");
69 }
70 },
71 RandomnessSource::Time => Self::seed_from_time(),
72 }
73 }
74
75 fn randomness_config() -> RandomnessConfig {
76 ConfigOps::current_canister().randomness
77 }
78
79 fn seed_from_time() {
80 let now = crate::cdk::api::time();
81 let canister_id = crate::cdk::api::canister_self();
82
83 let mut hasher = Sha256::new();
84 hasher.update(now.to_be_bytes());
85 hasher.update(canister_id.as_slice());
86 let seed: [u8; 32] = hasher.finalize().into();
87
88 rand_utils::seed_from(seed);
89 }
90}