tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

pocket_conversion.rs (11481B)


      1 #![forbid(unsafe_code)]
      2 
      3 use crate::errors::BaseRelayError;
      4 #[cfg(test)]
      5 use std::str;
      6 use tangle_protocol::EventId;
      7 #[cfg(test)]
      8 use tangle_protocol::{
      9     Event, Filter, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent,
     10 };
     11 #[cfg(test)]
     12 use tangle_store_pocket::PocketEvent;
     13 use tangle_store_pocket::PocketEventId;
     14 #[cfg(test)]
     15 use tangle_store_pocket::{
     16     PocketKind, PocketOwnedEvent, PocketOwnedFilter, PocketOwnedTags, PocketPubkey, PocketSig,
     17     PocketTags, PocketTime,
     18 };
     19 
     20 #[cfg(test)]
     21 pub(crate) fn tangle_event_to_pocket(event: &Event) -> Result<PocketOwnedEvent, BaseRelayError> {
     22     let tags = tangle_tags_to_pocket(event.unsigned().tags())?;
     23     ensure_event_size(tags.as_bytes().len(), event.unsigned().content().len())?;
     24     PocketOwnedEvent::new(
     25         pocket_event_id(event.id())?,
     26         tangle_kind_to_pocket(event.unsigned().kind())?,
     27         pocket_pubkey(event.unsigned().pubkey())?,
     28         pocket_sig(event.sig())?,
     29         &tags,
     30         PocketTime::from_u64(event.unsigned().created_at().as_u64()),
     31         event.unsigned().content().as_bytes(),
     32     )
     33     .map_err(|error| BaseRelayError::error(error.to_string()))
     34 }
     35 
     36 #[cfg(test)]
     37 pub(crate) fn tangle_filter_to_pocket(
     38     filter: &Filter,
     39 ) -> Result<PocketOwnedFilter, BaseRelayError> {
     40     let ids = filter
     41         .ids()
     42         .iter()
     43         .map(pocket_event_id)
     44         .collect::<Result<Vec<_>, _>>()?;
     45     let authors = filter
     46         .authors()
     47         .iter()
     48         .map(pocket_pubkey)
     49         .collect::<Result<Vec<_>, _>>()?;
     50     let kinds = filter
     51         .kinds()
     52         .iter()
     53         .copied()
     54         .map(tangle_kind_to_pocket)
     55         .collect::<Result<Vec<_>, _>>()?;
     56     let tag_parts = filter
     57         .tag_filters()
     58         .iter()
     59         .map(|(name, values)| {
     60             core::iter::once(name.as_str().to_owned())
     61                 .chain(values.iter().map(|value| value.as_str().to_owned()))
     62                 .collect::<Vec<_>>()
     63         })
     64         .collect::<Vec<_>>();
     65     ensure_filter_array_len("ids", ids.len())?;
     66     ensure_filter_array_len("authors", authors.len())?;
     67     ensure_filter_array_len("kinds", kinds.len())?;
     68     ensure_tag_size(PocketTags::output_size_needed(&tag_parts))?;
     69     let tags = PocketOwnedTags::new(&tag_parts)
     70         .map_err(|error| BaseRelayError::error(error.to_string()))?;
     71     let limit = filter
     72         .limit()
     73         .map(|limit| {
     74             u32::try_from(limit)
     75                 .map_err(|_| BaseRelayError::invalid(format!("filter limit {limit} exceeds u32")))
     76         })
     77         .transpose()?;
     78     ensure_filter_size(&ids, &authors, &kinds, &tags)?;
     79     PocketOwnedFilter::new(
     80         &ids,
     81         &authors,
     82         &kinds,
     83         &tags,
     84         filter
     85             .since()
     86             .map(|since| PocketTime::from_u64(since.as_u64())),
     87         filter
     88             .until()
     89             .map(|until| PocketTime::from_u64(until.as_u64())),
     90         limit,
     91     )
     92     .map_err(|error| BaseRelayError::error(error.to_string()))
     93 }
     94 
     95 #[cfg(test)]
     96 pub(crate) fn pocket_event_to_tangle(event: &PocketEvent) -> Result<Event, BaseRelayError> {
     97     let tags = event
     98         .tags()
     99         .map_err(|error| BaseRelayError::error(error.to_string()))?
    100         .iter()
    101         .map(|tag| {
    102             tag.map(|value| {
    103                 str::from_utf8(value)
    104                     .map(str::to_owned)
    105                     .map_err(|error| BaseRelayError::error(error.to_string()))
    106             })
    107             .collect::<Result<Vec<_>, _>>()
    108             .and_then(|values| Tag::new(values).map_err(BaseRelayError::error))
    109         })
    110         .collect::<Result<Vec<_>, _>>()?;
    111     let content = str::from_utf8(event.content())
    112         .map_err(|error| BaseRelayError::error(error.to_string()))?;
    113     Ok(Event::new(
    114         EventId::new(&event.id().as_hex_string()).map_err(BaseRelayError::error)?,
    115         UnsignedEvent::new(
    116             PublicKeyHex::new(&event.pubkey().as_hex_string()).map_err(BaseRelayError::error)?,
    117             UnixTimestamp::new(event.created_at().as_u64()),
    118             Kind::new(u64::from(event.kind().as_u16())).map_err(BaseRelayError::error)?,
    119             tags,
    120             content,
    121         ),
    122         SignatureHex::new(&event.sig().to_string()).map_err(BaseRelayError::error)?,
    123     ))
    124 }
    125 
    126 pub(crate) fn pocket_event_id(event_id: &EventId) -> Result<PocketEventId, BaseRelayError> {
    127     PocketEventId::read_hex(event_id.as_str().as_bytes())
    128         .map_err(|error| BaseRelayError::error(error.to_string()))
    129 }
    130 
    131 #[cfg(test)]
    132 pub(crate) fn pocket_pubkey(pubkey: &PublicKeyHex) -> Result<PocketPubkey, BaseRelayError> {
    133     PocketPubkey::read_hex(pubkey.as_str().as_bytes())
    134         .map_err(|error| BaseRelayError::error(error.to_string()))
    135 }
    136 
    137 #[cfg(test)]
    138 fn pocket_sig(sig: &SignatureHex) -> Result<PocketSig, BaseRelayError> {
    139     PocketSig::read_hex(sig.as_str().as_bytes())
    140         .map_err(|error| BaseRelayError::error(error.to_string()))
    141 }
    142 
    143 #[cfg(test)]
    144 fn tangle_kind_to_pocket(kind: Kind) -> Result<PocketKind, BaseRelayError> {
    145     u16::try_from(kind.as_u32())
    146         .map(PocketKind::from_u16)
    147         .map_err(|_| {
    148             BaseRelayError::invalid(format!(
    149                 "event kind {} exceeds Pocket kind range",
    150                 kind.as_u32()
    151             ))
    152         })
    153 }
    154 
    155 #[cfg(test)]
    156 fn tangle_tags_to_pocket(tags: &[Tag]) -> Result<PocketOwnedTags, BaseRelayError> {
    157     let parts = tags
    158         .iter()
    159         .map(|tag| tag.values().iter().map(String::as_str).collect::<Vec<_>>())
    160         .collect::<Vec<_>>();
    161     ensure_tag_size(PocketTags::output_size_needed(&parts))?;
    162     PocketOwnedTags::new(&parts).map_err(|error| BaseRelayError::error(error.to_string()))
    163 }
    164 
    165 #[cfg(test)]
    166 fn ensure_tag_size(size: usize) -> Result<(), BaseRelayError> {
    167     if size > usize::from(u16::MAX) {
    168         return Err(BaseRelayError::invalid(format!(
    169             "tag section size {size} exceeds Pocket range"
    170         )));
    171     }
    172     Ok(())
    173 }
    174 
    175 #[cfg(test)]
    176 fn ensure_filter_array_len(name: &str, len: usize) -> Result<(), BaseRelayError> {
    177     if len > usize::from(u16::MAX) {
    178         return Err(BaseRelayError::invalid(format!(
    179             "filter {name} count {len} exceeds Pocket range"
    180         )));
    181     }
    182     Ok(())
    183 }
    184 
    185 #[cfg(test)]
    186 fn ensure_filter_size(
    187     ids: &[PocketEventId],
    188     authors: &[PocketPubkey],
    189     kinds: &[PocketKind],
    190     tags: &PocketTags,
    191 ) -> Result<(), BaseRelayError> {
    192     let size = tangle_store_pocket::PocketFilter::output_size_needed(ids, authors, kinds, tags);
    193     if size > usize::try_from(u32::MAX).expect("u32 max fits usize") {
    194         return Err(BaseRelayError::invalid(format!(
    195             "filter size {size} exceeds Pocket range"
    196         )));
    197     }
    198     Ok(())
    199 }
    200 
    201 #[cfg(test)]
    202 fn ensure_event_size(tags_len: usize, content_len: usize) -> Result<(), BaseRelayError> {
    203     if content_len > usize::try_from(u32::MAX).expect("u32 max fits usize") {
    204         return Err(BaseRelayError::invalid(format!(
    205             "event content size {content_len} exceeds Pocket range"
    206         )));
    207     }
    208     let size = PocketEvent::output_size_needed(tags_len, content_len);
    209     if size > usize::try_from(u32::MAX).expect("u32 max fits usize") {
    210         return Err(BaseRelayError::invalid(format!(
    211             "event size {size} exceeds Pocket range"
    212         )));
    213     }
    214     Ok(())
    215 }
    216 
    217 #[cfg(test)]
    218 mod tests {
    219     use super::{
    220         pocket_event_id, pocket_event_to_tangle, tangle_event_to_pocket, tangle_filter_to_pocket,
    221     };
    222     use tangle_protocol::{
    223         Event, EventId, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent,
    224         filter_from_value,
    225     };
    226     use tangle_test_support::{FixtureKey, tangle_v2_event};
    227 
    228     #[test]
    229     fn pocket_event_conversion_round_trips_signed_events() {
    230         let event = tangle_v2_event(FixtureKey::Member, 1_714_124_433, 1, Vec::new(), "hello")
    231             .expect("event");
    232         let pocket = tangle_event_to_pocket(&event).expect("pocket");
    233         let converted = pocket_event_to_tangle(&pocket).expect("converted");
    234 
    235         assert_eq!(converted, event);
    236         pocket_event_id(event.id()).expect("event id");
    237     }
    238 
    239     #[test]
    240     fn pocket_event_conversion_preserves_tags_and_utf8_content_without_json_bridge() {
    241         let event = Event::new(
    242             EventId::new(&"1".repeat(64)).expect("id"),
    243             UnsignedEvent::new(
    244                 PublicKeyHex::new(&"2".repeat(64)).expect("pubkey"),
    245                 UnixTimestamp::new(1_714_124_433),
    246                 Kind::new(30_402).expect("kind"),
    247                 vec![
    248                     Tag::from_parts("d", &["market"]).expect("d"),
    249                     Tag::from_parts("p", &[&"3".repeat(64), "relay"]).expect("p"),
    250                 ],
    251                 "harvest \u{2022} update",
    252             ),
    253             SignatureHex::new(&"4".repeat(128)).expect("sig"),
    254         );
    255         let pocket = tangle_event_to_pocket(&event).expect("pocket");
    256         let converted = pocket_event_to_tangle(&pocket).expect("converted");
    257 
    258         assert_eq!(converted, event);
    259     }
    260 
    261     #[test]
    262     fn pocket_filter_conversion_uses_native_filter_matching() {
    263         let event = tangle_v2_event(
    264             FixtureKey::Member,
    265             1_714_124_433,
    266             1,
    267             vec![Tag::from_parts("t", &["market"]).expect("tag")],
    268             "hello",
    269         )
    270         .expect("event");
    271         let filter = filter_from_value(&serde_json::json!({
    272             "authors": [event.unsigned().pubkey().as_str()],
    273             "kinds": [1],
    274             "#t": ["market"],
    275             "since": 1_714_124_400,
    276             "limit": 1,
    277             "search": "ignored by Pocket and Tangle matching"
    278         }))
    279         .expect("filter");
    280         let pocket_event = tangle_event_to_pocket(&event).expect("event");
    281         let pocket_filter = tangle_filter_to_pocket(&filter).expect("filter");
    282 
    283         assert!(pocket_filter.event_matches(&pocket_event).expect("match"));
    284     }
    285 
    286     #[test]
    287     fn pocket_filter_conversion_matches_tangle_filter_matching_for_supported_fields() {
    288         let event = tangle_v2_event(
    289             FixtureKey::Member,
    290             1_714_124_433,
    291             1,
    292             vec![
    293                 Tag::from_parts("e", &[&"a".repeat(64)]).expect("e"),
    294                 Tag::from_parts("p", &[FixtureKey::Owner.public_key().as_str()]).expect("p"),
    295                 Tag::from_parts("t", &["market"]).expect("t"),
    296             ],
    297             "filter parity",
    298         )
    299         .expect("event");
    300         let pocket_event = tangle_event_to_pocket(&event).expect("event");
    301         for value in [
    302             serde_json::json!({"ids": [event.id().as_str()]}),
    303             serde_json::json!({"authors": [event.unsigned().pubkey().as_str()]}),
    304             serde_json::json!({"kinds": [1]}),
    305             serde_json::json!({"#e": ["a".repeat(64)]}),
    306             serde_json::json!({"#p": [FixtureKey::Owner.public_key().as_str()]}),
    307             serde_json::json!({"#t": ["market"]}),
    308             serde_json::json!({"since": 1_714_124_400, "until": 1_714_124_500}),
    309             serde_json::json!({"limit": 1}),
    310             serde_json::json!({"kinds": [2]}),
    311             serde_json::json!({"#t": ["other"]}),
    312         ] {
    313             let filter = filter_from_value(&value).expect("filter");
    314             let pocket_filter = tangle_filter_to_pocket(&filter).expect("pocket filter");
    315 
    316             assert_eq!(
    317                 pocket_filter.event_matches(&pocket_event).expect("match"),
    318                 filter.matches(&event)
    319             );
    320         }
    321     }
    322 }