Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion error/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ jsonwebtoken = { workspace = true }
url = "*"
tungstenite = { workspace = true }
reqwest = "0.12"
webrtc = { workspace = true }
webrtc = { workspace = true }
2 changes: 2 additions & 0 deletions libturms/src/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub fn handle_channel(sender: mpsc::Sender<Event>, 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) => {
Expand Down Expand Up @@ -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");
}
Expand Down
1 change: 1 addition & 0 deletions libturms/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub extern crate error;
pub extern crate p2p;

mod channel;
mod padding;

use discover::spawn_heartbeat;
use discover::websocket::WebSocket;
Expand Down
54 changes: 54 additions & 0 deletions libturms/src/padding.rs
Original file line number Diff line number Diff line change
@@ -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.
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation says "Fill an entry with bunch of paddings" which should be "Fill an entry with padding" or "Add padding to an entry" - the phrase "bunch of paddings" is grammatically incorrect.

Suggested change
/// Fill an entry with bunch of paddings using ISO/IEC 7816-4.
/// Add padding to an entry using ISO/IEC 7816-4.

Copilot uses AI. Check for mistakes.
pub fn pad(data: impl AsRef<[u8]>) -> Vec<u8> {
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.
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation says "Remove zero padding at the end of data" which should be "Remove padding from the end of data" since ISO/IEC 7816-4 padding includes both the 0x80 marker byte and zero bytes.

Suggested change
/// Remove zero padding at the end of data using ISO/IEC 7816-4.
/// Remove padding from the end of data using ISO/IEC 7816-4.

Copilot uses AI. Check for mistakes.
pub fn unpad(mut data: Vec<u8>) -> Vec<u8> {
// Scan from the end.
while let Some(&last) = data.last() {
match last {
0x00 => {
data.pop();
},
0x80 => {
data.pop();
return data;
},
_ => break,
}
}

data
}
}
Comment on lines +1 to +54
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The padding functionality is critical for message privacy but lacks test coverage. Consider adding tests to verify: 1) correct padding for messages under 1000 bytes, 2) correct padding for messages between 1001-8192 bytes (bucket rounding to 1024), 3) correct padding for messages over 8192 bytes, 4) round-trip testing (pad then unpad returns original data), and 5) edge cases like empty messages or messages exactly at bucket boundaries.

Copilot uses AI. Check for mistakes.
5 changes: 3 additions & 2 deletions p2p/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 2 additions & 0 deletions p2p/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub enum Event {
Typing,
}

/// Triple Diffie-Hellman exchange.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct X3DH {
/// Curve25519 public key.
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions p2p/src/webrtc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down