1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! The protocol for communicating with the tracker.
use std::{
    ops::{Deref, Sub},
    time::{Duration, SystemTime},
};

use iroh_bytes::HashAndFormat;
use iroh_net::NodeId;
use serde::{Deserialize, Serialize};
use serde_big_array::BigArray;

/// The ALPN string for this protocol
pub const ALPN: &[u8] = b"n0/tracker/1";
/// Maximum size of a request
pub const REQUEST_SIZE_LIMIT: usize = 1024 * 16;

/// Announce kind
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum AnnounceKind {
    /// The peer supposedly has some of the data.
    Partial = 0,
    /// The peer supposedly has the complete data.
    Complete,
}

impl AnnounceKind {
    pub fn from_complete(complete: bool) -> Self {
        if complete {
            Self::Complete
        } else {
            Self::Partial
        }
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct AbsoluteTime(u64);

impl AbsoluteTime {
    pub fn now() -> Self {
        Self::try_from(SystemTime::now()).unwrap()
    }

    pub fn from_micros(micros: u64) -> Self {
        Self(micros)
    }

    pub fn as_micros(&self) -> u64 {
        self.0
    }
}

impl Sub for AbsoluteTime {
    type Output = Duration;

    fn sub(self, rhs: Self) -> Self::Output {
        Duration::from_micros(self.0 - rhs.0)
    }
}

impl TryFrom<SystemTime> for AbsoluteTime {
    type Error = anyhow::Error;

    fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
        Ok(Self(
            value
                .duration_since(std::time::UNIX_EPOCH)
                .expect("Time went backwards")
                .as_micros()
                .try_into()
                .expect("time too large"),
        ))
    }
}

impl From<AbsoluteTime> for SystemTime {
    fn from(value: AbsoluteTime) -> Self {
        std::time::UNIX_EPOCH + Duration::from_micros(value.0)
    }
}

/// Announce that a peer claims to have some blobs or set of blobs.
///
/// A peer can announce having some data, but it should also be able to announce
/// that another peer has the data. This is why the peer is included.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Announce {
    /// The peer that supposedly has the data.
    pub host: NodeId,
    /// The content that the peer claims to have.
    pub content: HashAndFormat,
    /// The kind of the announcement.
    pub kind: AnnounceKind,
    /// The timestamp of the announce.
    pub timestamp: AbsoluteTime,
}

/// A signed announce.
#[derive(derive_more::Debug, Clone, Copy, Serialize, Deserialize)]
pub struct SignedAnnounce {
    /// Announce.
    pub announce: Announce,
    /// Signature of the announce, signed by the host of the announce.
    ///
    /// The signature is over the announce, serialized with postcard.
    #[serde(with = "BigArray")]
    #[debug("{}", hex::encode(self.signature))]
    pub signature: [u8; 64],
}

impl Deref for SignedAnnounce {
    type Target = Announce;

    fn deref(&self) -> &Self::Target {
        &self.announce
    }
}

impl SignedAnnounce {
    /// Create a new signed announce.
    pub fn new(announce: Announce, secret_key: &iroh_net::key::SecretKey) -> anyhow::Result<Self> {
        let announce_bytes = postcard::to_allocvec(&announce)?;
        let signature = secret_key.sign(&announce_bytes).to_bytes();
        Ok(Self {
            announce,
            signature,
        })
    }

    /// Verify the announce, and return the announce if it's valid.
    pub fn verify(&self) -> anyhow::Result<()> {
        let announce_bytes = postcard::to_allocvec(&self.announce)?;
        let signature = iroh_net::key::Signature::from_bytes(&self.signature);
        self.announce.host.verify(&announce_bytes, &signature)?;
        Ok(())
    }
}

///
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct QueryFlags {
    /// Only return peers that supposedly have the complete data.
    ///
    /// If this is false, the response might contain peers that only have some of the data.
    pub complete: bool,

    /// Only return hosts that have been verified.
    ///
    /// In case of a partial query, verification just means a check that the host exists
    /// and returns the size for the data.
    ///
    /// In case of a complete query, verification means that the host has been randomly
    /// probed for the data.
    pub verified: bool,
}

/// Query a peer for a blob or set of blobs.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Query {
    /// The content we want to find.
    ///
    /// It's a difference if a peer has a blob or a hash seq and all of its children.
    pub content: HashAndFormat,
    /// The mode of the query.
    pub flags: QueryFlags,
}

/// A response to a query.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryResponse {
    /// The hosts that supposedly have the content.
    ///
    /// If there are any addrs, they are as seen from the tracker,
    /// so they might or might not be useful.
    pub hosts: Vec<SignedAnnounce>,
}

/// A request to the tracker.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Request {
    /// Announce info
    Announce(SignedAnnounce),
    /// Query info
    Query(Query),
}

/// A response from the tracker.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Response {
    /// Response to a query
    QueryResponse(QueryResponse),
}

#[cfg(test)]
mod tests {}