nostr-rs-relay

My dev fork of nostr-rs-relay
git clone git://jb55.com/nostr-rs-relay
Log | Files | Refs | README | LICENSE

subscription.rs (17309B)


      1 //! Subscription and filter parsing
      2 use crate::error::Result;
      3 use crate::event::Event;
      4 use serde::de::Unexpected;
      5 use serde::{Deserialize, Deserializer, Serialize};
      6 use serde_json::Value;
      7 use std::collections::HashMap;
      8 use std::collections::HashSet;
      9 
     10 /// Subscription identifier and set of request filters
     11 #[derive(Serialize, PartialEq, Eq, Debug, Clone)]
     12 pub struct Subscription {
     13     pub id: String,
     14     pub filters: Vec<ReqFilter>,
     15 }
     16 
     17 /// Filter for requests
     18 ///
     19 /// Corresponds to client-provided subscription request elements.  Any
     20 /// element can be present if it should be used in filtering, or
     21 /// absent ([`None`]) if it should be ignored.
     22 #[derive(Serialize, PartialEq, Eq, Debug, Clone)]
     23 pub struct ReqFilter {
     24     /// Event hashes
     25     pub ids: Option<Vec<String>>,
     26     /// Event kinds
     27     pub kinds: Option<Vec<u64>>,
     28     /// Events published after this time
     29     pub since: Option<u64>,
     30     /// Events published before this time
     31     pub until: Option<u64>,
     32     /// List of author public keys
     33     pub authors: Option<Vec<String>>,
     34     /// Limit number of results
     35     pub limit: Option<u64>,
     36     /// Set of tags
     37     #[serde(skip)]
     38     pub tags: Option<HashMap<char, HashSet<String>>>,
     39     /// Force no matches due to malformed data
     40     pub force_no_match: bool,
     41 }
     42 
     43 impl<'de> Deserialize<'de> for ReqFilter {
     44     fn deserialize<D>(deserializer: D) -> Result<ReqFilter, D::Error>
     45     where
     46         D: Deserializer<'de>,
     47     {
     48         let received: Value = Deserialize::deserialize(deserializer)?;
     49         let filter = received.as_object().ok_or_else(|| {
     50             serde::de::Error::invalid_type(
     51                 Unexpected::Other("reqfilter is not an object"),
     52                 &"a json object",
     53             )
     54         })?;
     55         let mut rf = ReqFilter {
     56             ids: None,
     57             kinds: None,
     58             since: None,
     59             until: None,
     60             authors: None,
     61             limit: None,
     62             tags: None,
     63             force_no_match: false,
     64         };
     65         let mut ts = None;
     66         // iterate through each key, and assign values that exist
     67         for (key, val) in filter.into_iter() {
     68             // ids
     69             if key == "ids" {
     70                 rf.ids = Deserialize::deserialize(val).ok();
     71             } else if key == "kinds" {
     72                 rf.kinds = Deserialize::deserialize(val).ok();
     73             } else if key == "since" {
     74                 rf.since = Deserialize::deserialize(val).ok();
     75             } else if key == "until" {
     76                 rf.until = Deserialize::deserialize(val).ok();
     77             } else if key == "limit" {
     78                 rf.limit = Deserialize::deserialize(val).ok();
     79             } else if key == "authors" {
     80                 rf.authors = Deserialize::deserialize(val).ok();
     81             } else if key.starts_with('#') && key.len() > 1 && val.is_array() {
     82                 if let Some(tag_search) = tag_search_char_from_filter(key) {
     83                     if ts.is_none() {
     84                         // Initialize the tag if necessary
     85                         ts = Some(HashMap::new());
     86                     }
     87                     if let Some(m) = ts.as_mut() {
     88                         let tag_vals: Option<Vec<String>> = Deserialize::deserialize(val).ok();
     89                         if let Some(v) = tag_vals {
     90                             let hs = HashSet::from_iter(v.into_iter());
     91                             m.insert(tag_search.to_owned(), hs);
     92                         }
     93                     };
     94                 } else {
     95                     // tag search that is multi-character, don't add to subscription
     96                     rf.force_no_match = true;
     97                     continue;
     98                 }
     99             }
    100         }
    101         rf.tags = ts;
    102         Ok(rf)
    103     }
    104 }
    105 
    106 /// Attempt to form a single-char identifier from a tag search filter
    107 fn tag_search_char_from_filter(tagname: &str) -> Option<char> {
    108     let tagname_nohash = &tagname[1..];
    109     // We return the tag character if and only if the tagname consists
    110     // of a single char.
    111     let mut tagnamechars = tagname_nohash.chars();
    112     let firstchar = tagnamechars.next();
    113     match firstchar {
    114         Some(_) => {
    115             // check second char
    116             if tagnamechars.next().is_none() {
    117                 firstchar
    118             } else {
    119                 None
    120             }
    121         }
    122         None => None,
    123     }
    124 }
    125 
    126 impl<'de> Deserialize<'de> for Subscription {
    127     /// Custom deserializer for subscriptions, which have a more
    128     /// complex structure than the other message types.
    129     fn deserialize<D>(deserializer: D) -> Result<Subscription, D::Error>
    130     where
    131         D: Deserializer<'de>,
    132     {
    133         let mut v: Value = Deserialize::deserialize(deserializer)?;
    134         // this shoud be a 3-or-more element array.
    135         // verify the first element is a String, REQ
    136         // get the subscription from the second element.
    137         // convert each of the remaining objects into filters
    138 
    139         // check for array
    140         let va = v
    141             .as_array_mut()
    142             .ok_or_else(|| serde::de::Error::custom("not array"))?;
    143 
    144         // check length
    145         if va.len() < 3 {
    146             return Err(serde::de::Error::custom("not enough fields"));
    147         }
    148         let mut i = va.iter_mut();
    149         // get command ("REQ") and ensure it is a string
    150         let req_cmd_str: serde_json::Value = i.next().unwrap().take();
    151         let req = req_cmd_str
    152             .as_str()
    153             .ok_or_else(|| serde::de::Error::custom("first element of request was not a string"))?;
    154         if req != "REQ" {
    155             return Err(serde::de::Error::custom("missing REQ command"));
    156         }
    157 
    158         // ensure sub id is a string
    159         let sub_id_str: serde_json::Value = i.next().unwrap().take();
    160         let sub_id = sub_id_str
    161             .as_str()
    162             .ok_or_else(|| serde::de::Error::custom("missing subscription id"))?;
    163 
    164         let mut filters = vec![];
    165         for fv in i {
    166             let f: ReqFilter = serde_json::from_value(fv.take())
    167                 .map_err(|_| serde::de::Error::custom("could not parse filter"))?;
    168             // create indexes
    169             filters.push(f);
    170         }
    171         Ok(Subscription {
    172             id: sub_id.to_owned(),
    173             filters,
    174         })
    175     }
    176 }
    177 
    178 impl Subscription {
    179     /// Get a copy of the subscription identifier.
    180     pub fn get_id(&self) -> String {
    181         self.id.clone()
    182     }
    183     /// Determine if this subscription matches a given [`Event`].  Any
    184     /// individual filter match is sufficient.
    185     pub fn interested_in_event(&self, event: &Event) -> bool {
    186         for f in self.filters.iter() {
    187             if f.interested_in_event(event) {
    188                 return true;
    189             }
    190         }
    191         false
    192     }
    193 }
    194 
    195 fn prefix_match(prefixes: &[String], target: &str) -> bool {
    196     for prefix in prefixes {
    197         if target.starts_with(prefix) {
    198             return true;
    199         }
    200     }
    201     // none matched
    202     false
    203 }
    204 
    205 impl ReqFilter {
    206     fn ids_match(&self, event: &Event) -> bool {
    207         self.ids
    208             .as_ref()
    209             .map(|vs| prefix_match(vs, &event.id))
    210             .unwrap_or(true)
    211     }
    212 
    213     fn authors_match(&self, event: &Event) -> bool {
    214         self.authors
    215             .as_ref()
    216             .map(|vs| prefix_match(vs, &event.pubkey))
    217             .unwrap_or(true)
    218     }
    219 
    220     fn delegated_authors_match(&self, event: &Event) -> bool {
    221         if let Some(delegated_pubkey) = &event.delegated_by {
    222             self.authors
    223                 .as_ref()
    224                 .map(|vs| prefix_match(vs, delegated_pubkey))
    225                 .unwrap_or(true)
    226         } else {
    227             false
    228         }
    229     }
    230 
    231     fn tag_match(&self, event: &Event) -> bool {
    232         // get the hashset from the filter.
    233         if let Some(map) = &self.tags {
    234             for (key, val) in map.iter() {
    235                 let tag_match = event.generic_tag_val_intersect(*key, val);
    236                 // if there is no match for this tag, the match fails.
    237                 if !tag_match {
    238                     return false;
    239                 }
    240                 // if there was a match, we move on to the next one.
    241             }
    242         }
    243         // if the tag map is empty, the match succeeds (there was no filter)
    244         true
    245     }
    246 
    247     /// Check if this filter either matches, or does not care about the kind.
    248     fn kind_match(&self, kind: u64) -> bool {
    249         self.kinds
    250             .as_ref()
    251             .map(|ks| ks.contains(&kind))
    252             .unwrap_or(true)
    253     }
    254 
    255     /// Determine if all populated fields in this filter match the provided event.
    256     pub fn interested_in_event(&self, event: &Event) -> bool {
    257         //        self.id.as_ref().map(|v| v == &event.id).unwrap_or(true)
    258         self.ids_match(event)
    259             && self.since.map(|t| event.created_at > t).unwrap_or(true)
    260             && self.until.map(|t| event.created_at < t).unwrap_or(true)
    261             && self.kind_match(event.kind)
    262             && (self.authors_match(event) || self.delegated_authors_match(event))
    263             && self.tag_match(event)
    264             && !self.force_no_match
    265     }
    266 }
    267 
    268 #[cfg(test)]
    269 mod tests {
    270     use super::*;
    271 
    272     #[test]
    273     fn empty_request_parse() -> Result<()> {
    274         let raw_json = "[\"REQ\",\"some-id\",{}]";
    275         let s: Subscription = serde_json::from_str(raw_json)?;
    276         assert_eq!(s.id, "some-id");
    277         assert_eq!(s.filters.len(), 1);
    278         assert_eq!(s.filters.get(0).unwrap().authors, None);
    279         Ok(())
    280     }
    281 
    282     #[test]
    283     fn incorrect_header() {
    284         let raw_json = "[\"REQUEST\",\"some-id\",\"{}\"]";
    285         assert!(serde_json::from_str::<Subscription>(raw_json).is_err());
    286     }
    287 
    288     #[test]
    289     fn req_missing_filters() {
    290         let raw_json = "[\"REQ\",\"some-id\"]";
    291         assert!(serde_json::from_str::<Subscription>(raw_json).is_err());
    292     }
    293 
    294     #[test]
    295     fn legacy_filter() {
    296         // legacy field in filter
    297         let raw_json = "[\"REQ\",\"some-id\",{\"kind\": 3}]";
    298         assert!(serde_json::from_str::<Subscription>(raw_json).is_ok());
    299     }
    300 
    301     #[test]
    302     fn author_filter() -> Result<()> {
    303         let raw_json = r#"["REQ","some-id",{"authors": ["test-author-id"]}]"#;
    304         let s: Subscription = serde_json::from_str(raw_json)?;
    305         assert_eq!(s.id, "some-id");
    306         assert_eq!(s.filters.len(), 1);
    307         let first_filter = s.filters.get(0).unwrap();
    308         assert_eq!(
    309             first_filter.authors,
    310             Some(vec!("test-author-id".to_owned()))
    311         );
    312         Ok(())
    313     }
    314 
    315     #[test]
    316     fn interest_author_prefix_match() -> Result<()> {
    317         // subscription with a filter for ID
    318         let s: Subscription = serde_json::from_str(r#"["REQ","xyz",{"authors": ["abc"]}]"#)?;
    319         let e = Event {
    320             id: "foo".to_owned(),
    321             pubkey: "abcd".to_owned(),
    322             delegated_by: None,
    323             created_at: 0,
    324             kind: 0,
    325             tags: Vec::new(),
    326             content: "".to_owned(),
    327             sig: "".to_owned(),
    328             tagidx: None,
    329         };
    330         assert!(s.interested_in_event(&e));
    331         Ok(())
    332     }
    333 
    334     #[test]
    335     fn interest_id_prefix_match() -> Result<()> {
    336         // subscription with a filter for ID
    337         let s: Subscription = serde_json::from_str(r#"["REQ","xyz",{"ids": ["abc"]}]"#)?;
    338         let e = Event {
    339             id: "abcd".to_owned(),
    340             pubkey: "".to_owned(),
    341             delegated_by: None,
    342             created_at: 0,
    343             kind: 0,
    344             tags: Vec::new(),
    345             content: "".to_owned(),
    346             sig: "".to_owned(),
    347             tagidx: None,
    348         };
    349         assert!(s.interested_in_event(&e));
    350         Ok(())
    351     }
    352 
    353     #[test]
    354     fn interest_id_nomatch() -> Result<()> {
    355         // subscription with a filter for ID
    356         let s: Subscription = serde_json::from_str(r#"["REQ","xyz",{"ids": ["xyz"]}]"#)?;
    357         let e = Event {
    358             id: "abcde".to_owned(),
    359             pubkey: "".to_owned(),
    360             delegated_by: None,
    361             created_at: 0,
    362             kind: 0,
    363             tags: Vec::new(),
    364             content: "".to_owned(),
    365             sig: "".to_owned(),
    366             tagidx: None,
    367         };
    368         assert!(!s.interested_in_event(&e));
    369         Ok(())
    370     }
    371 
    372     #[test]
    373     fn interest_until() -> Result<()> {
    374         // subscription with a filter for ID and time
    375         let s: Subscription =
    376             serde_json::from_str(r#"["REQ","xyz",{"ids": ["abc"], "until": 1000}]"#)?;
    377         let e = Event {
    378             id: "abc".to_owned(),
    379             pubkey: "".to_owned(),
    380             delegated_by: None,
    381             created_at: 50,
    382             kind: 0,
    383             tags: Vec::new(),
    384             content: "".to_owned(),
    385             sig: "".to_owned(),
    386             tagidx: None,
    387         };
    388         assert!(s.interested_in_event(&e));
    389         Ok(())
    390     }
    391 
    392     #[test]
    393     fn interest_range() -> Result<()> {
    394         // subscription with a filter for ID and time
    395         let s_in: Subscription =
    396             serde_json::from_str(r#"["REQ","xyz",{"ids": ["abc"], "since": 100, "until": 200}]"#)?;
    397         let s_before: Subscription =
    398             serde_json::from_str(r#"["REQ","xyz",{"ids": ["abc"], "since": 100, "until": 140}]"#)?;
    399         let s_after: Subscription =
    400             serde_json::from_str(r#"["REQ","xyz",{"ids": ["abc"], "since": 160, "until": 200}]"#)?;
    401         let e = Event {
    402             id: "abc".to_owned(),
    403             pubkey: "".to_owned(),
    404             delegated_by: None,
    405             created_at: 150,
    406             kind: 0,
    407             tags: Vec::new(),
    408             content: "".to_owned(),
    409             sig: "".to_owned(),
    410             tagidx: None,
    411         };
    412         assert!(s_in.interested_in_event(&e));
    413         assert!(!s_before.interested_in_event(&e));
    414         assert!(!s_after.interested_in_event(&e));
    415         Ok(())
    416     }
    417 
    418     #[test]
    419     fn interest_time_and_id() -> Result<()> {
    420         // subscription with a filter for ID and time
    421         let s: Subscription =
    422             serde_json::from_str(r#"["REQ","xyz",{"ids": ["abc"], "since": 1000}]"#)?;
    423         let e = Event {
    424             id: "abc".to_owned(),
    425             pubkey: "".to_owned(),
    426             delegated_by: None,
    427             created_at: 50,
    428             kind: 0,
    429             tags: Vec::new(),
    430             content: "".to_owned(),
    431             sig: "".to_owned(),
    432             tagidx: None,
    433         };
    434         assert!(!s.interested_in_event(&e));
    435         Ok(())
    436     }
    437 
    438     #[test]
    439     fn interest_time_and_id2() -> Result<()> {
    440         // subscription with a filter for ID and time
    441         let s: Subscription = serde_json::from_str(r#"["REQ","xyz",{"id":"abc", "since": 1000}]"#)?;
    442         let e = Event {
    443             id: "abc".to_owned(),
    444             pubkey: "".to_owned(),
    445             delegated_by: None,
    446             created_at: 1001,
    447             kind: 0,
    448             tags: Vec::new(),
    449             content: "".to_owned(),
    450             sig: "".to_owned(),
    451             tagidx: None,
    452         };
    453         assert!(s.interested_in_event(&e));
    454         Ok(())
    455     }
    456 
    457     #[test]
    458     fn interest_id() -> Result<()> {
    459         // subscription with a filter for ID
    460         let s: Subscription = serde_json::from_str(r#"["REQ","xyz",{"id":"abc"}]"#)?;
    461         let e = Event {
    462             id: "abc".to_owned(),
    463             pubkey: "".to_owned(),
    464             delegated_by: None,
    465             created_at: 0,
    466             kind: 0,
    467             tags: Vec::new(),
    468             content: "".to_owned(),
    469             sig: "".to_owned(),
    470             tagidx: None,
    471         };
    472         assert!(s.interested_in_event(&e));
    473         Ok(())
    474     }
    475 
    476     #[test]
    477     fn authors_single() -> Result<()> {
    478         // subscription with a filter for ID
    479         let s: Subscription = serde_json::from_str(r#"["REQ","xyz",{"authors":["abc"]}]"#)?;
    480         let e = Event {
    481             id: "123".to_owned(),
    482             pubkey: "abc".to_owned(),
    483             delegated_by: None,
    484             created_at: 0,
    485             kind: 0,
    486             tags: Vec::new(),
    487             content: "".to_owned(),
    488             sig: "".to_owned(),
    489             tagidx: None,
    490         };
    491         assert!(s.interested_in_event(&e));
    492         Ok(())
    493     }
    494 
    495     #[test]
    496     fn authors_multi_pubkey() -> Result<()> {
    497         // check for any of a set of authors, against the pubkey
    498         let s: Subscription = serde_json::from_str(r#"["REQ","xyz",{"authors":["abc", "bcd"]}]"#)?;
    499         let e = Event {
    500             id: "123".to_owned(),
    501             pubkey: "bcd".to_owned(),
    502             delegated_by: None,
    503             created_at: 0,
    504             kind: 0,
    505             tags: Vec::new(),
    506             content: "".to_owned(),
    507             sig: "".to_owned(),
    508             tagidx: None,
    509         };
    510         assert!(s.interested_in_event(&e));
    511         Ok(())
    512     }
    513 
    514     #[test]
    515     fn authors_multi_no_match() -> Result<()> {
    516         // check for any of a set of authors, against the pubkey
    517         let s: Subscription = serde_json::from_str(r#"["REQ","xyz",{"authors":["abc", "bcd"]}]"#)?;
    518         let e = Event {
    519             id: "123".to_owned(),
    520             pubkey: "xyz".to_owned(),
    521             delegated_by: None,
    522             created_at: 0,
    523             kind: 0,
    524             tags: Vec::new(),
    525             content: "".to_owned(),
    526             sig: "".to_owned(),
    527             tagidx: None,
    528         };
    529         assert!(!s.interested_in_event(&e));
    530         Ok(())
    531     }
    532 }