1use std::str::FromStr;
2use std::{fmt, mem};
3
4use bytes::{Buf, BufMut};
5use nonempty::NonEmpty;
6
7use radicle::crypto;
8use radicle::crypto::Signer as _;
9use radicle::git;
10use radicle::identity::RepoId;
11use radicle::node;
12use radicle::node::{Address, Alias, UserAgent};
13use radicle::storage;
14use radicle::storage::refs::RefsAt;
15
16use crate::bounded::BoundedVec;
17use crate::service::filter::Filter;
18use crate::service::{Link, NodeId, Timestamp};
19use crate::wire;
20use crate::wire::Encode as _;
21
22pub const ADDRESS_LIMIT: usize = 16;
24pub const REF_REMOTE_LIMIT: usize = 1024;
26pub const INVENTORY_LIMIT: usize = 2973;
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Subscribe {
31 pub filter: Filter,
33 pub since: Timestamp,
35 pub until: Timestamp,
37}
38
39impl Subscribe {
40 pub fn all() -> Self {
41 Self {
42 filter: Filter::default(),
43 since: Timestamp::MIN,
44 until: Timestamp::MAX,
45 }
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct NodeAnnouncement {
52 pub version: u8,
54 pub features: node::Features,
56 pub timestamp: Timestamp,
58 pub alias: Alias,
60 pub addresses: BoundedVec<Address, ADDRESS_LIMIT>,
62 pub nonce: u64,
64 pub agent: UserAgent,
66}
67
68impl NodeAnnouncement {
69 pub fn work(&self) -> u32 {
83 let (n, r, p) = Announcement::POW_PARAMS;
84 let params = scrypt::Params::new(n, r, p, 32).expect("proof-of-work parameters are valid");
85 let mut output = [0u8; 32];
86
87 scrypt::scrypt(
88 &self.encode_to_vec(),
89 Announcement::POW_SALT,
90 ¶ms,
91 &mut output,
92 )
93 .expect("proof-of-work output vector is a valid length");
94
95 if let Some((zero_bytes, non_zero)) = output.iter().enumerate().find(|&(_, &x)| x != 0) {
97 zero_bytes as u32 * 8 + non_zero.leading_zeros()
98 } else {
99 output.len() as u32 * 8
100 }
101 }
102
103 pub fn solve(mut self, target: u32) -> Option<Self> {
109 loop {
110 let nonce = self.nonce.checked_add(1)?;
111 self.nonce = nonce;
112
113 if self.work() >= target {
114 break;
115 }
116 }
117 Some(self)
118 }
119}
120
121impl wire::Encode for NodeAnnouncement {
122 fn encode(&self, buf: &mut impl BufMut) {
123 self.version.encode(buf);
124 self.features.encode(buf);
125 self.timestamp.encode(buf);
126 self.alias.encode(buf);
127 self.addresses.encode(buf);
128 self.nonce.encode(buf);
129 self.agent.encode(buf);
130 }
131}
132
133impl wire::Decode for NodeAnnouncement {
134 fn decode(buf: &mut impl Buf) -> Result<Self, wire::Error> {
135 let version = u8::decode(buf)?;
136 let features = node::Features::decode(buf)?;
137 let timestamp = Timestamp::decode(buf)?;
138 let alias = wire::Decode::decode(buf)?;
139 let addresses = BoundedVec::<Address, ADDRESS_LIMIT>::decode(buf)?;
140 let nonce = u64::decode(buf)?;
141 let agent = match UserAgent::decode(buf) {
142 Ok(ua) => ua,
143 Err(wire::Error::UnexpectedEnd { .. }) => {
144 UserAgent::from_str("/radicle/message/truncated/").expect("valid user agent")
145 }
146 Err(e) => return Err(e),
147 };
148
149 Ok(Self {
150 version,
151 features,
152 timestamp,
153 alias,
154 addresses,
155 nonce,
156 agent,
157 })
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct RefsAnnouncement {
164 pub rid: RepoId,
166 pub refs: BoundedVec<RefsAt, REF_REMOTE_LIMIT>,
168 pub timestamp: Timestamp,
170}
171
172#[derive(Default)]
174pub struct RefsStatus {
175 pub want: Vec<RefsAt>,
178 pub have: Vec<RefsAt>,
180}
181
182impl RefsStatus {
183 pub fn new<D: node::refs::Store>(
191 rid: RepoId,
192 refs: NonEmpty<RefsAt>,
193 db: &D,
194 ) -> Result<RefsStatus, storage::Error> {
195 let mut status = RefsStatus::default();
196 for theirs in refs.iter() {
197 status.insert(&rid, *theirs, db)?;
198 }
199 Ok(status)
200 }
201
202 fn insert<D: node::refs::Store>(
203 &mut self,
204 repo: &RepoId,
205 theirs: RefsAt,
206 db: &D,
207 ) -> Result<(), storage::Error> {
208 match db.get(repo, &theirs.remote, &storage::refs::SIGREFS_BRANCH) {
209 Ok(Some((ours, _))) => {
210 if theirs.at != ours {
211 self.want.push(theirs);
212 } else {
213 self.have.push(theirs);
214 }
215 }
216 Ok(None) => {
217 self.want.push(theirs);
218 }
219 Err(e) => {
220 log::debug!(
221 target: "service",
222 "Failed to get cached 'rad/sigrefs' of {} in {repo} for refs status: {e}", theirs.remote,
223 );
224 }
225 }
226 Ok(())
227 }
228}
229
230#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct InventoryAnnouncement {
234 pub inventory: BoundedVec<RepoId, INVENTORY_LIMIT>,
236 pub timestamp: Timestamp,
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
245pub enum Info {
246 RefsAlreadySynced { rid: RepoId, at: git::Oid },
249}
250
251#[derive(Clone, PartialEq, Eq)]
253pub enum AnnouncementMessage {
254 Inventory(InventoryAnnouncement),
256 Node(NodeAnnouncement),
258 Refs(RefsAnnouncement),
260}
261
262impl AnnouncementMessage {
263 pub fn signed(self, secret_key: &crypto::SigningKey) -> Announcement {
265 use crypto::signature::Signer as _;
266
267 let msg = self.encode_to_vec();
268
269 let signature = secret_key.sign(&msg);
270
271 Announcement {
272 node: *secret_key.public_key(),
273 message: self,
274 signature,
275 }
276 }
277
278 pub fn timestamp(&self) -> Timestamp {
279 match self {
280 Self::Inventory(InventoryAnnouncement { timestamp, .. }) => *timestamp,
281 Self::Refs(RefsAnnouncement { timestamp, .. }) => *timestamp,
282 Self::Node(NodeAnnouncement { timestamp, .. }) => *timestamp,
283 }
284 }
285
286 pub fn is_node_announcement(&self) -> bool {
287 matches!(self, Self::Node(_))
288 }
289}
290
291impl From<NodeAnnouncement> for AnnouncementMessage {
292 fn from(ann: NodeAnnouncement) -> Self {
293 Self::Node(ann)
294 }
295}
296
297impl From<InventoryAnnouncement> for AnnouncementMessage {
298 fn from(ann: InventoryAnnouncement) -> Self {
299 Self::Inventory(ann)
300 }
301}
302
303impl From<RefsAnnouncement> for AnnouncementMessage {
304 fn from(ann: RefsAnnouncement) -> Self {
305 Self::Refs(ann)
306 }
307}
308
309impl fmt::Debug for AnnouncementMessage {
310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311 match self {
312 Self::Node(message) => write!(f, "Node({})", message.timestamp),
313 Self::Inventory(message) => {
314 write!(
315 f,
316 "Inventory([{}], {})",
317 message
318 .inventory
319 .iter()
320 .map(|i| i.to_string())
321 .collect::<Vec<String>>()
322 .join(", "),
323 message.timestamp
324 )
325 }
326 Self::Refs(message) => {
327 write!(
328 f,
329 "Refs({}, {}, {:?})",
330 message.rid, message.timestamp, message.refs
331 )
332 }
333 }
334 }
335}
336
337#[derive(Debug, Clone, PartialEq, Eq)]
338pub struct Announcement {
339 pub node: NodeId,
341 pub signature: crypto::Signature,
343 pub message: AnnouncementMessage,
345}
346
347impl Announcement {
348 #[cfg(debug_assertions)]
360 pub const POW_PARAMS: (u8, u32, u32) = (1, 1, 1);
361 #[cfg(not(debug_assertions))]
362 pub const POW_PARAMS: (u8, u32, u32) = (15, 8, 1);
363 pub const POW_SALT: &'static [u8] = b"rad";
365
366 pub fn verify(&self) -> bool {
368 use crypto::signature::Verifier as _;
369
370 let Ok(verifier) = crypto::VerifyingKey::try_from(&self.node) else {
371 return false;
373 };
374
375 let msg = self.message.encode_to_vec();
376 verifier.verify(&msg, &self.signature).is_ok()
377 }
378
379 pub fn matches(&self, filter: &Filter) -> bool {
380 match &self.message {
381 AnnouncementMessage::Inventory(_) => true,
382 AnnouncementMessage::Node(_) => true,
383 AnnouncementMessage::Refs(RefsAnnouncement { rid, .. }) => filter.contains(rid),
384 }
385 }
386
387 pub fn variant_eq(&self, other: &Self) -> bool {
389 std::mem::discriminant(&self.message) == std::mem::discriminant(&other.message)
390 }
391
392 pub fn timestamp(&self) -> Timestamp {
394 self.message.timestamp()
395 }
396}
397
398#[derive(Clone, PartialEq, Eq)]
401pub enum Message {
402 Subscribe(Subscribe),
404
405 Announcement(Announcement),
408
409 Info(Info),
413
414 Ping(Ping),
419
420 Pong {
422 zeroes: ZeroBytes,
424 },
425}
426
427impl PartialOrd for Message {
428 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
429 Some(self.cmp(other))
430 }
431}
432
433impl Ord for Message {
434 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
435 let this = self.encode_to_vec();
436 let other = other.encode_to_vec();
437
438 this.cmp(&other)
439 }
440}
441
442impl Message {
443 pub fn announcement(
444 node: NodeId,
445 message: impl Into<AnnouncementMessage>,
446 signature: crypto::Signature,
447 ) -> Self {
448 Announcement {
449 node,
450 signature,
451 message: message.into(),
452 }
453 .into()
454 }
455
456 pub fn node(message: NodeAnnouncement, secret_key: &crypto::SigningKey) -> Self {
457 AnnouncementMessage::from(message).signed(secret_key).into()
458 }
459
460 pub fn inventory(message: InventoryAnnouncement, secret_key: &crypto::SigningKey) -> Self {
461 AnnouncementMessage::from(message).signed(secret_key).into()
462 }
463
464 pub fn subscribe(filter: Filter, since: Timestamp, until: Timestamp) -> Self {
465 Self::Subscribe(Subscribe {
466 filter,
467 since,
468 until,
469 })
470 }
471
472 pub fn log(&self, level: log::Level, remote: &NodeId, link: Link) {
473 if !log::log_enabled!(level) {
474 return;
475 }
476 let (verb, prep) = if link.is_inbound() {
477 ("Received", "from")
478 } else {
479 ("Sending", "to")
480 };
481 let msg = match self {
482 Self::Announcement(Announcement { node, message, .. }) => match message {
483 AnnouncementMessage::Node(NodeAnnouncement {
484 addresses,
485 timestamp,
486 ..
487 }) => format!(
488 "{verb} node announcement of {node} with {} address(es) {prep} {remote} (t={timestamp})",
489 addresses.len()
490 ),
491 AnnouncementMessage::Refs(RefsAnnouncement {
492 rid,
493 refs,
494 timestamp,
495 }) => format!(
496 "{verb} refs announcement of {node} for {rid} with {} remote(s) {prep} {remote} (t={timestamp})",
497 refs.len()
498 ),
499 AnnouncementMessage::Inventory(InventoryAnnouncement {
500 inventory,
501 timestamp,
502 }) => {
503 format!(
504 "{verb} inventory announcement of {node} with {} item(s) {prep} {remote} (t={timestamp})",
505 inventory.len()
506 )
507 }
508 },
509 Self::Info(Info::RefsAlreadySynced { rid, .. }) => {
510 format!("{verb} `refs-already-synced` info {prep} {remote} for {rid}")
511 }
512 Self::Ping { .. } => format!("{verb} ping {prep} {remote}"),
513 Self::Pong { .. } => format!("{verb} pong {prep} {remote}"),
514 Self::Subscribe(Subscribe { .. }) => {
515 format!("{verb} subscription filter {prep} {remote}")
516 }
517 };
518 log::log!(target: "service", level, "{msg}");
519 }
520}
521
522#[derive(Debug, PartialEq, Eq, Clone)]
524pub struct Ping {
525 pub ponglen: wire::Size,
527 pub zeroes: ZeroBytes,
529}
530
531impl Ping {
532 pub const MAX_PING_ZEROES: wire::Size = Message::MAX_SIZE - mem::size_of::<wire::Size>() as wire::Size - mem::size_of::<wire::Size>() as wire::Size; pub const MAX_PONG_ZEROES: wire::Size =
539 Message::MAX_SIZE - mem::size_of::<wire::Size>() as wire::Size; pub fn new(rng: &mut fastrand::Rng) -> Self {
543 let ponglen = rng.u16(0..Self::MAX_PONG_ZEROES);
544
545 Ping {
546 ponglen,
547 zeroes: ZeroBytes::new(rng.u16(0..Self::MAX_PING_ZEROES)),
548 }
549 }
550}
551
552impl From<Announcement> for Message {
553 fn from(ann: Announcement) -> Self {
554 Self::Announcement(ann)
555 }
556}
557
558impl From<Info> for Message {
559 fn from(info: Info) -> Self {
560 Self::Info(info)
561 }
562}
563
564impl fmt::Debug for Message {
565 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
566 match self {
567 Self::Subscribe(Subscribe { since, until, .. }) => {
568 write!(f, "Subscribe({since}..{until})")
569 }
570 Self::Announcement(Announcement { node, message, .. }) => {
571 write!(f, "Announcement({node}, {message:?})")
572 }
573 Self::Info(info) => {
574 write!(f, "Info({info:?})")
575 }
576 Self::Ping(Ping { ponglen, zeroes }) => write!(f, "Ping({ponglen}, {zeroes:?})"),
577 Self::Pong { zeroes } => write!(f, "Pong({zeroes:?})"),
578 }
579 }
580}
581
582#[derive(Clone, Debug, PartialEq, Eq)]
584pub struct ZeroBytes(wire::Size);
585
586impl ZeroBytes {
587 pub fn new(size: wire::Size) -> Self {
588 ZeroBytes(size)
589 }
590
591 pub fn is_empty(&self) -> bool {
592 self.0 == 0
593 }
594
595 pub fn len(&self) -> usize {
596 self.0.into()
597 }
598}
599
600#[cfg(any(test, feature = "test"))]
601#[allow(clippy::unwrap_used)]
602impl qcheck::Arbitrary for Message {
603 fn arbitrary(g: &mut qcheck::Gen) -> Self {
604 use qcheck::Arbitrary;
605
606 match g.choose(&[1, 2, 3, 4, 5, 6, 7]).unwrap() {
607 1 => Announcement {
608 node: NodeId::arbitrary(g),
609 message: InventoryAnnouncement {
610 inventory: BoundedVec::arbitrary(g),
611 timestamp: Timestamp::arbitrary(g),
612 }
613 .into(),
614 signature: crypto::Signature::from(<[u8; 64]>::arbitrary(g)),
615 }
616 .into(),
617 2 => Announcement {
618 node: NodeId::arbitrary(g),
619 message: RefsAnnouncement {
620 rid: RepoId::arbitrary(g),
621 refs: BoundedVec::arbitrary(g),
622 timestamp: Timestamp::arbitrary(g),
623 }
624 .into(),
625 signature: crypto::Signature::from(<[u8; 64]>::arbitrary(g)),
626 }
627 .into(),
628 3 => {
629 let message = NodeAnnouncement {
630 version: u8::arbitrary(g),
631 features: u64::arbitrary(g).into(),
632 timestamp: Timestamp::arbitrary(g),
633 alias: Alias::arbitrary(g),
634 addresses: Arbitrary::arbitrary(g),
635 nonce: u64::arbitrary(g),
636 agent: UserAgent::arbitrary(g),
637 }
638 .into();
639 let bytes: [u8; 64] = Arbitrary::arbitrary(g);
640 let signature = crypto::Signature::from(bytes);
641
642 Announcement {
643 node: NodeId::arbitrary(g),
644 signature,
645 message,
646 }
647 .into()
648 }
649 4 => {
650 let message = Info::RefsAlreadySynced {
651 rid: RepoId::arbitrary(g),
652 at: radicle::test::arbitrary::oid(),
653 };
654 Self::Info(message)
655 }
656 5 => Self::Subscribe(Subscribe {
657 filter: Filter::arbitrary(g),
658 since: Timestamp::arbitrary(g),
659 until: Timestamp::arbitrary(g),
660 }),
661 6 => {
662 let mut rng = fastrand::Rng::with_seed(u64::arbitrary(g));
663
664 Self::Ping(Ping::new(&mut rng))
665 }
666 7 => Self::Pong {
667 zeroes: ZeroBytes::new(u16::arbitrary(g).min(Ping::MAX_PONG_ZEROES)),
668 },
669 _ => panic!("Invalid choice for Message::arbitrary"),
670 }
671 }
672}
673
674#[cfg(any(test, feature = "test"))]
675impl qcheck::Arbitrary for ZeroBytes {
676 fn arbitrary(g: &mut qcheck::Gen) -> Self {
677 ZeroBytes::new(u16::arbitrary(g))
678 }
679}
680
681#[cfg(test)]
682#[allow(clippy::unwrap_used)]
683mod tests {
684
685 use localtime::LocalTime;
686 use qcheck_macros::quickcheck;
687 use radicle::crypto::SigningKey;
688 use radicle::test::arbitrary;
689
690 use crate::wire::Decode as _;
691
692 use super::*;
693
694 #[test]
695 fn test_ref_remote_limit() {
696 let mut refs = BoundedVec::<_, REF_REMOTE_LIMIT>::new();
697 let signer = SigningKey::mock(18);
698 let at = git::Oid::ZERO_SHA1;
699
700 assert_eq!(refs.capacity(), REF_REMOTE_LIMIT);
701
702 for _ in 0..refs.capacity() {
703 refs.push(RefsAt {
704 remote: *signer.public_key(),
705 at,
706 })
707 .unwrap();
708 }
709
710 let msg: Message = AnnouncementMessage::from(RefsAnnouncement {
711 rid: arbitrary::r#gen(1),
712 refs,
713 timestamp: LocalTime::now().into(),
714 })
715 .signed(&crypto::SigningKey::mock(93))
716 .into();
717
718 let mut buf = Vec::new();
719 msg.encode(&mut buf);
720
721 let decoded = Message::decode_exact(buf.as_slice());
722 assert!(decoded.is_ok());
723 assert_eq!(msg, decoded.unwrap());
724 }
725
726 #[test]
727 fn test_inventory_limit() {
728 let msg = Message::inventory(
729 InventoryAnnouncement {
730 inventory: arbitrary::vec(INVENTORY_LIMIT)
731 .try_into()
732 .expect("size within bounds limit"),
733 timestamp: LocalTime::now().into(),
734 },
735 &crypto::SigningKey::mock(218),
736 );
737 let mut buf: Vec<u8> = Vec::new();
738 msg.encode(&mut buf);
739
740 let decoded = Message::decode_exact(buf.as_slice());
741 assert!(
742 decoded.is_ok(),
743 "INVENTORY_LIMIT is a valid limit for decoding"
744 );
745 assert_eq!(
746 msg,
747 decoded.unwrap(),
748 "encoding and decoding should be safe for message at INVENTORY_LIMIT",
749 );
750 }
751
752 #[quickcheck]
753 fn prop_refs_announcement_signing(rid: RepoId) {
754 let secret_key = crypto::SigningKey::mock(242);
755 let timestamp = Timestamp::EPOCH;
756 let at = git::Oid::ZERO_SHA1;
757 let refs = BoundedVec::collect_from(
758 &mut [RefsAt {
759 remote: *secret_key.public_key(),
760 at,
761 }]
762 .into_iter(),
763 );
764 let message = AnnouncementMessage::Refs(RefsAnnouncement {
765 rid,
766 refs,
767 timestamp,
768 });
769 let ann = message.signed(&secret_key);
770
771 assert!(ann.verify());
772 }
773
774 #[test]
775 fn test_node_announcement_validate() {
776 let ann = NodeAnnouncement {
777 version: 1,
778 features: node::Features::SEED,
779 timestamp: Timestamp::try_from(42491841u64).unwrap(),
780 alias: Alias::new("alice"),
781 addresses: BoundedVec::new(),
782 nonce: 0,
783 agent: UserAgent::test(),
784 };
785
786 assert_eq!(ann.work(), 2);
787 assert_eq!(ann.clone().solve(1).unwrap().work(), 1);
788 assert_eq!(ann.clone().solve(8).unwrap().work(), 8);
789 assert_eq!(ann.solve(14).unwrap().work(), 14);
790 }
791}