Skip to main content

huddle_protocol/
capability.rs

1//! Peer capability bits (huddle 2.2 / M-C4).
2//!
3//! A peer advertises which **new wire forms** it understands so a sender can
4//! retire a legacy/cleartext form *only* once the other end is known-capable —
5//! the additive way to make an otherwise non-additive wire change. Caps ride in
6//! the signed `MemberAnnounce` (per-member, in-room) and in `RoomAnnouncement`
7//! (the announcer's caps, learned at discovery time). Because the carrying
8//! `MemberAnnounce` is signed end-to-end, a relay can't forge or strip caps
9//! without breaking the signature.
10//!
11//! Encoded as a `u32` bitset. Unknown/absent → `0` (a legacy peer), so a missing
12//! field decodes to "supports nothing new" and every gate below stays false.
13//! New bits are append-only; never renumber an existing bit.
14
15/// Code-join v2 (audit PA-1): the peer sends an Argon2 *proof of knowledge* of
16/// the join code bound to its ephemeral X25519 key + room id, instead of the
17/// cleartext bearer code. A v2 joiner omits the cleartext code entirely, so a
18/// malicious relay can no longer read it and rebind it to a forged ephemeral.
19pub const CODE_JOIN_V2: u32 = 1 << 0;
20
21/// Private file metadata (audit FILES-2): the peer accepts a keyed-MAC content
22/// commitment (`EncryptedFileMeta.content_mac_b64`) in place of the plaintext
23/// `content_hash = SHA256(plaintext)`, so the relay no longer sees a
24/// plaintext-confirmation oracle for attachments.
25pub const FILE_META_PRIVATE: u32 = 1 << 1;
26
27/// Everything THIS build implements — advertised in our announces.
28pub const OURS: u32 = CODE_JOIN_V2 | FILE_META_PRIVATE;
29
30/// True iff `caps` advertises `bit`.
31#[inline]
32pub fn supports(caps: u32, bit: u32) -> bool {
33    caps & bit == bit
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn bits_are_distinct_and_in_ours() {
42        assert_ne!(CODE_JOIN_V2, FILE_META_PRIVATE);
43        assert!(supports(OURS, CODE_JOIN_V2));
44        assert!(supports(OURS, FILE_META_PRIVATE));
45    }
46
47    #[test]
48    fn legacy_zero_supports_nothing() {
49        assert!(!supports(0, CODE_JOIN_V2));
50        assert!(!supports(0, FILE_META_PRIVATE));
51    }
52
53    #[test]
54    fn supports_requires_all_bits_of_the_query() {
55        // `supports` is an all-bits-present test, not "any overlap".
56        assert!(supports(CODE_JOIN_V2 | FILE_META_PRIVATE, CODE_JOIN_V2));
57        assert!(!supports(CODE_JOIN_V2, CODE_JOIN_V2 | FILE_META_PRIVATE));
58    }
59}