hopr_transport_network/
messaging.rs

1use crate::errors::NetworkingError::MessagingError;
2use hopr_crypto_sphinx::derivation::derive_ping_pong;
3use hopr_primitive_types::errors::GeneralError;
4use hopr_primitive_types::prelude::BytesEncodable;
5use serde::{Deserialize, Serialize};
6
7use crate::errors::Result;
8
9/// Implementation of the Control Message sub-protocol, which currently consists of Ping/Pong
10/// messaging for the HOPR protocol.
11#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
12pub enum ControlMessage {
13    /// Ping challenge
14    Ping(PingMessage),
15    /// Pong response to a Ping
16    Pong(PingMessage),
17}
18
19impl ControlMessage {
20    /// Creates a ping challenge message.
21    pub fn generate_ping_request() -> Self {
22        let mut ping = PingMessage::default();
23        ping.nonce.copy_from_slice(&derive_ping_pong(None));
24
25        Self::Ping(ping)
26    }
27
28    /// Given the received ping challenge, generates an appropriate response to the challenge.
29    pub fn generate_pong_response(request: &ControlMessage) -> Result<Self> {
30        match request {
31            ControlMessage::Ping(ping) => {
32                let mut pong = PingMessage::default();
33                pong.nonce.copy_from_slice(&derive_ping_pong(Some(ping.nonce())));
34                Ok(Self::Pong(pong))
35            }
36            ControlMessage::Pong(_) => Err(MessagingError("invalid ping message".into())),
37        }
38    }
39
40    /// Given the original ping challenge message, verifies that the received pong response is valid
41    /// according to the challenge.
42    pub fn validate_pong_response(request: &ControlMessage, response: &ControlMessage) -> Result<()> {
43        if let Self::Pong(expected_pong) = Self::generate_pong_response(request).unwrap() {
44            match response {
45                ControlMessage::Pong(received_pong) => match expected_pong.nonce.eq(&received_pong.nonce) {
46                    true => Ok(()),
47                    false => Err(MessagingError("pong response does not match the challenge".into())),
48                },
49                ControlMessage::Ping(_) => Err(MessagingError("invalid pong response".into())),
50            }
51        } else {
52            Err(MessagingError("request is not a valid ping message".into()))
53        }
54    }
55
56    /// Convenience method to de-structure the ping message payload, if this
57    /// instance is a Ping or Pong.
58    pub fn get_ping_message(&self) -> Result<&PingMessage> {
59        match self {
60            ControlMessage::Ping(m) | ControlMessage::Pong(m) => Ok(m),
61        }
62    }
63}
64
65#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)]
66pub struct PingMessage {
67    nonce: [u8; hopr_crypto_sphinx::derivation::PING_PONG_NONCE_SIZE],
68}
69
70impl PingMessage {
71    /// Gets the challenge or response in this ping/pong message.
72    pub fn nonce(&self) -> &[u8] {
73        &self.nonce
74    }
75}
76
77impl From<PingMessage> for [u8; PING_MESSAGE_LEN] {
78    fn from(value: PingMessage) -> Self {
79        let mut ret = [0u8; PING_MESSAGE_LEN];
80        ret[0..PING_MESSAGE_LEN].copy_from_slice(&value.nonce);
81        ret
82    }
83}
84
85impl TryFrom<&[u8]> for PingMessage {
86    type Error = GeneralError;
87
88    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
89        if value.len() >= Self::SIZE {
90            let mut ret = PingMessage::default();
91            ret.nonce.copy_from_slice(&value[0..Self::SIZE]);
92            Ok(ret)
93        } else {
94            Err(GeneralError::ParseError("PingMessage".into()))
95        }
96    }
97}
98
99const PING_MESSAGE_LEN: usize = hopr_crypto_sphinx::derivation::PING_PONG_NONCE_SIZE;
100impl BytesEncodable<PING_MESSAGE_LEN> for PingMessage {}
101
102#[cfg(test)]
103mod tests {
104    use crate::messaging::ControlMessage::{Ping, Pong};
105    use crate::messaging::{ControlMessage, PingMessage};
106    use hopr_primitive_types::prelude::BytesEncodable;
107
108    #[test]
109    fn test_ping_pong_roundtrip() {
110        // ping initiator
111        let sent_req_s: Box<[u8]>;
112        let sent_req: ControlMessage;
113        {
114            sent_req = ControlMessage::generate_ping_request();
115            sent_req_s = sent_req.get_ping_message().unwrap().clone().into_boxed();
116        }
117
118        // pong responder
119        let sent_resp_s: Box<[u8]>;
120        {
121            let recv_req = PingMessage::try_from(sent_req_s.as_ref()).unwrap();
122            let send_resp = ControlMessage::generate_pong_response(&Ping(recv_req)).unwrap();
123            sent_resp_s = send_resp.get_ping_message().unwrap().clone().into_boxed();
124        }
125
126        // verify pong
127        {
128            let recv_resp = PingMessage::try_from(sent_resp_s.as_ref()).unwrap();
129            assert!(ControlMessage::validate_pong_response(&sent_req, &Pong(recv_resp)).is_ok());
130        }
131    }
132}