pass_it_on/
notifications.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
//! Representation of notification messages.

use crate::{Error, KEY_CONTEXT};
use blake3::Hash;
use serde::{Deserialize, Serialize};
use serde_json::StreamDeserializer;
use std::collections::HashSet;
use std::time::{SystemTime, UNIX_EPOCH};

/// The actual message data that is being transmitted in a [`Notification`].
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
pub struct Message {
    text: String,
    time: u128,
}

/// A [`Message`] that has been assigned a notification name
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct ClientReadyMessage {
    message: Message,
    notification_name: String,
}

/// [`Notification`] that has been validated against identified as a particular notification name.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct ValidatedNotification {
    message: Message,
    sub_name: String,
}

/// Notification ready to be send with [`Message`] data and a hash value for validation.
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
pub struct Notification {
    message: Message,
    key: String,
}

/// Convenience wrapper around a [BLAKE3] [`Hash`] used for validation.
///
/// [BLAKE3]: https://crates.io/crates/blake3
/// [`Hash`]: https://docs.rs/blake3/latest/blake3/struct.Hash.html
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Key {
    hash: Hash,
}

impl Notification {
    /// Create a new `Notification` from a text value and key for notification name.
    pub fn new(message: Message, notification_key: &Key) -> Notification {
        let key = message.create_key(notification_key).to_hex();
        Notification { message, key }
    }

    /// Parse single `Notification` from JSON.
    pub fn from_json<S: AsRef<str>>(input: S) -> Result<Notification, Error> {
        Ok(serde_json::from_str(input.as_ref())?)
    }

    /// Parse multiple `Notification`s from JSON.
    pub fn from_json_multi<S: AsRef<str>>(input: S) -> Vec<Result<Notification, Error>> {
        let mut notifications = Vec::new();
        let stream: StreamDeserializer<_, Notification> =
            serde_json::Deserializer::from_str(input.as_ref()).into_iter();

        for item in stream {
            match item {
                Err(e) => notifications.push(Err(Error::SerdeJsonError(e))),
                Ok(n) => notifications.push(Ok(n)),
            };
        }
        notifications
    }

    /// Serialize `Notification` to JSON.
    pub fn to_json(&self) -> Result<String, Error> {
        Ok(serde_json::to_string(self)?)
    }
    
    /// Compare provided notification name ['Key'] to this notification.
    pub(crate) fn validate(&self, hash_key: &Key) -> bool {
        let new_key = self.message.create_key(hash_key);
        self.key == new_key.to_hex()
    }
    
    /// Compare provided set of  notification name ['Key']s to this notification.
    pub(crate) fn validate_set(&self, hash_keys: &HashSet<Key>) -> bool {
        for hash_key in hash_keys {
            match self.validate(hash_key) {
                true => return true,
                false => (),
            }
        }
        false
    }

    /// Return inner [`Message`].
    pub fn message(&self) -> Message {
        self.message.clone()
    }

    /// Return notification key as a string slice.
    pub fn key(&self) -> &str {
        &self.key
    }
}

impl Message {
    /// Create a new `Message` from provide text.
    pub fn new<S: AsRef<str>>(text: S) -> Message {
        let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos();
        let body = String::from(text.as_ref());
        Self { text: body, time }
    }

    /// Return inner text value.
    pub fn text(&self) -> &str {
        &self.text
    }

    /// Return time when this `Message` was created.
    pub fn time(&self) -> u128 {
        self.time
    }

    /// Create a [`Key`] for this [`Message`] based on the [`Key`] for the notification name.
    fn create_key(&self, notification_key: &Key) -> Key {
        let hash_string = format!("{}{}", self.text, self.time);
        Key::generate(hash_string.as_str(), notification_key)
    }

    /// Assign a notification name to a [`Message`] and transform it into a [`ClientReadyMessage`]
    pub fn to_client_ready_message<S: AsRef<str>>(self, notification_name: S) -> ClientReadyMessage {
        ClientReadyMessage::new(notification_name, self)
    }
}

impl ClientReadyMessage {
    /// Create a new `ClientReadyMessage`
    pub(crate) fn new<S: AsRef<str>>(notification_name: S, message: Message) -> Self {
        Self { notification_name: notification_name.as_ref().into(), message }
    }

    /// Create a [`Notification`] for the contained [`Message`] based on the notification name and client [`Key`]
    pub fn to_notification(self, client_key: &Key) -> Notification {
        let key = Key::generate(self.notification_name(), client_key);
        let message = self.message;
        Notification::new(message, &key)
    }

    /// Return inner [`Message`]
    pub fn message(&self) -> &Message {
        &self.message
    }

    /// Return assigned notification name
    pub fn notification_name(&self) -> &str {
        &self.notification_name
    }
}

impl ValidatedNotification {
    /// Create a new `ValidatedNotification`.
    pub fn new<S: AsRef<str>>(name_id: S, message: Message) -> ValidatedNotification {
        Self { sub_name: name_id.as_ref().into(), message }
    }

    /// Return inner [`Message`] value.
    pub fn message(&self) -> &Message {
        &self.message
    }

    /// Return the sub-name for this `ValidatedNotification`.
    pub fn sub_name(&self) -> &str {
        &self.sub_name
    }
}

impl Key {
    /// Generate a new keyed hash based on the provide notification name.
    pub fn generate<S: AsRef<str>>(name: S, hash_key: &Key) -> Key {
        let mut hasher = blake3::Hasher::new_keyed(hash_key.as_bytes());
        hasher.update(name.as_ref().as_bytes());
        Self { hash: hasher.finalize() }
    }

    /// Generate a key from the pass-it-on static context and  a provided key string.
    pub fn derive_shared_key<S: AsRef<str>>(key_string: S) -> Key {
        Self::from_bytes(&blake3::derive_key(KEY_CONTEXT, key_string.as_ref().as_bytes()))
    }

    /// Create `Key` from a byte array.
    pub fn from_bytes(key: &[u8; 32]) -> Key {
        let hash = Hash::from(*key);
        Self { hash }
    }

    /// Create `Key` from a hexadecimal.
    pub fn from_hex<S: AsRef<str>>(key: S) -> Key {
        let hash = Hash::from_hex(key.as_ref()).expect("Unable to create Key from hex");
        Self { hash }
    }

    /// Return `Key` as a hexadecimal.
    pub fn to_hex(&self) -> String {
        self.hash.to_hex().to_string()
    }

    /// Return `Key` as a byte array.
    pub fn as_bytes(&self) -> &[u8; 32] {
        self.hash.as_bytes()
    }
}