Skip to main content

radicle_protocol/service/
message.rs

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
22/// Maximum number of addresses which can be announced to other nodes.
23pub const ADDRESS_LIMIT: usize = 16;
24/// Maximum number of repository remotes that can be included in a [`RefsAnnouncement`] message.
25pub const REF_REMOTE_LIMIT: usize = 1024;
26/// Maximum number of inventory which can be announced to other nodes.
27pub const INVENTORY_LIMIT: usize = 2973;
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Subscribe {
31    /// Subscribe to events matching this filter.
32    pub filter: Filter,
33    /// Request messages since this time.
34    pub since: Timestamp,
35    /// Request messages until this time.
36    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/// Node announcing itself to the network.
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct NodeAnnouncement {
52    /// Supported protocol version.
53    pub version: u8,
54    /// Advertised features.
55    pub features: node::Features,
56    /// Monotonic timestamp.
57    pub timestamp: Timestamp,
58    /// Non-unique alias.
59    pub alias: Alias,
60    /// Announced addresses.
61    pub addresses: BoundedVec<Address, ADDRESS_LIMIT>,
62    /// Nonce used for announcement proof-of-work.
63    pub nonce: u64,
64    /// User-agent string.
65    pub agent: UserAgent,
66}
67
68impl NodeAnnouncement {
69    /// Calculate the amount of work that went into creating this announcement.
70    ///
71    /// Proof-of-work uses the [`scrypt`] algorithm with the parameters in
72    /// [`Announcement::POW_PARAMS`]. The "work" is calculated by counting the number of leading
73    /// zero bits after running `scrypt` on a serialized [`NodeAnnouncement`] using
74    /// [`Encode::encode_to_vec`].
75    ///
76    /// In other words, `work = leading-zeros(scrypt(serialize(announcement)))`.
77    ///
78    /// Higher numbers mean higher difficulty. For each increase in work, difficulty is doubled.
79    /// For instance, an output of `7` is *four* times more work than an output of `5`.
80    ///
81    /// [`Encode::encode_to_vec`]: crate::wire::Encode::encode_to_vec
82    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            &params,
91            &mut output,
92        )
93        .expect("proof-of-work output vector is a valid length");
94
95        // Calculate the number of leading zero bits in the output vector.
96        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    /// Solve the proof-of-work of a node announcement for the given target, by iterating through
104    /// different nonces.
105    ///
106    /// If the given difficulty target is too high, there may not be a result. In that case, `None`
107    /// is returned.
108    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/// Node announcing project refs being created or updated.
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct RefsAnnouncement {
164    /// Repository identifier.
165    pub rid: RepoId,
166    /// Updated `rad/sigrefs`.
167    pub refs: BoundedVec<RefsAt, REF_REMOTE_LIMIT>,
168    /// Time of announcement.
169    pub timestamp: Timestamp,
170}
171
172/// Track the status of `RefsAt` within a given repository.
173#[derive(Default)]
174pub struct RefsStatus {
175    /// The `rad/sigrefs` was missing or it's ahead of the local
176    /// `rad/sigrefs`. We want it.
177    pub want: Vec<RefsAt>,
178    /// The `rad/sigrefs` has been seen before. We already have it.
179    pub have: Vec<RefsAt>,
180}
181
182impl RefsStatus {
183    /// Get the set of `want` and `have` `RefsAt`'s for the given
184    /// announcement.
185    ///
186    /// Nb. We use the refs database as a cache for quick lookups. This does *not* check
187    /// for ancestry matches, since we don't cache the whole history (only the tips).
188    /// This, however, is not a problem because the signed refs branch is fast-forward only,
189    /// and old refs announcements will be discarded due to their lower timestamps.
190    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/// Node announcing its inventory to the network.
231/// This should be the whole inventory every time.
232#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct InventoryAnnouncement {
234    /// Node inventory.
235    pub inventory: BoundedVec<RepoId, INVENTORY_LIMIT>,
236    /// Time of announcement.
237    pub timestamp: Timestamp,
238}
239
240/// Node announcing information to a connected peer.
241///
242/// This should not be relayed and should be used to send an
243/// informational message a peer.
244#[derive(Debug, Clone, PartialEq, Eq)]
245pub enum Info {
246    /// Tell a node that sent a refs announcement that it was already synced at the given `Oid`,
247    /// for this particular `rid`.
248    RefsAlreadySynced { rid: RepoId, at: git::Oid },
249}
250
251/// Announcement messages are messages that are relayed between peers.
252#[derive(Clone, PartialEq, Eq)]
253pub enum AnnouncementMessage {
254    /// Inventory announcement.
255    Inventory(InventoryAnnouncement),
256    /// Node announcement.
257    Node(NodeAnnouncement),
258    /// Refs announcement.
259    Refs(RefsAnnouncement),
260}
261
262impl AnnouncementMessage {
263    /// Sign this announcement message.
264    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    /// Node identifier.
340    pub node: NodeId,
341    /// Signature over the announcement.
342    pub signature: crypto::Signature,
343    /// Unsigned node announcement.
344    pub message: AnnouncementMessage,
345}
346
347impl Announcement {
348    /// Proof-of-work parameters for announcements.
349    ///
350    /// These parameters are fed into `scrypt`.
351    /// They represent the `log2(N)`, `r`, `p` parameters, respectively.
352    ///
353    /// * log2(N) – iterations count (affects memory and CPU usage), e.g. 15
354    /// * r – block size (affects memory and CPU usage), e.g. 8
355    /// * p – parallelism factor (threads to run in parallel - affects the memory, CPU usage), usually 1
356    ///
357    /// `15, 8, 1` are usually the recommended parameters.
358    ///
359    #[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    /// Salt used for generating PoW.
364    pub const POW_SALT: &'static [u8] = b"rad";
365
366    /// Verify this announcement's signature.
367    pub fn verify(&self) -> bool {
368        use crypto::signature::Verifier as _;
369
370        let Ok(verifier) = crypto::VerifyingKey::try_from(&self.node) else {
371            // Public key is not a valid verifying key, so the signature cannot be valid.
372            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    /// Check whether this announcement is of the same variant as another.
388    pub fn variant_eq(&self, other: &Self) -> bool {
389        std::mem::discriminant(&self.message) == std::mem::discriminant(&other.message)
390    }
391
392    /// Get the announcement timestamp.
393    pub fn timestamp(&self) -> Timestamp {
394        self.message.timestamp()
395    }
396}
397
398/// Message payload.
399/// These are the messages peers send to each other.
400#[derive(Clone, PartialEq, Eq)]
401pub enum Message {
402    /// Subscribe to gossip messages matching the filter and time range.
403    Subscribe(Subscribe),
404
405    /// Gossip announcement. These messages are relayed to peers, and filtered
406    /// using [`Message::Subscribe`].
407    Announcement(Announcement),
408
409    /// Informational message. These messages are sent between peers for information
410    /// and do not need to be acted upon. They can be safely ignored, though handling
411    /// them can be useful for the user.
412    Info(Info),
413
414    /// Ask a connected peer for a Pong.
415    ///
416    /// Used to check if the remote peer is responsive, or a side-effect free way to keep a
417    /// connection alive.
418    Ping(Ping),
419
420    /// Response to `Ping` message.
421    Pong {
422        /// The pong payload.
423        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/// A ping message.
523#[derive(Debug, PartialEq, Eq, Clone)]
524pub struct Ping {
525    /// The requested length of the pong message.
526    pub ponglen: wire::Size,
527    /// Zero bytes (ignored).
528    pub zeroes: ZeroBytes,
529}
530
531impl Ping {
532    /// Maximum number of zero bytes in a ping message.
533    pub const MAX_PING_ZEROES: wire::Size = Message::MAX_SIZE // Message size without the type.
534        - mem::size_of::<wire::Size>() as wire::Size // Account for pong length.
535        - mem::size_of::<wire::Size>() as wire::Size; // Account for zeroes length prefix.
536
537    /// Maximum number of zero bytes in a pong message.
538    pub const MAX_PONG_ZEROES: wire::Size =
539        Message::MAX_SIZE - mem::size_of::<wire::Size>() as wire::Size; // Account for zeroes length
540    // prefix.
541
542    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/// Represents a vector of zeroes of a certain length.
583#[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}