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 }