feat: Sprint 10+11 — privacy hardening and multi-device support
Privacy Hardening (Sprint 10): - Server --redact-logs flag: SHA-256 hashed identity prefixes in audit logs, payload_len omitted when enabled - Client /privacy command suite: redact-keys on|off, auto-clear with duration parsing, padding on|off for traffic analysis resistance - Forward secrecy: /verify-fs checks MLS epoch advancement, /rotate-all-keys rotates MLS leaf + hybrid KEM keypair - Dummy message type (0x09): constant-rate traffic padding every 30s, silently discarded by recipients, serialize_dummy() + parse support - delete_messages_before() for auto-clear in ConversationStore Multi-Device Support (Sprint 11): - Device registry: registerDevice @24, listDevices @25, revokeDevice @26 RPCs with Device struct (deviceId, deviceName, registeredAt) - Server storage: devices table (migration 008), max 5 per identity, E029_DEVICE_LIMIT and E030_DEVICE_NOT_FOUND error codes - Device cleanup integrated into deleteAccount transaction - Client REPL: /devices, /register-device <name>, /revoke-device <id> 72 core + 35 server tests pass.
This commit is contained in:
@@ -27,6 +27,7 @@ pub enum MessageType {
|
||||
Edit = 0x06,
|
||||
Delete = 0x07,
|
||||
FileRef = 0x08,
|
||||
Dummy = 0x09,
|
||||
}
|
||||
|
||||
impl MessageType {
|
||||
@@ -40,6 +41,7 @@ impl MessageType {
|
||||
0x06 => Some(MessageType::Edit),
|
||||
0x07 => Some(MessageType::Delete),
|
||||
0x08 => Some(MessageType::FileRef),
|
||||
0x09 => Some(MessageType::Dummy),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -84,6 +86,8 @@ pub enum AppMessage {
|
||||
file_size: u64,
|
||||
mime_type: Vec<u8>,
|
||||
},
|
||||
/// Dummy message for traffic analysis resistance (no user-visible content).
|
||||
Dummy,
|
||||
}
|
||||
|
||||
/// Generate a new 16-byte message ID (e.g. for Chat/Reply so recipients can reference it).
|
||||
@@ -203,6 +207,11 @@ pub fn serialize_file_ref(
|
||||
Ok(serialize(MessageType::FileRef, &payload))
|
||||
}
|
||||
|
||||
/// Serialize a Dummy message (traffic padding — no user content).
|
||||
pub fn serialize_dummy() -> Vec<u8> {
|
||||
serialize(MessageType::Dummy, &[])
|
||||
}
|
||||
|
||||
/// Parse bytes into (MessageType, AppMessage). Fails if version/type unknown or payload too short.
|
||||
pub fn parse(bytes: &[u8]) -> Result<(MessageType, AppMessage), CoreError> {
|
||||
if bytes.len() < 2 {
|
||||
@@ -225,6 +234,7 @@ pub fn parse(bytes: &[u8]) -> Result<(MessageType, AppMessage), CoreError> {
|
||||
MessageType::Edit => parse_edit(payload)?,
|
||||
MessageType::Delete => parse_delete(payload)?,
|
||||
MessageType::FileRef => parse_file_ref(payload)?,
|
||||
MessageType::Dummy => AppMessage::Dummy,
|
||||
};
|
||||
Ok((msg_type, app))
|
||||
}
|
||||
@@ -502,4 +512,12 @@ mod tests {
|
||||
_ => panic!("expected FileRef"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_dummy() {
|
||||
let encoded = serialize_dummy();
|
||||
let (t, msg) = parse(&encoded).unwrap();
|
||||
assert_eq!(t, MessageType::Dummy);
|
||||
assert_eq!(msg, AppMessage::Dummy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,9 +59,9 @@ pub mod opaque_auth;
|
||||
// ── Public API (always available) ───────────────────────────────────────────
|
||||
|
||||
pub use app_message::{
|
||||
serialize, serialize_chat, serialize_delete, serialize_edit, serialize_file_ref,
|
||||
serialize_reaction, serialize_read_receipt, serialize_reply, serialize_typing,
|
||||
parse, generate_message_id,
|
||||
serialize, serialize_chat, serialize_delete, serialize_dummy, serialize_edit,
|
||||
serialize_file_ref, serialize_reaction, serialize_read_receipt, serialize_reply,
|
||||
serialize_typing, parse, generate_message_id,
|
||||
AppMessage, MessageType, VERSION as APP_MESSAGE_VERSION,
|
||||
};
|
||||
pub use error::CoreError;
|
||||
|
||||
Reference in New Issue
Block a user