1use super::signaling::{decode_fields, optional_u32, unknown_fields};
10use crate::protobuf::{self as p, Field, Value};
11use anyhow::{bail, ensure, Context, Result};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum CarrierKind {
15 Invite,
16 Cancel,
17}
18
19pub struct NotificationCarrier {
23 kind: CarrierKind,
24 fields: Vec<Field>,
25}
26
27impl NotificationCarrier {
28 pub fn decode(kind: CarrierKind, input: &[u8]) -> Result<Self> {
29 let fields = decode_fields(input)?;
30 match kind {
31 CarrierKind::Invite => {
32 for number in [1, 3, 4, 5, 6] {
33 p::optional_data(&fields, number)?;
34 }
35 optional_u64(&fields, 2)?;
36 for field in fields.iter().filter(|field| field.number == 7) {
37 ensure!(
38 matches!(field.value, Value::Bytes(_)),
39 "Invalid invite member field"
40 );
41 }
42 }
43 CarrierKind::Cancel => {
44 optional_u64(&fields, 1)?;
45 p::optional_data(&fields, 2)?;
46 }
47 }
48 Ok(Self { kind, fields })
49 }
50
51 pub fn kind(&self) -> CarrierKind {
52 self.kind
53 }
54
55 pub fn fields(&self) -> &[Field] {
56 &self.fields
57 }
58
59 pub fn conference_bytes(&self) -> Result<&[u8]> {
62 let number = match self.kind {
63 CarrierKind::Invite => 6,
64 CarrierKind::Cancel => 2,
65 };
66 p::optional_data(&self.fields, number)?.context("Missing conference notification")
67 }
68
69 pub fn conference_notification(&self) -> Result<ConferenceNotification> {
70 let notification = ConferenceNotification::decode(self.conference_bytes()?)?;
71 ensure!(
72 matches!(
73 (self.kind, notification.kind),
74 (CarrierKind::Invite, Some(2)) | (CarrierKind::Cancel, Some(3))
75 ),
76 "Outer and conference notification kinds disagree"
77 );
78 Ok(notification)
79 }
80}
81
82pub struct ConferenceNotification {
85 pub kind: Option<u32>,
86 pub data: Option<Vec<u8>>,
87 pub unknown_fields: Vec<Field>,
88}
89
90impl ConferenceNotification {
91 pub fn decode(input: &[u8]) -> Result<Self> {
92 let fields = decode_fields(input)?;
93 Ok(Self {
94 kind: optional_u32(&fields, 1)?,
95 data: p::optional_data(&fields, 2)?.map(Vec::from),
96 unknown_fields: unknown_fields(fields, 2),
97 })
98 }
99
100 pub fn payload(&self) -> Result<NotificationPayload> {
101 let data = self.data.as_deref().context("Missing conference payload")?;
102 match self.kind {
103 Some(2) => Ok(NotificationPayload::Invite(InviteNotification::decode(
104 data,
105 )?)),
106 Some(3) => Ok(NotificationPayload::Cancel(CancelNotification::decode(
107 data,
108 )?)),
109 _ => bail!("Unsupported conference notification kind"),
110 }
111 }
112}
113
114pub enum NotificationPayload {
115 Invite(InviteNotification),
116 Cancel(CancelNotification),
117}
118
119pub struct InviteNotification {
123 pub room_id: Option<u64>,
124 pub room_key: Option<u64>,
125 pub member_id: Option<u32>,
126 pub room_type: Option<u32>,
127 pub caller_openid: Option<String>,
128 pub callee_openid: Option<String>,
129 pub caller_username: Option<String>,
130 pub callee_username: Option<String>,
131 pub usernames: Vec<String>,
132 pub app_id: Option<String>,
133 pub sub_app_id: Option<String>,
134 pub caller_member_id: Option<u32>,
135 pub wechat_group_id: Option<String>,
136 pub unknown_fields: Vec<Field>,
137}
138
139impl InviteNotification {
140 pub fn decode(input: &[u8]) -> Result<Self> {
141 let fields = decode_fields(input)?;
142 let usernames = fields
143 .iter()
144 .filter(|field| field.number == 9)
145 .map(|field| {
146 let Value::Bytes(value) = &field.value else {
147 bail!("Invalid invite username field")
148 };
149 Ok(std::str::from_utf8(value)?.to_owned())
150 })
151 .collect::<Result<Vec<_>>>()?;
152 Ok(Self {
153 room_id: optional_u64(&fields, 1)?,
154 room_key: optional_u64(&fields, 2)?,
155 member_id: optional_u32(&fields, 3)?,
156 room_type: optional_u32(&fields, 4)?,
157 caller_openid: optional_string(&fields, 5)?,
158 callee_openid: optional_string(&fields, 6)?,
159 caller_username: optional_string(&fields, 7)?,
160 callee_username: optional_string(&fields, 8)?,
161 usernames,
162 app_id: optional_string(&fields, 10)?,
163 sub_app_id: optional_string(&fields, 11)?,
164 caller_member_id: optional_u32(&fields, 12)?,
165 wechat_group_id: optional_string(&fields, 13)?,
166 unknown_fields: unknown_fields(fields, 13),
167 })
168 }
169}
170
171pub struct CancelNotification {
172 pub room_id: Option<u64>,
173 pub room_key: Option<u64>,
174 pub member_id: Option<u32>,
175 pub reason: Option<u32>,
176 pub unknown_fields: Vec<Field>,
177}
178
179impl CancelNotification {
180 pub fn decode(input: &[u8]) -> Result<Self> {
181 let fields = decode_fields(input)?;
182 Ok(Self {
183 room_id: optional_u64(&fields, 1)?,
184 room_key: optional_u64(&fields, 2)?,
185 member_id: optional_u32(&fields, 3)?,
186 reason: optional_u32(&fields, 4)?,
187 unknown_fields: unknown_fields(fields, 4),
188 })
189 }
190}
191
192fn optional_u64(fields: &[Field], number: u32) -> Result<Option<u64>> {
193 match p::optional(fields, number)? {
194 None => Ok(None),
195 Some(Value::Varint(value)) => Ok(Some(*value)),
196 _ => bail!("Invalid notification scalar field {number}"),
197 }
198}
199
200fn optional_string(fields: &[Field], number: u32) -> Result<Option<String>> {
201 p::optional_data(fields, number)?
202 .map(|value| Ok(std::str::from_utf8(value)?.to_owned()))
203 .transpose()
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn cancellation_literal_uses_each_distinct_envelope() {
212 let raw = [
214 0x08, 0x63, 0x12, 0x0c, 0x08, 0x03, 0x12, 0x08, 0x08, 0x2a, 0x10, 0x2b, 0x18, 0x07,
215 0x20, 0x04,
216 ];
217 let outer = NotificationCarrier::decode(CarrierKind::Cancel, &raw).unwrap();
218 assert_eq!(outer.kind(), CarrierKind::Cancel);
219 assert_eq!(outer.conference_bytes().unwrap(), &raw[4..]);
220 let third = outer.conference_notification().unwrap();
221 let NotificationPayload::Cancel(cancel) = third.payload().unwrap() else {
222 panic!("wrong kind")
223 };
224 assert_eq!(cancel.room_id, Some(42));
225 assert_eq!(cancel.room_key, Some(43));
226 assert_eq!(cancel.member_id, Some(7));
227 assert_eq!(cancel.reason, Some(4));
228 assert!(ConferenceNotification::decode(&raw)
230 .unwrap()
231 .payload()
232 .is_err());
233 }
234
235 #[test]
236 fn invite_preserves_widths_names_presence_and_unknowns() {
237 let raw = p::message(&[
238 p::uint(1, u64::MAX),
239 p::uint(2, u64::MAX - 1),
240 p::uint(3, u32::MAX.into()),
241 p::uint(4, 2),
242 p::bytes(5, b"caller-open"),
243 p::bytes(6, b"callee-open"),
244 p::bytes(7, b"synthetic-caller"),
245 p::bytes(8, b"synthetic-callee"),
246 p::bytes(9, b"one"),
247 p::bytes(9, b"two"),
248 p::bytes(10, b""),
249 p::uint(12, 0),
250 p::bytes(13, b"synthetic-group"),
251 p::bytes(99, b"unknown"),
252 ])
253 .unwrap();
254 let invite = InviteNotification::decode(&raw).unwrap();
255 assert_eq!(invite.room_id, Some(u64::MAX));
256 assert_eq!(invite.room_key, Some(u64::MAX - 1));
257 assert_eq!(invite.member_id, Some(u32::MAX));
258 assert_eq!(invite.room_type, Some(2));
259 assert_eq!(invite.caller_username.as_deref(), Some("synthetic-caller"));
260 assert_eq!(invite.callee_username.as_deref(), Some("synthetic-callee"));
261 assert_eq!(invite.usernames, vec!["one", "two"]);
262 assert_eq!(invite.app_id.as_deref(), Some(""));
263 assert!(invite.sub_app_id.is_none());
264 assert_eq!(invite.caller_member_id, Some(0));
265 assert_eq!(invite.unknown_fields.len(), 1);
266 assert_eq!(p::data(&invite.unknown_fields, 99).unwrap(), b"unknown");
267 let third = p::message(&[p::uint(1, 2), p::bytes(2, &raw), p::uint(99, 7)]).unwrap();
268 let outer = p::message(&[
269 p::bytes(1, b"opaque"),
270 p::uint(2, 9),
271 p::bytes(6, &third),
272 p::bytes(7, &p::uint(1, 7)),
273 p::uint(88, 3),
274 ])
275 .unwrap();
276 let outer = NotificationCarrier::decode(CarrierKind::Invite, &outer).unwrap();
277 assert_eq!(p::scalar(outer.fields(), 88).unwrap(), 3);
278 let third = outer.conference_notification().unwrap();
279 assert_eq!(p::scalar(&third.unknown_fields, 99).unwrap(), 7);
280 assert!(matches!(
281 third.payload().unwrap(),
282 NotificationPayload::Invite(_)
283 ));
284 }
285}