1use std::fmt;
2
3use bytes::{BufMut, BytesMut};
4use derive_more::{Display, Error};
5use hkdf::Hkdf;
6use hmac::{Hmac, Mac};
7use sha2::Sha256;
8
9#[cfg(feature = "chrono")]
10use chrono::{DateTime, Utc};
11
12use crate::rpi::Rpi;
13use crate::tkey::TracingKey;
14use crate::utils::Base64DebugFmtHelper;
15
16#[cfg(feature = "chrono")]
17use crate::utils::day_number_for_timestamp;
18
19#[derive(Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
21pub struct DailyTracingKey {
22 bytes: [u8; 16],
23}
24
25impl fmt::Debug for DailyTracingKey {
26 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27 f.debug_tuple("DailyTracingKey")
28 .field(&Base64DebugFmtHelper(self))
29 .finish()
30 }
31}
32
33impl DailyTracingKey {
34 pub fn for_day(tk: &TracingKey, day: u32) -> DailyTracingKey {
36 let h = Hkdf::<Sha256>::new(None, tk.as_bytes());
37 let mut info = BytesMut::from(&"CT-DTK"[..]);
38 info.put_u32_le(day);
39 let mut out = [0u8; 16];
40 h.expand(&info, &mut out).unwrap();
41 DailyTracingKey::from_bytes(&out[..]).unwrap()
42 }
43
44 #[cfg(feature = "chrono")]
46 pub fn for_today(tk: &TracingKey) -> DailyTracingKey {
47 DailyTracingKey::for_timestamp(tk, &Utc::now())
48 }
49
50 #[cfg(feature = "chrono")]
52 pub fn for_timestamp(tk: &TracingKey, timestamp: &DateTime<Utc>) -> DailyTracingKey {
53 DailyTracingKey::for_day(tk, day_number_for_timestamp(timestamp))
54 }
55
56 pub fn from_bytes(b: &[u8]) -> Result<DailyTracingKey, InvalidDailyTracingKey> {
58 if b.len() != 16 {
59 return Err(InvalidDailyTracingKey);
60 }
61 let mut bytes = [0u8; 16];
62 bytes.copy_from_slice(b);
63 Ok(DailyTracingKey { bytes })
64 }
65
66 pub fn as_bytes(&self) -> &[u8] {
68 &self.bytes
69 }
70
71 pub fn iter_rpis(&self) -> impl Iterator<Item = Rpi> {
75 let clone = *self;
76 let mut tin = 0;
77 std::iter::from_fn(move || {
78 clone.get_rpi_for_tin(tin).map(|rv| {
79 tin += 1;
80 rv
81 })
82 })
83 }
84
85 pub fn get_rpi_for_tin(&self, tin: u8) -> Option<Rpi> {
89 if tin > 143 {
90 return None;
91 }
92
93 let mut hmac = Hmac::<Sha256>::new_varkey(&self.as_bytes()).unwrap();
94 let mut info = BytesMut::from(&"CT-RPI"[..]);
95 info.put_u8(tin);
96 hmac.input(&info[..]);
97 let result = hmac.result();
98 let bytes = &result.code()[..];
99 Some(Rpi::from_bytes(&bytes[..16]).unwrap())
100 }
101}
102
103#[derive(Error, Display, Debug)]
105#[display(fmt = "invalid daily tracing key")]
106pub struct InvalidDailyTracingKey;
107
108#[cfg(feature = "base64")]
109mod base64_impl {
110 use super::*;
111 use std::{fmt, str};
112
113 impl str::FromStr for DailyTracingKey {
114 type Err = InvalidDailyTracingKey;
115
116 fn from_str(value: &str) -> Result<DailyTracingKey, InvalidDailyTracingKey> {
117 let mut bytes = [0u8; 16];
118 if value.len() != 22 {
119 return Err(InvalidDailyTracingKey);
120 }
121 base64_::decode_config_slice(value, base64_::URL_SAFE_NO_PAD, &mut bytes[..])
122 .map_err(|_| InvalidDailyTracingKey)?;
123 Ok(DailyTracingKey { bytes })
124 }
125 }
126
127 #[cfg(feature = "base64")]
128 impl fmt::Display for DailyTracingKey {
129 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130 let mut buf = [0u8; 50];
131 let len = base64_::encode_config_slice(self.bytes, base64_::URL_SAFE_NO_PAD, &mut buf);
132 f.write_str(unsafe { std::str::from_utf8_unchecked(&buf[..len]) })
133 }
134 }
135}
136
137#[cfg(feature = "serde")]
138mod serde_impl {
139 use super::*;
140
141 use serde_::de::Deserializer;
142 use serde_::ser::Serializer;
143 use serde_::{Deserialize, Serialize};
144
145 impl Serialize for DailyTracingKey {
146 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
147 where
148 S: Serializer,
149 {
150 if serializer.is_human_readable() {
151 serializer.serialize_str(&self.to_string())
152 } else {
153 serializer.serialize_bytes(self.as_bytes())
154 }
155 }
156 }
157
158 impl<'de> Deserialize<'de> for DailyTracingKey {
159 fn deserialize<D>(deserializer: D) -> Result<DailyTracingKey, D::Error>
160 where
161 D: Deserializer<'de>,
162 {
163 use serde_::de::Error;
164 if deserializer.is_human_readable() {
165 let s = String::deserialize(deserializer).map_err(D::Error::custom)?;
166 s.parse().map_err(D::Error::custom)
167 } else {
168 let buf = Vec::<u8>::deserialize(deserializer).map_err(D::Error::custom)?;
169 DailyTracingKey::from_bytes(&buf).map_err(D::Error::custom)
170 }
171 }
172 }
173}