use std::{collections::BTreeSet, net::SocketAddr, str::FromStr};
use anyhow::Result;
use iroh::{NodeAddr, NodeId, RelayUrl};
use iroh_base::ticket::{self, Ticket};
use serde::{Deserialize, Serialize};
use crate::{BlobFormat, Hash};
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
#[display("{}", Ticket::serialize(self))]
pub struct BlobTicket {
node: NodeAddr,
format: BlobFormat,
hash: Hash,
}
#[derive(Serialize, Deserialize)]
enum TicketWireFormat {
Variant0(Variant0BlobTicket),
}
#[derive(Serialize, Deserialize)]
struct Variant0BlobTicket {
node: Variant0NodeAddr,
format: BlobFormat,
hash: Hash,
}
#[derive(Serialize, Deserialize)]
struct Variant0NodeAddr {
node_id: NodeId,
info: Variant0AddrInfo,
}
#[derive(Serialize, Deserialize)]
struct Variant0AddrInfo {
relay_url: Option<RelayUrl>,
direct_addresses: BTreeSet<SocketAddr>,
}
impl Ticket for BlobTicket {
const KIND: &'static str = "blob";
fn to_bytes(&self) -> Vec<u8> {
let data = TicketWireFormat::Variant0(Variant0BlobTicket {
node: Variant0NodeAddr {
node_id: self.node.node_id,
info: Variant0AddrInfo {
relay_url: self.node.relay_url.clone(),
direct_addresses: self.node.direct_addresses.clone(),
},
},
format: self.format,
hash: self.hash,
});
postcard::to_stdvec(&data).expect("postcard serialization failed")
}
fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, ticket::Error> {
let res: TicketWireFormat = postcard::from_bytes(bytes).map_err(ticket::Error::Postcard)?;
let TicketWireFormat::Variant0(Variant0BlobTicket { node, format, hash }) = res;
Ok(Self {
node: NodeAddr {
node_id: node.node_id,
relay_url: node.info.relay_url,
direct_addresses: node.info.direct_addresses,
},
format,
hash,
})
}
}
impl FromStr for BlobTicket {
type Err = ticket::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ticket::deserialize(s)
}
}
impl BlobTicket {
pub fn new(node: NodeAddr, hash: Hash, format: BlobFormat) -> Result<Self> {
Ok(Self { hash, format, node })
}
pub fn hash(&self) -> Hash {
self.hash
}
pub fn node_addr(&self) -> &NodeAddr {
&self.node
}
pub fn format(&self) -> BlobFormat {
self.format
}
pub fn recursive(&self) -> bool {
self.format.is_hash_seq()
}
pub fn into_parts(self) -> (NodeAddr, Hash, BlobFormat) {
let BlobTicket { node, hash, format } = self;
(node, hash, format)
}
}
impl Serialize for BlobTicket {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string())
} else {
let BlobTicket { node, format, hash } = self;
(node, format, hash).serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for BlobTicket {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(serde::de::Error::custom)
} else {
let (peer, format, hash) = Deserialize::deserialize(deserializer)?;
Self::new(peer, hash, format).map_err(serde::de::Error::custom)
}
}
}
#[cfg(test)]
mod tests {
use std::net::SocketAddr;
use iroh::{PublicKey, SecretKey};
use iroh_test::{assert_eq_hex, hexdump::parse_hexdump};
use super::*;
fn make_ticket() -> BlobTicket {
let hash = Hash::new(b"hi there");
let peer = SecretKey::generate(rand::thread_rng()).public();
let addr = SocketAddr::from_str("127.0.0.1:1234").unwrap();
let relay_url = None;
BlobTicket {
hash,
node: NodeAddr::from_parts(peer, relay_url, [addr]),
format: BlobFormat::HashSeq,
}
}
#[test]
fn test_ticket_postcard() {
let ticket = make_ticket();
let bytes = postcard::to_stdvec(&ticket).unwrap();
let ticket2: BlobTicket = postcard::from_bytes(&bytes).unwrap();
assert_eq!(ticket2, ticket);
}
#[test]
fn test_ticket_json() {
let ticket = make_ticket();
let json = serde_json::to_string(&ticket).unwrap();
let ticket2: BlobTicket = serde_json::from_str(&json).unwrap();
assert_eq!(ticket2, ticket);
}
#[test]
fn test_ticket_base32() {
let hash =
Hash::from_str("0b84d358e4c8be6c38626b2182ff575818ba6bd3f4b90464994be14cb354a072")
.unwrap();
let node_id =
PublicKey::from_str("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6")
.unwrap();
let ticket = BlobTicket {
node: NodeAddr::from_parts(node_id, None, []),
format: BlobFormat::Raw,
hash,
};
let encoded = ticket.to_string();
let stripped = encoded.strip_prefix("blob").unwrap();
let base32 = data_encoding::BASE32_NOPAD
.decode(stripped.to_ascii_uppercase().as_bytes())
.unwrap();
let expected = parse_hexdump("
00 # discriminator for variant 0
ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6 # node id, 32 bytes, see above
00 # relay url
00 # number of addresses (0)
00 # format (raw)
0b84d358e4c8be6c38626b2182ff575818ba6bd3f4b90464994be14cb354a072 # hash, 32 bytes, see above
").unwrap();
assert_eq_hex!(base32, expected);
}
}