use super::bencode::{Dict, Value, bytes, entry};
use std::fmt;
use std::net::{IpAddr, SocketAddr};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(pub [u8; 20]);
impl NodeId {
pub fn distance(&self, other: &NodeId) -> [u8; 20] {
let mut out = [0u8; 20];
for (o, (a, b)) in out.iter_mut().zip(self.0.iter().zip(other.0.iter())) {
*o = a ^ b;
}
out
}
pub(crate) fn parse(b: &[u8]) -> Option<NodeId> {
<[u8; 20]>::try_from(b).ok().map(NodeId)
}
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.iter().try_for_each(|b| write!(f, "{b:02x}"))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Node {
pub id: NodeId,
pub addr: SocketAddr,
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Message {
Reply {
tid: Vec<u8>,
r: Dict,
ip: Option<SocketAddr>,
},
Error {
tid: Vec<u8>,
code: i64,
message: String,
},
}
pub(crate) fn query(tid: &[u8], id: &NodeId, q: &str, mut args: Dict) -> Vec<u8> {
args.insert(b"id".to_vec(), bytes(&id.0));
Value::Dict(Dict::from([
entry("t", bytes(tid)),
entry("y", bytes(b"q")),
entry("q", bytes(q.as_bytes())),
entry("a", Value::Dict(args)),
]))
.encode()
}
pub(crate) fn parse(datagram: &[u8]) -> Option<Message> {
let v = Value::decode(datagram).ok()?;
let tid = v.get("t")?.as_bytes()?.to_vec();
match v.get("y")?.as_bytes()? {
b"r" => Some(Message::Reply {
tid,
r: v.get("r")?.as_dict()?.clone(),
ip: v.get("ip").and_then(Value::as_bytes).and_then(compact_addr),
}),
b"e" => {
let Value::List(e) = v.get("e")? else {
return None;
};
let code = e.first().and_then(Value::as_int).unwrap_or(-1);
let message = e
.get(1)
.and_then(Value::as_bytes)
.map(|m| String::from_utf8_lossy(m).into_owned())
.unwrap_or_default();
Some(Message::Error { tid, code, message })
}
_ => None,
}
}
pub(crate) fn nodes_of(r: &Dict) -> Vec<Node> {
let mut out = Vec::new();
if let Some(Value::Bytes(b)) = r.get(b"nodes".as_slice()) {
out.extend(b.chunks_exact(26).filter_map(compact_node));
}
if let Some(Value::Bytes(b)) = r.get(b"nodes6".as_slice()) {
out.extend(b.chunks_exact(38).filter_map(compact_node));
}
out
}
fn compact_node(c: &[u8]) -> Option<Node> {
Some(Node {
id: NodeId::parse(c.get(..20)?)?,
addr: compact_addr(c.get(20..)?)?,
})
}
pub(crate) fn compact_addr(b: &[u8]) -> Option<SocketAddr> {
let (ip, port) = b.split_at_checked(b.len().checked_sub(2)?)?;
let ip = <[u8; 4]>::try_from(ip)
.map(IpAddr::from)
.or_else(|_| <[u8; 16]>::try_from(ip).map(IpAddr::from))
.ok()?;
Some(SocketAddr::new(
ip,
u16::from_be_bytes(port.try_into().ok()?),
))
}
#[cfg(test)]
mod tests;