pass_it_on/
notifications.rs

1//! Representation of notification messages.
2
3use crate::{Error, KEY_CONTEXT};
4use blake3::Hash;
5use serde::{Deserialize, Serialize};
6use serde_json::StreamDeserializer;
7use std::collections::HashSet;
8use std::time::{SystemTime, UNIX_EPOCH};
9
10/// The actual message data that is being transmitted in a [`Notification`].
11#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
12pub struct Message {
13    text: String,
14    time: u128,
15}
16
17/// A [`Message`] that has been assigned a notification name
18#[derive(Debug, PartialEq, Eq, Hash, Clone)]
19pub struct ClientReadyMessage {
20    message: Message,
21    notification_name: String,
22}
23
24/// [`Notification`] that has been validated against identified as a particular notification name.
25#[derive(Debug, PartialEq, Eq, Hash, Clone)]
26pub struct ValidatedNotification {
27    message: Message,
28    sub_name: String,
29}
30
31/// Notification ready to be send with [`Message`] data and a hash value for validation.
32#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
33pub struct Notification {
34    message: Message,
35    key: String,
36}
37
38/// Convenience wrapper around a [BLAKE3] [`Hash`] used for validation.
39///
40/// [BLAKE3]: https://crates.io/crates/blake3
41/// [`Hash`]: https://docs.rs/blake3/latest/blake3/struct.Hash.html
42#[derive(Debug, PartialEq, Eq, Hash, Clone)]
43pub struct Key {
44    hash: Hash,
45}
46
47impl Notification {
48    /// Create a new `Notification` from a text value and key for notification name.
49    pub fn new(message: Message, notification_key: &Key) -> Notification {
50        let key = message.create_key(notification_key).to_hex();
51        Notification { message, key }
52    }
53
54    /// Parse single `Notification` from JSON.
55    pub fn from_json<S: AsRef<str>>(input: S) -> Result<Notification, Error> {
56        Ok(serde_json::from_str(input.as_ref())?)
57    }
58
59    /// Parse multiple `Notification`s from JSON.
60    pub fn from_json_multi<S: AsRef<str>>(input: S) -> Vec<Result<Notification, Error>> {
61        let mut notifications = Vec::new();
62        let stream: StreamDeserializer<_, Notification> =
63            serde_json::Deserializer::from_str(input.as_ref()).into_iter();
64
65        for item in stream {
66            match item {
67                Err(e) => notifications.push(Err(Error::SerdeJsonError(e))),
68                Ok(n) => notifications.push(Ok(n)),
69            };
70        }
71        notifications
72    }
73
74    /// Serialize `Notification` to JSON.
75    pub fn to_json(&self) -> Result<String, Error> {
76        Ok(serde_json::to_string(self)?)
77    }
78    
79    /// Compare provided notification name ['Key'] to this notification.
80    pub(crate) fn validate(&self, hash_key: &Key) -> bool {
81        let new_key = self.message.create_key(hash_key);
82        self.key == new_key.to_hex()
83    }
84    
85    /// Compare provided set of  notification name ['Key']s to this notification.
86    pub(crate) fn validate_set(&self, hash_keys: &HashSet<Key>) -> bool {
87        for hash_key in hash_keys {
88            match self.validate(hash_key) {
89                true => return true,
90                false => (),
91            }
92        }
93        false
94    }
95
96    /// Return inner [`Message`].
97    pub fn message(&self) -> Message {
98        self.message.clone()
99    }
100
101    /// Return notification key as a string slice.
102    pub fn key(&self) -> &str {
103        &self.key
104    }
105}
106
107impl Message {
108    /// Create a new `Message` from provide text.
109    pub fn new<S: AsRef<str>>(text: S) -> Message {
110        let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos();
111        let body = String::from(text.as_ref());
112        Self { text: body, time }
113    }
114
115    /// Return inner text value.
116    pub fn text(&self) -> &str {
117        &self.text
118    }
119
120    /// Return time when this `Message` was created.
121    pub fn time(&self) -> u128 {
122        self.time
123    }
124
125    /// Create a [`Key`] for this [`Message`] based on the [`Key`] for the notification name.
126    fn create_key(&self, notification_key: &Key) -> Key {
127        let hash_string = format!("{}{}", self.text, self.time);
128        Key::generate(hash_string.as_str(), notification_key)
129    }
130
131    /// Assign a notification name to a [`Message`] and transform it into a [`ClientReadyMessage`]
132    pub fn to_client_ready_message<S: AsRef<str>>(self, notification_name: S) -> ClientReadyMessage {
133        ClientReadyMessage::new(notification_name, self)
134    }
135}
136
137impl ClientReadyMessage {
138    /// Create a new `ClientReadyMessage`
139    pub(crate) fn new<S: AsRef<str>>(notification_name: S, message: Message) -> Self {
140        Self { notification_name: notification_name.as_ref().into(), message }
141    }
142
143    /// Create a [`Notification`] for the contained [`Message`] based on the notification name and client [`Key`]
144    pub fn to_notification(self, client_key: &Key) -> Notification {
145        let key = Key::generate(self.notification_name(), client_key);
146        let message = self.message;
147        Notification::new(message, &key)
148    }
149
150    /// Return inner [`Message`]
151    pub fn message(&self) -> &Message {
152        &self.message
153    }
154
155    /// Return assigned notification name
156    pub fn notification_name(&self) -> &str {
157        &self.notification_name
158    }
159}
160
161impl ValidatedNotification {
162    /// Create a new `ValidatedNotification`.
163    pub fn new<S: AsRef<str>>(name_id: S, message: Message) -> ValidatedNotification {
164        Self { sub_name: name_id.as_ref().into(), message }
165    }
166
167    /// Return inner [`Message`] value.
168    pub fn message(&self) -> &Message {
169        &self.message
170    }
171
172    /// Return the sub-name for this `ValidatedNotification`.
173    pub fn sub_name(&self) -> &str {
174        &self.sub_name
175    }
176}
177
178impl Key {
179    /// Generate a new keyed hash based on the provide notification name.
180    pub fn generate<S: AsRef<str>>(name: S, hash_key: &Key) -> Key {
181        let mut hasher = blake3::Hasher::new_keyed(hash_key.as_bytes());
182        hasher.update(name.as_ref().as_bytes());
183        Self { hash: hasher.finalize() }
184    }
185
186    /// Generate a key from the pass-it-on static context and  a provided key string.
187    pub fn derive_shared_key<S: AsRef<str>>(key_string: S) -> Key {
188        Self::from_bytes(&blake3::derive_key(KEY_CONTEXT, key_string.as_ref().as_bytes()))
189    }
190
191    /// Create `Key` from a byte array.
192    pub fn from_bytes(key: &[u8; 32]) -> Key {
193        let hash = Hash::from(*key);
194        Self { hash }
195    }
196
197    /// Create `Key` from a hexadecimal.
198    pub fn from_hex<S: AsRef<str>>(key: S) -> Key {
199        let hash = Hash::from_hex(key.as_ref()).expect("Unable to create Key from hex");
200        Self { hash }
201    }
202
203    /// Return `Key` as a hexadecimal.
204    pub fn to_hex(&self) -> String {
205        self.hash.to_hex().to_string()
206    }
207
208    /// Return `Key` as a byte array.
209    pub fn as_bytes(&self) -> &[u8; 32] {
210        self.hash.as_bytes()
211    }
212}