codec.rs (33672B)
1 use crate::error::RadrootsSimplexChatProtoError; 2 use crate::model::{ 3 RadrootsSimplexChatBase64Url, RadrootsSimplexChatContactEvent, 4 RadrootsSimplexChatContainerKind, RadrootsSimplexChatContent, RadrootsSimplexChatDeleteEvent, 5 RadrootsSimplexChatEvent, RadrootsSimplexChatFileAcceptEvent, 6 RadrootsSimplexChatFileAcceptInvitationEvent, RadrootsSimplexChatFileCancelEvent, 7 RadrootsSimplexChatFileDescription, RadrootsSimplexChatFileDescriptionEvent, 8 RadrootsSimplexChatFileInvitation, RadrootsSimplexChatForwardMarker, 9 RadrootsSimplexChatInfoEvent, RadrootsSimplexChatMention, RadrootsSimplexChatMessage, 10 RadrootsSimplexChatMessageContainer, RadrootsSimplexChatMessageContentReference, 11 RadrootsSimplexChatMessageRef, RadrootsSimplexChatMsgNewEvent, 12 RadrootsSimplexChatMsgUpdateEvent, RadrootsSimplexChatNoParamsEvent, RadrootsSimplexChatObject, 13 RadrootsSimplexChatProbeCheckEvent, RadrootsSimplexChatProbeEvent, RadrootsSimplexChatProfile, 14 RadrootsSimplexChatQuotedMessage, RadrootsSimplexChatScope, 15 }; 16 use crate::version::RadrootsSimplexChatVersionRange; 17 use alloc::boxed::Box; 18 use alloc::collections::BTreeMap; 19 use alloc::string::{String, ToString}; 20 use alloc::vec; 21 use alloc::vec::Vec; 22 use serde::{Deserialize, Serialize}; 23 use serde_json::Value; 24 25 pub const RADROOTS_SIMPLEX_CHAT_MAX_PASSTHROUGH_LENGTH: usize = 180; 26 pub const RADROOTS_SIMPLEX_CHAT_COMPRESSION_LEVEL: i32 = 3; 27 pub const RADROOTS_SIMPLEX_CHAT_MAX_COMPRESSED_LENGTH: usize = 13_380; 28 pub const RADROOTS_SIMPLEX_CHAT_MAX_DECOMPRESSED_LENGTH: usize = 65_536; 29 30 const COMPRESSED_ENVELOPE_PREFIX: u8 = b'X'; 31 const PASSTHROUGH_TAG: u8 = b'0'; 32 const COMPRESSED_TAG: u8 = b'1'; 33 34 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 35 struct WireMessage { 36 #[serde(rename = "v", skip_serializing_if = "Option::is_none")] 37 version: Option<RadrootsSimplexChatVersionRange>, 38 #[serde(rename = "msgId", skip_serializing_if = "Option::is_none")] 39 msg_id: Option<RadrootsSimplexChatBase64Url>, 40 event: String, 41 params: RadrootsSimplexChatObject, 42 } 43 44 pub fn decode_messages( 45 input: &[u8], 46 ) -> Result<Vec<RadrootsSimplexChatMessage>, RadrootsSimplexChatProtoError> { 47 let Some(first) = input.first() else { 48 return Err(RadrootsSimplexChatProtoError::EmptyInput); 49 }; 50 51 match *first { 52 COMPRESSED_ENVELOPE_PREFIX => decode_compressed_messages(&input[1..]), 53 b'{' => { 54 let wire = serde_json::from_slice::<WireMessage>(input).map_err( 55 |source: serde_json::Error| { 56 RadrootsSimplexChatProtoError::InvalidJson(source.to_string()) 57 }, 58 )?; 59 Ok(vec![decode_wire_message(wire)?]) 60 } 61 b'[' => { 62 let wires = serde_json::from_slice::<Vec<WireMessage>>(input).map_err( 63 |source: serde_json::Error| { 64 RadrootsSimplexChatProtoError::InvalidJson(source.to_string()) 65 }, 66 )?; 67 wires.into_iter().map(decode_wire_message).collect() 68 } 69 _ => Err(RadrootsSimplexChatProtoError::UnsupportedBinaryMessage), 70 } 71 } 72 73 pub fn encode_message( 74 message: &RadrootsSimplexChatMessage, 75 ) -> Result<Vec<u8>, RadrootsSimplexChatProtoError> { 76 let wire = encode_wire_message(message)?; 77 serde_json::to_vec(&wire) 78 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string())) 79 } 80 81 pub fn encode_batch( 82 messages: &[RadrootsSimplexChatMessage], 83 ) -> Result<Vec<u8>, RadrootsSimplexChatProtoError> { 84 if messages.len() == 1 { 85 return encode_message(&messages[0]); 86 } 87 88 let wires = messages 89 .iter() 90 .map(encode_wire_message) 91 .collect::<Result<Vec<_>, _>>()?; 92 serde_json::to_vec(&wires) 93 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string())) 94 } 95 96 pub fn encode_compressed_batch( 97 messages: &[RadrootsSimplexChatMessage], 98 ) -> Result<Vec<u8>, RadrootsSimplexChatProtoError> { 99 let body = encode_batch(messages)?; 100 let mut encoded = Vec::new(); 101 encoded.push(COMPRESSED_ENVELOPE_PREFIX); 102 encoded.push(1); 103 if body.len() <= RADROOTS_SIMPLEX_CHAT_MAX_PASSTHROUGH_LENGTH { 104 encoded.push(PASSTHROUGH_TAG); 105 encoded.push(u8::try_from(body.len()).map_err(|_| { 106 RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 107 "passthrough payload exceeds one-byte length".to_string(), 108 ) 109 })?); 110 encoded.extend_from_slice(&body); 111 } else { 112 #[cfg(feature = "std")] 113 { 114 let compressed = zstd::bulk::compress(&body, RADROOTS_SIMPLEX_CHAT_COMPRESSION_LEVEL) 115 .map_err(|source| { 116 RadrootsSimplexChatProtoError::InvalidCompressedEnvelope(source.to_string()) 117 })?; 118 encoded.push(COMPRESSED_TAG); 119 let length = u16::try_from(compressed.len()).map_err(|_| { 120 RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 121 "compressed payload exceeds two-byte length".to_string(), 122 ) 123 })?; 124 encoded.extend_from_slice(&length.to_be_bytes()); 125 encoded.extend_from_slice(&compressed); 126 } 127 #[cfg(not(feature = "std"))] 128 { 129 let _ = body; 130 return Err(RadrootsSimplexChatProtoError::CompressionUnavailable); 131 } 132 } 133 134 if encoded.len().saturating_sub(1) > RADROOTS_SIMPLEX_CHAT_MAX_COMPRESSED_LENGTH { 135 return Err(RadrootsSimplexChatProtoError::CompressedMessageTooLarge( 136 encoded.len() - 1, 137 )); 138 } 139 140 Ok(encoded) 141 } 142 143 fn decode_compressed_messages( 144 input: &[u8], 145 ) -> Result<Vec<RadrootsSimplexChatMessage>, RadrootsSimplexChatProtoError> { 146 let mut cursor = 0; 147 let Some(&count) = input.get(cursor) else { 148 return Err(RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 149 "missing compressed chunk count".to_string(), 150 )); 151 }; 152 cursor += 1; 153 if count == 0 { 154 return Err(RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 155 "compressed envelope must contain at least one chunk".to_string(), 156 )); 157 } 158 159 let mut messages = Vec::new(); 160 for _ in 0..count { 161 let Some(&tag) = input.get(cursor) else { 162 return Err(RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 163 "missing compressed chunk tag".to_string(), 164 )); 165 }; 166 cursor += 1; 167 let payload = match tag { 168 PASSTHROUGH_TAG => { 169 let Some(&length) = input.get(cursor) else { 170 return Err(RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 171 "missing passthrough length".to_string(), 172 )); 173 }; 174 cursor += 1; 175 read_exact(input, &mut cursor, usize::from(length))?.to_vec() 176 } 177 COMPRESSED_TAG => { 178 let length_bytes = read_exact(input, &mut cursor, 2)?; 179 let length = usize::from(u16::from_be_bytes([length_bytes[0], length_bytes[1]])); 180 let compressed = read_exact(input, &mut cursor, length)?; 181 #[cfg(feature = "std")] 182 { 183 decompress_compressed_chunk(compressed)? 184 } 185 #[cfg(not(feature = "std"))] 186 { 187 let _ = compressed; 188 return Err(RadrootsSimplexChatProtoError::CompressionUnavailable); 189 } 190 } 191 _ => { 192 return Err(RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 193 alloc::format!("unknown compressed chunk tag `{}`", char::from(tag)), 194 )); 195 } 196 }; 197 messages.extend(decode_messages(&payload)?); 198 } 199 200 if cursor != input.len() { 201 return Err(RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 202 "trailing bytes after compressed envelope".to_string(), 203 )); 204 } 205 206 Ok(messages) 207 } 208 209 #[cfg(feature = "std")] 210 fn decompress_compressed_chunk( 211 compressed: &[u8], 212 ) -> Result<Vec<u8>, RadrootsSimplexChatProtoError> { 213 let declared_size = zstd::zstd_safe::get_frame_content_size(compressed) 214 .map_err(|_| { 215 RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 216 "compressed size not specified or corrupted".to_string(), 217 ) 218 })? 219 .ok_or_else(|| { 220 RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 221 "compressed size not specified or exceeds limit".to_string(), 222 ) 223 })?; 224 if declared_size > RADROOTS_SIMPLEX_CHAT_MAX_DECOMPRESSED_LENGTH as u64 { 225 return Err(RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 226 "compressed size not specified or exceeds limit".to_string(), 227 )); 228 } 229 230 zstd::bulk::decompress(compressed, RADROOTS_SIMPLEX_CHAT_MAX_DECOMPRESSED_LENGTH).map_err( 231 |source| RadrootsSimplexChatProtoError::InvalidCompressedEnvelope(source.to_string()), 232 ) 233 } 234 235 fn read_exact<'a>( 236 input: &'a [u8], 237 cursor: &mut usize, 238 length: usize, 239 ) -> Result<&'a [u8], RadrootsSimplexChatProtoError> { 240 let end = cursor.saturating_add(length); 241 if end > input.len() { 242 return Err(RadrootsSimplexChatProtoError::InvalidCompressedEnvelope( 243 "unexpected end of compressed envelope".to_string(), 244 )); 245 } 246 let slice = &input[*cursor..end]; 247 *cursor = end; 248 Ok(slice) 249 } 250 251 fn decode_wire_message( 252 wire: WireMessage, 253 ) -> Result<RadrootsSimplexChatMessage, RadrootsSimplexChatProtoError> { 254 let event = match wire.event.as_str() { 255 "x.contact" => RadrootsSimplexChatEvent::Contact(decode_contact_event(wire.params)?), 256 "x.info" => RadrootsSimplexChatEvent::Info(decode_info_event(wire.params)?), 257 "x.info.probe" => RadrootsSimplexChatEvent::InfoProbe(decode_probe_event(wire.params)?), 258 "x.info.probe.check" => { 259 RadrootsSimplexChatEvent::InfoProbeCheck(decode_probe_check_event(wire.params)?) 260 } 261 "x.info.probe.ok" => { 262 RadrootsSimplexChatEvent::InfoProbeOk(decode_probe_event(wire.params)?) 263 } 264 "x.msg.new" => RadrootsSimplexChatEvent::MsgNew(Box::new(RadrootsSimplexChatMsgNewEvent { 265 container: decode_message_container(wire.params)?, 266 })), 267 "x.msg.file.descr" => { 268 RadrootsSimplexChatEvent::MsgFileDescr(decode_file_description_event(wire.params)?) 269 } 270 "x.msg.update" => RadrootsSimplexChatEvent::MsgUpdate(decode_update_event(wire.params)?), 271 "x.msg.del" => RadrootsSimplexChatEvent::MsgDel(decode_delete_event(wire.params)?), 272 "x.file.acpt" => RadrootsSimplexChatEvent::FileAcpt(decode_file_accept_event(wire.params)?), 273 "x.file.acpt.inv" => { 274 RadrootsSimplexChatEvent::FileAcptInv(decode_file_accept_inv_event(wire.params)?) 275 } 276 "x.file.cancel" => { 277 RadrootsSimplexChatEvent::FileCancel(decode_file_cancel_event(wire.params)?) 278 } 279 "x.direct.del" => RadrootsSimplexChatEvent::DirectDel(RadrootsSimplexChatNoParamsEvent { 280 extra: wire.params, 281 }), 282 "x.ok" => { 283 RadrootsSimplexChatEvent::Ok(RadrootsSimplexChatNoParamsEvent { extra: wire.params }) 284 } 285 _ => RadrootsSimplexChatEvent::Unknown { 286 event: wire.event, 287 params: wire.params, 288 }, 289 }; 290 291 Ok(RadrootsSimplexChatMessage { 292 version: wire.version, 293 msg_id: wire.msg_id, 294 event, 295 }) 296 } 297 298 fn encode_wire_message( 299 message: &RadrootsSimplexChatMessage, 300 ) -> Result<WireMessage, RadrootsSimplexChatProtoError> { 301 let (event, params) = match &message.event { 302 RadrootsSimplexChatEvent::Contact(event) => { 303 (String::from("x.contact"), encode_contact_event(event)?) 304 } 305 RadrootsSimplexChatEvent::Info(event) => { 306 (String::from("x.info"), encode_info_event(event)?) 307 } 308 RadrootsSimplexChatEvent::InfoProbe(event) => ( 309 String::from("x.info.probe"), 310 encode_probe_event("probe", &event.probe, &event.extra), 311 ), 312 RadrootsSimplexChatEvent::InfoProbeCheck(event) => ( 313 String::from("x.info.probe.check"), 314 encode_probe_event("probeHash", &event.probe_hash, &event.extra), 315 ), 316 RadrootsSimplexChatEvent::InfoProbeOk(event) => ( 317 String::from("x.info.probe.ok"), 318 encode_probe_event("probe", &event.probe, &event.extra), 319 ), 320 RadrootsSimplexChatEvent::MsgNew(event) => ( 321 String::from("x.msg.new"), 322 encode_message_container(&event.container)?, 323 ), 324 RadrootsSimplexChatEvent::MsgFileDescr(event) => ( 325 String::from("x.msg.file.descr"), 326 encode_file_description_event(event)?, 327 ), 328 RadrootsSimplexChatEvent::MsgUpdate(event) => { 329 (String::from("x.msg.update"), encode_update_event(event)?) 330 } 331 RadrootsSimplexChatEvent::MsgDel(event) => { 332 (String::from("x.msg.del"), encode_delete_event(event)) 333 } 334 RadrootsSimplexChatEvent::FileAcpt(event) => { 335 (String::from("x.file.acpt"), encode_file_accept_event(event)) 336 } 337 RadrootsSimplexChatEvent::FileAcptInv(event) => ( 338 String::from("x.file.acpt.inv"), 339 encode_file_accept_inv_event(event), 340 ), 341 RadrootsSimplexChatEvent::FileCancel(event) => ( 342 String::from("x.file.cancel"), 343 encode_file_cancel_event(event), 344 ), 345 RadrootsSimplexChatEvent::DirectDel(event) => { 346 (String::from("x.direct.del"), event.extra.clone()) 347 } 348 RadrootsSimplexChatEvent::Ok(event) => (String::from("x.ok"), event.extra.clone()), 349 RadrootsSimplexChatEvent::Unknown { event, params } => (event.clone(), params.clone()), 350 }; 351 352 Ok(WireMessage { 353 version: message.version, 354 msg_id: message.msg_id.clone(), 355 event, 356 params, 357 }) 358 } 359 360 fn decode_contact_event( 361 mut params: RadrootsSimplexChatObject, 362 ) -> Result<RadrootsSimplexChatContactEvent, RadrootsSimplexChatProtoError> { 363 let profile = parse_from_map::<RadrootsSimplexChatProfile>(&mut params, "profile")?; 364 let contact_req_id = take_optional_base64url(&mut params, "contactReqId")?; 365 let welcome_msg_id = take_optional_base64url(&mut params, "welcomeMsgId")?; 366 let req_msg_id = take_optional_base64url(&mut params, "msgId")?; 367 let req_content = take_optional_content(&mut params, "content")?; 368 let request_message = match (req_msg_id, req_content) { 369 (Some(msg_id), Some(content)) => { 370 Some(RadrootsSimplexChatMessageContentReference { msg_id, content }) 371 } 372 (Some(msg_id), None) => { 373 params.insert( 374 String::from("msgId"), 375 Value::String(msg_id.as_str().to_string()), 376 ); 377 None 378 } 379 (None, Some(content)) => { 380 params.insert( 381 String::from("content"), 382 serde_json::to_value(content).map_err(|source| { 383 RadrootsSimplexChatProtoError::InvalidJson(source.to_string()) 384 })?, 385 ); 386 None 387 } 388 (None, None) => None, 389 }; 390 391 Ok(RadrootsSimplexChatContactEvent { 392 profile, 393 contact_req_id, 394 welcome_msg_id, 395 request_message, 396 extra: params, 397 }) 398 } 399 400 fn encode_contact_event( 401 event: &RadrootsSimplexChatContactEvent, 402 ) -> Result<RadrootsSimplexChatObject, RadrootsSimplexChatProtoError> { 403 let mut params = event.extra.clone(); 404 params.insert(String::from("profile"), to_value(&event.profile)?); 405 insert_optional_base64url(&mut params, "contactReqId", event.contact_req_id.as_ref()); 406 insert_optional_base64url(&mut params, "welcomeMsgId", event.welcome_msg_id.as_ref()); 407 if let Some(request_message) = &event.request_message { 408 insert_optional_base64url(&mut params, "msgId", Some(&request_message.msg_id)); 409 params.insert(String::from("content"), to_value(&request_message.content)?); 410 } else { 411 params.remove("msgId"); 412 params.remove("content"); 413 } 414 Ok(params) 415 } 416 417 fn decode_info_event( 418 mut params: RadrootsSimplexChatObject, 419 ) -> Result<RadrootsSimplexChatInfoEvent, RadrootsSimplexChatProtoError> { 420 Ok(RadrootsSimplexChatInfoEvent { 421 profile: parse_from_map::<RadrootsSimplexChatProfile>(&mut params, "profile")?, 422 extra: params, 423 }) 424 } 425 426 fn encode_info_event( 427 event: &RadrootsSimplexChatInfoEvent, 428 ) -> Result<RadrootsSimplexChatObject, RadrootsSimplexChatProtoError> { 429 let mut params = event.extra.clone(); 430 params.insert(String::from("profile"), to_value(&event.profile)?); 431 Ok(params) 432 } 433 434 fn decode_probe_event( 435 mut params: RadrootsSimplexChatObject, 436 ) -> Result<RadrootsSimplexChatProbeEvent, RadrootsSimplexChatProtoError> { 437 Ok(RadrootsSimplexChatProbeEvent { 438 probe: take_required_base64url(&mut params, "probe")?, 439 extra: params, 440 }) 441 } 442 443 fn decode_probe_check_event( 444 mut params: RadrootsSimplexChatObject, 445 ) -> Result<RadrootsSimplexChatProbeCheckEvent, RadrootsSimplexChatProtoError> { 446 Ok(RadrootsSimplexChatProbeCheckEvent { 447 probe_hash: take_required_base64url(&mut params, "probeHash")?, 448 extra: params, 449 }) 450 } 451 452 fn encode_probe_event( 453 field: &str, 454 value: &RadrootsSimplexChatBase64Url, 455 extra: &RadrootsSimplexChatObject, 456 ) -> RadrootsSimplexChatObject { 457 let mut params = extra.clone(); 458 params.insert( 459 String::from(field), 460 Value::String(value.as_str().to_string()), 461 ); 462 params 463 } 464 465 fn decode_file_description_event( 466 mut params: RadrootsSimplexChatObject, 467 ) -> Result<RadrootsSimplexChatFileDescriptionEvent, RadrootsSimplexChatProtoError> { 468 Ok(RadrootsSimplexChatFileDescriptionEvent { 469 msg_id: take_required_base64url(&mut params, "msgId")?, 470 file_descr: parse_from_map::<RadrootsSimplexChatFileDescription>(&mut params, "fileDescr")?, 471 extra: params, 472 }) 473 } 474 475 fn encode_file_description_event( 476 event: &RadrootsSimplexChatFileDescriptionEvent, 477 ) -> Result<RadrootsSimplexChatObject, RadrootsSimplexChatProtoError> { 478 let mut params = event.extra.clone(); 479 params.insert( 480 String::from("msgId"), 481 Value::String(event.msg_id.as_str().to_string()), 482 ); 483 params.insert(String::from("fileDescr"), to_value(&event.file_descr)?); 484 Ok(params) 485 } 486 487 fn decode_update_event( 488 mut params: RadrootsSimplexChatObject, 489 ) -> Result<RadrootsSimplexChatMsgUpdateEvent, RadrootsSimplexChatProtoError> { 490 Ok(RadrootsSimplexChatMsgUpdateEvent { 491 msg_id: take_required_base64url(&mut params, "msgId")?, 492 content: take_required_content(&mut params, "content")?, 493 mentions: take_optional_mentions(&mut params)?, 494 ttl: take_optional_i64(&mut params, "ttl")?, 495 live: take_optional_bool(&mut params, "live")?, 496 scope: take_optional_scope(&mut params)?, 497 extra: params, 498 }) 499 } 500 501 fn encode_update_event( 502 event: &RadrootsSimplexChatMsgUpdateEvent, 503 ) -> Result<RadrootsSimplexChatObject, RadrootsSimplexChatProtoError> { 504 let mut params = event.extra.clone(); 505 params.insert( 506 String::from("msgId"), 507 Value::String(event.msg_id.as_str().to_string()), 508 ); 509 params.insert(String::from("content"), to_value(&event.content)?); 510 insert_optional_mentions(&mut params, &event.mentions)?; 511 insert_optional_i64(&mut params, "ttl", event.ttl); 512 insert_optional_bool(&mut params, "live", event.live); 513 insert_optional_scope(&mut params, "scope", event.scope.as_ref())?; 514 Ok(params) 515 } 516 517 fn decode_delete_event( 518 mut params: RadrootsSimplexChatObject, 519 ) -> Result<RadrootsSimplexChatDeleteEvent, RadrootsSimplexChatProtoError> { 520 Ok(RadrootsSimplexChatDeleteEvent { 521 msg_id: take_required_base64url(&mut params, "msgId")?, 522 member_id: take_optional_base64url(&mut params, "memberId")?, 523 scope: take_optional_scope(&mut params)?, 524 extra: params, 525 }) 526 } 527 528 fn encode_delete_event(event: &RadrootsSimplexChatDeleteEvent) -> RadrootsSimplexChatObject { 529 let mut params = event.extra.clone(); 530 params.insert( 531 String::from("msgId"), 532 Value::String(event.msg_id.as_str().to_string()), 533 ); 534 insert_optional_base64url(&mut params, "memberId", event.member_id.as_ref()); 535 if let Some(scope) = &event.scope { 536 params.insert( 537 String::from("scope"), 538 serde_json::to_value(scope).unwrap_or(Value::Null), 539 ); 540 } else { 541 params.remove("scope"); 542 } 543 params 544 } 545 546 fn decode_file_accept_event( 547 mut params: RadrootsSimplexChatObject, 548 ) -> Result<RadrootsSimplexChatFileAcceptEvent, RadrootsSimplexChatProtoError> { 549 Ok(RadrootsSimplexChatFileAcceptEvent { 550 file_name: take_required_string(&mut params, "fileName")?, 551 extra: params, 552 }) 553 } 554 555 fn encode_file_accept_event( 556 event: &RadrootsSimplexChatFileAcceptEvent, 557 ) -> RadrootsSimplexChatObject { 558 let mut params = event.extra.clone(); 559 params.insert( 560 String::from("fileName"), 561 Value::String(event.file_name.clone()), 562 ); 563 params 564 } 565 566 fn decode_file_accept_inv_event( 567 mut params: RadrootsSimplexChatObject, 568 ) -> Result<RadrootsSimplexChatFileAcceptInvitationEvent, RadrootsSimplexChatProtoError> { 569 Ok(RadrootsSimplexChatFileAcceptInvitationEvent { 570 msg_id: take_required_base64url(&mut params, "msgId")?, 571 file_conn_req: take_optional_string(&mut params, "fileConnReq")?, 572 file_name: take_required_string(&mut params, "fileName")?, 573 extra: params, 574 }) 575 } 576 577 fn encode_file_accept_inv_event( 578 event: &RadrootsSimplexChatFileAcceptInvitationEvent, 579 ) -> RadrootsSimplexChatObject { 580 let mut params = event.extra.clone(); 581 params.insert( 582 String::from("msgId"), 583 Value::String(event.msg_id.as_str().to_string()), 584 ); 585 insert_optional_string(&mut params, "fileConnReq", event.file_conn_req.as_ref()); 586 params.insert( 587 String::from("fileName"), 588 Value::String(event.file_name.clone()), 589 ); 590 params 591 } 592 593 fn decode_file_cancel_event( 594 mut params: RadrootsSimplexChatObject, 595 ) -> Result<RadrootsSimplexChatFileCancelEvent, RadrootsSimplexChatProtoError> { 596 Ok(RadrootsSimplexChatFileCancelEvent { 597 msg_id: take_required_base64url(&mut params, "msgId")?, 598 extra: params, 599 }) 600 } 601 602 fn encode_file_cancel_event( 603 event: &RadrootsSimplexChatFileCancelEvent, 604 ) -> RadrootsSimplexChatObject { 605 let mut params = event.extra.clone(); 606 params.insert( 607 String::from("msgId"), 608 Value::String(event.msg_id.as_str().to_string()), 609 ); 610 params 611 } 612 613 fn decode_message_container( 614 mut params: RadrootsSimplexChatObject, 615 ) -> Result<RadrootsSimplexChatMessageContainer, RadrootsSimplexChatProtoError> { 616 let kind = if let Some(value) = params.remove("quote") { 617 RadrootsSimplexChatContainerKind::Quote(Box::new( 618 serde_json::from_value::<RadrootsSimplexChatQuotedMessage>(value) 619 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string()))?, 620 )) 621 } else if let Some(value) = params.remove("parent") { 622 RadrootsSimplexChatContainerKind::Comment( 623 serde_json::from_value::<RadrootsSimplexChatMessageRef>(value) 624 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string()))?, 625 ) 626 } else if let Some(value) = params.remove("forward") { 627 match value { 628 Value::Bool(false) => RadrootsSimplexChatContainerKind::Simple, 629 Value::Bool(true) => { 630 RadrootsSimplexChatContainerKind::Forward(RadrootsSimplexChatForwardMarker::Flag) 631 } 632 Value::Object(object) => RadrootsSimplexChatContainerKind::Forward( 633 RadrootsSimplexChatForwardMarker::Object(object), 634 ), 635 _ => return Err(RadrootsSimplexChatProtoError::InvalidField("forward")), 636 } 637 } else { 638 RadrootsSimplexChatContainerKind::Simple 639 }; 640 641 Ok(RadrootsSimplexChatMessageContainer { 642 kind, 643 content: take_required_content(&mut params, "content")?, 644 mentions: take_optional_mentions(&mut params)?, 645 file: take_optional_from_map::<RadrootsSimplexChatFileInvitation>(&mut params, "file")?, 646 ttl: take_optional_i64(&mut params, "ttl")?, 647 live: take_optional_bool(&mut params, "live")?, 648 scope: take_optional_scope(&mut params)?, 649 extra: params, 650 }) 651 } 652 653 fn encode_message_container( 654 container: &RadrootsSimplexChatMessageContainer, 655 ) -> Result<RadrootsSimplexChatObject, RadrootsSimplexChatProtoError> { 656 let mut params = container.extra.clone(); 657 params.insert(String::from("content"), to_value(&container.content)?); 658 insert_optional_mentions(&mut params, &container.mentions)?; 659 insert_optional_to_map(&mut params, "file", container.file.as_ref())?; 660 insert_optional_i64(&mut params, "ttl", container.ttl); 661 insert_optional_bool(&mut params, "live", container.live); 662 insert_optional_scope(&mut params, "scope", container.scope.as_ref())?; 663 664 match &container.kind { 665 RadrootsSimplexChatContainerKind::Simple => { 666 params.remove("quote"); 667 params.remove("parent"); 668 params.remove("forward"); 669 } 670 RadrootsSimplexChatContainerKind::Quote(quoted) => { 671 params.insert(String::from("quote"), to_value(quoted)?); 672 params.remove("parent"); 673 params.remove("forward"); 674 } 675 RadrootsSimplexChatContainerKind::Comment(reference) => { 676 params.insert(String::from("parent"), to_value(reference)?); 677 params.remove("quote"); 678 params.remove("forward"); 679 } 680 RadrootsSimplexChatContainerKind::Forward(RadrootsSimplexChatForwardMarker::Flag) => { 681 params.insert(String::from("forward"), Value::Bool(true)); 682 params.remove("quote"); 683 params.remove("parent"); 684 } 685 RadrootsSimplexChatContainerKind::Forward(RadrootsSimplexChatForwardMarker::Object( 686 object, 687 )) => { 688 params.insert(String::from("forward"), Value::Object(object.clone())); 689 params.remove("quote"); 690 params.remove("parent"); 691 } 692 } 693 694 Ok(params) 695 } 696 697 fn parse_from_map<T>( 698 params: &mut RadrootsSimplexChatObject, 699 field: &'static str, 700 ) -> Result<T, RadrootsSimplexChatProtoError> 701 where 702 T: serde::de::DeserializeOwned, 703 { 704 let value = params 705 .remove(field) 706 .ok_or(RadrootsSimplexChatProtoError::MissingField(field))?; 707 serde_json::from_value(value) 708 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string())) 709 } 710 711 fn take_required_string( 712 params: &mut RadrootsSimplexChatObject, 713 field: &'static str, 714 ) -> Result<String, RadrootsSimplexChatProtoError> { 715 match params.remove(field) { 716 Some(Value::String(value)) => Ok(value), 717 Some(_) => Err(RadrootsSimplexChatProtoError::InvalidField(field)), 718 None => Err(RadrootsSimplexChatProtoError::MissingField(field)), 719 } 720 } 721 722 fn take_optional_string( 723 params: &mut RadrootsSimplexChatObject, 724 field: &'static str, 725 ) -> Result<Option<String>, RadrootsSimplexChatProtoError> { 726 match params.remove(field) { 727 Some(Value::String(value)) => Ok(Some(value)), 728 Some(_) => Err(RadrootsSimplexChatProtoError::InvalidField(field)), 729 None => Ok(None), 730 } 731 } 732 733 fn take_required_base64url( 734 params: &mut RadrootsSimplexChatObject, 735 field: &'static str, 736 ) -> Result<RadrootsSimplexChatBase64Url, RadrootsSimplexChatProtoError> { 737 let value = take_required_string(params, field)?; 738 RadrootsSimplexChatBase64Url::parse_field(value, field) 739 } 740 741 fn take_optional_base64url( 742 params: &mut RadrootsSimplexChatObject, 743 field: &'static str, 744 ) -> Result<Option<RadrootsSimplexChatBase64Url>, RadrootsSimplexChatProtoError> { 745 let value = take_optional_string(params, field)?; 746 value 747 .map(|value| RadrootsSimplexChatBase64Url::parse_field(value, field)) 748 .transpose() 749 } 750 751 fn take_optional_i64( 752 params: &mut RadrootsSimplexChatObject, 753 field: &'static str, 754 ) -> Result<Option<i64>, RadrootsSimplexChatProtoError> { 755 match params.remove(field) { 756 Some(Value::Number(value)) => value 757 .as_i64() 758 .map(Some) 759 .ok_or(RadrootsSimplexChatProtoError::InvalidField(field)), 760 Some(_) => Err(RadrootsSimplexChatProtoError::InvalidField(field)), 761 None => Ok(None), 762 } 763 } 764 765 fn take_optional_bool( 766 params: &mut RadrootsSimplexChatObject, 767 field: &'static str, 768 ) -> Result<Option<bool>, RadrootsSimplexChatProtoError> { 769 match params.remove(field) { 770 Some(Value::Bool(value)) => Ok(Some(value)), 771 Some(_) => Err(RadrootsSimplexChatProtoError::InvalidField(field)), 772 None => Ok(None), 773 } 774 } 775 776 fn take_optional_scope( 777 params: &mut RadrootsSimplexChatObject, 778 ) -> Result<Option<RadrootsSimplexChatScope>, RadrootsSimplexChatProtoError> { 779 match params.remove("scope") { 780 Some(value) => serde_json::from_value(value) 781 .map(Some) 782 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string())), 783 None => Ok(None), 784 } 785 } 786 787 fn take_optional_mentions( 788 params: &mut RadrootsSimplexChatObject, 789 ) -> Result<BTreeMap<String, RadrootsSimplexChatMention>, RadrootsSimplexChatProtoError> { 790 match params.remove("mentions") { 791 Some(value) => serde_json::from_value(value) 792 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string())), 793 None => Ok(BTreeMap::new()), 794 } 795 } 796 797 fn take_required_content( 798 params: &mut RadrootsSimplexChatObject, 799 field: &'static str, 800 ) -> Result<RadrootsSimplexChatContent, RadrootsSimplexChatProtoError> { 801 let value = params 802 .remove(field) 803 .ok_or(RadrootsSimplexChatProtoError::MissingField(field))?; 804 serde_json::from_value(value) 805 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string())) 806 } 807 808 fn take_optional_content( 809 params: &mut RadrootsSimplexChatObject, 810 field: &'static str, 811 ) -> Result<Option<RadrootsSimplexChatContent>, RadrootsSimplexChatProtoError> { 812 match params.remove(field) { 813 Some(value) => serde_json::from_value(value) 814 .map(Some) 815 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string())), 816 None => Ok(None), 817 } 818 } 819 820 fn take_optional_from_map<T>( 821 params: &mut RadrootsSimplexChatObject, 822 field: &'static str, 823 ) -> Result<Option<T>, RadrootsSimplexChatProtoError> 824 where 825 T: serde::de::DeserializeOwned, 826 { 827 match params.remove(field) { 828 Some(value) => serde_json::from_value(value) 829 .map(Some) 830 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string())), 831 None => Ok(None), 832 } 833 } 834 835 fn insert_optional_base64url( 836 params: &mut RadrootsSimplexChatObject, 837 field: &str, 838 value: Option<&RadrootsSimplexChatBase64Url>, 839 ) { 840 if let Some(value) = value { 841 params.insert( 842 String::from(field), 843 Value::String(value.as_str().to_string()), 844 ); 845 } else { 846 params.remove(field); 847 } 848 } 849 850 fn insert_optional_string( 851 params: &mut RadrootsSimplexChatObject, 852 field: &str, 853 value: Option<&String>, 854 ) { 855 if let Some(value) = value { 856 params.insert(String::from(field), Value::String(value.clone())); 857 } else { 858 params.remove(field); 859 } 860 } 861 862 fn insert_optional_i64(params: &mut RadrootsSimplexChatObject, field: &str, value: Option<i64>) { 863 if let Some(value) = value { 864 params.insert(String::from(field), Value::from(value)); 865 } else { 866 params.remove(field); 867 } 868 } 869 870 fn insert_optional_bool(params: &mut RadrootsSimplexChatObject, field: &str, value: Option<bool>) { 871 if let Some(value) = value { 872 params.insert(String::from(field), Value::Bool(value)); 873 } else { 874 params.remove(field); 875 } 876 } 877 878 fn insert_optional_scope( 879 params: &mut RadrootsSimplexChatObject, 880 field: &str, 881 value: Option<&RadrootsSimplexChatScope>, 882 ) -> Result<(), RadrootsSimplexChatProtoError> { 883 if let Some(value) = value { 884 params.insert(String::from(field), to_value(value)?); 885 } else { 886 params.remove(field); 887 } 888 Ok(()) 889 } 890 891 fn insert_optional_mentions( 892 params: &mut RadrootsSimplexChatObject, 893 mentions: &BTreeMap<String, RadrootsSimplexChatMention>, 894 ) -> Result<(), RadrootsSimplexChatProtoError> { 895 if mentions.is_empty() { 896 params.remove("mentions"); 897 } else { 898 params.insert(String::from("mentions"), to_value(mentions)?); 899 } 900 Ok(()) 901 } 902 903 fn insert_optional_to_map<T>( 904 params: &mut RadrootsSimplexChatObject, 905 field: &str, 906 value: Option<&T>, 907 ) -> Result<(), RadrootsSimplexChatProtoError> 908 where 909 T: Serialize, 910 { 911 if let Some(value) = value { 912 params.insert(String::from(field), to_value(value)?); 913 } else { 914 params.remove(field); 915 } 916 Ok(()) 917 } 918 919 fn to_value<T>(value: &T) -> Result<Value, RadrootsSimplexChatProtoError> 920 where 921 T: Serialize, 922 { 923 serde_json::to_value(value) 924 .map_err(|source| RadrootsSimplexChatProtoError::InvalidJson(source.to_string())) 925 }