use enr::{CombinedKey, Enr}; use rlp::{DecoderError, RlpStream}; use std::net::{IpAddr, Ipv6Addr}; use tracing::{debug, warn}; type TopicHash = [u8; 32]; /// Type to manage the request IDs. #[derive(Debug, Clone, PartialEq, Hash, Eq)] pub struct RequestId(pub Vec); impl From for Vec { fn from(id: RequestId) -> Self { id.0 } } impl RequestId { /// Decodes the ID from a raw bytes. pub fn decode(data: Vec) -> Result { if data.len() > 8 { return Err(DecoderError::Custom("Invalid ID length")); } Ok(RequestId(data)) } pub fn random() -> Self { let rand: u64 = rand::random(); RequestId(rand.to_be_bytes().to_vec()) } pub fn as_bytes(&self) -> &[u8] { &self.0 } } #[derive(Debug, Clone, PartialEq)] /// A combined type representing requests and responses. pub enum Message { /// A request, which contains its [`RequestId`]. Request(Request), /// A Response, which contains the [`RequestId`] of its associated request. Response(Response), } #[derive(Debug, Clone, PartialEq)] /// A request sent between nodes. pub struct Request { /// The [`RequestId`] of the request. pub id: RequestId, /// The body of the request. pub body: RequestBody, } #[derive(Debug, Clone, PartialEq)] /// A response sent in response to a [`Request`] pub struct Response { /// The [`RequestId`] of the request that triggered this response. pub id: RequestId, /// The body of this response. pub body: ResponseBody, } #[derive(Debug, Clone, PartialEq)] pub enum RequestBody { /// A PING request. Ping { /// Our current ENR sequence number. enr_seq: u64, }, /// A FINDNODE request. FindNode { /// The distance(s) of peers we expect to be returned in the response. distances: Vec, }, /// A Talk request. Talk { /// The protocol requesting. protocol: Vec, /// The request. request: Vec, }, /// A REGISTERTOPIC request. RegisterTopic { topic: Vec, enr: crate::Enr, ticket: Vec, }, /// A TOPICQUERY request. TopicQuery { topic: TopicHash }, } #[derive(Debug, Clone, PartialEq)] pub enum ResponseBody { /// A PONG response. Pong { /// The current ENR sequence number of the responder. enr_seq: u64, /// Our external IP address as observed by the responder. ip: IpAddr, /// Our external UDP port as observed by the responder. port: u16, }, /// A NODES response. Nodes { /// The total number of responses that make up this response. total: u64, /// A list of ENR's returned by the responder. nodes: Vec>, }, /// The TALK response. Talk { /// The response for the talk. response: Vec, }, Ticket { ticket: Vec, wait_time: u64, }, RegisterConfirmation { topic: Vec, }, } impl Request { pub fn msg_type(&self) -> u8 { match self.body { RequestBody::Ping { .. } => 1, RequestBody::FindNode { .. } => 3, RequestBody::Talk { .. } => 5, RequestBody::RegisterTopic { .. } => 7, RequestBody::TopicQuery { .. } => 10, } } /// Encodes a Message to RLP-encoded bytes. pub fn encode(self) -> Vec { let mut buf = Vec::with_capacity(10); let msg_type = self.msg_type(); buf.push(msg_type); let id = &self.id; match self.body { RequestBody::Ping { enr_seq } => { let mut s = RlpStream::new(); s.begin_list(2); s.append(&id.as_bytes()); s.append(&enr_seq); buf.extend_from_slice(&s.out()); buf } RequestBody::FindNode { distances } => { let mut s = RlpStream::new(); s.begin_list(2); s.append(&id.as_bytes()); s.begin_list(distances.len()); for distance in distances { s.append(&distance); } buf.extend_from_slice(&s.out()); buf } RequestBody::Talk { protocol, request } => { let mut s = RlpStream::new(); s.begin_list(3); s.append(&id.as_bytes()); s.append(&protocol); s.append(&request); buf.extend_from_slice(&s.out()); buf } RequestBody::RegisterTopic { topic, enr, ticket } => { let mut s = RlpStream::new(); s.begin_list(4); s.append(&id.as_bytes()); s.append(&topic); s.append(&enr); s.append(&ticket); buf.extend_from_slice(&s.out()); buf } RequestBody::TopicQuery { topic } => { let mut s = RlpStream::new(); s.begin_list(2); s.append(&id.as_bytes()); s.append(&(&topic as &[u8])); buf.extend_from_slice(&s.out()); buf } } } } impl Response { pub fn msg_type(&self) -> u8 { match &self.body { ResponseBody::Pong { .. } => 2, ResponseBody::Nodes { .. } => 4, ResponseBody::Talk { .. } => 6, ResponseBody::Ticket { .. } => 8, ResponseBody::RegisterConfirmation { .. } => 9, } } /// Determines if the response is a valid response to the given request. pub fn match_request(&self, req: &RequestBody) -> bool { match self.body { ResponseBody::Pong { .. } => matches!(req, RequestBody::Ping { .. }), ResponseBody::Nodes { .. } => { matches!( req, RequestBody::FindNode { .. } | RequestBody::TopicQuery { .. } ) } ResponseBody::Talk { .. } => matches!(req, RequestBody::Talk { .. }), ResponseBody::Ticket { .. } => matches!(req, RequestBody::RegisterTopic { .. }), ResponseBody::RegisterConfirmation { .. } => { matches!(req, RequestBody::RegisterTopic { .. }) } } } /// Encodes a Message to RLP-encoded bytes. pub fn encode(self) -> Vec { let mut buf = Vec::with_capacity(10); let msg_type = self.msg_type(); buf.push(msg_type); let id = &self.id; match self.body { ResponseBody::Pong { enr_seq, ip, port } => { let mut s = RlpStream::new(); s.begin_list(4); s.append(&id.as_bytes()); s.append(&enr_seq); match ip { IpAddr::V4(addr) => s.append(&(&addr.octets() as &[u8])), IpAddr::V6(addr) => s.append(&(&addr.octets() as &[u8])), }; s.append(&port); buf.extend_from_slice(&s.out()); buf } ResponseBody::Nodes { total, nodes } => { let mut s = RlpStream::new(); s.begin_list(3); s.append(&id.as_bytes()); s.append(&total); if nodes.is_empty() { s.begin_list(0); } else { s.begin_list(nodes.len()); for node in nodes { s.append(&node); } } buf.extend_from_slice(&s.out()); buf } ResponseBody::Talk { response } => { let mut s = RlpStream::new(); s.begin_list(2); s.append(&id.as_bytes()); s.append(&response); buf.extend_from_slice(&s.out()); buf } ResponseBody::Ticket { ticket, wait_time } => { let mut s = RlpStream::new(); s.begin_list(3); s.append(&id.as_bytes()); s.append(&ticket); s.append(&wait_time); buf.extend_from_slice(&s.out()); buf } ResponseBody::RegisterConfirmation { topic } => { let mut s = RlpStream::new(); s.begin_list(2); s.append(&id.as_bytes()); s.append(&topic); buf.extend_from_slice(&s.out()); buf } } } } impl std::fmt::Display for RequestId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", hex::encode(&self.0)) } } impl std::fmt::Display for Message { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Message::Request(request) => write!(f, "{}", request), Message::Response(response) => write!(f, "{}", response), } } } impl std::fmt::Display for Response { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Response: id: {}: {}", self.id, self.body) } } impl std::fmt::Display for ResponseBody { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ResponseBody::Pong { enr_seq, ip, port } => write!( f, "PONG: Enr-seq: {}, Ip: {:?}, Port: {}", enr_seq, ip, port ), ResponseBody::Nodes { total, nodes } => { let _ = write!(f, "NODES: total: {}, Nodes: [", total); let mut first = true; for id in nodes { if !first { write!(f, ", {}", id)?; } else { write!(f, "{}", id)?; } first = false; } write!(f, "]") } ResponseBody::Talk { response } => { write!(f, "Response: Response {}", hex::encode(response)) } ResponseBody::Ticket { ticket, wait_time } => { write!(f, "TICKET: Ticket: {:?}, Wait time: {}", ticket, wait_time) } ResponseBody::RegisterConfirmation { topic } => { write!(f, "REGTOPIC: Registered: {}", hex::encode(topic)) } } } } impl std::fmt::Display for Request { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Request: id: {}: {}", self.id, self.body) } } impl std::fmt::Display for RequestBody { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RequestBody::Ping { enr_seq } => write!(f, "PING: enr_seq: {}", enr_seq), RequestBody::FindNode { distances } => { write!(f, "FINDNODE Request: distance: {:?}", distances) } RequestBody::Talk { protocol, request } => write!( f, "TALK: protocol: {}, request: {}", hex::encode(protocol), hex::encode(request) ), RequestBody::TopicQuery { topic } => write!(f, "TOPICQUERY: topic: {:?}", topic), RequestBody::RegisterTopic { topic, enr, ticket } => write!( f, "RegisterTopic: topic: {}, enr: {}, ticket: {}", hex::encode(topic), enr.to_base64(), hex::encode(ticket) ), } } } #[allow(dead_code)] impl Message { pub fn encode(self) -> Vec { match self { Self::Request(request) => request.encode(), Self::Response(response) => response.encode(), } } pub fn decode(data: &[u8]) -> Result { if data.len() < 3 { return Err(DecoderError::RlpIsTooShort); } let msg_type = data[0]; let rlp = rlp::Rlp::new(&data[1..]); let list_len = rlp.item_count().and_then(|size| { if size < 2 { Err(DecoderError::RlpIncorrectListLen) } else { Ok(size) } })?; let id = RequestId::decode(rlp.val_at::>(0)?)?; let message = match msg_type { 1 => { // PingRequest if list_len != 2 { debug!( "Ping Request has an invalid RLP list length. Expected 2, found {}", list_len ); return Err(DecoderError::RlpIncorrectListLen); } Message::Request(Request { id, body: RequestBody::Ping { enr_seq: rlp.val_at::(1)?, }, }) } 2 => { // PingResponse if list_len != 4 { debug!( "Ping Response has an invalid RLP list length. Expected 4, found {}", list_len ); return Err(DecoderError::RlpIncorrectListLen); } let ip_bytes = rlp.val_at::>(2)?; let ip = match ip_bytes.len() { 4 => { let mut ip = [0u8; 4]; ip.copy_from_slice(&ip_bytes); IpAddr::from(ip) } 16 => { let mut ip = [0u8; 16]; ip.copy_from_slice(&ip_bytes); let ipv6 = Ipv6Addr::from(ip); // If the ipv6 is ipv4 compatible/mapped, simply return the ipv4. if let Some(ipv4) = ipv6.to_ipv4() { IpAddr::V4(ipv4) } else { IpAddr::V6(ipv6) } } _ => { debug!("Ping Response has incorrect byte length for IP"); return Err(DecoderError::RlpIncorrectListLen); } }; let port = rlp.val_at::(3)?; Message::Response(Response { id, body: ResponseBody::Pong { enr_seq: rlp.val_at::(1)?, ip, port, }, }) } 3 => { // FindNodeRequest if list_len != 2 { debug!( "FindNode Request has an invalid RLP list length. Expected 2, found {}", list_len ); return Err(DecoderError::RlpIncorrectListLen); } let distances = rlp.list_at::(1)?; if distances.len() > 10 { warn!( "Rejected FindNode request asking for too many buckets {}, maximum 10", distances.len() ); return Err(DecoderError::Custom("FINDNODE request too large")); } for distance in distances.iter() { if distance > &256u64 { warn!( "Rejected FindNode request asking for unknown distance {}, maximum 256", distance ); return Err(DecoderError::Custom("FINDNODE request distance invalid")); } } Message::Request(Request { id, body: RequestBody::FindNode { distances }, }) } 4 => { // NodesResponse if list_len != 3 { debug!( "Nodes Response has an invalid RLP list length. Expected 3, found {}", list_len ); return Err(DecoderError::RlpIncorrectListLen); } let nodes = { let enr_list_rlp = rlp.at(2)?; if enr_list_rlp.is_empty() { // no records vec![] } else { enr_list_rlp.as_list::>()? } }; Message::Response(Response { id, body: ResponseBody::Nodes { total: rlp.val_at::(1)?, nodes, }, }) } 5 => { // Talk Request if list_len != 3 { debug!( "Talk Request has an invalid RLP list length. Expected 3, found {}", list_len ); return Err(DecoderError::RlpIncorrectListLen); } let protocol = rlp.val_at::>(1)?; let request = rlp.val_at::>(2)?; Message::Request(Request { id, body: RequestBody::Talk { protocol, request }, }) } 6 => { // Talk Response if list_len != 2 { debug!( "Talk Response has an invalid RLP list length. Expected 2, found {}", list_len ); return Err(DecoderError::RlpIncorrectListLen); } let response = rlp.val_at::>(1)?; Message::Response(Response { id, body: ResponseBody::Talk { response }, }) } _ => { return Err(DecoderError::Custom("Unknown RPC message type")); } /* * All other RPC messages are currently not supported as per the 5.1 specification. 7 => { // RegisterTopicRequest if list_len != 2 { debug!("RegisterTopic Request has an invalid RLP list length. Expected 2, found {}", list_len); return Err(DecoderError::RlpIncorrectListLen); } let ticket = rlp.val_at::>(1)?; Message::Request(Request { id, body: RequestBody::RegisterTopic { ticket }, }) } 8 => { // RegisterTopicResponse if list_len != 2 { debug!("RegisterTopic Response has an invalid RLP list length. Expected 2, found {}", list_len); return Err(DecoderError::RlpIncorrectListLen); } Message::Response(Response { id, body: ResponseBody::RegisterTopic { registered: rlp.val_at::(1)?, }, }) } 9 => { // TopicQueryRequest if list_len != 2 { debug!( "TopicQuery Request has an invalid RLP list length. Expected 2, found {}", list_len ); return Err(DecoderError::RlpIncorrectListLen); } let topic = { let topic_bytes = rlp.val_at::>(1)?; if topic_bytes.len() > 32 { debug!("Ticket Request has a topic greater than 32 bytes"); return Err(DecoderError::RlpIsTooBig); } let mut topic = [0u8; 32]; topic[32 - topic_bytes.len()..].copy_from_slice(&topic_bytes); topic }; Message::Request(Request { id, body: RequestBody::TopicQuery { topic }, }) } */ }; Ok(message) } } #[cfg(test)] mod tests { use super::*; use enr::EnrBuilder; #[test] fn ref_test_encode_request_ping() { // reference input let id = RequestId(vec![1]); let enr_seq = 1; let message = Message::Request(Request { id, body: RequestBody::Ping { enr_seq }, }); // expected hex output let expected_output = hex::decode("01c20101").unwrap(); dbg!(hex::encode(message.clone().encode())); assert_eq!(message.encode(), expected_output); } #[test] fn ref_test_encode_request_findnode() { // reference input let id = RequestId(vec![1]); let distances = vec![256]; let message = Message::Request(Request { id, body: RequestBody::FindNode { distances }, }); // expected hex output let expected_output = hex::decode("03c501c3820100").unwrap(); dbg!(hex::encode(message.clone().encode())); assert_eq!(message.encode(), expected_output); } #[test] fn ref_test_encode_response_ping() { // reference input let id = RequestId(vec![1]); let enr_seq = 1; let ip: IpAddr = "127.0.0.1".parse().unwrap(); let port = 5000; let message = Message::Response(Response { id, body: ResponseBody::Pong { enr_seq, ip, port }, }); // expected hex output let expected_output = hex::decode("02ca0101847f000001821388").unwrap(); dbg!(hex::encode(message.clone().encode())); assert_eq!(message.encode(), expected_output); } #[test] fn ref_test_encode_response_nodes_empty() { // reference input let id = RequestId(vec![1]); let total = 1; // expected hex output let expected_output = hex::decode("04c30101c0").unwrap(); let message = Message::Response(Response { id, body: ResponseBody::Nodes { total, nodes: vec![], }, }); assert_eq!(message.encode(), expected_output); } #[test] fn ref_test_encode_response_nodes() { // reference input let id = RequestId(vec![1]); let total = 1; let enr = "-HW4QCjfjuCfSmIJHxqLYfGKrSz-Pq3G81DVJwd_muvFYJiIOkf0bGtJu7kZVCOPnhSTMneyvR4MRbF3G5TNB4wy2ssBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTg".parse::>().unwrap(); // expected hex output let expected_output = hex::decode("04f87b0101f877f875b84028df8ee09f4a62091f1a8b61f18aad2cfe3eadc6f350d527077f9aebc56098883a47f46c6b49bbb91954238f9e14933277b2bd1e0c45b1771b94cd078c32dacb0182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138").unwrap(); let message = Message::Response(Response { id, body: ResponseBody::Nodes { total, nodes: vec![enr], }, }); dbg!(hex::encode(message.clone().encode())); assert_eq!(message.encode(), expected_output); } #[test] fn ref_test_encode_response_nodes_multiple() { // reference input let id = RequestId(vec![1]); let total = 1; let enr = "enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxaagKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTg".parse::>().unwrap(); let enr2 = "enr:-HW4QNfxw543Ypf4HXKXdYxkyzfcxcO-6p9X986WldfVpnVTQX1xlTnWrktEWUbeTZnmgOuAY_KUhbVV1Ft98WoYUBMBgmlkgnY0iXNlY3AyNTZrMaEDDiy3QkHAxPyOgWbxp5oF1bDdlYE6dLCUUp8xfVw50jU".parse::>().unwrap(); // expected hex output let expected_output = hex::decode("04f8f20101f8eef875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235").unwrap(); let message = Message::Response(Response { id, body: ResponseBody::Nodes { total, nodes: vec![enr, enr2], }, }); dbg!(hex::encode(message.clone().encode())); assert_eq!(message.encode(), expected_output); } #[test] fn ref_decode_response_nodes_multiple() { let input = hex::decode("04f8f20101f8eef875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235").unwrap(); let expected_enr1 = "enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxaagKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTg".parse::>().unwrap(); let expected_enr2 = "enr:-HW4QNfxw543Ypf4HXKXdYxkyzfcxcO-6p9X986WldfVpnVTQX1xlTnWrktEWUbeTZnmgOuAY_KUhbVV1Ft98WoYUBMBgmlkgnY0iXNlY3AyNTZrMaEDDiy3QkHAxPyOgWbxp5oF1bDdlYE6dLCUUp8xfVw50jU".parse::>().unwrap(); let decoded = Message::decode(&input).unwrap(); match decoded { Message::Response(response) => match response.body { ResponseBody::Nodes { total, nodes } => { assert_eq!(total, 1); assert_eq!(nodes[0], expected_enr1); assert_eq!(nodes[1], expected_enr2); } _ => panic!("Invalid decoding"), }, _ => panic!("Invalid decoding"), } } #[test] fn encode_decode_ping_request() { let id = RequestId(vec![1]); let request = Message::Request(Request { id, body: RequestBody::Ping { enr_seq: 15 }, }); let encoded = request.clone().encode(); let decoded = Message::decode(&encoded).unwrap(); assert_eq!(request, decoded); } #[test] fn encode_decode_ping_response() { let id = RequestId(vec![1]); let request = Message::Response(Response { id, body: ResponseBody::Pong { enr_seq: 15, ip: "127.0.0.1".parse().unwrap(), port: 80, }, }); let encoded = request.clone().encode(); let decoded = Message::decode(&encoded).unwrap(); assert_eq!(request, decoded); } #[test] fn encode_decode_find_node_request() { let id = RequestId(vec![1]); let request = Message::Request(Request { id, body: RequestBody::FindNode { distances: vec![12], }, }); let encoded = request.clone().encode(); let decoded = Message::decode(&encoded).unwrap(); assert_eq!(request, decoded); } #[test] fn encode_decode_nodes_response() { let key = CombinedKey::generate_secp256k1(); let enr1 = EnrBuilder::new("v4") .ip("127.0.0.1".parse().unwrap()) .udp(500) .build(&key) .unwrap(); let enr2 = EnrBuilder::new("v4") .ip("10.0.0.1".parse().unwrap()) .tcp4(8080) .build(&key) .unwrap(); let enr3 = EnrBuilder::new("v4") .ip("10.4.5.6".parse().unwrap()) .build(&key) .unwrap(); let enr_list = vec![enr1, enr2, enr3]; let id = RequestId(vec![1]); let request = Message::Response(Response { id, body: ResponseBody::Nodes { total: 1, nodes: enr_list, }, }); let encoded = request.clone().encode(); let decoded = Message::decode(&encoded).unwrap(); assert_eq!(request, decoded); } #[test] fn encode_decode_ticket_request() { let id = RequestId(vec![1]); let request = Message::Request(Request { id, body: RequestBody::Talk { protocol: vec![17u8; 32], request: vec![1, 2, 3], }, }); let encoded = request.clone().encode(); let decoded = Message::decode(&encoded).unwrap(); assert_eq!(request, decoded); } /* * These RPC messages are not in use yet * #[test] fn ref_test_encode_request_ticket() { // reference input let id = 1; let hash_bytes = hex::decode("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") .unwrap(); // expected hex output let expected_output = hex::decode("05e201a0fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") .unwrap(); let mut topic_hash = [0; 32]; topic_hash.copy_from_slice(&hash_bytes); let message = Message::Request(Request { id, body: RequestBody::Ticket { topic: topic_hash }, }); assert_eq!(message.encode(), expected_output); } #[test] fn ref_test_encode_request_register_topic() { // reference input let id = 1; let ticket = hex::decode("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") .unwrap(); // expected hex output let expected_output = hex::decode("07e201a0fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") .unwrap(); let message = Message::Request(Request { id, body: RequestBody::RegisterTopic { ticket }, }); assert_eq!(message.encode(), expected_output); } #[test] fn ref_test_encode_request_topic_query() { // reference input let id = 1; let hash_bytes = hex::decode("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") .unwrap(); // expected hex output let expected_output = hex::decode("09e201a0fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") .unwrap(); let mut topic_hash = [0; 32]; topic_hash.copy_from_slice(&hash_bytes); let message = Message::Request(Request { id, body: RequestBody::TopicQuery { topic: topic_hash }, }); assert_eq!(message.encode(), expected_output); } #[test] fn ref_test_encode_response_register_topic() { // reference input let id = 1; let registered = true; // expected hex output let expected_output = hex::decode("08c20101").unwrap(); let message = Message::Response(Response { id, body: ResponseBody::RegisterTopic { registered }, }); assert_eq!(message.encode(), expected_output); } #[test] fn encode_decode_register_topic_request() { let request = Message::Request(Request { id: 1, body: RequestBody::RegisterTopic { topic: vec![1,2,3], ticket: vec![1, 2, 3, 4, 5], }, }); let encoded = request.clone().encode(); let decoded = Message::decode(encoded).unwrap(); assert_eq!(request, decoded); } #[test] fn encode_decode_register_topic_response() { let request = Message::Response(Response { id: 0, body: ResponseBody::RegisterTopic { registered: true }, }); let encoded = request.clone().encode(); let decoded = Message::decode(encoded).unwrap(); assert_eq!(request, decoded); } #[test] fn encode_decode_topic_query_request() { let request = Message::Request(Request { id: 1, body: RequestBody::TopicQuery { topic: [17u8; 32] }, }); let encoded = request.clone().encode(); let decoded = Message::decode(encoded).unwrap(); assert_eq!(request, decoded); } #[test] fn ref_test_encode_response_ticket() { // reference input let id = 1; let ticket = [0; 32].to_vec(); // all 0's let wait_time = 5; // expected hex output let expected_output = hex::decode( "06e301a0000000000000000000000000000000000000000000000000000000000000000005", ) .unwrap(); let message = Message::Response(Response { id, body: ResponseBody::Ticket { ticket, wait_time }, }); assert_eq!(message.encode(), expected_output); } #[test] fn encode_decode_ticket_response() { let request = Message::Response(Response { id: 0, body: ResponseBody::Ticket { ticket: vec![1, 2, 3, 4, 5], wait_time: 5, }, }); let encoded = request.clone().encode(); let decoded = Message::decode(encoded).unwrap(); assert_eq!(request, decoded); } */ }