Skip to main content

iroh_content_discovery/
protocol.rs

1//! The protocol for communicating with the tracker.
2use std::{
3    ops::{Deref, Sub},
4    result,
5    time::{Duration, SystemTime},
6};
7
8use iroh::NodeId;
9use iroh_blobs::HashAndFormat;
10use serde::{Deserialize, Serialize};
11use serde_big_array::BigArray;
12use snafu::prelude::*;
13
14/// The ALPN string for this protocol
15pub const ALPN: &[u8] = b"n0/tracker/1";
16/// Maximum size of a request
17pub const REQUEST_SIZE_LIMIT: usize = 1024 * 16;
18
19/// Announce kind
20#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
21pub enum AnnounceKind {
22    /// The peer supposedly has some of the data.
23    Partial = 0,
24    /// The peer supposedly has the complete data.
25    Complete,
26}
27
28impl AnnounceKind {
29    pub fn from_complete(complete: bool) -> Self {
30        if complete {
31            Self::Complete
32        } else {
33            Self::Partial
34        }
35    }
36}
37
38/// An absolute time in microseconds since the UNIX epoch.
39#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
40pub struct AbsoluteTime(u64);
41
42impl AbsoluteTime {
43    pub fn now() -> Self {
44        Self::try_from(SystemTime::now()).unwrap()
45    }
46
47    pub fn from_micros(micros: u64) -> Self {
48        Self(micros)
49    }
50
51    pub fn as_micros(&self) -> u64 {
52        self.0
53    }
54}
55
56impl Sub for AbsoluteTime {
57    type Output = Duration;
58
59    fn sub(self, rhs: Self) -> Self::Output {
60        Duration::from_micros(self.0 - rhs.0)
61    }
62}
63
64impl TryFrom<SystemTime> for AbsoluteTime {
65    type Error = &'static str;
66
67    fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
68        Ok(Self(
69            value
70                .duration_since(std::time::UNIX_EPOCH)
71                .map_err(|_| "SystemTime before UNIX EPOCH")?
72                .as_micros()
73                .try_into()
74                .map_err(|_| "SystemTime too large")?,
75        ))
76    }
77}
78
79impl From<AbsoluteTime> for SystemTime {
80    fn from(value: AbsoluteTime) -> Self {
81        std::time::UNIX_EPOCH + Duration::from_micros(value.0)
82    }
83}
84
85/// Announce that a peer claims to have some blobs or set of blobs.
86///
87/// A peer can announce having some data, but it should also be able to announce
88/// that another peer has the data. This is why the peer is included.
89#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
90pub struct Announce {
91    /// The peer that supposedly has the data.
92    pub host: NodeId,
93    /// The content that the peer claims to have.
94    pub content: HashAndFormat,
95    /// The kind of the announcement.
96    pub kind: AnnounceKind,
97    /// The timestamp of the announce.
98    pub timestamp: AbsoluteTime,
99}
100
101/// A signed announce.
102#[derive(derive_more::Debug, Clone, Copy, Serialize, Deserialize)]
103pub struct SignedAnnounce {
104    /// Announce.
105    pub announce: Announce,
106    /// Signature of the announce, signed by the host of the announce.
107    ///
108    /// The signature is over the announce, serialized with postcard.
109    #[serde(with = "BigArray")]
110    #[debug("{}", hex::encode(self.signature))]
111    pub signature: [u8; 64],
112}
113
114impl Deref for SignedAnnounce {
115    type Target = Announce;
116
117    fn deref(&self) -> &Self::Target {
118        &self.announce
119    }
120}
121
122#[derive(Debug, Snafu)]
123pub enum VerifyError {
124    SignatureError {
125        source: ed25519_dalek::SignatureError,
126    },
127    SerializationError {
128        source: postcard::Error,
129    },
130}
131
132impl SignedAnnounce {
133    /// Create a new signed announce.
134    pub fn new(
135        announce: Announce,
136        secret_key: &iroh::SecretKey,
137    ) -> result::Result<Self, postcard::Error> {
138        let announce_bytes = postcard::to_allocvec(&announce)?;
139        let signature = secret_key.sign(&announce_bytes).to_bytes();
140        Ok(Self {
141            announce,
142            signature,
143        })
144    }
145
146    /// Verify the announce, and return the announce if it's valid.
147    pub fn verify(&self) -> result::Result<(), VerifyError> {
148        let announce_bytes = postcard::to_allocvec(&self.announce).context(SerializationSnafu)?;
149        let signature = iroh_base::Signature::from_bytes(&self.signature);
150        self.announce
151            .host
152            .verify(&announce_bytes, &signature)
153            .context(SignatureSnafu)?;
154        Ok(())
155    }
156}
157
158/// Flags for a query.
159#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
160pub struct QueryFlags {
161    /// Only return peers that supposedly have the complete data.
162    ///
163    /// If this is false, the response might contain peers that only have some of the data.
164    pub complete: bool,
165
166    /// Only return hosts that have been verified.
167    ///
168    /// In case of a partial query, verification just means a check that the host exists
169    /// and returns the size for the data.
170    ///
171    /// In case of a complete query, verification means that the host has been randomly
172    /// probed for the data.
173    pub verified: bool,
174}
175
176/// Query a peer for a blob or set of blobs.
177#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
178pub struct Query {
179    /// The content we want to find.
180    ///
181    /// It's a difference if a peer has a blob or a hash seq and all of its children.
182    pub content: HashAndFormat,
183    /// The mode of the query.
184    pub flags: QueryFlags,
185}
186
187/// A response to a query.
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct QueryResponse {
190    /// The hosts that supposedly have the content.
191    ///
192    /// If there are any addrs, they are as seen from the tracker,
193    /// so they might or might not be useful.
194    pub hosts: Vec<SignedAnnounce>,
195}
196
197/// A request to the tracker.
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub enum Request {
200    /// Announce info
201    Announce(SignedAnnounce),
202    /// Query info
203    Query(Query),
204}
205
206/// A response from the tracker.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub enum Response {
209    /// Response to a query
210    QueryResponse(QueryResponse),
211}
212
213#[cfg(test)]
214mod tests {}