1use rings_core::dht::Did;
4use rings_core::ecc::PublicKey;
5use rings_core::ecc::VerificationPublicKey;
6use rings_core::error::Error;
7use rings_core::error::Result;
8use rings_core::message::Decoder;
9use rings_core::message::DhtProtocolMode;
10use rings_core::message::Encoded;
11use rings_core::message::Encoder;
12use rings_core::message::MessageVerification;
13use rings_core::session::SessionSk;
14use serde::Deserialize;
15use serde::Serialize;
16
17use crate::descriptor::decode_descriptor;
18use crate::descriptor::encode_descriptor;
19use crate::descriptor::latest_valid_by_did;
20use crate::descriptor::sign_descriptor_body;
21use crate::descriptor::SignedDescriptor;
22use crate::descriptor::SignedDescriptorBody;
23
24pub const ONLINE_NODES_TOPIC: &str = "online_nodes";
26pub const ONLINE_NODE_CAPABILITY_STORAGE: &str = "storage";
28
29#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
31pub enum OnlineNodeType {
32 Browser,
34 Native,
36 Ffi,
38}
39
40#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
42pub struct OnlineNodeDescriptorBody {
43 pub did: Did,
45 pub public_key: VerificationPublicKey,
47 pub session_public_key: PublicKey<33>,
49 pub node_type: OnlineNodeType,
51 pub network_id: u32,
53 pub storage_redundancy: u16,
55 pub dht_virtual_nodes: u16,
57 pub capabilities: Vec<String>,
59 pub endpoint_hint: Option<String>,
61 pub started_at_ms: u128,
63 pub heartbeat_at_ms: u128,
65 pub expires_at_ms: u128,
67 pub version: String,
69}
70
71impl OnlineNodeDescriptorBody {
72 fn body_ref(&self) -> OnlineNodeDescriptorBodyRef<'_> {
73 OnlineNodeDescriptorBodyRef {
74 did: self.did,
75 public_key: &self.public_key,
76 session_public_key: &self.session_public_key,
77 node_type: &self.node_type,
78 network_id: self.network_id,
79 storage_redundancy: self.storage_redundancy,
80 dht_virtual_nodes: self.dht_virtual_nodes,
81 capabilities: &self.capabilities,
82 endpoint_hint: &self.endpoint_hint,
83 started_at_ms: self.started_at_ms,
84 heartbeat_at_ms: self.heartbeat_at_ms,
85 expires_at_ms: self.expires_at_ms,
86 version: self.version.as_str(),
87 }
88 }
89
90 fn signing_data(&self) -> Result<Vec<u8>> {
91 self.body_ref().signing_data()
92 }
93}
94
95impl SignedDescriptorBody for OnlineNodeDescriptorBody {
96 type Descriptor = OnlineNodeDescriptor;
97
98 fn body_did(&self) -> Did {
99 self.did
100 }
101
102 fn body_public_key(&self) -> &VerificationPublicKey {
103 &self.public_key
104 }
105
106 fn body_signing_data(&self) -> Result<Vec<u8>> {
107 self.signing_data()
108 }
109
110 fn into_signed_descriptor(self, signature: MessageVerification) -> Self::Descriptor {
111 OnlineNodeDescriptor {
112 did: self.did,
113 public_key: self.public_key,
114 session_public_key: self.session_public_key,
115 node_type: self.node_type,
116 network_id: self.network_id,
117 storage_redundancy: self.storage_redundancy,
118 dht_virtual_nodes: self.dht_virtual_nodes,
119 capabilities: self.capabilities,
120 endpoint_hint: self.endpoint_hint,
121 started_at_ms: self.started_at_ms,
122 heartbeat_at_ms: self.heartbeat_at_ms,
123 expires_at_ms: self.expires_at_ms,
124 version: self.version,
125 signature,
126 }
127 }
128}
129
130#[derive(Serialize)]
131struct OnlineNodeDescriptorBodyRef<'a> {
132 did: Did,
133 public_key: &'a VerificationPublicKey,
134 session_public_key: &'a PublicKey<33>,
135 node_type: &'a OnlineNodeType,
136 network_id: u32,
137 storage_redundancy: u16,
138 dht_virtual_nodes: u16,
139 capabilities: &'a [String],
140 endpoint_hint: &'a Option<String>,
141 started_at_ms: u128,
142 heartbeat_at_ms: u128,
143 expires_at_ms: u128,
144 version: &'a str,
145}
146
147impl OnlineNodeDescriptorBodyRef<'_> {
148 fn signing_data(&self) -> Result<Vec<u8>> {
149 rings_codec::serialize(self).map_err(Error::CodecSerialize)
150 }
151}
152
153#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
155pub struct OnlineNodeDescriptor {
156 pub did: Did,
158 pub public_key: VerificationPublicKey,
160 pub session_public_key: PublicKey<33>,
162 pub node_type: OnlineNodeType,
164 pub network_id: u32,
166 pub storage_redundancy: u16,
168 pub dht_virtual_nodes: u16,
170 pub capabilities: Vec<String>,
172 pub endpoint_hint: Option<String>,
174 pub started_at_ms: u128,
176 pub heartbeat_at_ms: u128,
178 pub expires_at_ms: u128,
180 pub version: String,
182 pub signature: MessageVerification,
184}
185
186impl OnlineNodeDescriptor {
187 pub fn new_signed(body: OnlineNodeDescriptorBody, session_sk: &SessionSk) -> Result<Self> {
189 sign_descriptor_body(
190 body,
191 session_sk,
192 "online node descriptor DID/public key/session mismatch",
193 )
194 }
195
196 fn body_ref(&self) -> OnlineNodeDescriptorBodyRef<'_> {
197 let Self {
198 did,
199 public_key,
200 session_public_key,
201 node_type,
202 network_id,
203 storage_redundancy,
204 dht_virtual_nodes,
205 capabilities,
206 endpoint_hint,
207 started_at_ms,
208 heartbeat_at_ms,
209 expires_at_ms,
210 version,
211 signature: _,
212 } = self;
213
214 OnlineNodeDescriptorBodyRef {
215 did: *did,
216 public_key,
217 session_public_key,
218 node_type,
219 network_id: *network_id,
220 storage_redundancy: *storage_redundancy,
221 dht_virtual_nodes: *dht_virtual_nodes,
222 capabilities,
223 endpoint_hint,
224 started_at_ms: *started_at_ms,
225 heartbeat_at_ms: *heartbeat_at_ms,
226 expires_at_ms: *expires_at_ms,
227 version: version.as_str(),
228 }
229 }
230
231 fn signing_data(&self) -> Result<Vec<u8>> {
232 self.body_ref().signing_data()
233 }
234
235 pub const fn dht_protocol_mode(&self) -> DhtProtocolMode {
237 DhtProtocolMode::new(
238 self.network_id,
239 self.storage_redundancy,
240 self.dht_virtual_nodes,
241 )
242 }
243
244 pub fn matches_dht_protocol(&self, expected: DhtProtocolMode) -> bool {
246 self.dht_protocol_mode().matches(expected)
247 }
248
249 pub fn verify_signature(&self) -> bool {
256 self.descriptor_verify_signature()
257 }
258
259 pub fn is_expired_at(&self, now_ms: u128) -> bool {
261 self.descriptor_is_expired_at(now_ms)
262 }
263
264 pub fn is_live_at(&self, now_ms: u128) -> bool {
266 self.descriptor_is_live_at(now_ms)
267 }
268
269 pub fn latest_valid_by_did(
271 descriptors: impl IntoIterator<Item = Self>,
272 now_ms: u128,
273 include_expired: bool,
274 ) -> Vec<Self> {
275 latest_valid_by_did(descriptors, now_ms, include_expired)
276 }
277}
278
279impl SignedDescriptor for OnlineNodeDescriptor {
280 fn descriptor_did(&self) -> Did {
281 self.did
282 }
283
284 fn descriptor_public_key(&self) -> &VerificationPublicKey {
285 &self.public_key
286 }
287
288 fn descriptor_signature(&self) -> &MessageVerification {
289 &self.signature
290 }
291
292 fn descriptor_heartbeat_at_ms(&self) -> u128 {
293 self.heartbeat_at_ms
294 }
295
296 fn descriptor_expires_at_ms(&self) -> u128 {
297 self.expires_at_ms
298 }
299
300 fn descriptor_signing_data(&self) -> Result<Vec<u8>> {
301 self.signing_data()
302 }
303}
304
305impl Encoder for OnlineNodeDescriptor {
306 fn encode(&self) -> Result<Encoded> {
307 encode_descriptor(self)
308 }
309}
310
311impl Decoder for OnlineNodeDescriptor {
312 fn from_encoded(encoded: &Encoded) -> Result<Self> {
313 decode_descriptor(encoded)
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use rings_core::ecc::SecretKey;
320 use rings_core::session::SessionSk;
321
322 use super::*;
323
324 fn descriptor_at(heartbeat_at_ms: u128, expires_at_ms: u128) -> Result<OnlineNodeDescriptor> {
325 let key = SecretKey::random();
326 let session_sk = SessionSk::new_with_seckey(&key)?;
327 let did = session_sk.account_did();
328 OnlineNodeDescriptor::new_signed(
329 OnlineNodeDescriptorBody {
330 did,
331 public_key: session_sk.session().account_verification_pubkey()?,
332 session_public_key: session_sk.session_public_key(),
333 node_type: OnlineNodeType::Native,
334 network_id: 1,
335 storage_redundancy: 6,
336 dht_virtual_nodes: 0,
337 capabilities: vec![ONLINE_NODE_CAPABILITY_STORAGE.to_string()],
338 endpoint_hint: None,
339 started_at_ms: 10,
340 heartbeat_at_ms,
341 expires_at_ms,
342 version: "test".to_string(),
343 },
344 &session_sk,
345 )
346 }
347
348 #[test]
349 fn test_descriptor_signature_covers_mutable_fields() -> Result<()> {
350 let mut descriptor = descriptor_at(20, 30)?;
351 assert!(descriptor.verify_signature());
352
353 descriptor.node_type = OnlineNodeType::Browser;
354 assert!(!descriptor.verify_signature());
355 descriptor = descriptor_at(20, 30)?;
356 descriptor.storage_redundancy = 7;
357 assert!(!descriptor.verify_signature());
358 Ok(())
359 }
360
361 #[test]
362 fn test_descriptor_round_trips_through_dht_encoding() -> Result<()> {
363 let descriptor = descriptor_at(20, 30)?;
364 let encoded = descriptor.encode()?;
365 let decoded = OnlineNodeDescriptor::from_encoded(&encoded)?;
366
367 assert_eq!(decoded, descriptor);
368 assert!(decoded.verify_signature());
369 Ok(())
370 }
371
372 #[test]
373 fn test_latest_valid_by_did_filters_expired_and_keeps_newest() -> Result<()> {
374 let key = SecretKey::random();
375 let session_sk = SessionSk::new_with_seckey(&key)?;
376 let did = session_sk.account_did();
377 let public_key = session_sk.session().account_verification_pubkey()?;
378
379 let older = OnlineNodeDescriptor::new_signed(
380 OnlineNodeDescriptorBody {
381 did,
382 public_key: public_key.clone(),
383 session_public_key: session_sk.session_public_key(),
384 node_type: OnlineNodeType::Native,
385 network_id: 1,
386 storage_redundancy: 6,
387 dht_virtual_nodes: 0,
388 capabilities: vec![],
389 endpoint_hint: None,
390 started_at_ms: 1,
391 heartbeat_at_ms: 10,
392 expires_at_ms: 100,
393 version: "old".to_string(),
394 },
395 &session_sk,
396 )?;
397 let newer = OnlineNodeDescriptor::new_signed(
398 OnlineNodeDescriptorBody {
399 did,
400 public_key,
401 session_public_key: session_sk.session_public_key(),
402 node_type: OnlineNodeType::Native,
403 network_id: 1,
404 storage_redundancy: 6,
405 dht_virtual_nodes: 0,
406 capabilities: vec![],
407 endpoint_hint: None,
408 started_at_ms: 1,
409 heartbeat_at_ms: 20,
410 expires_at_ms: 100,
411 version: "new".to_string(),
412 },
413 &session_sk,
414 )?;
415 let other_live = descriptor_at(25, 100)?;
416 let expired = descriptor_at(30, 40)?;
417
418 let descriptors = OnlineNodeDescriptor::latest_valid_by_did(
419 vec![
420 older.clone(),
421 newer.clone(),
422 other_live.clone(),
423 expired.clone(),
424 ],
425 50,
426 false,
427 );
428
429 assert_eq!(descriptors.len(), 2);
430 assert!(descriptors.iter().any(|descriptor| descriptor == &newer));
431 assert!(descriptors
432 .iter()
433 .any(|descriptor| descriptor == &other_live));
434
435 let with_expired = OnlineNodeDescriptor::latest_valid_by_did(
436 vec![older, newer, other_live, expired],
437 50,
438 true,
439 );
440 assert_eq!(with_expired.len(), 3);
441 Ok(())
442 }
443}