1#![warn(missing_docs)]
2
3use saorsa_gossip_types::PeerId;
48use serde::{Deserialize, Serialize};
49use std::time::{Duration, SystemTime};
50
51pub const SHARD_BITS: u32 = 16;
53pub const SHARD_COUNT: u32 = 1 << SHARD_BITS; pub const SHARD_MASK: u32 = SHARD_COUNT - 1; const RENDEZVOUS_PREFIX: &[u8] = b"saorsa-rendezvous";
60
61pub type ShardId = u16;
63
64fn current_time_millis() -> u64 {
65 match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
66 Ok(duration) => duration.as_millis() as u64,
67 Err(_) => Duration::ZERO.as_millis() as u64,
68 }
69}
70
71pub fn calculate_shard(target_id: &[u8; 32]) -> ShardId {
91 let mut hasher = blake3::Hasher::new();
92 hasher.update(RENDEZVOUS_PREFIX);
93 hasher.update(target_id);
94 let hash = hasher.finalize();
95
96 let bytes = hash.as_bytes();
98 let value = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
99 (value & SHARD_MASK) as u16
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(rename_all = "UPPERCASE")]
105pub enum Capability {
106 Site,
108 Identity,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct ProviderSummary {
130 pub v: u8,
132 pub target: [u8; 32],
134 pub provider: PeerId,
136 pub cap: Vec<Capability>,
138 pub have_root: bool,
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub manifest_ver: Option<u64>,
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub summary: Option<SummaryData>,
146 pub exp: u64,
148 #[serde(
157 default,
158 skip_serializing_if = "Option::is_none",
159 with = "optional_serde_bytes"
160 )]
161 pub extensions: Option<Vec<u8>>,
162 #[serde(with = "serde_bytes")]
164 pub sig: Vec<u8>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct SummaryData {
170 #[serde(skip_serializing_if = "Option::is_none", with = "optional_serde_bytes")]
172 pub bloom: Option<Vec<u8>>,
173 #[serde(skip_serializing_if = "Option::is_none", with = "optional_serde_bytes")]
175 pub iblt: Option<Vec<u8>>,
176}
177
178mod serde_bytes {
179 use serde::{de::Visitor, Deserializer, Serializer};
180
181 pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
182 where
183 S: Serializer,
184 {
185 serializer.serialize_bytes(bytes)
186 }
187
188 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
189 where
190 D: Deserializer<'de>,
191 {
192 struct BytesVisitor;
193
194 impl<'de> Visitor<'de> for BytesVisitor {
195 type Value = Vec<u8>;
196
197 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
198 formatter.write_str("a byte array")
199 }
200
201 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
202 where
203 E: serde::de::Error,
204 {
205 Ok(v.to_vec())
206 }
207
208 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
209 where
210 E: serde::de::Error,
211 {
212 Ok(v)
213 }
214 }
215
216 deserializer.deserialize_bytes(BytesVisitor)
217 }
218}
219
220mod optional_serde_bytes {
221 use serde::{Deserializer, Serializer};
222
223 pub fn serialize<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
224 where
225 S: Serializer,
226 {
227 match bytes {
228 Some(b) => serializer.serialize_bytes(b),
229 None => serializer.serialize_none(),
230 }
231 }
232
233 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
234 where
235 D: Deserializer<'de>,
236 {
237 use serde::de::Visitor;
238
239 struct OptionalBytesVisitor;
240
241 impl<'de> Visitor<'de> for OptionalBytesVisitor {
242 type Value = Option<Vec<u8>>;
243
244 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
245 formatter.write_str("an optional byte array")
246 }
247
248 fn visit_none<E>(self) -> Result<Self::Value, E>
249 where
250 E: serde::de::Error,
251 {
252 Ok(None)
253 }
254
255 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
256 where
257 D: Deserializer<'de>,
258 {
259 struct BytesVisitorWrapper;
260 impl<'de> Visitor<'de> for BytesVisitorWrapper {
261 type Value = Vec<u8>;
262
263 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
264 formatter.write_str("a byte array")
265 }
266
267 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
268 where
269 E: serde::de::Error,
270 {
271 Ok(v.to_vec())
272 }
273
274 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
275 where
276 E: serde::de::Error,
277 {
278 Ok(v)
279 }
280 }
281
282 deserializer
283 .deserialize_bytes(BytesVisitorWrapper)
284 .map(Some)
285 }
286 }
287
288 deserializer.deserialize_option(OptionalBytesVisitor)
289 }
290}
291
292impl ProviderSummary {
293 pub fn new(
301 target: [u8; 32],
302 provider: PeerId,
303 capabilities: Vec<Capability>,
304 validity_ms: u64,
305 ) -> Self {
306 let now = current_time_millis();
307
308 Self {
309 v: 1,
310 target,
311 provider,
312 cap: capabilities,
313 have_root: false,
314 manifest_ver: None,
315 summary: None,
316 exp: now + validity_ms,
317 extensions: None,
318 sig: Vec::new(),
319 }
320 }
321
322 pub fn with_root(mut self, has_root: bool) -> Self {
324 self.have_root = has_root;
325 self
326 }
327
328 pub fn with_manifest_version(mut self, version: u64) -> Self {
330 self.manifest_ver = Some(version);
331 self
332 }
333
334 pub fn with_summary(mut self, summary: SummaryData) -> Self {
336 self.summary = Some(summary);
337 self
338 }
339
340 pub fn with_extensions(mut self, data: Vec<u8>) -> Self {
345 self.extensions = Some(data);
346 self
347 }
348
349 pub fn is_valid(&self) -> bool {
351 let now = current_time_millis();
352
353 now <= self.exp
354 }
355
356 pub fn sign(&mut self, signing_key: &saorsa_pqc::MlDsaSecretKey) -> anyhow::Result<()> {
361 use saorsa_pqc::{MlDsa65, MlDsaOperations};
362
363 let mut to_sign = Vec::new();
365 ciborium::into_writer(
366 &SignableFields {
367 v: self.v,
368 target: &self.target,
369 provider: &self.provider,
370 cap: &self.cap,
371 have_root: self.have_root,
372 manifest_ver: self.manifest_ver,
373 summary: &self.summary,
374 exp: self.exp,
375 extensions: &self.extensions,
376 },
377 &mut to_sign,
378 )?;
379
380 let signer = MlDsa65::new();
382 let signature = signer.sign(signing_key, &to_sign)?;
383
384 self.sig = signature.as_bytes().to_vec();
385 Ok(())
386 }
387
388 pub fn verify(&self, public_key: &saorsa_pqc::MlDsaPublicKey) -> anyhow::Result<bool> {
393 use saorsa_pqc::{MlDsa65, MlDsaOperations, MlDsaSignature};
394
395 let mut to_verify = Vec::new();
397 ciborium::into_writer(
398 &SignableFields {
399 v: self.v,
400 target: &self.target,
401 provider: &self.provider,
402 cap: &self.cap,
403 have_root: self.have_root,
404 manifest_ver: self.manifest_ver,
405 summary: &self.summary,
406 exp: self.exp,
407 extensions: &self.extensions,
408 },
409 &mut to_verify,
410 )?;
411
412 let verifier = MlDsa65::new();
414 let sig = MlDsaSignature::from_bytes(&self.sig)?;
415
416 Ok(verifier.verify(public_key, &to_verify, &sig)?)
417 }
418
419 pub fn sign_raw(&mut self, secret_key_bytes: &[u8]) -> anyhow::Result<()> {
430 let key = saorsa_pqc::MlDsaSecretKey::from_bytes(secret_key_bytes)
431 .map_err(|e| anyhow::anyhow!("invalid secret key bytes: {:?}", e))?;
432 self.sign(&key)
433 }
434
435 pub fn verify_raw(&self, public_key_bytes: &[u8]) -> anyhow::Result<bool> {
443 let key = saorsa_pqc::MlDsaPublicKey::from_bytes(public_key_bytes)
444 .map_err(|e| anyhow::anyhow!("invalid public key bytes: {:?}", e))?;
445 self.verify(&key)
446 }
447
448 pub fn to_cbor(&self) -> anyhow::Result<Vec<u8>> {
450 let mut buffer = Vec::new();
451 ciborium::into_writer(self, &mut buffer)?;
452 Ok(buffer)
453 }
454
455 pub fn from_cbor(data: &[u8]) -> anyhow::Result<Self> {
457 let summary = ciborium::from_reader(data)?;
458 Ok(summary)
459 }
460
461 pub fn to_bytes(&self) -> anyhow::Result<bytes::Bytes> {
463 Ok(bytes::Bytes::from(self.to_cbor()?))
464 }
465
466 pub fn from_bytes(data: &[u8]) -> anyhow::Result<Self> {
468 Self::from_cbor(data)
469 }
470
471 pub fn shard(&self) -> ShardId {
473 calculate_shard(&self.target)
474 }
475}
476
477#[derive(Serialize)]
479struct SignableFields<'a> {
480 v: u8,
481 target: &'a [u8; 32],
482 provider: &'a PeerId,
483 cap: &'a Vec<Capability>,
484 have_root: bool,
485 manifest_ver: Option<u64>,
486 summary: &'a Option<SummaryData>,
487 exp: u64,
488 #[serde(skip_serializing_if = "Option::is_none", with = "optional_serde_bytes")]
489 extensions: &'a Option<Vec<u8>>,
490}
491
492#[cfg(test)]
493#[allow(clippy::expect_used, clippy::unwrap_used)]
494mod tests {
495 use super::*;
496
497 #[test]
498 fn test_shard_calculation_deterministic() {
499 let target = [42u8; 32];
500
501 let shard1 = calculate_shard(&target);
502 let shard2 = calculate_shard(&target);
503
504 assert_eq!(shard1, shard2, "Shard calculation must be deterministic");
505 }
507
508 #[test]
509 fn test_shard_calculation_different_targets() {
510 let target1 = [1u8; 32];
511 let target2 = [2u8; 32];
512
513 let shard1 = calculate_shard(&target1);
514 let shard2 = calculate_shard(&target2);
515
516 assert_ne!(
519 shard1, shard2,
520 "Different targets should map to different shards"
521 );
522 }
523
524 #[test]
525 fn test_shard_within_bounds() {
526 for i in 0..100 {
528 let target = [i; 32];
529 let _shard = calculate_shard(&target);
530 }
532 }
533
534 #[test]
535 fn test_provider_summary_creation() {
536 let target = [1u8; 32];
537 let provider = PeerId::new([2u8; 32]);
538
539 let summary = ProviderSummary::new(target, provider, vec![Capability::Site], 3_600_000);
540
541 assert_eq!(summary.v, 1);
542 assert_eq!(summary.target, target);
543 assert_eq!(summary.provider, provider);
544 assert_eq!(summary.cap.len(), 1);
545 assert!(summary.is_valid());
546 }
547
548 #[test]
549 fn test_provider_summary_expiry() {
550 let target = [2u8; 32];
551 let provider = PeerId::new([3u8; 32]);
552
553 let summary = ProviderSummary::new(
555 target,
556 provider,
557 vec![Capability::Identity],
558 1, );
560
561 assert!(summary.is_valid(), "Should be valid immediately");
562
563 std::thread::sleep(std::time::Duration::from_millis(5));
565
566 assert!(!summary.is_valid(), "Should be expired after sleep");
567 }
568
569 #[test]
570 fn test_provider_summary_builder() {
571 let target = [3u8; 32];
572 let provider = PeerId::new([4u8; 32]);
573
574 let summary = ProviderSummary::new(
575 target,
576 provider,
577 vec![Capability::Site, Capability::Identity],
578 60_000,
579 )
580 .with_root(true)
581 .with_manifest_version(42);
582
583 assert!(summary.have_root);
584 assert_eq!(summary.manifest_ver, Some(42));
585 }
586
587 #[test]
588 fn test_provider_summary_shard() {
589 let target = [5u8; 32];
590 let provider = PeerId::new([6u8; 32]);
591
592 let summary = ProviderSummary::new(target, provider, vec![Capability::Site], 3_600_000);
593
594 let shard = summary.shard();
595 assert_eq!(shard, calculate_shard(&target));
596 }
597
598 #[test]
599 fn test_cbor_round_trip() {
600 let target = [7u8; 32];
601 let provider = PeerId::new([8u8; 32]);
602
603 let summary =
604 ProviderSummary::new(target, provider, vec![Capability::Site], 60_000).with_root(true);
605
606 let cbor = summary.to_cbor().expect("CBOR serialization");
607 let decoded = ProviderSummary::from_cbor(&cbor).expect("CBOR deserialization");
608
609 assert_eq!(decoded.v, summary.v);
610 assert_eq!(decoded.target, summary.target);
611 assert_eq!(decoded.provider, summary.provider);
612 assert_eq!(decoded.have_root, summary.have_root);
613 }
614
615 #[test]
616 fn test_sign_and_verify() {
617 use saorsa_pqc::{MlDsa65, MlDsaOperations};
618
619 let target = [9u8; 32];
620 let provider = PeerId::new([10u8; 32]);
621
622 let mut summary =
623 ProviderSummary::new(target, provider, vec![Capability::Identity], 60_000);
624
625 let signer = MlDsa65::new();
627 let (pk, sk) = signer.generate_keypair().expect("keypair");
628
629 summary.sign(&sk).expect("signing");
631 assert!(!summary.sig.is_empty(), "Signature should be populated");
632
633 let valid = summary.verify(&pk).expect("verification");
635 assert!(valid, "Signature should be valid");
636
637 summary.have_root = true;
639
640 let valid = summary.verify(&pk).expect("verification");
642 assert!(!valid, "Tampered signature should be invalid");
643 }
644
645 #[test]
646 fn test_capability_serialization() {
647 let caps = vec![Capability::Site, Capability::Identity];
648
649 let mut buffer = Vec::new();
650 ciborium::into_writer(&caps, &mut buffer).expect("serialize");
651
652 let decoded: Vec<Capability> = ciborium::from_reader(&buffer[..]).expect("deserialize");
653
654 assert_eq!(decoded, caps);
655 }
656
657 #[test]
658 fn test_summary_data() {
659 let data = SummaryData {
660 bloom: Some(vec![1, 2, 3]),
661 iblt: Some(vec![4, 5, 6]),
662 };
663
664 let mut buffer = Vec::new();
665 ciborium::into_writer(&data, &mut buffer).expect("serialize");
666
667 let decoded: SummaryData = ciborium::from_reader(&buffer[..]).expect("deserialize");
668
669 assert_eq!(decoded.bloom, data.bloom);
670 assert_eq!(decoded.iblt, data.iblt);
671 }
672}