diff --git a/error/Cargo.toml b/error/Cargo.toml index a17ea5b..b0f0ebd 100644 --- a/error/Cargo.toml +++ b/error/Cargo.toml @@ -14,4 +14,4 @@ jsonwebtoken = { workspace = true } url = "*" tungstenite = { workspace = true } reqwest = "0.12" -webrtc = { workspace = true } \ No newline at end of file +webrtc = { workspace = true } diff --git a/libturms/src/channel.rs b/libturms/src/channel.rs index 652a912..c16a388 100644 --- a/libturms/src/channel.rs +++ b/libturms/src/channel.rs @@ -79,6 +79,7 @@ pub fn handle_channel(sender: mpsc::Sender, webrtc: WebRTCManager) { if data.is_empty() { return; } + let data = crate::padding::Padding::unpad(data); let json = match serde_json::from_slice(&data) { Ok(json) => json, Err(err) => { @@ -130,6 +131,7 @@ async fn handle_dhkey_event(webrtc: &WebRTCManager, x3dh: X3DH) { prekey: Some(pk), })) { Ok(message) => { + let message = crate::padding::Padding::pad(message); if let Err(err) = webrtc.send(message).await { tracing::error!(%err, "failed to send message"); } diff --git a/libturms/src/lib.rs b/libturms/src/lib.rs index 5927466..619afd4 100644 --- a/libturms/src/lib.rs +++ b/libturms/src/lib.rs @@ -5,6 +5,7 @@ pub extern crate error; pub extern crate p2p; mod channel; +mod padding; use discover::spawn_heartbeat; use discover::websocket::WebSocket; diff --git a/libturms/src/padding.rs b/libturms/src/padding.rs new file mode 100644 index 0000000..b7ee145 --- /dev/null +++ b/libturms/src/padding.rs @@ -0,0 +1,54 @@ +//! Deterministic ISO/IEC 7816-4 padding. + +// Minimum required by specification. +const MIN_LENGTH: usize = 1000; // ~1kB. + +/// Padding structure. +#[derive(Debug, Clone)] +pub(crate) struct Padding; + +impl Padding { + fn bucket_size(len: usize) -> usize { + match len { + 0..=MIN_LENGTH => MIN_LENGTH, + 1001..=8192 => len.div_ceil(1024) * 1024, + _ => len, + } + } + + /// Fill an entry with bunch of paddings using ISO/IEC 7816-4. + pub fn pad(data: impl AsRef<[u8]>) -> Vec { + let data = data.as_ref(); + + let target_len = Self::bucket_size(data.len()); + let mut out = Vec::with_capacity(target_len); + + out.extend_from_slice(data); + out.push(0x80); + + if out.len() < target_len { + out.resize(MIN_LENGTH, 0x00); + } + + out + } + + /// Remove zero padding at the end of data using ISO/IEC 7816-4. + pub fn unpad(mut data: Vec) -> Vec { + // Scan from the end. + while let Some(&last) = data.last() { + match last { + 0x00 => { + data.pop(); + }, + 0x80 => { + data.pop(); + return data; + }, + _ => break, + } + } + + data + } +} diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs index 2d067bf..39a2eaf 100644 --- a/p2p/src/lib.rs +++ b/p2p/src/lib.rs @@ -1,8 +1,9 @@ //! peer-to-peer communication via WebRTC. +#![forbid(unsafe_code)] +#![deny(missing_docs, missing_debug_implementations)] + /// Models. pub mod models; -#[forbid(unsafe_code)] -#[deny(missing_docs, missing_debug_implementations)] /// WebRTC interface. pub mod webrtc; /// X3DH over WebRTC for Turms. diff --git a/p2p/src/models.rs b/p2p/src/models.rs index 0a9b81b..93d8f43 100644 --- a/p2p/src/models.rs +++ b/p2p/src/models.rs @@ -17,6 +17,7 @@ pub enum Event { Typing, } +/// Triple Diffie-Hellman exchange. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct X3DH { /// Curve25519 public key. @@ -76,6 +77,7 @@ bitflags! { /// Represents a set of message/attachment flags. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Flags: u32 { + /// Message flagged as urgent. const URGENT = 1 << 0; /// Message MUST NOT be saved. const EPHEMERAL = 1 << 1; diff --git a/p2p/src/webrtc.rs b/p2p/src/webrtc.rs index 50826b0..440cbcc 100644 --- a/p2p/src/webrtc.rs +++ b/p2p/src/webrtc.rs @@ -149,11 +149,11 @@ impl WebRTCManager { } /// Sender with retries. - pub async fn send(&self, message: String) -> Result<()> { + pub async fn send(&self, message: impl AsRef<[u8]>) -> Result<()> { // Encryption is CPU-bound. Async have no effect. May use `spawn_blocking` thread later. let msg = match self.session.clone().lock().as_mut() { Some(session) => session.encrypt(message).message().to_vec(), - None => message.as_bytes().to_vec(), + None => message.as_ref().to_vec(), }; match self.channel.as_ref() {