1use super::*;
2
3pub fn controlled_contact_fields(raw: &[u8]) -> Result<Vec<u32>> {
6 let f = p::decode(raw)?;
7 let Some(raw) = p::optional_data(&f, 89)? else {
8 return Ok(vec![]);
9 };
10 let list = p::decode(raw)?;
11 let mut controls = 0u32;
12 let mut ids = std::collections::BTreeSet::new();
13 for field in list.iter().filter(|f| f.number == 2) {
14 controls += 1;
15 ensure!(controls <= 256, "contact_control_limit");
16 let p::Value::Bytes(raw) = &field.value else {
17 bail!("invalid_contact_control");
18 };
19 let info = p::decode(raw)?;
20 for n in [1, 3] {
21 if let Some(value) = p::optional(&info, n)? {
22 let p::Value::Varint(v) = value else {
23 bail!("invalid_contact_control");
24 };
25 u32::try_from(*v)?;
26 }
27 }
28 if let Some(value) = p::optional(&info, 2)? {
29 ensure!(
30 matches!(value, p::Value::Varint(_)),
31 "invalid_contact_control"
32 );
33 }
34 let mut count = 0u32;
35 for field in info.iter().filter(|f| f.number == 4) {
36 count += 1;
37 ensure!(count <= 256, "contact_control_limit");
38 let p::Value::Bytes(raw) = &field.value else {
39 bail!("invalid_contact_control");
40 };
41 let field = p::decode(raw)?;
42 let id = u32::try_from(p::scalar(&field, 1)?)?;
43 ensure!(
44 field.len() == 1
45 && matches!(
46 id,
47 8 | 10 | 11 | 12 | 27 | 59 | 60 | 61 | 62 | 68 | 83 | 84 | 87
48 ),
49 "unsupported_contact_update_control"
50 );
51 ids.insert(id);
52 }
53 if let Some(p::Value::Varint(n)) = p::optional(&info, 3)? {
54 ensure!(*n == u64::from(count), "contact_control_count_mismatch");
55 }
56 }
57 match p::optional(&list, 1)? {
58 None => {}
59 Some(p::Value::Varint(n)) => {
60 ensure!(*n == u64::from(controls), "contact_control_count_mismatch")
61 }
62 _ => bail!("invalid_contact_control"),
63 }
64 Ok(ids.into_iter().collect())
65}
66
67#[derive(Clone, Serialize, Deserialize)]
68pub struct GroupDetail {
69 pub group_id: String,
70 pub version: u32,
71 pub announcement: String,
72 pub editor: String,
73 pub published_at: u32,
74 pub status: u32,
75 pub xml: String,
76}
77pub fn group_detail_request(state: &AccountState, group: &str) -> Result<Vec<u8>> {
78 ordinary_identity(group)?;
79 ensure!(group.ends_with("@chatroom"), "invalid_group_identity");
80 p::message(&[
81 p::bytes(1, &wire::base_request(&state.device, state.uin, 0)?),
82 p::bytes(2, group.as_bytes()),
83 ])
84}
85pub fn decode_group_detail(raw: &[u8], group: &str) -> Result<GroupDetail> {
86 let f = p::decode(raw)?;
87 ensure!(p::base_code(&f)? == 0, "group_detail_rejected");
88 let text = |n| -> Result<String> {
89 Ok(std::str::from_utf8(p::optional_data(&f, n)?.unwrap_or_default())?.into())
90 };
91 let number = |n| -> Result<u32> {
92 match p::optional(&f, n)? {
93 None => Ok(0),
94 Some(p::Value::Varint(n)) => Ok(u32::try_from(*n)?),
95 _ => bail!("invalid_group_detail"),
96 }
97 };
98 Ok(GroupDetail {
101 group_id: group.into(),
102 version: number(3)?,
103 announcement: text(2)?,
104 editor: text(4)?,
105 published_at: number(5)?,
106 status: number(6)?,
107 xml: text(12)?,
108 })
109}
110impl NativeClient {
111 pub fn group_detail_raw(&self, state: &mut AccountState, group: &str) -> Result<Vec<u8>> {
112 self.authenticated_rpc(
113 state,
114 223,
115 "/cgi-bin/micromsg-bin/getchatroominfodetail",
116 &group_detail_request(state, group)?,
117 )
118 }
119}
120#[cfg(test)]
121mod tests {
122 use super::*;
123 #[test]
124 fn contact_controls_preserve_ids_without_guessing_version_order() {
125 let info = |count, id| {
126 p::message(&[
127 p::uint(1, 1),
128 p::uint(2, u64::MAX),
129 p::uint(3, count),
130 p::bytes(4, &p::uint(1, id)),
131 ])
132 .unwrap()
133 };
134 let wrap = |entries: Vec<Vec<u8>>| {
135 let mut list = vec![p::uint(1, entries.len() as u64)];
136 list.extend(entries.iter().map(|e| p::bytes(2, e)));
137 p::bytes(89, &p::message(&list).unwrap())
138 };
139 assert_eq!(
140 controlled_contact_fields(&wrap(vec![info(1, 68), info(1, 10), info(1, 10)])).unwrap(),
141 [10, 68]
142 );
143 assert!(controlled_contact_fields(&wrap(vec![info(2, 10)])).is_err());
144 assert!(controlled_contact_fields(&wrap(vec![info(1, 999)])).is_err());
145 assert!(controlled_contact_fields(&p::bytes(89, &p::bytes(1, b"wrong type"))).is_err());
146 }
147 #[test]
148 fn successful_omission_clears_but_failure_and_wrong_types_do_not() {
149 let raw = p::message(&[
150 p::bytes(1, &p::int32(1, 0)),
151 p::uint(3, 5),
152 p::bytes(4, b"editor"),
153 p::uint(5, 100),
154 ])
155 .unwrap();
156 let detail = decode_group_detail(&raw, "room@chatroom").unwrap();
157 assert_eq!(detail.announcement, "");
158 assert_eq!(detail.xml, "");
159 assert_eq!(detail.editor, "editor");
160 assert_eq!(detail.published_at, 100);
161 assert!(decode_group_detail(&p::bytes(1, &p::int32(1, -1)), "room@chatroom").is_err());
162 assert!(decode_group_detail(
163 &p::message(&[p::bytes(1, &p::uint(1, 0)), p::bytes(5, b"bad")]).unwrap(),
164 "room@chatroom"
165 )
166 .is_err());
167 }
168}