Skip to main content

wechat_protocol/calls/
relay.rs

1//! Selected IPv4 personal relay setup: one initial check-in, at most one learned
2//! continuation and explicit Ready. CRC/TEA structural checks are not media AEAD.
3//! The host owns sockets, current account/key generations, time and randomness.
4
5use crate::protobuf::{self as p, Field, Value};
6use anyhow::{bail, ensure, Result};
7use std::net::{Ipv4Addr, SocketAddrV4};
8use zeroize::Zeroizing;
9
10const MAX_PACKET: usize = 8197;
11const MAX_BODY: usize = 8192;
12
13/// An endpoint selected from an admitted Strategy. No DNS or fallback is used.
14/// Room key and media material intentionally have no Debug representation.
15pub struct SelectedRelay {
16    pub room_id: u64,
17    pub room_key: u64,
18    pub own_member: u16,
19    pub connection_id: u16,
20    pub endpoint: SocketAddrV4,
21    pub media_key: Zeroizing<[u8; 32]>,
22    pub merge_mode: bool,
23}
24impl SelectedRelay {
25    /// Strategy field15 contains modern connection records. Indices must be
26    /// chosen by the caller from the current admitted Strategy, not by retries.
27    pub fn from_strategy(
28        room_id: u64,
29        room_key: u64,
30        own_member: u16,
31        strategy: &[u8],
32        connection_index: usize,
33        udp_index: usize,
34    ) -> Result<Self> {
35        ensure!(room_id != 0 && room_key != 0, "Unallocated relay room");
36        let strategy = fields(strategy)?;
37        let connections = repeated(&strategy, 15, 32)?;
38        let connection = fields(
39            connections
40                .get(connection_index)
41                .ok_or_else(|| anyhow::anyhow!("Relay connection selection unavailable"))?,
42        )?;
43        let connection_id = u16::try_from(number(&connection, 1, None)?)?;
44        if let Some(nic) = p::optional_data(&connection, 2)? {
45            ensure!(
46                nic.len() <= 128 && !nic.contains(&0),
47                "Invalid relay NIC identity"
48            );
49            std::str::from_utf8(nic)?;
50        }
51        u16::try_from(number(&connection, 3, Some(0))?)?;
52        let endpoints = repeated(&connection, 4, 32)?;
53        let endpoint = fields(
54            endpoints
55                .get(udp_index)
56                .ok_or_else(|| anyhow::anyhow!("Relay UDP selection unavailable"))?,
57        )?;
58        let address = ipv4(data(&endpoint, 1)?, false)?;
59        let port = u16::try_from(number(&endpoint, 2, None)?)?;
60        ensure!(port != 0, "Invalid relay endpoint port");
61        let key = data(&strategy, 10)?;
62        ensure!(key.len() == 32, "Unsupported relay media key size");
63        Ok(Self {
64            room_id,
65            room_key,
66            own_member,
67            connection_id,
68            endpoint: SocketAddrV4::new(address, port),
69            media_key: Zeroizing::new(key.try_into()?),
70            merge_mode: number(&strategy, 16, Some(0))? & 1 != 0,
71        })
72    }
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct RelayEpoch {
77    pub account_generation: u64,
78    pub logout_epoch: u64,
79    pub call_generation: u64,
80    pub key_generation: u64,
81    pub socket_generation: u64,
82    pub attempt_id: u64,
83}
84impl RelayEpoch {
85    fn validate(self) -> Result<()> {
86        ensure!(
87            [
88                self.account_generation,
89                self.call_generation,
90                self.key_generation,
91                self.socket_generation,
92                self.attempt_id
93            ]
94            .into_iter()
95            .all(|v| v != 0),
96            "Inactive relay generation"
97        );
98        Ok(())
99    }
100}
101
102/// Selected initial/reset room values. Constructors are deliberately scoped to
103/// this personal profile; they do not describe every supported server transport.
104#[derive(Clone)]
105pub struct RoomInputs {
106    pub room_id: u64,
107    pub own_member: u16,
108    pub connection_id: u16,
109    pub client_ip: u32,
110    pub client_text: String,
111    pub client_port: u16,
112    pub server_ip: u32,
113    pub server_text: String,
114    pub server_port: u16,
115    pub checkin_sequence: u16,
116    pub network_type: u16,
117    pub arq_flag: u16,
118    pub has_accept: bool,
119    pub data_sequence: u32,
120    pub mix_timestamp: u32,
121    pub mix_sequence: u16,
122    pub merge_mode: bool,
123    pub header_timestamp: u32,
124    pub body_timestamp: u32,
125}
126impl RoomInputs {
127    pub fn initial(
128        selected: &SelectedRelay,
129        network_type: u16,
130        header_timestamp: u32,
131        body_timestamp: u32,
132    ) -> Self {
133        Self {
134            room_id: selected.room_id,
135            own_member: selected.own_member,
136            connection_id: selected.connection_id,
137            client_ip: 0,
138            client_text: String::new(),
139            client_port: 0,
140            server_ip: u32::from_le_bytes(selected.endpoint.ip().octets()),
141            server_text: selected.endpoint.ip().to_string(),
142            server_port: selected.endpoint.port(),
143            checkin_sequence: 1,
144            network_type,
145            arq_flag: 0,
146            has_accept: true,
147            data_sequence: 0,
148            mix_timestamp: 0,
149            mix_sequence: 0,
150            merge_mode: selected.merge_mode,
151            header_timestamp,
152            body_timestamp,
153        }
154    }
155    fn validate(&self) -> Result<()> {
156        ensure!(self.room_id != 0, "Unallocated relay room");
157        for text in [&self.client_text, &self.server_text] {
158            ensure!(
159                text.len() <= 128 && text.is_ascii() && !text.as_bytes().contains(&0),
160                "Invalid relay address text"
161            );
162        }
163        Ok(())
164    }
165    pub fn body(&self) -> Result<Vec<u8>> {
166        self.validate()?;
167        let mut values = vec![
168            p::uint(1, 0),
169            p::uint(2, self.room_id & 0xffff_ffff),
170            p::uint(3, u64::from(self.client_ip)),
171            p::uint(4, u64::from(self.server_ip)),
172            p::uint(5, u64::from(self.client_port)),
173            p::uint(6, u64::from(self.server_port)),
174            p::uint(7, u64::from(self.checkin_sequence)),
175            p::uint(8, u64::from(self.own_member)),
176            p::uint(9, u64::from(self.own_member)),
177            p::uint(10, u64::from(self.body_timestamp)),
178            p::uint(12, u64::from(self.arq_flag)),
179            p::uint(13, u64::from(self.network_type)),
180            p::bytes(14, self.client_text.as_bytes()),
181            p::bytes(15, self.server_text.as_bytes()),
182            p::uint(16, 1),
183            p::uint(17, u64::from(self.data_sequence)),
184            p::uint(18, u64::from(self.mix_timestamp)),
185            p::uint(19, u64::from(self.mix_sequence)),
186            p::uint(20, u64::from(self.has_accept)),
187        ];
188        if self.merge_mode {
189            values.push(p::uint(21, 1));
190        }
191        bounded_message(&values)
192    }
193    pub fn random_bytes_needed(&self) -> Result<usize> {
194        Ok((8 - (self.body()?.len() + 10) % 8) % 8 + 3)
195    }
196    fn packet(&self, room_key: u64, random: &[u8]) -> Result<Vec<u8>> {
197        ensure!(!self.merge_mode, "Merged relay check-in unsupported");
198        let cipher = encrypt_checkin(&self.body()?, &*checkin_key(room_key)?, random)?;
199        let pub_header = bounded_message(&[p::uint(1, 2), p::uint(2, 3), p::uint(3, 0)])?;
200        let (room32, room64) = room_pair(self.room_id);
201        let header = bounded_message(&[
202            p::bytes(1, &pub_header),
203            p::uint(2, room32),
204            p::uint(3, 10),
205            p::uint(4, u64::from(self.own_member)),
206            p::bytes(5, &[]),
207            p::uint(6, u64::from(self.own_member)),
208            p::uint(8, u64::from(self.header_timestamp)),
209            p::uint(9, room64),
210            p::uint(10, u64::from(self.connection_id)),
211        ])?;
212        frame_outer(&bounded_message(&[
213            p::bytes(1, &header),
214            p::uint(2, cipher.len() as u64),
215            p::bytes(3, &cipher),
216        ])?)
217    }
218}
219
220#[derive(Clone, Copy, Debug, PartialEq, Eq)]
221pub enum RelayProgress {
222    Opaque,
223    FirstCheckin,
224    DuplicateCheckin,
225    Ready,
226}
227#[derive(Clone, Copy, Debug, PartialEq, Eq)]
228enum Phase {
229    New,
230    AwaitingCheckin,
231    ContinuationPending,
232    AwaitingReady,
233    Ready,
234    Closed,
235}
236
237/// No retries or network I/O. Every preparation is spent before randomness or
238/// encryption; failure closes this attempt instead of making it reusable.
239pub struct TwoRttRelay {
240    room: RoomInputs,
241    key: Zeroizing<u64>,
242    endpoint: SocketAddrV4,
243    epoch: RelayEpoch,
244    deadline_ms: u64,
245    last_ms: Option<u64>,
246    phase: Phase,
247}
248impl TwoRttRelay {
249    pub fn new(
250        room: RoomInputs,
251        room_key: u64,
252        endpoint: SocketAddrV4,
253        epoch: RelayEpoch,
254        deadline_ms: u64,
255    ) -> Result<Self> {
256        room.validate()?;
257        epoch.validate()?;
258        ensure!(
259            room_key != 0 && endpoint.port() != 0,
260            "Invalid relay key or endpoint"
261        );
262        ensure!(
263            room.server_text == endpoint.ip().to_string()
264                && room.server_ip == u32::from_le_bytes(endpoint.ip().octets())
265                && room.server_port == endpoint.port(),
266            "Relay request endpoint mismatch"
267        );
268        Ok(Self {
269            room,
270            key: Zeroizing::new(room_key),
271            endpoint,
272            epoch,
273            deadline_ms,
274            last_ms: None,
275            phase: Phase::New,
276        })
277    }
278    pub fn room(&self) -> &RoomInputs {
279        &self.room
280    }
281    pub fn is_ready(&self) -> bool {
282        self.phase == Phase::Ready
283    }
284    pub fn random_bytes_needed(&self) -> Result<usize> {
285        self.room.random_bytes_needed()
286    }
287    pub fn continuation_random_bytes_needed(
288        &self,
289        header_timestamp: u32,
290        body_timestamp: u32,
291    ) -> Result<usize> {
292        let mut room = self.room.clone();
293        room.header_timestamp = header_timestamp;
294        room.body_timestamp = body_timestamp;
295        room.random_bytes_needed()
296    }
297
298    fn live(&mut self, epoch: RelayEpoch, now_ms: u64) -> Result<()> {
299        if epoch.validate().is_err()
300            || epoch != self.epoch
301            || now_ms >= self.deadline_ms
302            || self.last_ms.is_some_and(|last| now_ms < last)
303            || self.phase == Phase::Closed
304        {
305            self.close();
306            bail!("Relay generation, clock or deadline changed");
307        }
308        self.last_ms = Some(now_ms);
309        Ok(())
310    }
311    pub fn prepare_initial(
312        &mut self,
313        epoch: RelayEpoch,
314        now_ms: u64,
315        random: &[u8],
316    ) -> Result<Vec<u8>> {
317        self.live(epoch, now_ms)?;
318        ensure!(
319            self.phase == Phase::New,
320            "Relay initial check-in already spent"
321        );
322        self.phase = Phase::Closed;
323        let packet = self.room.packet(*self.key, random)?;
324        self.phase = Phase::AwaitingCheckin;
325        Ok(packet)
326    }
327    pub fn prepare_continuation(
328        &mut self,
329        epoch: RelayEpoch,
330        now_ms: u64,
331        header_timestamp: u32,
332        body_timestamp: u32,
333        random: &[u8],
334    ) -> Result<Vec<u8>> {
335        self.live(epoch, now_ms)?;
336        ensure!(
337            self.phase == Phase::ContinuationPending,
338            "Relay continuation unavailable or spent"
339        );
340        self.phase = Phase::Closed;
341        self.room.header_timestamp = header_timestamp;
342        self.room.body_timestamp = body_timestamp;
343        let packet = self.room.packet(*self.key, random)?;
344        self.phase = Phase::AwaitingReady;
345        Ok(packet)
346    }
347    pub fn receive(
348        &mut self,
349        packet: &[u8],
350        source: SocketAddrV4,
351        epoch: RelayEpoch,
352        now_ms: u64,
353    ) -> Result<RelayProgress> {
354        self.live(epoch, now_ms)?;
355        ensure!(
356            matches!(
357                self.phase,
358                Phase::AwaitingCheckin | Phase::ContinuationPending | Phase::AwaitingReady
359            ),
360            "Relay receive unavailable"
361        );
362        let result = self.receive_checked(packet, source);
363        if result.is_err() {
364            self.close();
365        }
366        result
367    }
368    fn receive_checked(&mut self, packet: &[u8], source: SocketAddrV4) -> Result<RelayProgress> {
369        ensure!(source == self.endpoint, "Unexpected relay source endpoint");
370        ensure!(packet.len() <= MAX_PACKET, "Relay packet too large");
371        if packet.first() != Some(&0xd5) {
372            return Ok(RelayProgress::Opaque);
373        }
374        let outer = fields(unframe_outer(packet)?)?;
375        let header = fields(data(&outer, 1)?)?;
376        let public = fields(data(&header, 1)?)?;
377        let command = number(&public, 1, None)?;
378        if !matches!(command, 0x1002 | 0x1004) {
379            return Ok(RelayProgress::Opaque);
380        }
381        ensure!(
382            number(&public, 2, None)? == 3 && number(&public, 3, None)? == 0,
383            "Unsupported relay response profile"
384        );
385        let (room32, room64) = room_pair(self.room.room_id);
386        ensure!(
387            number(&header, 2, None)? == room32
388                && number(&header, 5, Some(0))? == room64
389                && number(&header, 3, None)? == 10
390                && number(&header, 7, Some(0))? == 0,
391            "Relay response context mismatch"
392        );
393        let cipher = data(&outer, 3)?;
394        ensure!(
395            number(&outer, 2, None)? == cipher.len() as u64,
396            "Relay response length mismatch"
397        );
398        let clear = decrypt_checkin(cipher, &*checkin_key(*self.key)?, 4096)?;
399        if command == 0x1002 && self.phase != Phase::AwaitingCheckin {
400            return Ok(RelayProgress::DuplicateCheckin);
401        }
402        let body = fields(&clear)?;
403        if command == 0x1004 {
404            for n in [1, 2, 3, 5, 6, 7] {
405                let v = number(&body, n, None)?;
406                if n != 1 {
407                    u32::try_from(v)?;
408                }
409            }
410            repeated(&body, 4, 64)?;
411            ensure!(
412                [number(&body, 5, None)?, number(&body, 7, None)?]
413                    .contains(&u64::from(self.room.own_member)),
414                "Relay Ready member mismatch"
415            );
416            self.phase = Phase::Ready;
417            return Ok(RelayProgress::Ready);
418        }
419        for n in 1..=9 {
420            u32::try_from(number(&body, n, None)?)?;
421        }
422        ensure!(
423            number(&body, 6, None)? == u64::from(self.room.checkin_sequence)
424                && number(&body, 14, Some(0))? == 0,
425            "Relay check-in sequence or merge mismatch"
426        );
427        let client = p::optional_data(&body, 12)?.unwrap_or_default();
428        let server = p::optional_data(&body, 13)?.unwrap_or_default();
429        let client_address = ipv4(client, true)?;
430        let server_address = ipv4(server, true)?;
431        let client_numeric = number(&body, 2, None)? as u32;
432        self.room.client_ip = if client_numeric == 0 {
433            u32::from_le_bytes(client_address.octets())
434        } else {
435            client_numeric
436        };
437        self.room.client_text = std::str::from_utf8(client)?.to_owned();
438        self.room.server_text = std::str::from_utf8(server)?.to_owned();
439        self.room.server_ip = u32::from_le_bytes(server_address.octets());
440        self.room.client_port = number(&body, 4, None)? as u16;
441        self.room.server_port = number(&body, 5, None)? as u16;
442        self.phase = Phase::ContinuationPending;
443        Ok(RelayProgress::FirstCheckin)
444    }
445    pub fn close(&mut self) {
446        self.key = Zeroizing::new(0);
447        self.phase = Phase::Closed;
448    }
449}
450
451fn room_pair(room: u64) -> (u64, u64) {
452    if room <= u64::from(u32::MAX) {
453        (room, 0)
454    } else {
455        (0, room)
456    }
457}
458fn fields(raw: &[u8]) -> Result<Vec<Field>> {
459    ensure!(raw.len() <= MAX_BODY, "Relay message too large");
460    super::signaling::decode_fields(raw)
461}
462fn bounded_message(parts: &[Vec<u8>]) -> Result<Vec<u8>> {
463    let out = p::message(parts)?;
464    ensure!(out.len() <= MAX_BODY, "Relay message too large");
465    Ok(out)
466}
467fn data(values: &[Field], n: u32) -> Result<&[u8]> {
468    p::optional_data(values, n)?.ok_or_else(|| anyhow::anyhow!("Missing relay bytes field {n}"))
469}
470fn number(values: &[Field], n: u32, default: Option<u64>) -> Result<u64> {
471    match p::optional(values, n)? {
472        Some(Value::Varint(v)) => Ok(*v),
473        None => default.ok_or_else(|| anyhow::anyhow!("Missing relay integer {n}")),
474        _ => bail!("Invalid relay integer {n}"),
475    }
476}
477fn repeated(values: &[Field], n: u32, limit: usize) -> Result<Vec<&[u8]>> {
478    let mut out = Vec::new();
479    for f in values.iter().filter(|v| v.number == n) {
480        ensure!(out.len() < limit, "Too many relay entries");
481        if let Value::Bytes(raw) = &f.value {
482            ensure!(raw.len() <= 4096, "Relay entry too large");
483            fields(raw)?;
484            out.push(raw.as_slice());
485        } else {
486            bail!("Invalid relay entry wire type");
487        }
488    }
489    Ok(out)
490}
491fn ipv4(raw: &[u8], allow_empty: bool) -> Result<Ipv4Addr> {
492    if raw.is_empty() && allow_empty {
493        return Ok(Ipv4Addr::UNSPECIFIED);
494    }
495    ensure!(raw.len() <= 15, "Relay IPv4 text too large");
496    let text = std::str::from_utf8(raw)?;
497    let ip: Ipv4Addr = text.parse()?;
498    ensure!(ip.to_string() == text, "Noncanonical relay IPv4 address");
499    Ok(ip)
500}
501fn checkin_key(room_key: u64) -> Result<Zeroizing<[u8; 16]>> {
502    ensure!(room_key != 0, "Missing relay key");
503    let digest = openssl::hash::hash(openssl::hash::MessageDigest::md5(), &room_key.to_le_bytes())?;
504    Ok(Zeroizing::new(digest.as_ref().try_into()?))
505}
506
507pub fn frame_outer(body: &[u8]) -> Result<Vec<u8>> {
508    ensure!(
509        !body.is_empty() && body.len() <= MAX_BODY,
510        "Invalid relay outer size"
511    );
512    let mut out = vec![0xd5];
513    out.extend(crc32(body).to_be_bytes());
514    out.extend(body);
515    Ok(out)
516}
517pub fn unframe_outer(packet: &[u8]) -> Result<&[u8]> {
518    ensure!(
519        (6..=MAX_PACKET).contains(&packet.len()) && packet[0] == 0xd5,
520        "Invalid relay outer packet"
521    );
522    let body = &packet[5..];
523    ensure!(
524        u32::from_be_bytes(packet[1..5].try_into()?) == crc32(body),
525        "Relay CRC mismatch"
526    );
527    Ok(body)
528}
529fn crc32(body: &[u8]) -> u32 {
530    let mut value = u32::MAX;
531    for byte in body {
532        value ^= u32::from(*byte);
533        for _ in 0..8 {
534            value = (value >> 1) ^ if value & 1 != 0 { 0xedb88320 } else { 0 };
535        }
536    }
537    value ^ u32::MAX
538}
539fn tea(block: [u8; 8], key: &[u8; 16], decrypt: bool) -> [u8; 8] {
540    let mut a = u32::from_be_bytes(block[..4].try_into().expect("block width"));
541    let mut b = u32::from_be_bytes(block[4..].try_into().expect("block width"));
542    let k: [u32; 4] = std::array::from_fn(|n| {
543        u32::from_be_bytes(key[n * 4..n * 4 + 4].try_into().expect("key width"))
544    });
545    if decrypt {
546        let mut sum = 0xe3779b90u32;
547        for _ in 0..16 {
548            b = b.wrapping_sub(
549                (a.wrapping_shl(4).wrapping_add(k[2]))
550                    ^ (a.wrapping_add(sum))
551                    ^ ((a >> 5).wrapping_add(k[3])),
552            );
553            a = a.wrapping_sub(
554                (b.wrapping_shl(4).wrapping_add(k[0]))
555                    ^ (b.wrapping_add(sum))
556                    ^ ((b >> 5).wrapping_add(k[1])),
557            );
558            sum = sum.wrapping_sub(0x9e3779b9);
559        }
560    } else {
561        let mut sum = 0u32;
562        for _ in 0..16 {
563            sum = sum.wrapping_add(0x9e3779b9);
564            a = a.wrapping_add(
565                (b.wrapping_shl(4).wrapping_add(k[0]))
566                    ^ (b.wrapping_add(sum))
567                    ^ ((b >> 5).wrapping_add(k[1])),
568            );
569            b = b.wrapping_add(
570                (a.wrapping_shl(4).wrapping_add(k[2]))
571                    ^ (a.wrapping_add(sum))
572                    ^ ((a >> 5).wrapping_add(k[3])),
573            );
574        }
575    }
576    let mut out = [0; 8];
577    out[..4].copy_from_slice(&a.to_be_bytes());
578    out[4..].copy_from_slice(&b.to_be_bytes());
579    out
580}
581/// Selector0 TEA chaining with its structural trailer. This is not an AEAD.
582pub fn encrypt_checkin(plain: &[u8], key: &[u8; 16], random: &[u8]) -> Result<Vec<u8>> {
583    ensure!(plain.len() <= 4096, "Relay check-in plaintext too large");
584    let pad = (8 - (plain.len() + 10) % 8) % 8;
585    ensure!(
586        random.len() == pad + 3,
587        "Wrong relay check-in randomness length"
588    );
589    let mut clear = Zeroizing::new(Vec::with_capacity(plain.len() + pad + 10));
590    clear.push((random[0] & 0xf8) | pad as u8);
591    clear.extend(&random[1..]);
592    clear.extend(plain);
593    clear.extend([0; 7]);
594    let mut previous_cipher = [0; 8];
595    let mut previous_x = [0; 8];
596    let mut out = Vec::with_capacity(clear.len());
597    for block in clear.chunks_exact(8) {
598        let x = std::array::from_fn(|n| block[n] ^ previous_cipher[n]);
599        let encrypted = tea(x, key, false);
600        let cipher = std::array::from_fn(|n| encrypted[n] ^ previous_x[n]);
601        out.extend(cipher);
602        previous_x = x;
603        previous_cipher = cipher;
604    }
605    Ok(out)
606}
607pub fn decrypt_checkin(cipher: &[u8], key: &[u8; 16], limit: usize) -> Result<Zeroizing<Vec<u8>>> {
608    ensure!(
609        (16..=4113).contains(&cipher.len()) && cipher.len().is_multiple_of(8) && limit <= 4096,
610        "Invalid relay check-in ciphertext size"
611    );
612    let mut previous_cipher = [0; 8];
613    let mut previous_x = [0; 8];
614    let mut clear = Zeroizing::new(Vec::with_capacity(cipher.len()));
615    for block in cipher.chunks_exact(8) {
616        let c: [u8; 8] = block.try_into()?;
617        let x = tea(std::array::from_fn(|n| c[n] ^ previous_x[n]), key, true);
618        clear.extend((0..8).map(|n| x[n] ^ previous_cipher[n]));
619        previous_x = x;
620        previous_cipher = c;
621    }
622    let start = usize::from(clear[0] & 7) + 3;
623    ensure!(
624        start + 7 <= clear.len() && clear[clear.len() - 7..] == [0; 7],
625        "Invalid relay check-in trailer"
626    );
627    ensure!(
628        clear.len() - start - 7 <= limit,
629        "Relay check-in output exceeds limit"
630    );
631    Ok(Zeroizing::new(clear[start..clear.len() - 7].to_vec()))
632}
633
634#[cfg(test)]
635mod tests {
636    use super::*;
637    #[test]
638    fn tea_and_crc_literal_vectors() {
639        assert_eq!(
640            tea([0; 8], &[0; 16], false),
641            [0xa8, 0x89, 0xf7, 0x98, 0x18, 0x2d, 0x80, 0x83]
642        );
643        assert_eq!(
644            tea(
645                [0xa8, 0x89, 0xf7, 0x98, 0x18, 0x2d, 0x80, 0x83],
646                &[0; 16],
647                true
648            ),
649            [0; 8]
650        );
651        assert_eq!(crc32(b"123456789"), 0xcbf43926);
652    }
653    #[test]
654    fn transform_roundtrip_structural_and_output_bounds() {
655        for n in [0, 1, 7, 8, 128, 4096] {
656            let input = vec![42; n];
657            let random = vec![9; (8 - (n + 10) % 8) % 8 + 3];
658            let key = [7; 16];
659            let cipher = encrypt_checkin(&input, &key, &random).unwrap();
660            assert_eq!(&*decrypt_checkin(&cipher, &key, 4096).unwrap(), &input);
661            assert!(decrypt_checkin(&cipher, &key, n.saturating_sub(1)).is_err() || n == 0);
662            let mut corrupt = cipher;
663            *corrupt.last_mut().unwrap() ^= 1;
664            assert!(decrypt_checkin(&corrupt, &key, 4096).is_err());
665        }
666    }
667    fn epoch() -> RelayEpoch {
668        RelayEpoch {
669            account_generation: 1,
670            logout_epoch: 0,
671            call_generation: 1,
672            key_generation: 1,
673            socket_generation: 1,
674            attempt_id: 1,
675        }
676    }
677    fn selected() -> SelectedRelay {
678        SelectedRelay {
679            room_id: 7,
680            room_key: 9,
681            own_member: 0,
682            connection_id: 1,
683            endpoint: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 6000),
684            media_key: Zeroizing::new([3; 32]),
685            merge_mode: false,
686        }
687    }
688    fn reply(command: u64, body: &[u8]) -> Vec<u8> {
689        let public = p::message(&[p::uint(1, command), p::uint(2, 3), p::uint(3, 0)]).unwrap();
690        let header = p::message(&[p::bytes(1, &public), p::uint(2, 7), p::uint(3, 10)]).unwrap();
691        let rand = vec![5; (8 - (body.len() + 10) % 8) % 8 + 3];
692        let encrypted = encrypt_checkin(body, &checkin_key(9).unwrap(), &rand).unwrap();
693        frame_outer(
694            &p::message(&[
695                p::bytes(1, &header),
696                p::uint(2, encrypted.len() as u64),
697                p::bytes(3, &encrypted),
698            ])
699            .unwrap(),
700        )
701        .unwrap()
702    }
703    #[test]
704    fn two_turn_setup_never_changes_actual_reply_destination() {
705        let selected = selected();
706        let mut attempt = TwoRttRelay::new(
707            RoomInputs::initial(&selected, 0, 0, 0),
708            9,
709            selected.endpoint,
710            epoch(),
711            100,
712        )
713        .unwrap();
714        let random = vec![1; attempt.random_bytes_needed().unwrap()];
715        attempt.prepare_initial(epoch(), 1, &random).unwrap();
716        assert!(attempt.prepare_initial(epoch(), 2, &random).is_err());
717        let body = p::message(
718            &(1..=9)
719                .map(|n| p::uint(n, if n == 6 { 1 } else { 0 }))
720                .chain([p::bytes(12, b"192.0.2.1"), p::bytes(13, b"198.51.100.1")])
721                .collect::<Vec<_>>(),
722        )
723        .unwrap();
724        assert_eq!(
725            attempt
726                .receive(&reply(0x1002, &body), selected.endpoint, epoch(), 3)
727                .unwrap(),
728            RelayProgress::FirstCheckin
729        );
730        assert_eq!(attempt.room().server_text, "198.51.100.1");
731        let random = vec![2; attempt.random_bytes_needed().unwrap()];
732        attempt
733            .prepare_continuation(epoch(), 4, 1, 1, &random)
734            .unwrap();
735        let ready = p::message(
736            &(1..=7)
737                .filter(|n| *n != 4)
738                .map(|n| p::uint(n, 0))
739                .collect::<Vec<_>>(),
740        )
741        .unwrap();
742        assert_eq!(
743            attempt
744                .receive(&reply(0x1004, &ready), selected.endpoint, epoch(), 5)
745                .unwrap(),
746            RelayProgress::Ready
747        );
748        assert!(attempt.is_ready());
749    }
750}