notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

message.rs (9328B)


      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         // Notice
     82         // Relay response format: ["NOTICE", <message>]
     83         if msg.len() >= 12 && &msg[0..=9] == "[\"NOTICE\"," {
     84             // TODO: there could be more than one space, whatever
     85             let start = if msg.as_bytes().get(10).copied() == Some(b' ') {
     86                 12
     87             } else {
     88                 11
     89             };
     90             let end = msg.len() - 2;
     91             return Ok(Self::notice(&msg[start..end]));
     92         }
     93 
     94         // Event
     95         // Relay response format: ["EVENT", <subscription id>, <event JSON>]
     96         if &msg[0..=7] == "[\"EVENT\"" {
     97             let mut start = 9;
     98             while let Some(&b' ') = msg.as_bytes().get(start) {
     99                 start += 1; // Move past optional spaces
    100             }
    101             if let Some(comma_index) = msg[start..].find(',') {
    102                 let subid_end = start + comma_index;
    103                 let subid = &msg[start..subid_end].trim().trim_matches('"');
    104                 return Ok(Self::event(msg, subid));
    105             } else {
    106                 return Ok(Self::event(msg, "fixme"));
    107             }
    108         }
    109 
    110         // EOSE (NIP-15)
    111         // Relay response format: ["EOSE", <subscription_id>]
    112         if &msg[0..=7] == "[\"EOSE\"," {
    113             let start = if msg.as_bytes().get(8).copied() == Some(b' ') {
    114                 10
    115             } else {
    116                 9
    117             };
    118             let end = msg.len() - 2;
    119             return Ok(Self::eose(&msg[start..end]));
    120         }
    121 
    122         // OK (NIP-20)
    123         // Relay response format: ["OK",<event_id>, <true|false>, <message>]
    124         if &msg[0..=5] == "[\"OK\"," && msg.len() >= 78 {
    125             // TODO: fix this
    126             let event_id = &msg[7..71];
    127             let booly = &msg[73..77];
    128             let status: bool = if booly == "true" {
    129                 true
    130             } else if booly == "false" {
    131                 false
    132             } else {
    133                 return Err(Error::DecodeFailed);
    134             };
    135 
    136             return Ok(Self::ok(event_id, status, "fixme"));
    137         }
    138 
    139         Err(Error::DecodeFailed)
    140     }
    141 }
    142 
    143 #[cfg(test)]
    144 mod tests {
    145     use super::*;
    146 
    147     #[test]
    148     fn test_handle_valid_notice() -> Result<()> {
    149         let valid_notice_msg = r#"["NOTICE","Invalid event format!"]"#;
    150         let handled_valid_notice_msg = RelayMessage::notice("Invalid event format!");
    151 
    152         assert_eq!(
    153             RelayMessage::from_json(valid_notice_msg)?,
    154             handled_valid_notice_msg
    155         );
    156 
    157         Ok(())
    158     }
    159     #[test]
    160     fn test_handle_invalid_notice() {
    161         //Missing content
    162         let invalid_notice_msg = r#"["NOTICE"]"#;
    163         //The content is not string
    164         let invalid_notice_msg_content = r#"["NOTICE": 404]"#;
    165 
    166         assert!(matches!(
    167             RelayMessage::from_json(invalid_notice_msg).unwrap_err(),
    168             Error::DecodeFailed
    169         ));
    170         assert!(matches!(
    171             RelayMessage::from_json(invalid_notice_msg_content).unwrap_err(),
    172             Error::DecodeFailed
    173         ));
    174     }
    175 
    176     /*
    177     #[test]
    178     fn test_handle_valid_event() -> Result<()> {
    179         use tracing::debug;
    180 
    181         let valid_event_msg = r#"["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe","created_at":1612809991,"kind":1,"tags":[],"content":"test","sig":"273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"}]"#;
    182 
    183         let id = "70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5";
    184         let pubkey = "379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe";
    185         let created_at = 1612809991;
    186         let kind = 1;
    187         let tags = vec![];
    188         let content = "test";
    189         let sig = "273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502";
    190 
    191         let handled_event = Note::new_dummy(id, pubkey, created_at, kind, tags, content, sig).expect("ev");
    192         debug!("event {:?}", handled_event);
    193 
    194         let msg = RelayMessage::from_json(valid_event_msg).expect("valid json");
    195         debug!("msg {:?}", msg);
    196 
    197         let note_json = serde_json::to_string(&handled_event).expect("json ev");
    198 
    199         assert_eq!(
    200             msg,
    201             RelayMessage::event(&note_json, "random_string")
    202         );
    203 
    204         Ok(())
    205     }
    206 
    207     #[test]
    208     fn test_handle_invalid_event() {
    209         //Mising Event field
    210         let invalid_event_msg = r#"["EVENT","random_string"]"#;
    211         //Event JSON with incomplete content
    212         let invalid_event_msg_content = r#"["EVENT","random_string",{"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"}]"#;
    213 
    214         assert!(matches!(
    215             RelayMessage::from_json(invalid_event_msg).unwrap_err(),
    216             Error::DecodeFailed
    217         ));
    218 
    219         assert!(matches!(
    220             RelayMessage::from_json(invalid_event_msg_content).unwrap_err(),
    221             Error::DecodeFailed
    222         ));
    223     }
    224     */
    225 
    226     #[test]
    227     fn test_handle_valid_eose() -> Result<()> {
    228         let valid_eose_msg = r#"["EOSE","random-subscription-id"]"#;
    229         let handled_valid_eose_msg = RelayMessage::eose("random-subscription-id");
    230 
    231         assert_eq!(
    232             RelayMessage::from_json(valid_eose_msg)?,
    233             handled_valid_eose_msg
    234         );
    235 
    236         Ok(())
    237     }
    238 
    239     // TODO: fix these tests
    240     /*
    241     #[test]
    242     fn test_handle_invalid_eose() {
    243         // Missing subscription ID
    244         assert!(matches!(
    245             RelayMessage::from_json(r#"["EOSE"]"#).unwrap_err(),
    246             Error::DecodeFailed
    247         ));
    248 
    249         // The subscription ID is not string
    250         assert!(matches!(
    251             RelayMessage::from_json(r#"["EOSE",404]"#).unwrap_err(),
    252             Error::DecodeFailed
    253         ));
    254     }
    255 
    256     #[test]
    257     fn test_handle_valid_ok() -> Result<()> {
    258         let valid_ok_msg = r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",true,"pow: difficulty 25>=24"]"#;
    259         let handled_valid_ok_msg = RelayMessage::ok(
    260             "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",
    261             true,
    262             "pow: difficulty 25>=24".into(),
    263         );
    264 
    265         assert_eq!(RelayMessage::from_json(valid_ok_msg)?, handled_valid_ok_msg);
    266 
    267         Ok(())
    268     }
    269     */
    270 
    271     #[test]
    272     fn test_handle_invalid_ok() {
    273         // Missing params
    274         assert!(matches!(
    275             RelayMessage::from_json(
    276                 r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30"]"#
    277             )
    278             .unwrap_err(),
    279             Error::DecodeFailed
    280         ));
    281 
    282         // Invalid status
    283         assert!(
    284             matches!(RelayMessage::from_json(r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,""]"#).unwrap_err(),
    285             Error::DecodeFailed)
    286         );
    287 
    288         // Invalid message
    289         assert!(
    290             matches!(RelayMessage::from_json(r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,404]"#).unwrap_err(),
    291             Error::DecodeFailed)
    292         );
    293     }
    294 }