1use super::signaling::{self, CallOperation, CloudRequest, CommonRequest};
8use crate::protobuf::{self as p, Field, Value};
9use anyhow::{bail, ensure, Result};
10
11const MAX_BODY: usize = 64 * 1024;
12const MAX_NESTED: usize = 16 * 1024;
13const MAX_MEMBERS: usize = 16;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum HostOperation {
18 RingingAck,
19 Invite,
20 Cancel,
21 LiveSync,
22}
23
24impl HostOperation {
25 pub const fn command(self) -> u16 {
26 match self {
27 Self::RingingAck => 8069,
28 Self::Invite => 10342,
29 Self::Cancel => 9332,
30 Self::LiveSync => 9262,
31 }
32 }
33
34 pub const fn path(self) -> &'static str {
35 match self {
36 Self::RingingAck => "/cgi-bin/micromsg-bin/voipilinkack",
37 Self::Invite => "/cgi-bin/micromsg-bin/voipilinkinvite",
38 Self::Cancel => "/cgi-bin/micromsg-bin/voipilinkcancel",
39 Self::LiveSync => "/cgi-bin/micromsg-bin/voipilinklivesync",
40 }
41 }
42}
43
44pub struct HostRequest {
47 pub operation: HostOperation,
48 pub sdk_task: u32,
49 body: Vec<u8>,
50 pub longpoll_seconds: Option<u32>,
51}
52
53impl HostRequest {
54 pub fn body(&self) -> &[u8] {
55 &self.body
56 }
57
58 pub fn common(&self, host_base: Vec<u8>) -> Result<Vec<u8>> {
61 CommonRequest {
62 base_request: Some(host_base),
63 body: Some(self.body.clone()),
64 unknown_fields: vec![],
65 }
66 .encode()
67 }
68}
69
70pub struct VoiceInputs<'a> {
73 pub sdk: &'a [u8],
74 pub group: &'a [u8],
75 pub device: &'a [u8],
76 pub engine_capability: &'a [u8],
77 pub channel_capability: &'a [u8],
78 pub network_type: u32,
79 pub sim_type: u32,
80 pub video_length: u32,
81 pub video_ratio: u32,
82 pub nics: &'a [Vec<u8>],
83}
84
85impl VoiceInputs<'_> {
86 pub fn validate(&self) -> Result<()> {
87 self.checked().map(|_| ())
88 }
89
90 fn checked(&self) -> Result<(u32, Vec<u8>, Vec<u8>)> {
91 ensure!(
92 self.network_type < 8 && self.nics.len() <= 8,
93 "Unsupported voice input configuration"
94 );
95 let sdk = fields(self.sdk)?;
96 let version = required_u32(&sdk, 1)?;
97 let owner_version = required_bytes(&sdk, 2)?;
98 text(owner_version, 128, true)?;
99 let group = fields(self.group)?;
100 ensure!(
101 required_bytes(&group, 1)? == b"wechat",
102 "Unsupported call application"
103 );
104 required_u64(&group, 2)?;
105 ensure!(
106 required_bytes(&group, 3)? == b"voip-2p",
107 "Unsupported call target kind"
108 );
109 text(required_bytes(&group, 4)?, 1024, false)?;
110 ensure!(
111 required_u32(&group, 5)? <= 1,
112 "Unsupported personal call mode"
113 );
114 let owner_group = required_bytes(&group, 6)?;
115 text(owner_group, 1024, false)?;
116 required_u32(&group, 7)?;
117 let device = fields(self.device)?;
118 for n in [1, 2, 3, 4, 5, 9] {
119 if let Some(v) = p::optional_data(&device, n)? {
120 text(v, 1024, false)?;
121 }
122 }
123 for n in [7, 8] {
124 signaling::optional_u32(&device, n)?;
125 }
126 p::optional_data(&device, 6)?;
127 for value in [self.engine_capability, self.channel_capability] {
128 ensure!(
129 !value.is_empty() && value.len() <= MAX_NESTED,
130 "Invalid call capability size"
131 );
132 }
133 for nic in self.nics {
134 ensure!(nic.len() <= 2048, "Call NIC description too large");
135 fields(nic)?;
136 }
137 Ok((version, owner_version.to_vec(), owner_group.to_vec()))
138 }
139
140 fn client_info(&self) -> Result<Vec<u8>> {
141 let (version, owner_version, _) = self.checked()?;
142 let capabilities = message(&[
143 p::bytes(1, self.engine_capability),
144 p::bytes(2, self.channel_capability),
145 ])?;
146 let mut values = vec![
147 p::uint(1, u64::from(version)),
148 p::bytes(2, &owner_version),
149 p::uint(3, u64::from(self.network_type)),
150 p::uint(4, 1),
151 p::bytes(5, &capabilities),
152 p::bytes(6, self.device),
153 p::uint(7, u64::from(self.video_length)),
154 p::uint(8, u64::from(self.video_ratio)),
155 ];
156 values.extend(self.nics.iter().map(|nic| p::bytes(9, nic)));
157 values.push(p::uint(10, u64::from(self.sim_type)));
158 message(&values)
159 }
160}
161
162pub fn invite(
166 inputs: &VoiceInputs<'_>,
167 peer: &str,
168 invite_id: u64,
169 sdk_task: u32,
170 custom_auth: Option<&[u8]>,
171) -> Result<HostRequest> {
172 text(peer.as_bytes(), 128, true)?;
173 ensure!(
174 invite_id != 0 && sdk_task != 0,
175 "Missing call intent or task"
176 );
177 let (_, _, owner_group) = inputs.checked()?;
178 let mut values = vec![
179 p::bytes(1, peer.as_bytes()),
180 p::uint(2, 2),
181 p::bytes(3, &owner_group),
182 p::bytes(4, &inputs.client_info()?),
183 p::uint(5, invite_id),
184 ];
185 if let Some(auth) = custom_auth {
186 ensure!(
187 !auth.is_empty() && auth.len() <= 8192,
188 "Invalid custom call authorization"
189 );
190 values.push(p::bytes(6, auth));
191 }
192 values.push(p::bytes(7, b"voip-2p"));
193 Ok(HostRequest {
194 operation: HostOperation::Invite,
195 sdk_task,
196 body: message(&values)?,
197 longpoll_seconds: None,
198 })
199}
200
201pub fn cancel(
205 inputs: &VoiceInputs<'_>,
206 room: u64,
207 room_key: u64,
208 invite_id: u64,
209 pending_recipients: &[String],
210 reason: CancelReason,
211 sdk_task: u32,
212) -> Result<HostRequest> {
213 inputs.checked()?;
214 ensure!(
215 invite_id != 0 && sdk_task != 0 && (room == 0) == (room_key == 0),
216 "Invalid cancellation context"
217 );
218 ensure!(
219 !pending_recipients.is_empty() && pending_recipients.len() <= 8,
220 "Invalid pending recipient count"
221 );
222 for peer in pending_recipients {
223 text(peer.as_bytes(), 128, true)?;
224 }
225 let inner = message(&[
226 p::bytes(1, inputs.sdk),
227 p::bytes(2, inputs.group),
228 p::uint(3, room),
229 p::uint(4, room_key),
230 p::uint(5, invite_id),
231 p::uint(6, reason.wire()),
232 ])?;
233 let mut outer = vec![p::bytes(1, &inner), p::uint(2, invite_id), p::uint(3, room)];
234 outer.extend(
235 pending_recipients
236 .iter()
237 .map(|name| p::bytes(4, name.as_bytes())),
238 );
239 outer.push(p::bytes(5, b"voip-2p"));
240 Ok(HostRequest {
241 operation: HostOperation::Cancel,
242 sdk_task,
243 body: message(&outer)?,
244 longpoll_seconds: None,
245 })
246}
247
248#[derive(Clone, Copy, Debug, PartialEq, Eq)]
249pub enum CancelReason {
250 Local,
251 Timeout,
252 NoResponse,
255 Other,
256}
257
258impl CancelReason {
259 const fn wire(self) -> u64 {
260 match self {
261 Self::Local => 1,
262 Self::Timeout => 4,
263 Self::NoResponse => 2,
264 Self::Other => 0,
265 }
266 }
267}
268
269pub fn live_sync(
273 live_id: u64,
274 cookies: &[u8],
275 timeout_seconds: u32,
276 sdk_task: u32,
277) -> Result<HostRequest> {
278 ensure!(
279 live_id != 0 && sdk_task != 0 && (3..=50).contains(&timeout_seconds),
280 "Invalid call sync context"
281 );
282 ensure!(cookies.len() <= MAX_NESTED, "Call sync cookie too large");
283 Ok(HostRequest {
284 operation: HostOperation::LiveSync,
285 sdk_task,
286 body: message(&[
287 p::uint(1, live_id),
288 p::bytes(2, cookies),
289 p::bytes(4, b"voip-2p"),
290 p::uint(5, u64::from(timeout_seconds)),
291 ])?,
292 longpoll_seconds: Some(timeout_seconds + 5),
293 })
294}
295
296#[derive(Clone, Copy, PartialEq, Eq)]
299pub struct VoiceRoom {
300 pub room: u64,
301 pub room_key: u64,
302 pub own_member: u32,
303}
304impl VoiceRoom {
305 fn validate(self) -> Result<()> {
306 ensure!(
307 self.room != 0 && self.room_key != 0,
308 "Unallocated call room"
309 );
310 Ok(())
311 }
312}
313
314pub fn acknowledge_voice(
316 inputs: &VoiceInputs<'_>,
317 room: VoiceRoom,
318 sdk_task: u32,
319) -> Result<HostRequest> {
320 inputs.checked()?;
321 room.validate()?;
322 ensure!(sdk_task != 0, "Missing incoming ACK task");
323 let caps = message(&[
324 p::bytes(1, inputs.engine_capability),
325 p::bytes(2, inputs.channel_capability),
326 ])?;
327 let mut values = vec![
328 p::bytes(1, inputs.sdk),
329 p::bytes(2, inputs.group),
330 p::uint(3, u64::from(inputs.network_type)),
331 p::uint(4, 1),
332 p::uint(5, room.room),
333 p::uint(6, room.room_key),
334 p::uint(7, u64::from(room.own_member)),
335 ];
336 values.extend(inputs.nics.iter().map(|nic| p::bytes(8, nic)));
337 values.extend([
338 p::uint(9, u64::from(inputs.sim_type)),
339 p::bytes(11, inputs.device),
340 p::bytes(12, &caps),
341 p::uint(13, 1),
342 p::uint(14, u64::from(inputs.video_length)),
343 p::uint(15, u64::from(inputs.video_ratio)),
344 ]);
345 Ok(HostRequest {
346 operation: HostOperation::RingingAck,
347 sdk_task,
348 body: message(&[p::bytes(1, &message(&values)?), p::bytes(3, b"voip-2p")])?,
349 longpoll_seconds: None,
350 })
351}
352pub fn reject(inputs: &VoiceInputs<'_>, room: VoiceRoom, sdk_task: u32) -> Result<CloudRequest> {
355 inputs.checked()?;
356 room.validate()?;
357 ensure!(sdk_task != 0, "Missing Reject task");
358 Ok(CloudRequest::for_operation(
359 sdk_task,
360 CallOperation::Reject,
361 message(&[
362 p::bytes(1, inputs.sdk),
363 p::bytes(2, inputs.group),
364 p::uint(3, room.room),
365 p::uint(4, room.room_key),
366 p::uint(5, u64::from(room.own_member)),
367 p::uint(6, 1),
368 ])?,
369 ))
370}
371
372pub fn join(
376 inputs: &VoiceInputs<'_>,
377 room: VoiceRoom,
378 timestamp: u64,
379 sdk_task: u32,
380) -> Result<CloudRequest> {
381 inputs.checked()?;
382 room.validate()?;
383 ensure!(sdk_task != 0, "Missing call task");
384 let capabilities = message(&[
385 p::bytes(1, inputs.engine_capability),
386 p::bytes(2, inputs.channel_capability),
387 ])?;
388 let mut values = vec![
389 p::bytes(1, inputs.sdk),
390 p::bytes(2, inputs.group),
391 p::uint(3, u64::from(inputs.network_type)),
392 p::bytes(4, &capabilities),
393 p::uint(5, 2),
394 p::uint(6, 1),
395 p::bytes(7, inputs.device),
396 p::uint(8, timestamp),
397 p::uint(9, u64::from(inputs.video_length)),
398 p::uint(10, u64::from(inputs.video_ratio)),
399 p::uint(11, room.room),
400 p::uint(12, room.room_key),
401 ];
402 values.extend(inputs.nics.iter().map(|nic| p::bytes(13, nic)));
403 values.push(p::uint(14, u64::from(inputs.sim_type)));
404 Ok(CloudRequest::for_operation(
405 sdk_task,
406 CallOperation::Join,
407 message(&values)?,
408 ))
409}
410
411#[derive(Clone, Copy, Debug, PartialEq, Eq)]
413pub enum RemoteExitReason {
414 LastPeerLeft,
415 PeerBusy,
416 PeerRejected,
417}
418impl RemoteExitReason {
419 pub const fn wire(self) -> u64 {
420 match self {
421 Self::LastPeerLeft => 5,
422 Self::PeerBusy => 7,
423 Self::PeerRejected => 6,
424 }
425 }
426}
427
428pub fn exit(
433 inputs: &VoiceInputs<'_>,
434 room: VoiceRoom,
435 source_reason: u32,
436 talk_success: bool,
437 sdk_task: u32,
438) -> Result<CloudRequest> {
439 inputs.checked()?;
440 room.validate()?;
441 ensure!(sdk_task != 0, "Missing call task");
442 let mapped = [1, 0, 0, 0, 0, 8, 9, 10, 0, 5]
443 .get(source_reason.wrapping_sub(1) as usize)
444 .copied()
445 .unwrap_or(0);
446 exit_with_wire(inputs, room, mapped, talk_success, sdk_task)
447}
448pub fn exit_remote(
451 inputs: &VoiceInputs<'_>,
452 room: VoiceRoom,
453 reason: RemoteExitReason,
454 talk_success: bool,
455 sdk_task: u32,
456) -> Result<CloudRequest> {
457 inputs.checked()?;
458 room.validate()?;
459 ensure!(sdk_task != 0, "Missing call task");
460 exit_with_wire(inputs, room, reason.wire(), talk_success, sdk_task)
461}
462fn exit_with_wire(
463 inputs: &VoiceInputs<'_>,
464 room: VoiceRoom,
465 mapped: u64,
466 talk_success: bool,
467 sdk_task: u32,
468) -> Result<CloudRequest> {
469 let body = message(&[
470 p::bytes(1, inputs.sdk),
471 p::bytes(2, inputs.group),
472 p::uint(3, room.room),
473 p::uint(4, room.room_key),
474 p::uint(5, u64::from(room.own_member)),
475 p::uint(6, mapped),
476 p::uint(7, u64::from(inputs.network_type)),
477 p::uint(8, u64::from(!talk_success)),
478 ])?;
479 Ok(CloudRequest::for_operation(
480 sdk_task,
481 CallOperation::Exit,
482 body,
483 ))
484}
485
486pub fn cloud_request_info(request: &CloudRequest, cloud_base: &[u8]) -> Result<Vec<u8>> {
489 let operation = request.operation()?;
490 let base = fields(cloud_base)?;
491 required_u64(&base, 1)?;
492 ensure!(
493 required_bytes(&base, 2)?.len() <= 128,
494 "Cloud device identity too large"
495 );
496 let signed = required_u64(&base, 3)? as i64;
497 i32::try_from(signed)?;
498 let body = request
499 .body
500 .as_deref()
501 .ok_or_else(|| anyhow::anyhow!("Missing Cloud call body"))?;
502 let cloud = message(&[p::bytes(1, cloud_base), p::bytes(2, body)])?;
503 message(&[
504 p::uint(1, u64::from(operation.command_id())),
505 p::bytes(2, operation.path().as_bytes()),
506 p::bytes(3, &cloud),
507 p::uint(5, 1),
508 p::uint(6, 1),
509 p::uint(7, 1),
510 ])
511}
512
513pub struct InviteResponse {
517 pub room: u64,
518 pub room_key: u64,
519 pub own_member: u32,
520 pub group_id: String,
521 pub client_response: Vec<u8>,
522 pub custom_auth: Option<Vec<u8>>,
523 pub invite_results: Vec<(String, u32)>,
524 pub original: Vec<u8>,
525 pub unknown_fields: Vec<Field>,
526}
527
528impl InviteResponse {
529 pub fn decode(input: &[u8]) -> Result<Self> {
530 let values = fields(input)?;
531 let room = required_u64(&values, 1)?;
532 let room_key = required_u64(&values, 2)?;
533 ensure!(room != 0 && room_key != 0, "Unallocated caller room");
534 let group = required_bytes(&values, 4)?;
535 text(group, 1024, true)?;
536 let client_response = required_bytes(&values, 5)?;
537 ensure!(
538 !client_response.is_empty(),
539 "Missing caller client response"
540 );
541 let mut results = Vec::new();
542 for value in values.iter().filter(|value| value.number == 8) {
543 ensure!(
544 results.len() < MAX_MEMBERS,
545 "Too many call invitation results"
546 );
547 let nested = fields(field_bytes(value)?)?;
548 let name = required_bytes(&nested, 1)?;
549 text(name, 128, true)?;
550 results.push((
551 String::from_utf8(name.to_vec())?,
552 signaling::optional_u32(&nested, 2)?.unwrap_or(0),
553 ));
554 }
555 let users = values
556 .iter()
557 .filter(|value| value.number == 6)
558 .collect::<Vec<_>>();
559 ensure!(users.len() <= MAX_MEMBERS, "Too many call user mappings");
560 for user in users {
561 fields(field_bytes(user)?)?;
562 }
563 if let Some(url) = p::optional_data(&values, 9)? {
564 text(url, 8192, false)?;
565 }
566 signaling::optional_u32(&values, 10)?;
567 Ok(Self {
568 room,
569 room_key,
570 own_member: required_u32(&values, 3)?,
571 group_id: String::from_utf8(group.to_vec())?,
572 client_response: client_response.to_vec(),
573 custom_auth: p::optional_data(&values, 7)?.map(Vec::from),
574 invite_results: results,
575 original: input.to_vec(),
576 unknown_fields: signaling::unknown_fields(values, 10),
577 })
578 }
579}
580
581pub struct LiveSyncResponse {
585 pub items: Vec<LiveSyncItem>,
586 pub cookies: Option<Vec<u8>>,
587 pub original: Vec<u8>,
588 pub unknown_fields: Vec<Field>,
589}
590
591pub struct LiveSyncItem {
592 pub kind: i32,
593 pub body: Vec<u8>,
594 pub original: Vec<u8>,
595 pub unknown_fields: Vec<Field>,
596}
597
598impl LiveSyncResponse {
599 pub fn decode(input: &[u8]) -> Result<Self> {
600 let values = fields(input)?;
601 let mut items = Vec::new();
602 for value in values.iter().filter(|v| v.number == 1) {
603 ensure!(items.len() < 256, "Too many call sync items");
604 let raw = field_bytes(value)?;
605 let nested = fields(raw)?;
606 let kind = i32::try_from(required_u64(&nested, 1)? as i64)?;
607 let body = required_bytes(&nested, 2)?.to_vec();
608 items.push(LiveSyncItem {
609 kind,
610 body,
611 original: raw.to_vec(),
612 unknown_fields: signaling::unknown_fields(nested, 2),
613 });
614 }
615 Ok(Self {
616 items,
617 cookies: p::optional_data(&values, 2)?.map(Vec::from),
618 original: input.to_vec(),
619 unknown_fields: signaling::unknown_fields(values, 2),
620 })
621 }
622}
623
624pub struct JoinClientResponse {
627 pub strategy: Vec<u8>,
628 pub members: Vec<u8>,
629 pub av_status: Option<Vec<u8>>,
630 pub live_id: u64,
631 pub cookies: Vec<u8>,
632 pub retry_seconds: Option<u32>,
633 pub original: Vec<u8>,
634}
635
636impl JoinClientResponse {
637 pub fn decode(input: &[u8]) -> Result<Self> {
638 let values = fields(input)?;
639 let strategy = required_bytes(&values, 1)?;
640 let members = required_bytes(&values, 2)?;
641 ensure!(
642 !strategy.is_empty() && !members.is_empty(),
643 "Missing caller room negotiation"
644 );
645 fields(strategy)?;
646 fields(members)?;
647 for n in [3, 4] {
648 if let Some(raw) = p::optional_data(&values, n)? {
649 fields(raw)?;
650 }
651 }
652 for n in [7, 8, 10] {
653 signaling::optional_u32(&values, n)?;
654 }
655 for value in values.iter().filter(|value| value.number == 9) {
656 fields(field_bytes(value)?)?;
657 }
658 let live_id = required_u64(&values, 5)?;
659 ensure!(live_id != 0, "Missing caller live identity");
660 Ok(Self {
661 strategy: strategy.to_vec(),
662 members: members.to_vec(),
663 av_status: p::optional_data(&values, 3)?.map(Vec::from),
664 live_id,
665 cookies: required_bytes(&values, 6)?.to_vec(),
666 retry_seconds: signaling::optional_u32(&values, 7)?,
667 original: input.to_vec(),
668 })
669 }
670}
671
672fn message(parts: &[Vec<u8>]) -> Result<Vec<u8>> {
673 let value = p::message(parts)?;
674 ensure!(value.len() <= MAX_BODY, "Personal call body too large");
675 Ok(value)
676}
677fn fields(input: &[u8]) -> Result<Vec<Field>> {
678 ensure!(input.len() <= MAX_BODY, "Personal call body too large");
679 signaling::decode_fields(input)
680}
681fn required_bytes(values: &[Field], number: u32) -> Result<&[u8]> {
682 p::optional_data(values, number)?
683 .ok_or_else(|| anyhow::anyhow!("Missing call bytes field {number}"))
684}
685fn required_u64(values: &[Field], number: u32) -> Result<u64> {
686 match p::optional(values, number)? {
687 Some(Value::Varint(v)) => Ok(*v),
688 _ => bail!("Missing or invalid call integer field {number}"),
689 }
690}
691fn required_u32(values: &[Field], number: u32) -> Result<u32> {
692 Ok(u32::try_from(required_u64(values, number)?)?)
693}
694fn field_bytes(field: &Field) -> Result<&[u8]> {
695 match &field.value {
696 Value::Bytes(value) => Ok(value),
697 _ => bail!("Invalid call bytes field {}", field.number),
698 }
699}
700fn text(value: &[u8], maximum: usize, nonempty: bool) -> Result<()> {
701 ensure!(
702 value.len() <= maximum && (!nonempty || !value.is_empty()) && !value.contains(&0),
703 "Invalid call text"
704 );
705 std::str::from_utf8(value)?;
706 Ok(())
707}
708
709#[cfg(test)]
710mod tests {
711 use super::*;
712
713 #[test]
714 fn full_sync_preserves_later_unknown_item_and_cookie_presence() {
715 let first = message(&[p::uint(1, 1), p::bytes(2, b"a")]).unwrap();
716 let later = message(&[p::int32(1, -1), p::bytes(2, b"b"), p::uint(99, 7)]).unwrap();
717 let wire = message(&[p::bytes(1, &first), p::bytes(1, &later), p::bytes(2, &[])]).unwrap();
718 let result = LiveSyncResponse::decode(&wire).unwrap();
719 assert_eq!(result.items.len(), 2);
720 assert_eq!(result.items[1].kind, -1);
721 assert_eq!(result.items[1].unknown_fields.len(), 1);
722 assert_eq!(result.cookies, Some(vec![]));
723 assert!(LiveSyncResponse::decode(&[]).unwrap().cookies.is_none());
724 let bad = message(&[p::bytes(1, &[8, 1, 8, 2, 18, 0])]).unwrap();
725 assert!(LiveSyncResponse::decode(&bad).is_err());
726 }
727
728 #[test]
729 fn caller_response_keeps_retry_separate_and_requires_full_widths() {
730 let client = message(&[
731 p::bytes(1, &[8, 1]),
732 p::bytes(2, &[8, 0]),
733 p::uint(5, 9),
734 p::bytes(6, &[]),
735 p::uint(7, 3),
736 ])
737 .unwrap();
738 let result = JoinClientResponse::decode(&client).unwrap();
739 assert_eq!(result.live_id, 9);
740 assert_eq!(result.retry_seconds, Some(3));
741 let mut bad = client.clone();
742 bad.extend(p::uint(7, 4));
743 assert!(JoinClientResponse::decode(&bad).is_err());
744 let mut response = message(&[
745 p::uint(1, 10),
746 p::uint(2, u64::MAX),
747 p::uint(3, 0),
748 p::bytes(4, b"g"),
749 p::bytes(5, &client),
750 p::bytes(7, b"auth"),
751 p::uint(100, 1),
752 ])
753 .unwrap();
754 let result = InviteResponse::decode(&response).unwrap();
755 assert_eq!(result.own_member, 0);
756 assert_eq!(result.room_key, u64::MAX);
757 assert_eq!(result.unknown_fields.len(), 1);
758 assert_eq!(result.original, response);
759 response.extend(p::uint(3, u64::MAX));
760 assert!(InviteResponse::decode(&response).is_err());
761 for (nested, expected) in [
762 (p::bytes(1, b"peer"), 0),
763 (message(&[p::bytes(1, b"peer"), p::uint(2, 7)]).unwrap(), 7),
764 ] {
765 let wire = message(&[
766 p::uint(1, 7),
767 p::uint(2, 9),
768 p::uint(3, 0),
769 p::bytes(4, b"group"),
770 p::bytes(5, &[8, 1]),
771 p::bytes(8, &nested),
772 ])
773 .unwrap();
774 assert_eq!(
775 InviteResponse::decode(&wire).unwrap().invite_results[0].1,
776 expected
777 );
778 }
779 let bad = message(&[
780 p::uint(1, 7),
781 p::uint(2, 9),
782 p::uint(3, 0),
783 p::bytes(4, b"group"),
784 p::bytes(5, &[8, 1]),
785 p::bytes(
786 8,
787 &message(&[p::bytes(1, b"peer"), p::bytes(2, &[])]).unwrap(),
788 ),
789 ])
790 .unwrap();
791 assert!(InviteResponse::decode(&bad).is_err());
792 }
793}