lib

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

codec.rs (33643B)


      1 use crate::error::RadrootsSimplexAgentProtoError;
      2 use crate::model::{
      3     RADROOTS_SIMPLEX_AGENT_CURRENT_VERSION, RadrootsSimplexAgentConnectionLink,
      4     RadrootsSimplexAgentDecryptedMessage, RadrootsSimplexAgentEncryptedPayload,
      5     RadrootsSimplexAgentEnvelope, RadrootsSimplexAgentMessage, RadrootsSimplexAgentMessageFrame,
      6     RadrootsSimplexAgentMessageHeader, RadrootsSimplexAgentMessageReceipt,
      7     RadrootsSimplexAgentQueueAddress, RadrootsSimplexAgentQueueDescriptor,
      8     RadrootsSimplexAgentQueueUseDecision,
      9 };
     10 use alloc::string::{String, ToString};
     11 use alloc::vec::Vec;
     12 use radroots_simplex_smp_crypto::prelude::{
     13     RadrootsSimplexOfficialX3dhParams, RadrootsSimplexSmpRatchetHeader,
     14 };
     15 use radroots_simplex_smp_proto::prelude::{
     16     RadrootsSimplexSmpQueueUri, RadrootsSimplexSmpServerAddress, RadrootsSimplexSmpVersionRange,
     17 };
     18 
     19 pub fn encode_connection_link(
     20     link: &RadrootsSimplexAgentConnectionLink,
     21 ) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
     22     let mut buffer = Vec::new();
     23     push_short_bytes(&mut buffer, link.invitation_queue.to_string().as_bytes())?;
     24     push_short_bytes(&mut buffer, &link.connection_id)?;
     25     let e2e_ratchet_params = encode_x3dh_params_binary(&link.e2e_ratchet_params)?;
     26     push_large_bytes(&mut buffer, &e2e_ratchet_params)?;
     27     buffer.push(encode_bool(link.contact_address));
     28     Ok(buffer)
     29 }
     30 
     31 pub fn decode_connection_link(
     32     bytes: &[u8],
     33 ) -> Result<RadrootsSimplexAgentConnectionLink, RadrootsSimplexAgentProtoError> {
     34     let mut cursor = Cursor::new(bytes);
     35     let invitation_queue = String::from_utf8(cursor.read_short_bytes()?)
     36         .map_err(|error| RadrootsSimplexAgentProtoError::InvalidUtf8(error.to_string()))?;
     37     let link = RadrootsSimplexAgentConnectionLink {
     38         invitation_queue: RadrootsSimplexSmpQueueUri::parse(&invitation_queue)?,
     39         connection_id: cursor.read_short_bytes()?,
     40         e2e_ratchet_params: decode_x3dh_params_binary(&cursor.read_large_bytes()?)?,
     41         contact_address: decode_bool(cursor.read_byte()?)?,
     42     };
     43     cursor.finish()?;
     44     Ok(link)
     45 }
     46 
     47 pub fn encode_agent_message_frame(
     48     frame: &RadrootsSimplexAgentMessageFrame,
     49 ) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
     50     let mut buffer = Vec::new();
     51     buffer.push(b'M');
     52     buffer.extend_from_slice(&frame.header.message_id.to_be_bytes());
     53     push_short_bytes(&mut buffer, &frame.header.previous_message_hash)?;
     54     buffer.extend_from_slice(&encode_agent_message(&frame.message)?);
     55     buffer.extend_from_slice(&frame.padding);
     56     Ok(buffer)
     57 }
     58 
     59 pub fn decode_agent_message_frame(
     60     bytes: &[u8],
     61 ) -> Result<RadrootsSimplexAgentMessageFrame, RadrootsSimplexAgentProtoError> {
     62     let mut cursor = Cursor::new(bytes);
     63     cursor.expect_tag(b"M")?;
     64     let header = RadrootsSimplexAgentMessageHeader {
     65         message_id: cursor.read_u64()?,
     66         previous_message_hash: cursor.read_short_bytes()?,
     67     };
     68     let message = decode_agent_message(&mut cursor)?;
     69     let padding = cursor.read_remaining().to_vec();
     70     Ok(RadrootsSimplexAgentMessageFrame {
     71         header,
     72         message,
     73         padding,
     74     })
     75 }
     76 
     77 pub fn encode_decrypted_message(
     78     message: &RadrootsSimplexAgentDecryptedMessage,
     79 ) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
     80     let mut buffer = Vec::new();
     81     match message {
     82         RadrootsSimplexAgentDecryptedMessage::ConnectionInfo(info) => {
     83             buffer.push(b'I');
     84             buffer.extend_from_slice(info);
     85         }
     86         RadrootsSimplexAgentDecryptedMessage::ConnectionInfoReply { reply_queues, info } => {
     87             buffer.push(b'D');
     88             push_list(&mut buffer, reply_queues, encode_queue_descriptor)?;
     89             push_large_bytes(&mut buffer, info)?;
     90         }
     91         RadrootsSimplexAgentDecryptedMessage::RatchetInfo(info) => {
     92             buffer.push(b'R');
     93             push_large_bytes(&mut buffer, info)?;
     94         }
     95         RadrootsSimplexAgentDecryptedMessage::Message(frame) => {
     96             buffer.extend_from_slice(&encode_agent_message_frame(frame)?);
     97         }
     98     }
     99     Ok(buffer)
    100 }
    101 
    102 pub fn decode_decrypted_message(
    103     bytes: &[u8],
    104 ) -> Result<RadrootsSimplexAgentDecryptedMessage, RadrootsSimplexAgentProtoError> {
    105     let mut cursor = Cursor::new(bytes);
    106     match cursor.read_byte()? {
    107         b'I' => Ok(RadrootsSimplexAgentDecryptedMessage::ConnectionInfo(
    108             cursor.read_remaining().to_vec(),
    109         )),
    110         b'D' => {
    111             let reply_queues = cursor.read_list(decode_queue_descriptor)?;
    112             let info = cursor.read_large_bytes()?;
    113             cursor.finish()?;
    114             Ok(RadrootsSimplexAgentDecryptedMessage::ConnectionInfoReply { reply_queues, info })
    115         }
    116         b'R' => {
    117             let info = cursor.read_large_bytes()?;
    118             cursor.finish()?;
    119             Ok(RadrootsSimplexAgentDecryptedMessage::RatchetInfo(info))
    120         }
    121         b'M' => {
    122             decode_agent_message_frame(bytes).map(RadrootsSimplexAgentDecryptedMessage::Message)
    123         }
    124         tag => Err(RadrootsSimplexAgentProtoError::InvalidTag(
    125             String::from_utf8_lossy(&[tag]).into_owned(),
    126         )),
    127     }
    128 }
    129 
    130 pub fn encode_envelope(
    131     envelope: &RadrootsSimplexAgentEnvelope,
    132 ) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
    133     let mut buffer = Vec::new();
    134     buffer.extend_from_slice(&RADROOTS_SIMPLEX_AGENT_CURRENT_VERSION.to_be_bytes());
    135     match envelope {
    136         RadrootsSimplexAgentEnvelope::Confirmation {
    137             reply_queue,
    138             e2e_ratchet_params,
    139             encrypted,
    140         } => {
    141             buffer.push(b'C');
    142             buffer.push(encode_bool(*reply_queue));
    143             encode_optional_x3dh_params(&mut buffer, e2e_ratchet_params)?;
    144             encode_encrypted_payload(&mut buffer, encrypted)?;
    145         }
    146         RadrootsSimplexAgentEnvelope::Message(encrypted) => {
    147             buffer.push(b'M');
    148             encode_encrypted_payload(&mut buffer, encrypted)?;
    149         }
    150         RadrootsSimplexAgentEnvelope::Invitation {
    151             request,
    152             connection_info,
    153         } => {
    154             buffer.push(b'I');
    155             push_large_bytes(&mut buffer, request)?;
    156             push_large_bytes(&mut buffer, connection_info)?;
    157         }
    158         RadrootsSimplexAgentEnvelope::RatchetKey { info, encrypted } => {
    159             buffer.push(b'R');
    160             push_large_bytes(&mut buffer, info)?;
    161             encode_encrypted_payload(&mut buffer, encrypted)?;
    162         }
    163     }
    164     Ok(buffer)
    165 }
    166 
    167 pub fn decode_envelope(
    168     bytes: &[u8],
    169 ) -> Result<RadrootsSimplexAgentEnvelope, RadrootsSimplexAgentProtoError> {
    170     let mut cursor = Cursor::new(bytes);
    171     let _version = cursor.read_u16()?;
    172     match cursor.read_byte()? {
    173         b'C' => {
    174             let reply_queue = decode_bool(cursor.read_byte()?)?;
    175             let e2e_ratchet_params = decode_optional_x3dh_params(&mut cursor)?;
    176             let encrypted = decode_encrypted_payload(&mut cursor)?;
    177             cursor.finish()?;
    178             Ok(RadrootsSimplexAgentEnvelope::Confirmation {
    179                 reply_queue,
    180                 e2e_ratchet_params,
    181                 encrypted,
    182             })
    183         }
    184         b'M' => {
    185             let encrypted = decode_encrypted_payload(&mut cursor)?;
    186             cursor.finish()?;
    187             Ok(RadrootsSimplexAgentEnvelope::Message(encrypted))
    188         }
    189         b'I' => {
    190             let request = cursor.read_large_bytes()?;
    191             let connection_info = cursor.read_large_bytes()?;
    192             cursor.finish()?;
    193             Ok(RadrootsSimplexAgentEnvelope::Invitation {
    194                 request,
    195                 connection_info,
    196             })
    197         }
    198         b'R' => {
    199             let info = cursor.read_large_bytes()?;
    200             let encrypted = decode_encrypted_payload(&mut cursor)?;
    201             cursor.finish()?;
    202             Ok(RadrootsSimplexAgentEnvelope::RatchetKey { info, encrypted })
    203         }
    204         tag => Err(RadrootsSimplexAgentProtoError::InvalidTag(
    205             String::from_utf8_lossy(&[tag]).into_owned(),
    206         )),
    207     }
    208 }
    209 
    210 fn encode_agent_message(
    211     message: &RadrootsSimplexAgentMessage,
    212 ) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
    213     let mut buffer = Vec::new();
    214     match message {
    215         RadrootsSimplexAgentMessage::Hello => buffer.push(b'H'),
    216         RadrootsSimplexAgentMessage::UserMessage(body) => {
    217             buffer.push(b'M');
    218             buffer.extend_from_slice(body);
    219         }
    220         RadrootsSimplexAgentMessage::Receipt(receipt) => {
    221             buffer.push(b'V');
    222             buffer.extend_from_slice(&receipt.message_id.to_be_bytes());
    223             push_short_bytes(&mut buffer, &receipt.message_hash)?;
    224             push_large_bytes(&mut buffer, &receipt.receipt_info)?;
    225         }
    226         RadrootsSimplexAgentMessage::EncryptionReady { up_to_message_id } => {
    227             buffer.push(b'E');
    228             buffer.extend_from_slice(&up_to_message_id.to_be_bytes());
    229         }
    230         RadrootsSimplexAgentMessage::QueueContinue(queue) => {
    231             buffer.extend_from_slice(b"QC");
    232             encode_queue_address(&mut buffer, queue)?;
    233         }
    234         RadrootsSimplexAgentMessage::QueueAdd(queues) => {
    235             buffer.extend_from_slice(b"QA");
    236             push_list(&mut buffer, queues, encode_queue_descriptor)?;
    237         }
    238         RadrootsSimplexAgentMessage::QueueKey(queues) => {
    239             buffer.extend_from_slice(b"QK");
    240             push_list(&mut buffer, queues, encode_queue_descriptor)?;
    241         }
    242         RadrootsSimplexAgentMessage::QueueUse(queues) => {
    243             buffer.extend_from_slice(b"QU");
    244             push_list(&mut buffer, queues, encode_queue_use_decision)?;
    245         }
    246         RadrootsSimplexAgentMessage::QueueTest(queues) => {
    247             buffer.extend_from_slice(b"QT");
    248             push_list(&mut buffer, queues, encode_queue_address)?;
    249         }
    250     }
    251     Ok(buffer)
    252 }
    253 
    254 fn decode_agent_message(
    255     cursor: &mut Cursor<'_>,
    256 ) -> Result<RadrootsSimplexAgentMessage, RadrootsSimplexAgentProtoError> {
    257     let first = cursor.read_byte()?;
    258     match first {
    259         b'H' => Ok(RadrootsSimplexAgentMessage::Hello),
    260         b'M' => Ok(RadrootsSimplexAgentMessage::UserMessage(
    261             cursor.read_remaining().to_vec(),
    262         )),
    263         b'V' => Ok(RadrootsSimplexAgentMessage::Receipt(
    264             RadrootsSimplexAgentMessageReceipt {
    265                 message_id: cursor.read_u64()?,
    266                 message_hash: cursor.read_short_bytes()?,
    267                 receipt_info: cursor.read_large_bytes()?,
    268             },
    269         )),
    270         b'E' => Ok(RadrootsSimplexAgentMessage::EncryptionReady {
    271             up_to_message_id: cursor.read_u64()?,
    272         }),
    273         b'Q' => match cursor.read_byte()? {
    274             b'C' => Ok(RadrootsSimplexAgentMessage::QueueContinue(
    275                 decode_queue_address(cursor)?,
    276             )),
    277             b'A' => Ok(RadrootsSimplexAgentMessage::QueueAdd(
    278                 cursor.read_list(decode_queue_descriptor)?,
    279             )),
    280             b'K' => Ok(RadrootsSimplexAgentMessage::QueueKey(
    281                 cursor.read_list(decode_queue_descriptor)?,
    282             )),
    283             b'U' => Ok(RadrootsSimplexAgentMessage::QueueUse(
    284                 cursor.read_list(decode_queue_use_decision)?,
    285             )),
    286             b'T' => Ok(RadrootsSimplexAgentMessage::QueueTest(
    287                 cursor.read_list(decode_queue_address)?,
    288             )),
    289             tag => Err(RadrootsSimplexAgentProtoError::InvalidTag(alloc::format!(
    290                 "Q{}",
    291                 tag as char
    292             ))),
    293         },
    294         tag => Err(RadrootsSimplexAgentProtoError::InvalidTag(
    295             String::from_utf8_lossy(&[tag]).into_owned(),
    296         )),
    297     }
    298 }
    299 
    300 fn encode_encrypted_payload(
    301     buffer: &mut Vec<u8>,
    302     encrypted: &RadrootsSimplexAgentEncryptedPayload,
    303 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    304     if let Some(official_message) = encrypted.official_message.as_ref() {
    305         if encrypted.ratchet_header.is_some() || !encrypted.ciphertext.is_empty() {
    306             return Err(RadrootsSimplexAgentProtoError::InvalidRatchetHeader(
    307                 "official encrypted payload cannot include legacy ratchet fields".into(),
    308             ));
    309         }
    310         buffer.push(2);
    311         push_large_bytes(buffer, official_message)?;
    312         return Ok(());
    313     }
    314     match &encrypted.ratchet_header {
    315         Some(header) => {
    316             buffer.push(1);
    317             let ratchet = encode_ratchet_header(header)?;
    318             push_large_bytes(buffer, &ratchet)?;
    319         }
    320         None => buffer.push(0),
    321     }
    322     push_large_bytes(buffer, &encrypted.ciphertext)
    323 }
    324 
    325 fn decode_encrypted_payload(
    326     cursor: &mut Cursor<'_>,
    327 ) -> Result<RadrootsSimplexAgentEncryptedPayload, RadrootsSimplexAgentProtoError> {
    328     match cursor.read_byte()? {
    329         0 => Ok(RadrootsSimplexAgentEncryptedPayload {
    330             ratchet_header: None,
    331             official_message: None,
    332             ciphertext: cursor.read_large_bytes()?,
    333         }),
    334         1 => Ok(RadrootsSimplexAgentEncryptedPayload {
    335             ratchet_header: Some(decode_ratchet_header(&cursor.read_large_bytes()?)?),
    336             official_message: None,
    337             ciphertext: cursor.read_large_bytes()?,
    338         }),
    339         2 => Ok(RadrootsSimplexAgentEncryptedPayload {
    340             ratchet_header: None,
    341             official_message: Some(cursor.read_large_bytes()?),
    342             ciphertext: Vec::new(),
    343         }),
    344         other => Err(RadrootsSimplexAgentProtoError::InvalidBoolEncoding(other)),
    345     }
    346 }
    347 
    348 fn encode_optional_x3dh_params(
    349     buffer: &mut Vec<u8>,
    350     params: &Option<RadrootsSimplexOfficialX3dhParams>,
    351 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    352     match params {
    353         Some(params) => {
    354             buffer.push(b'1');
    355             let encoded = encode_x3dh_params_binary(params)?;
    356             push_large_bytes(buffer, &encoded)
    357         }
    358         None => {
    359             buffer.push(b'0');
    360             Ok(())
    361         }
    362     }
    363 }
    364 
    365 fn encode_x3dh_params_binary(
    366     params: &RadrootsSimplexOfficialX3dhParams,
    367 ) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
    368     let mut buffer = Vec::new();
    369     buffer.extend_from_slice(&params.version_range.min.to_be_bytes());
    370     buffer.extend_from_slice(&params.version_range.max.to_be_bytes());
    371     push_short_bytes(&mut buffer, &params.key_1)?;
    372     push_short_bytes(&mut buffer, &params.key_2)?;
    373     push_maybe_large_bytes(&mut buffer, params.pq_public_key.as_deref())?;
    374     push_maybe_large_bytes(&mut buffer, params.pq_ciphertext.as_deref())?;
    375     Ok(buffer)
    376 }
    377 
    378 fn decode_x3dh_params_binary(
    379     bytes: &[u8],
    380 ) -> Result<RadrootsSimplexOfficialX3dhParams, RadrootsSimplexAgentProtoError> {
    381     let mut cursor = Cursor::new(bytes);
    382     let version_range = RadrootsSimplexSmpVersionRange::new(cursor.read_u16()?, cursor.read_u16()?)
    383         .map_err(|error| RadrootsSimplexAgentProtoError::InvalidE2eParameters(error.to_string()))?;
    384     let params = RadrootsSimplexOfficialX3dhParams {
    385         version_range,
    386         key_1: cursor.read_short_bytes()?,
    387         key_2: cursor.read_short_bytes()?,
    388         pq_public_key: cursor.read_maybe(decode_large_bytes)?,
    389         pq_ciphertext: cursor.read_maybe(decode_large_bytes)?,
    390     };
    391     cursor.finish()?;
    392     Ok(params)
    393 }
    394 
    395 fn decode_optional_x3dh_params(
    396     cursor: &mut Cursor<'_>,
    397 ) -> Result<Option<RadrootsSimplexOfficialX3dhParams>, RadrootsSimplexAgentProtoError> {
    398     match cursor.read_byte()? {
    399         b'0' => Ok(None),
    400         b'1' => {
    401             let encoded = cursor.read_large_bytes()?;
    402             decode_x3dh_params_binary(&encoded).map(Some)
    403         }
    404         tag => Err(RadrootsSimplexAgentProtoError::InvalidTag(
    405             String::from_utf8_lossy(&[tag]).into_owned(),
    406         )),
    407     }
    408 }
    409 
    410 fn encode_queue_descriptor(
    411     buffer: &mut Vec<u8>,
    412     descriptor: &RadrootsSimplexAgentQueueDescriptor,
    413 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    414     push_short_bytes(buffer, descriptor.queue_uri.to_string().as_bytes())?;
    415     push_maybe(
    416         buffer,
    417         descriptor.replaced_queue.as_ref(),
    418         encode_queue_address,
    419     )?;
    420     buffer.push(encode_bool(descriptor.primary));
    421     push_maybe_short_bytes(buffer, descriptor.sender_key.as_deref())?;
    422     Ok(())
    423 }
    424 
    425 fn decode_queue_descriptor(
    426     cursor: &mut Cursor<'_>,
    427 ) -> Result<RadrootsSimplexAgentQueueDescriptor, RadrootsSimplexAgentProtoError> {
    428     let queue_uri = String::from_utf8(cursor.read_short_bytes()?)
    429         .map_err(|error| RadrootsSimplexAgentProtoError::InvalidUtf8(error.to_string()))?;
    430     Ok(RadrootsSimplexAgentQueueDescriptor {
    431         queue_uri: RadrootsSimplexSmpQueueUri::parse(&queue_uri)?,
    432         replaced_queue: cursor.read_maybe(decode_queue_address)?,
    433         primary: decode_bool(cursor.read_byte()?)?,
    434         sender_key: cursor.read_maybe(decode_short_bytes)?,
    435     })
    436 }
    437 
    438 fn encode_queue_use_decision(
    439     buffer: &mut Vec<u8>,
    440     decision: &RadrootsSimplexAgentQueueUseDecision,
    441 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    442     encode_queue_address(buffer, &decision.queue_address)?;
    443     buffer.push(encode_bool(decision.primary));
    444     Ok(())
    445 }
    446 
    447 fn decode_queue_use_decision(
    448     cursor: &mut Cursor<'_>,
    449 ) -> Result<RadrootsSimplexAgentQueueUseDecision, RadrootsSimplexAgentProtoError> {
    450     Ok(RadrootsSimplexAgentQueueUseDecision {
    451         queue_address: decode_queue_address(cursor)?,
    452         primary: decode_bool(cursor.read_byte()?)?,
    453     })
    454 }
    455 
    456 fn encode_queue_address(
    457     buffer: &mut Vec<u8>,
    458     queue: &RadrootsSimplexAgentQueueAddress,
    459 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    460     push_short_bytes(buffer, queue.server.server_identity.as_bytes())?;
    461     push_list(buffer, &queue.server.hosts, |buffer, host| {
    462         push_short_bytes(buffer, host.as_bytes())
    463     })?;
    464     let port = queue
    465         .server
    466         .port
    467         .map(|value| value.to_string())
    468         .unwrap_or_default();
    469     push_short_bytes(buffer, port.as_bytes())?;
    470     push_short_bytes(buffer, &queue.sender_id)?;
    471     Ok(())
    472 }
    473 
    474 fn decode_queue_address(
    475     cursor: &mut Cursor<'_>,
    476 ) -> Result<RadrootsSimplexAgentQueueAddress, RadrootsSimplexAgentProtoError> {
    477     let server_identity = String::from_utf8(cursor.read_short_bytes()?)
    478         .map_err(|error| RadrootsSimplexAgentProtoError::InvalidUtf8(error.to_string()))?;
    479     let hosts = cursor.read_list(|cursor| {
    480         let host = String::from_utf8(cursor.read_short_bytes()?)
    481             .map_err(|error| RadrootsSimplexAgentProtoError::InvalidUtf8(error.to_string()))?;
    482         Ok(host)
    483     })?;
    484     let port_raw = String::from_utf8(cursor.read_short_bytes()?)
    485         .map_err(|error| RadrootsSimplexAgentProtoError::InvalidUtf8(error.to_string()))?;
    486     let port = if port_raw.is_empty() {
    487         None
    488     } else {
    489         Some(
    490             port_raw
    491                 .parse::<u16>()
    492                 .map_err(|_| RadrootsSimplexAgentProtoError::InvalidUtf8(port_raw.clone()))?,
    493         )
    494     };
    495     Ok(RadrootsSimplexAgentQueueAddress {
    496         server: RadrootsSimplexSmpServerAddress {
    497             server_identity,
    498             hosts,
    499             port,
    500         },
    501         sender_id: cursor.read_short_bytes()?,
    502     })
    503 }
    504 
    505 fn encode_ratchet_header(
    506     header: &RadrootsSimplexSmpRatchetHeader,
    507 ) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
    508     header
    509         .validate()
    510         .map_err(|error| RadrootsSimplexAgentProtoError::InvalidRatchetHeader(error.to_string()))?;
    511     let mut buffer = Vec::new();
    512     buffer.extend_from_slice(&header.previous_sending_chain_length.to_be_bytes());
    513     buffer.extend_from_slice(&header.message_number.to_be_bytes());
    514     push_short_bytes(&mut buffer, &header.dh_public_key)?;
    515     push_maybe_large_bytes(&mut buffer, header.pq_public_key.as_deref())?;
    516     push_maybe_large_bytes(&mut buffer, header.pq_ciphertext.as_deref())?;
    517     Ok(buffer)
    518 }
    519 
    520 fn decode_ratchet_header(
    521     bytes: &[u8],
    522 ) -> Result<RadrootsSimplexSmpRatchetHeader, RadrootsSimplexAgentProtoError> {
    523     let mut cursor = Cursor::new(bytes);
    524     let header = RadrootsSimplexSmpRatchetHeader {
    525         previous_sending_chain_length: cursor.read_u32()?,
    526         message_number: cursor.read_u32()?,
    527         dh_public_key: cursor.read_short_bytes()?,
    528         pq_public_key: cursor.read_maybe(decode_large_bytes)?,
    529         pq_ciphertext: cursor.read_maybe(decode_large_bytes)?,
    530     };
    531     cursor.finish()?;
    532     header
    533         .validate()
    534         .map_err(|error| RadrootsSimplexAgentProtoError::InvalidRatchetHeader(error.to_string()))?;
    535     Ok(header)
    536 }
    537 
    538 fn decode_short_bytes(cursor: &mut Cursor<'_>) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
    539     cursor.read_short_bytes()
    540 }
    541 
    542 fn decode_large_bytes(cursor: &mut Cursor<'_>) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
    543     cursor.read_large_bytes()
    544 }
    545 
    546 fn push_short_bytes(
    547     buffer: &mut Vec<u8>,
    548     value: &[u8],
    549 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    550     if value.len() > u8::MAX as usize {
    551         return Err(RadrootsSimplexAgentProtoError::InvalidShortFieldLength(
    552             value.len(),
    553         ));
    554     }
    555     buffer.push(value.len() as u8);
    556     buffer.extend_from_slice(value);
    557     Ok(())
    558 }
    559 
    560 fn push_large_bytes(
    561     buffer: &mut Vec<u8>,
    562     value: &[u8],
    563 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    564     if value.len() > u16::MAX as usize {
    565         return Err(RadrootsSimplexAgentProtoError::InvalidLargeFieldLength(
    566             value.len(),
    567         ));
    568     }
    569     buffer.extend_from_slice(&(value.len() as u16).to_be_bytes());
    570     buffer.extend_from_slice(value);
    571     Ok(())
    572 }
    573 
    574 fn push_maybe_short_bytes(
    575     buffer: &mut Vec<u8>,
    576     value: Option<&[u8]>,
    577 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    578     match value {
    579         Some(value) => {
    580             buffer.push(1);
    581             push_short_bytes(buffer, value)
    582         }
    583         None => {
    584             buffer.push(0);
    585             Ok(())
    586         }
    587     }
    588 }
    589 
    590 fn push_maybe_large_bytes(
    591     buffer: &mut Vec<u8>,
    592     value: Option<&[u8]>,
    593 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    594     match value {
    595         Some(value) => {
    596             buffer.push(1);
    597             push_large_bytes(buffer, value)
    598         }
    599         None => {
    600             buffer.push(0);
    601             Ok(())
    602         }
    603     }
    604 }
    605 
    606 fn push_maybe<T>(
    607     buffer: &mut Vec<u8>,
    608     value: Option<&T>,
    609     encode: fn(&mut Vec<u8>, &T) -> Result<(), RadrootsSimplexAgentProtoError>,
    610 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    611     match value {
    612         Some(value) => {
    613             buffer.push(1);
    614             encode(buffer, value)
    615         }
    616         None => {
    617             buffer.push(0);
    618             Ok(())
    619         }
    620     }
    621 }
    622 
    623 fn push_list<T>(
    624     buffer: &mut Vec<u8>,
    625     values: &[T],
    626     encode: fn(&mut Vec<u8>, &T) -> Result<(), RadrootsSimplexAgentProtoError>,
    627 ) -> Result<(), RadrootsSimplexAgentProtoError> {
    628     if values.len() > u8::MAX as usize {
    629         return Err(RadrootsSimplexAgentProtoError::InvalidShortFieldLength(
    630             values.len(),
    631         ));
    632     }
    633     buffer.push(values.len() as u8);
    634     for value in values {
    635         encode(buffer, value)?;
    636     }
    637     Ok(())
    638 }
    639 
    640 const fn encode_bool(value: bool) -> u8 {
    641     if value { 1 } else { 0 }
    642 }
    643 
    644 fn decode_bool(value: u8) -> Result<bool, RadrootsSimplexAgentProtoError> {
    645     match value {
    646         0 => Ok(false),
    647         1 => Ok(true),
    648         other => Err(RadrootsSimplexAgentProtoError::InvalidBoolEncoding(other)),
    649     }
    650 }
    651 
    652 struct Cursor<'a> {
    653     bytes: &'a [u8],
    654     position: usize,
    655 }
    656 
    657 impl<'a> Cursor<'a> {
    658     const fn new(bytes: &'a [u8]) -> Self {
    659         Self { bytes, position: 0 }
    660     }
    661 
    662     fn finish(&self) -> Result<(), RadrootsSimplexAgentProtoError> {
    663         if self.position == self.bytes.len() {
    664             Ok(())
    665         } else {
    666             Err(RadrootsSimplexAgentProtoError::TrailingBytes)
    667         }
    668     }
    669 
    670     fn read_byte(&mut self) -> Result<u8, RadrootsSimplexAgentProtoError> {
    671         let Some(value) = self.bytes.get(self.position) else {
    672             return Err(RadrootsSimplexAgentProtoError::UnexpectedEof);
    673         };
    674         self.position += 1;
    675         Ok(*value)
    676     }
    677 
    678     fn expect_tag(&mut self, tag: &[u8]) -> Result<(), RadrootsSimplexAgentProtoError> {
    679         let Some(value) = self.bytes.get(self.position..self.position + tag.len()) else {
    680             return Err(RadrootsSimplexAgentProtoError::UnexpectedEof);
    681         };
    682         if value != tag {
    683             return Err(RadrootsSimplexAgentProtoError::InvalidTag(
    684                 String::from_utf8_lossy(value).into_owned(),
    685             ));
    686         }
    687         self.position += tag.len();
    688         Ok(())
    689     }
    690 
    691     fn read_u16(&mut self) -> Result<u16, RadrootsSimplexAgentProtoError> {
    692         let Some(value) = self.bytes.get(self.position..self.position + 2) else {
    693             return Err(RadrootsSimplexAgentProtoError::UnexpectedEof);
    694         };
    695         self.position += 2;
    696         Ok(u16::from_be_bytes([value[0], value[1]]))
    697     }
    698 
    699     fn read_u32(&mut self) -> Result<u32, RadrootsSimplexAgentProtoError> {
    700         let Some(value) = self.bytes.get(self.position..self.position + 4) else {
    701             return Err(RadrootsSimplexAgentProtoError::UnexpectedEof);
    702         };
    703         self.position += 4;
    704         Ok(u32::from_be_bytes([value[0], value[1], value[2], value[3]]))
    705     }
    706 
    707     fn read_u64(&mut self) -> Result<u64, RadrootsSimplexAgentProtoError> {
    708         let Some(value) = self.bytes.get(self.position..self.position + 8) else {
    709             return Err(RadrootsSimplexAgentProtoError::UnexpectedEof);
    710         };
    711         self.position += 8;
    712         Ok(u64::from_be_bytes([
    713             value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7],
    714         ]))
    715     }
    716 
    717     fn read_short_bytes(&mut self) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
    718         let length = self.read_byte()? as usize;
    719         let Some(value) = self.bytes.get(self.position..self.position + length) else {
    720             return Err(RadrootsSimplexAgentProtoError::UnexpectedEof);
    721         };
    722         self.position += length;
    723         Ok(value.to_vec())
    724     }
    725 
    726     fn read_large_bytes(&mut self) -> Result<Vec<u8>, RadrootsSimplexAgentProtoError> {
    727         let length = self.read_u16()? as usize;
    728         let Some(value) = self.bytes.get(self.position..self.position + length) else {
    729             return Err(RadrootsSimplexAgentProtoError::UnexpectedEof);
    730         };
    731         self.position += length;
    732         Ok(value.to_vec())
    733     }
    734 
    735     fn read_maybe<T>(
    736         &mut self,
    737         decode: fn(&mut Cursor<'_>) -> Result<T, RadrootsSimplexAgentProtoError>,
    738     ) -> Result<Option<T>, RadrootsSimplexAgentProtoError> {
    739         match self.read_byte()? {
    740             0 => Ok(None),
    741             1 => decode(self).map(Some),
    742             other => Err(RadrootsSimplexAgentProtoError::InvalidBoolEncoding(other)),
    743         }
    744     }
    745 
    746     fn read_list<T>(
    747         &mut self,
    748         decode: fn(&mut Cursor<'_>) -> Result<T, RadrootsSimplexAgentProtoError>,
    749     ) -> Result<Vec<T>, RadrootsSimplexAgentProtoError> {
    750         let len = self.read_byte()? as usize;
    751         let mut values = Vec::with_capacity(len);
    752         for _ in 0..len {
    753             values.push(decode(self)?);
    754         }
    755         Ok(values)
    756     }
    757 
    758     fn read_remaining(&mut self) -> &'a [u8] {
    759         let remaining = &self.bytes[self.position..];
    760         self.position = self.bytes.len();
    761         remaining
    762     }
    763 }
    764 
    765 #[cfg(test)]
    766 mod tests {
    767     use super::*;
    768     use radroots_simplex_smp_crypto::prelude::{
    769         RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION, RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION,
    770         RadrootsSimplexOfficialX3dhParams, official_x448_keypair_from_seed,
    771     };
    772     use radroots_simplex_smp_proto::prelude::{
    773         RadrootsSimplexSmpQueueMode, RadrootsSimplexSmpQueueUri, RadrootsSimplexSmpVersionRange,
    774     };
    775 
    776     fn sample_queue_uri() -> RadrootsSimplexSmpQueueUri {
    777         RadrootsSimplexSmpQueueUri::parse(
    778             "smp://aGVsbG8@relay.example/cXVldWU#/?v=4&dh=Zm9vYmFy&q=m",
    779         )
    780         .unwrap()
    781     }
    782 
    783     fn sample_x3dh_params() -> RadrootsSimplexOfficialX3dhParams {
    784         RadrootsSimplexOfficialX3dhParams {
    785             version_range: RadrootsSimplexSmpVersionRange::new(
    786                 RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION,
    787                 RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
    788             )
    789             .unwrap(),
    790             key_1: official_x448_keypair_from_seed(b"rr-synth-agent-link-x3dh-1").public_key,
    791             key_2: official_x448_keypair_from_seed(b"rr-synth-agent-link-x3dh-2").public_key,
    792             pq_public_key: None,
    793             pq_ciphertext: None,
    794         }
    795     }
    796 
    797     #[test]
    798     fn roundtrips_connection_link() {
    799         let link = RadrootsSimplexAgentConnectionLink {
    800             invitation_queue: sample_queue_uri(),
    801             connection_id: b"conn-1".to_vec(),
    802             e2e_ratchet_params: sample_x3dh_params(),
    803             contact_address: true,
    804         };
    805         let encoded = encode_connection_link(&link).unwrap();
    806         let decoded = decode_connection_link(&encoded).unwrap();
    807         assert_eq!(decoded, link);
    808     }
    809 
    810     #[test]
    811     fn roundtrips_confirmation_x3dh_params() {
    812         let envelope = RadrootsSimplexAgentEnvelope::Confirmation {
    813             reply_queue: true,
    814             e2e_ratchet_params: Some(sample_x3dh_params()),
    815             encrypted: RadrootsSimplexAgentEncryptedPayload {
    816                 ratchet_header: None,
    817                 official_message: Some(b"official".to_vec()),
    818                 ciphertext: Vec::new(),
    819             },
    820         };
    821 
    822         let encoded = encode_envelope(&envelope).unwrap();
    823         let decoded = decode_envelope(&encoded).unwrap();
    824         assert_eq!(decoded, envelope);
    825     }
    826 
    827     #[test]
    828     fn roundtrips_message_frame_and_envelope() {
    829         let descriptor = RadrootsSimplexAgentQueueDescriptor {
    830             queue_uri: sample_queue_uri(),
    831             replaced_queue: None,
    832             primary: true,
    833             sender_key: Some(b"sender-key".to_vec()),
    834         };
    835         let frame = RadrootsSimplexAgentMessageFrame {
    836             header: RadrootsSimplexAgentMessageHeader {
    837                 message_id: 7,
    838                 previous_message_hash: b"hash".to_vec(),
    839             },
    840             message: RadrootsSimplexAgentMessage::QueueAdd(vec![descriptor.clone()]),
    841             padding: b"pad".to_vec(),
    842         };
    843         let decrypted = RadrootsSimplexAgentDecryptedMessage::Message(frame);
    844         let encoded_decrypted = encode_decrypted_message(&decrypted).unwrap();
    845         let decoded_decrypted = decode_decrypted_message(&encoded_decrypted).unwrap();
    846         assert_eq!(decoded_decrypted, decrypted);
    847 
    848         let envelope =
    849             RadrootsSimplexAgentEnvelope::Message(RadrootsSimplexAgentEncryptedPayload {
    850                 ratchet_header: Some(RadrootsSimplexSmpRatchetHeader {
    851                     previous_sending_chain_length: 1,
    852                     message_number: 2,
    853                     dh_public_key: b"dh".to_vec(),
    854                     pq_public_key: Some(b"pq".to_vec()),
    855                     pq_ciphertext: Some(b"ct".to_vec()),
    856                 }),
    857                 official_message: None,
    858                 ciphertext: encoded_decrypted,
    859             });
    860         let encoded_envelope = encode_envelope(&envelope).unwrap();
    861         let decoded_envelope = decode_envelope(&encoded_envelope).unwrap();
    862         assert_eq!(decoded_envelope, envelope);
    863 
    864         assert_eq!(
    865             descriptor.client_version_range(),
    866             RadrootsSimplexSmpVersionRange::single(4)
    867         );
    868         assert_eq!(
    869             descriptor.queue_uri.queue_mode,
    870             Some(RadrootsSimplexSmpQueueMode::Messaging)
    871         );
    872     }
    873 
    874     #[test]
    875     fn roundtrips_official_sized_pq_ratchet_header() {
    876         let envelope =
    877             RadrootsSimplexAgentEnvelope::Message(RadrootsSimplexAgentEncryptedPayload {
    878                 ratchet_header: Some(RadrootsSimplexSmpRatchetHeader {
    879                     previous_sending_chain_length: 4,
    880                     message_number: 9,
    881                     dh_public_key: vec![7_u8; 56],
    882                     pq_public_key: Some(vec![8_u8; 1158]),
    883                     pq_ciphertext: Some(vec![9_u8; 1039]),
    884                 }),
    885                 official_message: None,
    886                 ciphertext: b"opaque".to_vec(),
    887             });
    888 
    889         let encoded = encode_envelope(&envelope).unwrap();
    890         let decoded = decode_envelope(&encoded).unwrap();
    891         assert_eq!(decoded, envelope);
    892     }
    893 
    894     #[test]
    895     fn rejects_incomplete_pq_ratchet_header() {
    896         let envelope =
    897             RadrootsSimplexAgentEnvelope::Message(RadrootsSimplexAgentEncryptedPayload {
    898                 ratchet_header: Some(RadrootsSimplexSmpRatchetHeader {
    899                     previous_sending_chain_length: 0,
    900                     message_number: 0,
    901                     dh_public_key: vec![1_u8; 56],
    902                     pq_public_key: None,
    903                     pq_ciphertext: Some(vec![3_u8; 1039]),
    904                 }),
    905                 official_message: None,
    906                 ciphertext: b"opaque".to_vec(),
    907             });
    908 
    909         let error = encode_envelope(&envelope).unwrap_err();
    910         assert!(matches!(
    911             error,
    912             RadrootsSimplexAgentProtoError::InvalidRatchetHeader(_)
    913         ));
    914     }
    915 
    916     #[test]
    917     fn roundtrips_official_opaque_encrypted_payload() {
    918         let envelope =
    919             RadrootsSimplexAgentEnvelope::Message(RadrootsSimplexAgentEncryptedPayload {
    920                 ratchet_header: None,
    921                 official_message: Some(b"official-encrypted-ratchet-message".to_vec()),
    922                 ciphertext: Vec::new(),
    923             });
    924 
    925         let encoded = encode_envelope(&envelope).unwrap();
    926         let decoded = decode_envelope(&encoded).unwrap();
    927         assert_eq!(decoded, envelope);
    928     }
    929 }