lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

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 }