use serde::{Deserialize, Serialize};
use crate::types::{PeerAddress, Provider};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct DhtNode {
pub peer_id: String,
pub addresses: Vec<PeerAddress>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub expires_at: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub enum DhtRequest {
FindNode {
node_id: String,
target_id: String,
},
FindProviders {
node_id: String,
content_key: String,
},
AddProvider {
node_id: String,
content_key: String,
provider: Provider,
},
Ping {
node_id: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub enum DhtResponse {
FindProviders {
providers: Vec<DhtNode>,
closer: Vec<DhtNode>,
},
FindNode {
closer: Vec<DhtNode>,
},
AddProvider {
stored: bool,
},
Ping {
node_id: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct PexPeer {
pub peer_id: String,
pub addresses: Vec<PeerAddress>,
#[serde(default)]
pub flags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub provenance: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub enum PexMessage {
PexHandshake {
capabilities: Vec<String>,
version: u32,
},
PexSnapshot {
peers: Vec<PexPeer>,
},
PexDelta {
added: Vec<PexPeer>,
removed: Vec<String>,
},
PexError {
code: u32,
message: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dht_request_tagged_snake_case() {
let req = DhtRequest::FindProviders {
node_id: "ab".repeat(32),
content_key: "cd".repeat(32),
};
let v = serde_json::to_value(&req).unwrap();
assert_eq!(v["type"], "find_providers");
assert_eq!(serde_json::from_value::<DhtRequest>(v).unwrap(), req);
}
#[test]
fn dht_ping_round_trips() {
let req = DhtRequest::Ping {
node_id: "ef".repeat(32),
};
let s = serde_json::to_string(&req).unwrap();
assert!(s.contains("\"type\":\"ping\""));
assert_eq!(serde_json::from_str::<DhtRequest>(&s).unwrap(), req);
}
#[test]
fn pex_message_tagged() {
let msg = PexMessage::PexHandshake {
capabilities: vec!["snapshot".into()],
version: 1,
};
let v = serde_json::to_value(&msg).unwrap();
assert_eq!(v["type"], "pex_handshake");
assert_eq!(serde_json::from_value::<PexMessage>(v).unwrap(), msg);
}
#[test]
fn pex_delta_shape() {
let msg = PexMessage::PexDelta {
added: vec![PexPeer {
peer_id: "12".repeat(32),
addresses: vec![],
flags: vec![],
provenance: Some("firsthand".into()),
}],
removed: vec!["34".repeat(32)],
};
let v = serde_json::to_value(&msg).unwrap();
assert_eq!(v["type"], "pex_delta");
assert_eq!(v["added"][0]["provenance"], "firsthand");
assert_eq!(serde_json::from_value::<PexMessage>(v).unwrap(), msg);
}
}