message.rs (12359B)
1 use crate::{Error, Result}; 2 use ewebsock::{WsEvent, WsMessage}; 3 4 #[derive(Debug, Eq, PartialEq)] 5 pub struct CommandResult<'a> { 6 event_id: &'a str, 7 status: bool, 8 message: &'a str, 9 } 10 11 pub fn calculate_command_result_size(result: &CommandResult) -> usize { 12 std::mem::size_of_val(result) + result.event_id.len() + result.message.len() 13 } 14 15 #[derive(Debug, Eq, PartialEq)] 16 pub enum RelayMessage<'a> { 17 OK(CommandResult<'a>), 18 Eose(&'a str), 19 Event(&'a str, &'a str), 20 Notice(&'a str), 21 } 22 23 #[derive(Debug)] 24 pub enum RelayEvent<'a> { 25 Opened, 26 Closed, 27 Other(&'a WsMessage), 28 Error(Error), 29 Message(RelayMessage<'a>), 30 } 31 32 impl<'a> From<&'a WsEvent> for RelayEvent<'a> { 33 fn from(event: &'a WsEvent) -> RelayEvent<'a> { 34 match event { 35 WsEvent::Opened => RelayEvent::Opened, 36 WsEvent::Closed => RelayEvent::Closed, 37 WsEvent::Message(ref ws_msg) => ws_msg.into(), 38 WsEvent::Error(s) => RelayEvent::Error(Error::Generic(s.to_owned())), 39 } 40 } 41 } 42 43 impl<'a> From<&'a WsMessage> for RelayEvent<'a> { 44 fn from(wsmsg: &'a WsMessage) -> RelayEvent<'a> { 45 match wsmsg { 46 WsMessage::Text(s) => match RelayMessage::from_json(s).map(RelayEvent::Message) { 47 Ok(msg) => msg, 48 Err(err) => RelayEvent::Error(err), 49 }, 50 wsmsg => RelayEvent::Other(wsmsg), 51 } 52 } 53 } 54 55 impl<'a> RelayMessage<'a> { 56 pub fn eose(subid: &'a str) -> Self { 57 RelayMessage::Eose(subid) 58 } 59 60 pub fn notice(msg: &'a str) -> Self { 61 RelayMessage::Notice(msg) 62 } 63 64 pub fn ok(event_id: &'a str, status: bool, message: &'a str) -> Self { 65 RelayMessage::OK(CommandResult { 66 event_id, 67 status, 68 message, 69 }) 70 } 71 72 pub fn event(ev: &'a str, sub_id: &'a str) -> Self { 73 RelayMessage::Event(sub_id, ev) 74 } 75 76 pub fn from_json(msg: &'a str) -> Result<RelayMessage<'a>> { 77 if msg.is_empty() { 78 return Err(Error::Empty); 79 } 80 81 // make sure we can inspect the begning of the message below ... 82 if msg.len() < 12 { 83 return Err(Error::DecodeFailed("message too short".into())); 84 } 85 86 // Notice 87 // Relay response format: ["NOTICE", <message>] 88 if msg.len() >= 12 && &msg[0..=9] == "[\"NOTICE\"," { 89 // TODO: there could be more than one space, whatever 90 let start = if msg.as_bytes().get(10).copied() == Some(b' ') { 91 12 92 } else { 93 11 94 }; 95 let end = msg.len() - 2; 96 return Ok(Self::notice(&msg[start..end])); 97 } 98 99 // Event 100 // Relay response format: ["EVENT", <subscription id>, <event JSON>] 101 if &msg[0..=7] == "[\"EVENT\"" { 102 let mut start = 9; 103 while let Some(&b' ') = msg.as_bytes().get(start) { 104 start += 1; // Move past optional spaces 105 } 106 if let Some(comma_index) = msg[start..].find(',') { 107 let subid_end = start + comma_index; 108 let subid = &msg[start..subid_end].trim().trim_matches('"'); 109 return Ok(Self::event(msg, subid)); 110 } else { 111 return Err(Error::DecodeFailed("Invalid EVENT format".into())); 112 } 113 } 114 115 // EOSE (NIP-15) 116 // Relay response format: ["EOSE", <subscription_id>] 117 if &msg[0..=7] == "[\"EOSE\"," { 118 let start = if msg.as_bytes().get(8).copied() == Some(b' ') { 119 10 // Skip space after the comma 120 } else { 121 9 // Start immediately after the comma 122 }; 123 124 // Use rfind to locate the last quote 125 if let Some(end_bracket_index) = msg.rfind(']') { 126 let end = end_bracket_index - 1; // Account for space before bracket 127 if start < end { 128 // Trim subscription id and remove extra spaces and quotes 129 let subid = &msg[start..end].trim().trim_matches('"').trim(); 130 return Ok(RelayMessage::eose(subid)); 131 } 132 } 133 return Err(Error::DecodeFailed( 134 "Invalid subscription ID or format".into(), 135 )); 136 } 137 138 // OK (NIP-20) 139 // Relay response format: ["OK",<event_id>, <true|false>, <message>] 140 if &msg[0..=5] == "[\"OK\"," && msg.len() >= 78 { 141 let event_id = &msg[7..71]; 142 let booly = &msg[73..77]; 143 let status: bool = if booly == "true" { 144 true 145 } else if booly == "false" { 146 false 147 } else { 148 return Err(Error::DecodeFailed("bad boolean value".into())); 149 }; 150 let message_start = msg.rfind(',').unwrap() + 1; 151 let message = &msg[message_start..msg.len() - 2].trim().trim_matches('"'); 152 return Ok(Self::ok(event_id, status, message)); 153 } 154 155 Err(Error::DecodeFailed(format!( 156 "unrecognized message type: '{msg}'" 157 ))) 158 } 159 } 160 161 #[cfg(test)] 162 mod tests { 163 use super::*; 164 165 #[test] 166 fn test_handle_various_messages() -> Result<()> { 167 let tests = vec![ 168 // Valid cases 169 ( 170 // shortest valid message 171 r#"["EOSE","x"]"#, 172 Ok(RelayMessage::eose("x")), 173 ), 174 ( 175 // also very short 176 r#"["NOTICE",""]"#, 177 Ok(RelayMessage::notice("")), 178 ), 179 ( 180 r#"["NOTICE","Invalid event format!"]"#, 181 Ok(RelayMessage::notice("Invalid event format!")), 182 ), 183 ( 184 r#"["EVENT", "random_string", {"id":"example","content":"test"}]"#, 185 Ok(RelayMessage::event( 186 r#"["EVENT", "random_string", {"id":"example","content":"test"}]"#, 187 "random_string", 188 )), 189 ), 190 ( 191 r#"["EOSE","random-subscription-id"]"#, 192 Ok(RelayMessage::eose("random-subscription-id")), 193 ), 194 ( 195 r#"["EOSE", "random-subscription-id"]"#, 196 Ok(RelayMessage::eose("random-subscription-id")), 197 ), 198 ( 199 r#"["EOSE", "random-subscription-id" ]"#, 200 Ok(RelayMessage::eose("random-subscription-id")), 201 ), 202 ( 203 r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",true,"pow: difficulty 25>=24"]"#, 204 Ok(RelayMessage::ok( 205 "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", 206 true, 207 "pow: difficulty 25>=24", 208 )), 209 ), 210 // Invalid cases 211 ( 212 r#"["EVENT","random_string"]"#, 213 Err(Error::DecodeFailed("Invalid EVENT format".into())), 214 ), 215 ( 216 r#"["EOSE"]"#, 217 Err(Error::DecodeFailed("message too short".into())), 218 ), 219 ( 220 r#"["NOTICE"]"#, 221 Err(Error::DecodeFailed("message too short".into())), 222 ), 223 ( 224 r#"["NOTICE": 404]"#, 225 Err(Error::DecodeFailed("unrecognized message type: '[\"NOTICE\": 404]'".into())), 226 ), 227 ( 228 r#"["OK","event_id"]"#, 229 Err(Error::DecodeFailed("unrecognized message type: '[\"OK\",\"event_id\"]'".into())), 230 ), 231 ( 232 r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30"]"#, 233 Err(Error::DecodeFailed("unrecognized message type: '[\"OK\",\"b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30\"]'".into())), 234 ), 235 ( 236 r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,""]"#, 237 Err(Error::DecodeFailed("bad boolean value".into())), 238 ), 239 ( 240 r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,404]"#, 241 Err(Error::DecodeFailed("bad boolean value".into())), 242 ), 243 ]; 244 245 for (input, expected) in tests { 246 match expected { 247 Ok(expected_msg) => { 248 let result = RelayMessage::from_json(input); 249 assert_eq!( 250 result?, expected_msg, 251 "Expected {:?} for input: {}", 252 expected_msg, input 253 ); 254 } 255 Err(expected_err) => { 256 let result = RelayMessage::from_json(input); 257 assert!( 258 matches!(result, Err(ref e) if *e.to_string() == expected_err.to_string()), 259 "Expected error {:?} for input: {}, but got: {:?}", 260 expected_err, 261 input, 262 result 263 ); 264 } 265 } 266 } 267 Ok(()) 268 } 269 270 /* 271 #[test] 272 fn test_handle_valid_event() -> Result<()> { 273 use tracing::debug; 274 275 let valid_event_msg = r#"["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe","created_at":1612809991,"kind":1,"tags":[],"content":"test","sig":"273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"}]"#; 276 277 let id = "70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5"; 278 let pubkey = "379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"; 279 let created_at = 1612809991; 280 let kind = 1; 281 let tags = vec![]; 282 let content = "test"; 283 let sig = "273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"; 284 285 let handled_event = Note::new_dummy(id, pubkey, created_at, kind, tags, content, sig).expect("ev"); 286 debug!("event {:?}", handled_event); 287 288 let msg = RelayMessage::from_json(valid_event_msg).expect("valid json"); 289 debug!("msg {:?}", msg); 290 291 let note_json = serde_json::to_string(&handled_event).expect("json ev"); 292 293 assert_eq!( 294 msg, 295 RelayMessage::event(¬e_json, "random_string") 296 ); 297 298 Ok(()) 299 } 300 301 #[test] 302 fn test_handle_invalid_event() { 303 //Mising Event field 304 let invalid_event_msg = r#"["EVENT","random_string"]"#; 305 //Event JSON with incomplete content 306 let invalid_event_msg_content = r#"["EVENT","random_string",{"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"}]"#; 307 308 assert!(matches!( 309 RelayMessage::from_json(invalid_event_msg).unwrap_err(), 310 Error::DecodeFailed 311 )); 312 313 assert!(matches!( 314 RelayMessage::from_json(invalid_event_msg_content).unwrap_err(), 315 Error::DecodeFailed 316 )); 317 } 318 */ 319 320 // TODO: fix these tests 321 /* 322 #[test] 323 fn test_handle_invalid_eose() { 324 // Missing subscription ID 325 assert!(matches!( 326 RelayMessage::from_json(r#"["EOSE"]"#).unwrap_err(), 327 Error::DecodeFailed 328 )); 329 330 // The subscription ID is not string 331 assert!(matches!( 332 RelayMessage::from_json(r#"["EOSE",404]"#).unwrap_err(), 333 Error::DecodeFailed 334 )); 335 } 336 337 #[test] 338 fn test_handle_valid_ok() -> Result<()> { 339 let valid_ok_msg = r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",true,"pow: difficulty 25>=24"]"#; 340 let handled_valid_ok_msg = RelayMessage::ok( 341 "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", 342 true, 343 "pow: difficulty 25>=24".into(), 344 ); 345 346 assert_eq!(RelayMessage::from_json(valid_ok_msg)?, handled_valid_ok_msg); 347 348 Ok(()) 349 } 350 */ 351 }