1use rns_core::destination::destination_hash;
7use rns_core::transport::types::InterfaceId;
8use rns_core::types::{DestHash, DestinationType, Direction, IdentityHash, ProofStrategy};
9use rns_crypto::token::Token;
10use rns_crypto::OsRng;
11use rns_crypto::Rng;
12
13#[derive(Debug, PartialEq)]
15pub enum GroupKeyError {
16 NoKey,
18 InvalidKeyLength,
20 EncryptionFailed,
22 DecryptionFailed,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum MaxRequestSizeError {
29 Negative,
31}
32
33impl core::fmt::Display for MaxRequestSizeError {
34 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
35 match self {
36 MaxRequestSizeError::Negative => {
37 write!(f, "maximum request size cannot be negative")
38 }
39 }
40 }
41}
42
43impl core::fmt::Display for GroupKeyError {
44 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45 match self {
46 GroupKeyError::NoKey => write!(f, "No GROUP key loaded"),
47 GroupKeyError::InvalidKeyLength => write!(f, "Key must be 32 or 64 bytes"),
48 GroupKeyError::EncryptionFailed => write!(f, "Encryption failed"),
49 GroupKeyError::DecryptionFailed => write!(f, "Decryption failed"),
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
59pub struct Destination {
60 pub hash: DestHash,
62 pub dest_type: DestinationType,
64 pub direction: Direction,
66 pub app_name: String,
68 pub aspects: Vec<String>,
70 pub identity_hash: Option<IdentityHash>,
72 pub public_key: Option<[u8; 64]>,
74 pub group_key: Option<Vec<u8>>,
76 pub proof_strategy: ProofStrategy,
78 pub max_request_size: Option<usize>,
80}
81
82impl Destination {
83 pub fn single_in(app_name: &str, aspects: &[&str], identity_hash: IdentityHash) -> Self {
87 let dh = destination_hash(app_name, aspects, Some(&identity_hash.0));
88 Destination {
89 hash: DestHash(dh),
90 dest_type: DestinationType::Single,
91 direction: Direction::In,
92 app_name: app_name.into(),
93 aspects: aspects.iter().map(|s| s.to_string()).collect(),
94 identity_hash: Some(identity_hash),
95 public_key: None,
96 group_key: None,
97 proof_strategy: ProofStrategy::ProveNone,
98 max_request_size: None,
99 }
100 }
101
102 pub fn single_out(app_name: &str, aspects: &[&str], recalled: &AnnouncedIdentity) -> Self {
106 let dh = destination_hash(app_name, aspects, Some(&recalled.identity_hash.0));
107 Destination {
108 hash: DestHash(dh),
109 dest_type: DestinationType::Single,
110 direction: Direction::Out,
111 app_name: app_name.into(),
112 aspects: aspects.iter().map(|s| s.to_string()).collect(),
113 identity_hash: Some(recalled.identity_hash),
114 public_key: Some(recalled.public_key),
115 group_key: None,
116 proof_strategy: ProofStrategy::ProveNone,
117 max_request_size: None,
118 }
119 }
120
121 pub fn plain(app_name: &str, aspects: &[&str]) -> Self {
123 let dh = destination_hash(app_name, aspects, None);
124 Destination {
125 hash: DestHash(dh),
126 dest_type: DestinationType::Plain,
127 direction: Direction::In,
128 app_name: app_name.into(),
129 aspects: aspects.iter().map(|s| s.to_string()).collect(),
130 identity_hash: None,
131 public_key: None,
132 group_key: None,
133 proof_strategy: ProofStrategy::ProveNone,
134 max_request_size: None,
135 }
136 }
137
138 pub fn group(app_name: &str, aspects: &[&str]) -> Self {
143 let dh = destination_hash(app_name, aspects, None);
144 Destination {
145 hash: DestHash(dh),
146 dest_type: DestinationType::Group,
147 direction: Direction::In,
148 app_name: app_name.into(),
149 aspects: aspects.iter().map(|s| s.to_string()).collect(),
150 identity_hash: None,
151 public_key: None,
152 group_key: None,
153 proof_strategy: ProofStrategy::ProveNone,
154 max_request_size: None,
155 }
156 }
157
158 pub fn create_keys(&mut self) {
160 let mut key = vec![0u8; 64];
161 OsRng.fill_bytes(&mut key);
162 self.group_key = Some(key);
163 }
164
165 pub fn load_private_key(&mut self, key: Vec<u8>) -> Result<(), GroupKeyError> {
169 if key.len() != 32 && key.len() != 64 {
170 return Err(GroupKeyError::InvalidKeyLength);
171 }
172 self.group_key = Some(key);
173 Ok(())
174 }
175
176 pub fn get_private_key(&self) -> Option<&[u8]> {
178 self.group_key.as_deref()
179 }
180
181 pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, GroupKeyError> {
183 let key = self.group_key.as_ref().ok_or(GroupKeyError::NoKey)?;
184 let token = Token::new(key).map_err(|_| GroupKeyError::EncryptionFailed)?;
185 Ok(token.encrypt(plaintext, &mut OsRng))
186 }
187
188 pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, GroupKeyError> {
190 let key = self.group_key.as_ref().ok_or(GroupKeyError::NoKey)?;
191 let token = Token::new(key).map_err(|_| GroupKeyError::DecryptionFailed)?;
192 token
193 .decrypt(ciphertext)
194 .map_err(|_| GroupKeyError::DecryptionFailed)
195 }
196
197 pub fn set_proof_strategy(mut self, strategy: ProofStrategy) -> Self {
199 self.proof_strategy = strategy;
200 self
201 }
202
203 pub fn set_max_request_size(
209 &mut self,
210 max_request_size: i64,
211 ) -> Result<(), MaxRequestSizeError> {
212 if max_request_size < 0 {
213 return Err(MaxRequestSizeError::Negative);
214 }
215 self.max_request_size = Some(max_request_size as usize);
216 Ok(())
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct AnnouncedIdentity {
223 pub dest_hash: DestHash,
225 pub identity_hash: IdentityHash,
227 pub public_key: [u8; 64],
229 pub app_data: Option<Vec<u8>>,
231 pub hops: u8,
233 pub received_at: f64,
235 pub receiving_interface: InterfaceId,
237 pub rssi: Option<i16>,
239 pub snr: Option<f32>,
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 fn test_identity_hash() -> IdentityHash {
248 IdentityHash([0x42; 16])
249 }
250
251 fn test_announced() -> AnnouncedIdentity {
252 AnnouncedIdentity {
253 dest_hash: DestHash([0xAA; 16]),
254 identity_hash: IdentityHash([0x42; 16]),
255 public_key: [0xBB; 64],
256 app_data: Some(b"test_data".to_vec()),
257 hops: 3,
258 received_at: 1234567890.0,
259 receiving_interface: InterfaceId(0),
260 rssi: Some(-100),
261 snr: Some(10.5),
262 }
263 }
264
265 #[test]
266 fn single_in_hash_matches_raw() {
267 let ih = test_identity_hash();
268 let dest = Destination::single_in("echo", &["app"], ih);
269
270 let raw = destination_hash("echo", &["app"], Some(&ih.0));
271 assert_eq!(dest.hash.0, raw);
272 assert_eq!(dest.dest_type, DestinationType::Single);
273 assert_eq!(dest.direction, Direction::In);
274 assert_eq!(dest.app_name, "echo");
275 assert_eq!(dest.aspects, vec!["app".to_string()]);
276 assert_eq!(dest.identity_hash, Some(ih));
277 assert!(dest.public_key.is_none());
278 }
279
280 #[test]
281 fn single_out_from_recalled() {
282 let recalled = test_announced();
283 let dest = Destination::single_out("echo", &["app"], &recalled);
284
285 let raw = destination_hash("echo", &["app"], Some(&recalled.identity_hash.0));
286 assert_eq!(dest.hash.0, raw);
287 assert_eq!(dest.dest_type, DestinationType::Single);
288 assert_eq!(dest.direction, Direction::Out);
289 assert_eq!(dest.public_key, Some([0xBB; 64]));
290 }
291
292 #[test]
293 fn plain_destination() {
294 let dest = Destination::plain("broadcast", &["test"]);
295
296 let raw = destination_hash("broadcast", &["test"], None);
297 assert_eq!(dest.hash.0, raw);
298 assert_eq!(dest.dest_type, DestinationType::Plain);
299 assert!(dest.identity_hash.is_none());
300 assert!(dest.public_key.is_none());
301 }
302
303 #[test]
304 fn destination_deterministic() {
305 let ih = test_identity_hash();
306 let d1 = Destination::single_in("app", &["a", "b"], ih);
307 let d2 = Destination::single_in("app", &["a", "b"], ih);
308 assert_eq!(d1.hash, d2.hash);
309 }
310
311 #[test]
312 fn different_identity_different_hash() {
313 let d1 = Destination::single_in("app", &["a"], IdentityHash([1; 16]));
314 let d2 = Destination::single_in("app", &["a"], IdentityHash([2; 16]));
315 assert_ne!(d1.hash, d2.hash);
316 }
317
318 #[test]
319 fn proof_strategy_builder() {
320 let dest = Destination::plain("app", &["a"]).set_proof_strategy(ProofStrategy::ProveAll);
321 assert_eq!(dest.proof_strategy, ProofStrategy::ProveAll);
322 }
323
324 #[test]
325 fn maximum_request_size_is_unlimited_by_default() {
326 let dest = Destination::single_in("app", &["requests"], test_identity_hash());
327 assert_eq!(dest.max_request_size, None);
328 }
329
330 #[test]
331 fn maximum_request_size_accepts_zero_and_positive_values() {
332 let mut dest = Destination::single_in("app", &["requests"], test_identity_hash());
333 dest.set_max_request_size(0).unwrap();
334 assert_eq!(dest.max_request_size, Some(0));
335 dest.set_max_request_size(4096).unwrap();
336 assert_eq!(dest.max_request_size, Some(4096));
337 }
338
339 #[test]
340 fn maximum_request_size_rejects_negative_values_without_changing_limit() {
341 let mut dest = Destination::single_in("app", &["requests"], test_identity_hash());
342 dest.set_max_request_size(1024).unwrap();
343 assert_eq!(
344 dest.set_max_request_size(-1),
345 Err(MaxRequestSizeError::Negative)
346 );
347 assert_eq!(dest.max_request_size, Some(1024));
348 }
349
350 #[test]
351 fn announced_identity_fields() {
352 let ai = test_announced();
353 assert_eq!(ai.dest_hash, DestHash([0xAA; 16]));
354 assert_eq!(ai.identity_hash, IdentityHash([0x42; 16]));
355 assert_eq!(ai.public_key, [0xBB; 64]);
356 assert_eq!(ai.app_data, Some(b"test_data".to_vec()));
357 assert_eq!(ai.hops, 3);
358 assert_eq!(ai.received_at, 1234567890.0);
359 assert_eq!(ai.receiving_interface, InterfaceId(0));
360 }
361
362 #[test]
363 fn announced_identity_receiving_interface_nonzero() {
364 let ai = AnnouncedIdentity {
365 receiving_interface: InterfaceId(42),
366 ..test_announced()
367 };
368 assert_eq!(ai.receiving_interface, InterfaceId(42));
369 }
370
371 #[test]
372 fn announced_identity_clone_preserves_receiving_interface() {
373 let ai = AnnouncedIdentity {
374 receiving_interface: InterfaceId(7),
375 ..test_announced()
376 };
377 let cloned = ai.clone();
378 assert_eq!(cloned.receiving_interface, ai.receiving_interface);
379 }
380
381 #[test]
382 fn single_out_from_recalled_with_interface() {
383 let recalled = AnnouncedIdentity {
384 receiving_interface: InterfaceId(5),
385 ..test_announced()
386 };
387 let dest = Destination::single_out("echo", &["app"], &recalled);
389 assert_eq!(dest.dest_type, DestinationType::Single);
390 assert_eq!(dest.direction, Direction::Out);
391 assert_eq!(dest.public_key, Some([0xBB; 64]));
392 }
393
394 #[test]
395 fn multiple_aspects() {
396 let dest = Destination::plain("app", &["one", "two", "three"]);
397 assert_eq!(dest.aspects, vec!["one", "two", "three"]);
398 }
399
400 #[test]
403 fn group_destination_hash_deterministic() {
404 let d1 = Destination::group("myapp", &["chat", "room"]);
405 let d2 = Destination::group("myapp", &["chat", "room"]);
406 assert_eq!(d1.hash, d2.hash);
407 assert_eq!(d1.dest_type, DestinationType::Group);
408 assert_eq!(d1.direction, Direction::In);
409 assert!(d1.identity_hash.is_none());
410 assert!(d1.public_key.is_none());
411 assert!(d1.group_key.is_none());
412 }
413
414 #[test]
415 fn group_destination_hash_matches_plain_hash() {
416 let group = Destination::group("broadcast", &["test"]);
417 let plain = Destination::plain("broadcast", &["test"]);
418 assert_eq!(group.hash, plain.hash);
420 }
421
422 #[test]
423 fn group_create_keys() {
424 let mut dest = Destination::group("app", &["g"]);
425 assert!(dest.group_key.is_none());
426 dest.create_keys();
427 let key = dest.group_key.as_ref().unwrap();
428 assert_eq!(key.len(), 64);
429 assert!(key.iter().any(|&b| b != 0));
431 }
432
433 #[test]
434 fn group_load_private_key_64() {
435 let mut dest = Destination::group("app", &["g"]);
436 let key = vec![0x42u8; 64];
437 assert!(dest.load_private_key(key.clone()).is_ok());
438 assert_eq!(dest.get_private_key(), Some(key.as_slice()));
439 }
440
441 #[test]
442 fn group_load_private_key_32() {
443 let mut dest = Destination::group("app", &["g"]);
444 let key = vec![0xAB; 32];
445 assert!(dest.load_private_key(key.clone()).is_ok());
446 assert_eq!(dest.get_private_key(), Some(key.as_slice()));
447 }
448
449 #[test]
450 fn group_load_private_key_invalid_length() {
451 let mut dest = Destination::group("app", &["g"]);
452 assert_eq!(
453 dest.load_private_key(vec![0; 48]),
454 Err(GroupKeyError::InvalidKeyLength)
455 );
456 assert_eq!(
457 dest.load_private_key(vec![0; 16]),
458 Err(GroupKeyError::InvalidKeyLength)
459 );
460 }
461
462 #[test]
463 fn group_encrypt_decrypt_roundtrip() {
464 let mut dest = Destination::group("app", &["secure"]);
465 dest.load_private_key(vec![0x42u8; 64]).unwrap();
466
467 let plaintext = b"Hello, GROUP destination!";
468 let ciphertext = dest.encrypt(plaintext).unwrap();
469 assert_ne!(ciphertext.as_slice(), plaintext);
470 assert!(ciphertext.len() > plaintext.len()); let decrypted = dest.decrypt(&ciphertext).unwrap();
473 assert_eq!(decrypted, plaintext);
474 }
475
476 #[test]
477 fn group_decrypt_wrong_key_fails() {
478 let mut dest1 = Destination::group("app", &["a"]);
479 dest1.load_private_key(vec![0x42u8; 64]).unwrap();
480
481 let mut dest2 = Destination::group("app", &["a"]);
482 dest2.load_private_key(vec![0xBBu8; 64]).unwrap();
483
484 let ciphertext = dest1.encrypt(b"secret").unwrap();
485 assert_eq!(
486 dest2.decrypt(&ciphertext),
487 Err(GroupKeyError::DecryptionFailed)
488 );
489 }
490
491 #[test]
492 fn group_encrypt_without_key_fails() {
493 let dest = Destination::group("app", &["a"]);
494 assert_eq!(dest.encrypt(b"test"), Err(GroupKeyError::NoKey));
495 assert_eq!(dest.decrypt(b"test"), Err(GroupKeyError::NoKey));
496 }
497
498 #[test]
499 fn group_key_interop_with_token() {
500 let key = vec![0x42u8; 64];
502
503 let token = Token::new(&key).unwrap();
504 let ciphertext = token.encrypt(b"from token", &mut OsRng);
505
506 let mut dest = Destination::group("app", &["a"]);
507 dest.load_private_key(key.clone()).unwrap();
508 let decrypted = dest.decrypt(&ciphertext).unwrap();
509 assert_eq!(decrypted, b"from token");
510
511 let ciphertext2 = dest.encrypt(b"from dest").unwrap();
513 let decrypted2 = token.decrypt(&ciphertext2).unwrap();
514 assert_eq!(decrypted2, b"from dest");
515 }
516
517 #[test]
518 fn group_encrypt_decrypt_32byte_key() {
519 let mut dest = Destination::group("app", &["aes128"]);
520 dest.load_private_key(vec![0xABu8; 32]).unwrap();
521
522 let plaintext = b"AES-128 mode";
523 let ciphertext = dest.encrypt(plaintext).unwrap();
524 let decrypted = dest.decrypt(&ciphertext).unwrap();
525 assert_eq!(decrypted, plaintext);
526 }
527}