Skip to main content

wechat_protocol/calls/
voice_offer.rs

1//! Local voice advertisements are distinct from returned selected capabilities.
2//! A fresh offer advertises only the supported Opus17 bit. The server's selected
3//! codec number, packet format and channel flags require separate admission.
4
5use super::client::VoiceParameters;
6use crate::{crypto, protobuf as p};
7use anyhow::Result;
8use zeroize::Zeroizing;
9
10/// Owned request metadata for one call generation. It intentionally has no
11/// `Clone` or `Debug`: create a fresh identity for each new call. The embedding
12/// owner must supply new random bytes and keep this value until that call ends.
13pub struct VoiceOffer {
14    sdk: Vec<u8>,
15    engine: Vec<u8>,
16    channel_internal: Zeroizing<[u8; 12]>,
17    channel_advertised: [u8; 12],
18}
19impl VoiceOffer {
20    /// `owner_version` is this embedding client's version, not a captured peer's
21    /// version. Signed int32 representation is preserved in engine field27.
22    /// MD5 reproduces the channel identity transform; it is not authentication.
23    pub fn from_owned_random(owner_version: i32, random32: [u8; 32]) -> Result<Self> {
24        let random32 = Zeroizing::new(random32);
25        let digest = Zeroizing::new(crypto::md5(random32.as_ref()));
26        let mut internal = Zeroizing::new([0; 12]);
27        internal.copy_from_slice(&digest[..12]);
28        let mut advertised = *internal;
29        advertised[..4].reverse();
30        advertised[4..8].reverse();
31        advertised[8..10].reverse();
32        advertised[10..12].reverse();
33        let mut engine = Vec::new();
34        for field in (1..=27).chain(36..=40) {
35            if field == 27 {
36                engine.extend(p::int32(field, owner_version));
37            } else {
38                let value = match field {
39                    1 => 0x457,
40                    5 => 0x40000,
41                    6 => 0x100,
42                    25 => 5,
43                    26 => 2,
44                    _ => 0,
45                };
46                engine.extend(p::uint(field, value));
47            }
48        }
49        let sdk = p::message(&[
50            p::uint(1, 0x050000cb),
51            p::bytes(2, format!("0x{:x}", owner_version as u32).as_bytes()),
52        ])?;
53        Ok(Self {
54            sdk,
55            engine,
56            channel_internal: internal,
57            channel_advertised: advertised,
58        })
59    }
60    pub fn sdk_info(&self) -> &[u8] {
61        &self.sdk
62    }
63    pub fn engine_advertisement(&self) -> &[u8] {
64        &self.engine
65    }
66    pub fn advertised_channel_identity(&self) -> &[u8; 12] {
67        &self.channel_advertised
68    }
69    pub fn internal_channel_identity(&self) -> &[u8; 12] {
70        &self.channel_internal
71    }
72    /// Replace only request-side fields. Negotiated response buffers must never
73    /// be fed back through these fields as the next call's advertised support.
74    pub fn apply_to(&self, parameters: &mut VoiceParameters) {
75        parameters.sdk = self.sdk.clone();
76        parameters.engine_capability = self.engine.clone();
77        parameters.channel_capability = self.channel_advertised.to_vec();
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    #[test]
85    fn request_advertises_a_mask_and_fresh_channel_identity_not_selected_buffers() {
86        let seed = std::array::from_fn(|i| i as u8);
87        let offer = VoiceOffer::from_owned_random(0x01020300, seed).unwrap();
88        let fields = p::decode(offer.engine_advertisement()).unwrap();
89        assert_eq!(p::scalar(&fields, 1).unwrap(), 0x457);
90        assert_eq!(p::scalar(&fields, 5).unwrap(), 0x40000);
91        assert_ne!(p::scalar(&fields, 5).unwrap(), 17);
92        assert_eq!(p::scalar(&fields, 6).unwrap(), 0x100);
93        assert_eq!(p::scalar(&fields, 7).unwrap(), 0);
94        assert_eq!(p::scalar(&fields, 25).unwrap(), 5);
95        assert_eq!(p::scalar(&fields, 26).unwrap(), 2);
96        assert_eq!(p::scalar(&fields, 27).unwrap(), 0x01020300);
97        assert_eq!(fields.len(), 32);
98        assert_eq!(
99            hex::encode(offer.internal_channel_identity()),
100            "b4ffcb23737cec315a4a4d1a"
101        );
102        assert_eq!(
103            hex::encode(offer.advertised_channel_identity()),
104            "23cbffb431ec7c734a5a1a4d"
105        );
106        assert_ne!(
107            offer.advertised_channel_identity().as_slice(),
108            p::message(&[p::uint(1, 2), p::uint(2, 3)]).unwrap()
109        );
110        let next = VoiceOffer::from_owned_random(0x01020300, [7; 32]).unwrap();
111        assert_ne!(
112            next.advertised_channel_identity(),
113            offer.advertised_channel_identity()
114        );
115        let signed = VoiceOffer::from_owned_random(i32::MIN, [1; 32]).unwrap();
116        assert_eq!(
117            p::scalar(&p::decode(signed.engine_advertisement()).unwrap(), 27).unwrap() as i64,
118            i64::from(i32::MIN)
119        );
120        assert_eq!(
121            p::data(&p::decode(signed.sdk_info()).unwrap(), 2).unwrap(),
122            b"0x80000000"
123        );
124    }
125}