1use js_int::int;
2use ruma_common::{
3 room_version_rules::EventFormatRules, CanonicalJsonObject, CanonicalJsonValue, RoomId,
4 ID_MAX_BYTES,
5};
6use serde_json::to_string as to_json_string;
7
8const MAX_PDU_BYTES: usize = 65_535;
12
13const MAX_PREV_EVENTS_LENGTH: usize = 20;
17
18const MAX_AUTH_EVENTS_LENGTH: usize = 10;
22
23pub fn check_pdu_format(pdu: &CanonicalJsonObject, rules: &EventFormatRules) -> Result<(), String> {
45 let json =
47 to_json_string(&pdu).map_err(|e| format!("Failed to serialize canonical JSON: {e}"))?;
48 if json.len() > MAX_PDU_BYTES {
49 return Err("PDU is larger than maximum of {MAX_PDU_BYTES} bytes".to_owned());
50 }
51
52 let event_type = extract_required_string_field(pdu, "type")?;
54
55 extract_required_string_field(pdu, "sender")?;
57
58 let room_id = (event_type != "m.room.create" || rules.require_room_create_room_id)
60 .then(|| extract_required_string_field(pdu, "room_id"))
61 .transpose()?;
62
63 if rules.require_event_id {
65 extract_required_string_field(pdu, "event_id")?;
66 }
67
68 extract_optional_string_field(pdu, "state_key")?;
70
71 extract_required_array_field(pdu, "prev_events", MAX_PREV_EVENTS_LENGTH)?;
73
74 let auth_events = extract_required_array_field(pdu, "auth_events", MAX_AUTH_EVENTS_LENGTH)?;
76
77 if !rules.allow_room_create_in_auth_events {
78 if let Some(room_id) = room_id {
81 let room_create_event_reference_hash = <&RoomId>::try_from(room_id.as_str())
82 .map_err(|e| format!("invalid `room_id` field in PDU: {e}"))?
83 .strip_sigil();
84
85 for event_id in auth_events {
86 let CanonicalJsonValue::String(event_id) = event_id else {
87 return Err(format!(
88 "unexpected format of array item in `auth_events` field in PDU: \
89 expected string, got {event_id:?}"
90 ));
91 };
92
93 let reference_hash = event_id.strip_prefix('$').ok_or(
94 "unexpected format of array item in `auth_events` field in PDU: \
95 string not beginning with the `$` sigil",
96 )?;
97
98 if reference_hash == room_create_event_reference_hash {
99 return Err("invalid `auth_events` field in PDU: \
100 cannot contain the `m.room.create` event ID"
101 .to_owned());
102 }
103 }
104 }
105 }
106
107 match pdu.get("depth") {
109 Some(CanonicalJsonValue::Integer(value)) => {
110 if *value < int!(0) {
111 return Err("invalid `depth` field in PDU: cannot be a negative integer".to_owned());
112 }
113 }
114 Some(value) => {
115 return Err(format!(
116 "unexpected format of `depth` field in PDU: \
117 expected integer, got {value:?}"
118 ));
119 }
120 None => return Err("missing `depth` field in PDU".to_owned()),
121 }
122
123 Ok(())
124}
125
126fn extract_optional_string_field<'a>(
132 object: &'a CanonicalJsonObject,
133 field: &'a str,
134) -> Result<Option<&'a String>, String> {
135 match object.get(field) {
136 Some(CanonicalJsonValue::String(value)) => {
137 if value.len() > ID_MAX_BYTES {
138 Err(format!(
139 "invalid `{field}` field in PDU: \
140 string length is larger than maximum of {ID_MAX_BYTES} bytes"
141 ))
142 } else {
143 Ok(Some(value))
144 }
145 }
146 Some(value) => Err(format!(
147 "unexpected format of `{field}` field in PDU: \
148 expected string, got {value:?}"
149 )),
150 None => Ok(None),
151 }
152}
153
154fn extract_required_string_field<'a>(
159 object: &'a CanonicalJsonObject,
160 field: &'a str,
161) -> Result<&'a String, String> {
162 extract_optional_string_field(object, field)?
163 .ok_or_else(|| format!("missing `{field}` field in PDU"))
164}
165
166fn extract_required_array_field<'a>(
171 object: &'a CanonicalJsonObject,
172 field: &'a str,
173 max_len: usize,
174) -> Result<&'a [CanonicalJsonValue], String> {
175 match object.get(field) {
176 Some(CanonicalJsonValue::Array(value)) => {
177 if value.len() > max_len {
178 Err(format!(
179 "invalid `{field}` field in PDU: \
180 array length is larger than maximum of {max_len}"
181 ))
182 } else {
183 Ok(value)
184 }
185 }
186 Some(value) => Err(format!(
187 "unexpected format of `{field}` field in PDU: \
188 expected array, got {value:?}"
189 )),
190 None => Err(format!("missing `{field}` field in PDU")),
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use std::iter::repeat_n;
197
198 use js_int::int;
199 use ruma_common::{
200 room_version_rules::EventFormatRules, CanonicalJsonObject, CanonicalJsonValue,
201 };
202 use serde_json::{from_value as from_json_value, json};
203
204 use super::check_pdu_format;
205
206 fn pdu_v1() -> CanonicalJsonObject {
208 let pdu = json!({
209 "auth_events": [
210 [
211 "$af232176:example.org",
212 { "sha256": "abase64encodedsha256hashshouldbe43byteslong" },
213 ],
214 ],
215 "content": {
216 "key": "value",
217 },
218 "depth": 12,
219 "event_id": "$a4ecee13e2accdadf56c1025:example.com",
220 "hashes": {
221 "sha256": "thishashcoversallfieldsincasethisisredacted"
222 },
223 "origin_server_ts": 1_838_188_000,
224 "prev_events": [
225 [
226 "$af232176:example.org",
227 { "sha256": "abase64encodedsha256hashshouldbe43byteslong" }
228 ],
229 ],
230 "room_id": "!UcYsUzyxTGDxLBEvLy:example.org",
231 "sender": "@alice:example.com",
232 "signatures": {
233 "example.com": {
234 "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
235 },
236 },
237 "type": "m.room.message",
238 "unsigned": {
239 "age": 4612,
240 },
241 });
242 from_json_value(pdu).unwrap()
243 }
244
245 fn pdu_v3() -> CanonicalJsonObject {
247 let pdu = json!({
248 "auth_events": [
249 "$base64encodedeventid",
250 "$adifferenteventid",
251 ],
252 "content": {
253 "key": "value",
254 },
255 "depth": 12,
256 "hashes": {
257 "sha256": "thishashcoversallfieldsincasethisisredacted",
258 },
259 "origin_server_ts": 1_838_188_000,
260 "prev_events": [
261 "$base64encodedeventid",
262 "$adifferenteventid",
263 ],
264 "redacts": "$some/old+event",
265 "room_id": "!UcYsUzyxTGDxLBEvLy:example.org",
266 "sender": "@alice:example.com",
267 "signatures": {
268 "example.com": {
269 "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
270 },
271 },
272 "type": "m.room.message",
273 "unsigned": {
274 "age": 4612,
275 }
276 });
277 from_json_value(pdu).unwrap()
278 }
279
280 fn room_create_v12() -> CanonicalJsonObject {
282 let pdu = json!({
283 "auth_events": [],
284 "content": {
285 "room_version": "12",
286 },
287 "depth": 1,
288 "hashes": {
289 "sha256": "thishashcoversallfieldsincasethisisredacted",
290 },
291 "origin_server_ts": 1_838_188_000,
292 "prev_events": [],
293 "sender": "@alice:example.com",
294 "signatures": {
295 "example.com": {
296 "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
297 },
298 },
299 "type": "m.room.create",
300 "unsigned": {
301 "age": 4612,
302 }
303 });
304 from_json_value(pdu).unwrap()
305 }
306
307 fn pdu_v12() -> CanonicalJsonObject {
309 let pdu = json!({
310 "auth_events": [
311 "$base64encodedeventid",
312 "$adifferenteventid",
313 ],
314 "content": {
315 "key": "value",
316 },
317 "depth": 12,
318 "hashes": {
319 "sha256": "thishashcoversallfieldsincasethisisredacted",
320 },
321 "origin_server_ts": 1_838_188_000,
322 "prev_events": [
323 "$base64encodedeventid",
324 ],
325 "room_id": "!roomcreatereferencehash",
326 "sender": "@alice:example.com",
327 "signatures": {
328 "example.com": {
329 "ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus",
330 },
331 },
332 "type": "m.room.message",
333 "unsigned": {
334 "age": 4612,
335 }
336 });
337 from_json_value(pdu).unwrap()
338 }
339
340 #[test]
341 fn check_pdu_format_valid_v1() {
342 check_pdu_format(&pdu_v1(), &EventFormatRules::V1).unwrap();
343 }
344
345 #[test]
346 fn check_pdu_format_valid_v3() {
347 check_pdu_format(&pdu_v3(), &EventFormatRules::V3).unwrap();
348 }
349
350 #[test]
351 fn check_pdu_format_pdu_too_big() {
352 let mut pdu = pdu_v3();
354 let content = pdu.get_mut("content").unwrap().as_object_mut().unwrap();
355 let long_string = repeat_n('a', 66_000).collect::<String>();
356 content.insert("big_data".to_owned(), long_string.into());
357
358 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
359 }
360
361 #[test]
362 fn check_pdu_format_fields_missing() {
363 for field in
364 &["event_id", "sender", "room_id", "type", "prev_events", "auth_events", "depth"]
365 {
366 let mut pdu = pdu_v1();
367 pdu.remove(*field).unwrap();
368
369 check_pdu_format(&pdu, &EventFormatRules::V1).unwrap_err();
370 }
371 }
372
373 #[test]
374 fn check_pdu_format_strings_too_big() {
375 for field in &["event_id", "sender", "room_id", "type", "state_key"] {
376 let mut pdu = pdu_v1();
377 let value = repeat_n('a', 300).collect::<String>();
378 pdu.insert((*field).to_owned(), value.into());
379 check_pdu_format(&pdu, &EventFormatRules::V1).unwrap_err();
380 }
381 }
382
383 #[test]
384 fn check_pdu_format_strings_wrong_format() {
385 for field in &["event_id", "sender", "room_id", "type", "state_key"] {
386 let mut pdu = pdu_v1();
387 pdu.insert((*field).to_owned(), true.into());
388 check_pdu_format(&pdu, &EventFormatRules::V1).unwrap_err();
389 }
390 }
391
392 #[test]
393 fn check_pdu_format_arrays_too_big() {
394 for field in &["prev_events", "auth_events"] {
395 let mut pdu = pdu_v3();
396 let value =
397 repeat_n(CanonicalJsonValue::from("$eventid".to_owned()), 30).collect::<Vec<_>>();
398 pdu.insert((*field).to_owned(), value.into());
399 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
400 }
401 }
402
403 #[test]
404 fn check_pdu_format_arrays_wrong_format() {
405 for field in &["prev_events", "auth_events"] {
406 let mut pdu = pdu_v3();
407 pdu.insert((*field).to_owned(), true.into());
408 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
409 }
410 }
411
412 #[test]
413 fn check_pdu_format_negative_depth() {
414 let mut pdu = pdu_v3();
415 pdu.insert("depth".to_owned(), int!(-1).into()).unwrap();
416 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
417 }
418
419 #[test]
420 fn check_pdu_format_depth_wrong_format() {
421 let mut pdu = pdu_v3();
422 pdu.insert("depth".to_owned(), true.into());
423 check_pdu_format(&pdu, &EventFormatRules::V3).unwrap_err();
424 }
425
426 #[test]
427 fn check_pdu_format_valid_room_create_v12() {
428 let pdu = room_create_v12();
429 check_pdu_format(&pdu, &EventFormatRules::V12).unwrap();
430 }
431
432 #[test]
433 fn check_pdu_format_valid_v12() {
434 let pdu = pdu_v12();
435 check_pdu_format(&pdu, &EventFormatRules::V12).unwrap();
436 }
437
438 #[test]
439 fn check_pdu_format_v12_with_room_create() {
440 let mut pdu = pdu_v12();
441 pdu.get_mut("auth_events")
442 .unwrap()
443 .as_array_mut()
444 .unwrap()
445 .push("$roomcreatereferencehash".to_owned().into());
446
447 check_pdu_format(&pdu, &EventFormatRules::V12).unwrap_err();
448 }
449}