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::time::Duration;
14
15pub struct RandomOps;
25
26impl RandomOps {
27 pub fn start() {
29 let cfg = Self::randomness_config();
30 if !cfg.enabled {
31 crate::log!(Topic::Init, Info, "randomness seeding disabled by config");
32 return;
33 }
34
35 let interval_secs = cfg.reseed_interval_secs;
36 if interval_secs == 0 {
37 crate::log!(
38 Topic::Init,
39 Warn,
40 "randomness reseed_interval_secs is 0; seeding disabled"
41 );
42 return;
43 }
44
45 let interval = Duration::from_secs(interval_secs);
46 Self::schedule_seeding(Duration::ZERO, interval, cfg.source);
47 }
48
49 async fn seed_once(source: RandomnessSource) {
50 match source {
51 RandomnessSource::Ic => match mgmt::raw_rand().await {
52 Ok(seed) => rand_utils::seed_from(seed),
53 Err(err) => {
54 crate::log!(Topic::Init, Warn, "raw_rand reseed failed: {err}");
55 }
56 },
57 RandomnessSource::Time => Self::seed_from_time(),
58 }
59 }
60
61 fn schedule_seeding(delay: Duration, interval: Duration, source: RandomnessSource) {
62 let _ = TimerOps::set(delay, "random:seed", async move {
63 Self::seed_once(source).await;
64 Self::schedule_seeding(interval, interval, source);
65 });
66 }
67
68 fn randomness_config() -> RandomnessConfig {
69 match ConfigOps::current_canister() {
70 Ok(cfg) => cfg.randomness,
71 Err(err) => {
72 crate::log!(
73 Topic::Init,
74 Warn,
75 "randomness config unavailable, using defaults: {err}"
76 );
77 RandomnessConfig::default()
78 }
79 }
80 }
81
82 fn seed_from_time() {
83 let now = crate::cdk::api::time();
84 let canister_id = crate::cdk::api::canister_self();
85
86 let mut hasher = Sha256::new();
87 hasher.update(now.to_be_bytes());
88 hasher.update(canister_id.as_slice());
89 let seed: [u8; 32] = hasher.finalize().into();
90
91 rand_utils::seed_from(seed);
92 }
93}