Skip to content

Commit a45e058

Browse files
committed
feat(common): Detect cycles when loading last chunk of LinkedChunk.
This patch updates `RelationalLinkedChunk::load_last_chunk` to detect cycle for the last chunk only.
1 parent 278de71 commit a45e058

File tree

1 file changed

+55
-7
lines changed

1 file changed

+55
-7
lines changed

crates/matrix-sdk-common/src/linked_chunk/relational.rs

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
//! Implementation for a _relational linked chunk_, see
1616
//! [`RelationalLinkedChunk`].
1717
18+
use std::ops::ControlFlow;
19+
1820
use ruma::{OwnedRoomId, RoomId};
1921

2022
use super::{ChunkContent, ChunkIdentifierGenerator, RawChunk};
@@ -345,13 +347,36 @@ where
345347
};
346348

347349
// Find the last chunk.
348-
let Some(chunk_row) = self
349-
.chunks
350-
.iter()
351-
.find(|chunk_row| chunk_row.room_id == room_id && chunk_row.next_chunk.is_none())
352-
else {
353-
// Chunk is not found.
354-
return Ok((None, chunk_identifier_generator));
350+
let chunk_row = match self.chunks.iter().try_fold(0, |mut number_of_chunks, chunk_row| {
351+
if chunk_row.room_id == room_id {
352+
number_of_chunks += 1;
353+
354+
if chunk_row.next_chunk.is_none() {
355+
return ControlFlow::Break((number_of_chunks, chunk_row));
356+
}
357+
}
358+
359+
ControlFlow::Continue(number_of_chunks)
360+
}) {
361+
// Chunk has been found, all good.
362+
ControlFlow::Break((_, chunk_row)) => chunk_row,
363+
364+
// Chunk is not found and there is zero chunk for this room, this is consistent, all
365+
// good.
366+
ControlFlow::Continue(0) => {
367+
return Ok((None, chunk_identifier_generator));
368+
}
369+
370+
// Chunk is not found **but** there are chunks for this room, this is inconsistent. The
371+
// linked chunk is malformed.
372+
//
373+
// Returning `Ok(None)` would be invalid here: we must return an error.
374+
ControlFlow::Continue(_) => {
375+
return Err(
376+
"last chunk is not found but chunks exist: the linked chunk contains a cycle"
377+
.to_owned(),
378+
);
379+
}
355380
};
356381

357382
// Build the chunk.
@@ -1188,6 +1213,29 @@ mod tests {
11881213
}
11891214
}
11901215

1216+
#[test]
1217+
fn test_load_last_chunk_with_a_cycle() {
1218+
let room_id = room_id!("!r0:matrix.org");
1219+
let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
1220+
1221+
relational_linked_chunk.apply_updates(
1222+
room_id,
1223+
vec![
1224+
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1225+
Update::NewItemsChunk {
1226+
// Because `previous` connects to chunk #0, it will create a cycle.
1227+
// Chunk #0 will have a `next` set to chunk #1! Consequently, the last chunk
1228+
// **does not exist**. We have to detect this cycle.
1229+
previous: Some(CId::new(0)),
1230+
new: CId::new(1),
1231+
next: Some(CId::new(0)),
1232+
},
1233+
],
1234+
);
1235+
1236+
relational_linked_chunk.load_last_chunk(room_id).unwrap_err();
1237+
}
1238+
11911239
#[test]
11921240
fn test_load_previous_chunk() {
11931241
let room_id = room_id!("!r0:matrix.org");

0 commit comments

Comments
 (0)