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 {}