rustybit-leechy-dht 1.0.0

A partial (leechy) DHT implementation
Documentation
use std::borrow::Cow;
use std::fmt::Debug;

use serde::{Deserialize, Serialize};

pub trait KrpcQueryMessage<'a> {
    type ResponseType: KrpcResponseMessage<'a>;
}

pub trait KrpcResponseMessage<'a> {}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetPeersQueryMessage {
    #[serde(with = "serde_bytes")]
    pub id: [u8; 20],
    #[serde(with = "serde_bytes")]
    pub info_hash: [u8; 20],
}

impl<'a> KrpcQueryMessage<'a> for GetPeersQueryMessage {
    type ResponseType = GetPeersResponse<'a>;
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetPeersResponse<'a> {
    #[serde(default, borrow)]
    pub values: Option<Cow<'a, [CompactPeerInfo<'a>]>>,
    #[serde(with = "serde_bytes", default, borrow)]
    pub nodes: Option<Cow<'a, [u8]>>,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
pub struct CompactPeerInfo<'a>(#[serde(with = "serde_bytes", borrow)] pub Cow<'a, [u8]>);

impl<'a> KrpcResponseMessage<'a> for GetPeersResponse<'a> {}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PingQueryMessage {
    #[serde(with = "serde_bytes")]
    pub id: [u8; 20],
}

impl<'a> KrpcQueryMessage<'a> for PingQueryMessage {
    type ResponseType = PingResponse;
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PingResponse {
    #[serde(with = "serde_bytes")]
    pub id: [u8; 20],
}

impl<'a> KrpcResponseMessage<'a> for PingResponse {}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct KrpcMessage<'a, M = GetPeersQueryMessage>
where
    M: KrpcQueryMessage<'a> + Debug + PartialEq + Eq,
    M::ResponseType: Serialize + Debug + PartialEq + Eq,
{
    #[serde(rename = "t")]
    pub(crate) transaction_id: Cow<'a, str>,
    #[serde(
        flatten,
        borrow,
        bound(deserialize = "M: Deserialize<'de>, M::ResponseType: Deserialize<'de>")
    )]
    pub(crate) message_type: KrpcMessageType<'a, M>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "y")]
pub enum KrpcMessageType<'a, M: KrpcQueryMessage<'a>> {
    #[serde(rename = "q")]
    Query {
        #[serde(rename = "q")]
        name: Cow<'a, str>,
        #[serde(rename = "a")]
        query: M,
    },
    #[serde(rename = "r")]
    Response {
        #[serde(rename = "r")]
        response: M::ResponseType,
    },
    #[serde(rename = "e")]
    Error {
        #[serde(rename = "e", borrow)]
        error: (u16, Cow<'a, str>),
    },
}

#[cfg(test)]
mod tests {
    use super::{GetPeersQueryMessage, KrpcMessage, KrpcMessageType};
    use crate::requests::{CompactPeerInfo, GetPeersResponse};

    #[test]
    fn get_peers_roundtrip_test() {
        let query_message = KrpcMessage {
            transaction_id: "123".into(),
            message_type: KrpcMessageType::Query {
                name: "get_peers".into(),
                query: GetPeersQueryMessage {
                    id: [0; 20],
                    info_hash: [1; 20],
                },
            },
        };

        let ser_result = serde_bencode::to_string(&query_message).expect("serialization failed");

        let deserialized_query_message =
            serde_bencode::from_str::<KrpcMessage>(&ser_result).expect("deserialization failed");

        assert_eq!(query_message, deserialized_query_message);
    }

    #[test]
    fn get_peers_resp_roundtrip_test() {
        let peer_info = vec![CompactPeerInfo(b"test".into())];
        let nodes = b"big node";

        let query_resp_message = KrpcMessage {
            transaction_id: "123".into(),
            message_type: KrpcMessageType::Response {
                response: GetPeersResponse {
                    values: Some((&peer_info).into()),
                    nodes: Some(nodes.as_ref().into()),
                },
            },
        };

        let ser_result = serde_bencode::to_string(&query_resp_message).expect("serialization failed");

        dbg!(&ser_result);

        let deserialized_query_resp_message =
            serde_bencode::from_str::<KrpcMessage>(&ser_result).expect("deserialization failed");

        assert_eq!(query_resp_message, deserialized_query_resp_message);
    }
}