From 5d8ad3a4a920c5f0a85674f3c7dbac4154c5b16b Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 17 Dec 2024 17:22:57 +0100 Subject: [PATCH 1/9] fix(linked chunk): in LinkedChunk::ritems_from, skip as long as we're on the right chunk The previous code would skip based on the position's index, but not the position's chunk. It could be that the position's chunk is different from the first items chunk, as shown in the example, where the linked chunk ends with a gap; in this case, the position's index would be 0, while the first chunk found while iterating backwards had 3 items. As a result, items 'd' and 'e' would be skipped incorrectly. The fix is to take into account the chunk id when skipping over items. --- .../matrix-sdk-common/src/linked_chunk/mod.rs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-common/src/linked_chunk/mod.rs b/crates/matrix-sdk-common/src/linked_chunk/mod.rs index 6a67383b014..f42409d68f7 100644 --- a/crates/matrix-sdk-common/src/linked_chunk/mod.rs +++ b/crates/matrix-sdk-common/src/linked_chunk/mod.rs @@ -816,8 +816,9 @@ impl LinkedChunk { .skip_while({ let expected_index = position.index(); - move |(Position(_chunk_identifier, item_index), _item)| { - *item_index != expected_index + move |(Position(chunk_identifier, item_index), _item)| { + *chunk_identifier == position.chunk_identifier() + && *item_index != expected_index } })) } @@ -1813,6 +1814,26 @@ mod tests { assert_matches!(iterator.next(), None); } + #[test] + fn test_ritems_with_final_gap() -> Result<(), Error> { + let mut linked_chunk = LinkedChunk::<3, char, ()>::new(); + linked_chunk.push_items_back(['a', 'b']); + linked_chunk.push_gap_back(()); + linked_chunk.push_items_back(['c', 'd', 'e']); + linked_chunk.push_gap_back(()); + + let mut iterator = linked_chunk.ritems(); + + assert_matches!(iterator.next(), Some((Position(ChunkIdentifier(2), 2), 'e'))); + assert_matches!(iterator.next(), Some((Position(ChunkIdentifier(2), 1), 'd'))); + assert_matches!(iterator.next(), Some((Position(ChunkIdentifier(2), 0), 'c'))); + assert_matches!(iterator.next(), Some((Position(ChunkIdentifier(0), 1), 'b'))); + assert_matches!(iterator.next(), Some((Position(ChunkIdentifier(0), 0), 'a'))); + assert_matches!(iterator.next(), None); + + Ok(()) + } + #[test] fn test_ritems_empty() { let linked_chunk = LinkedChunk::<2, char, ()>::new(); From 373709fb38c28aa182bd6d94c588bf9c89ad7e1d Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Mon, 16 Dec 2024 18:36:50 +0100 Subject: [PATCH 2/9] feat(event cache): don't replace a gap chunk by an empty items chunks --- .../matrix-sdk-common/src/linked_chunk/mod.rs | 139 +++++++++++++++--- .../matrix-sdk/src/event_cache/pagination.rs | 9 +- .../matrix-sdk/src/event_cache/room/events.rs | 60 ++++++-- .../tests/integration/event_cache.rs | 73 ++++++++- 4 files changed, 246 insertions(+), 35 deletions(-) diff --git a/crates/matrix-sdk-common/src/linked_chunk/mod.rs b/crates/matrix-sdk-common/src/linked_chunk/mod.rs index f42409d68f7..3294f58845f 100644 --- a/crates/matrix-sdk-common/src/linked_chunk/mod.rs +++ b/crates/matrix-sdk-common/src/linked_chunk/mod.rs @@ -634,6 +634,47 @@ impl LinkedChunk { Ok(()) } + /// Remove a gap with the given identifier. + /// + /// This returns the next insert position, viz. the start of the next + /// chunk, if any, or none if there was no next chunk. + pub fn remove_gap_at( + &mut self, + chunk_identifier: ChunkIdentifier, + ) -> Result, Error> { + let chunk = self + .links + .chunk_mut(chunk_identifier) + .ok_or(Error::InvalidChunkIdentifier { identifier: chunk_identifier })?; + + if chunk.is_items() { + return Err(Error::ChunkIsItems { identifier: chunk_identifier }); + }; + + let next = chunk.next; + + chunk.unlink(&mut self.updates); + + let chunk_ptr = chunk.as_ptr(); + + // If this ever changes, we may need to update self.links.first too. + debug_assert!(chunk.is_first_chunk().not(), "A gap cannot be the first chunk"); + + if chunk.is_last_chunk() { + self.links.last = chunk.previous; + } + + // SAFETY: `chunk` is unlinked and not borrowed anymore. `LinkedChunk` doesn't + // use it anymore, it's a leak. It is time to re-`Box` it and drop it. + let _chunk_boxed = unsafe { Box::from_raw(chunk_ptr.as_ptr()) }; + + // Return the first position of the next chunk, if any. + Ok(next.map(|next| { + let chunk = unsafe { next.as_ref() }; + chunk.first_position() + })) + } + /// Replace the gap identified by `chunk_identifier`, by items. /// /// Because the `chunk_identifier` can represent non-gap chunk, this method @@ -661,27 +702,25 @@ impl LinkedChunk { .chunk_mut(chunk_identifier) .ok_or(Error::InvalidChunkIdentifier { identifier: chunk_identifier })?; - debug_assert!(chunk.is_first_chunk().not(), "A gap cannot be the first chunk"); + if chunk.is_items() { + return Err(Error::ChunkIsItems { identifier: chunk_identifier }); + }; - let maybe_last_chunk_ptr = match &mut chunk.content { - ChunkContent::Gap(..) => { - let items = items.into_iter(); + debug_assert!(chunk.is_first_chunk().not(), "A gap cannot be the first chunk"); - let last_inserted_chunk = chunk - // Insert a new items chunk… - .insert_next( - Chunk::new_items_leaked(self.chunk_identifier_generator.next()), - &mut self.updates, - ) - // … and insert the items. - .push_items(items, &self.chunk_identifier_generator, &mut self.updates); + let maybe_last_chunk_ptr = { + let items = items.into_iter(); - last_inserted_chunk.is_last_chunk().then(|| last_inserted_chunk.as_ptr()) - } + let last_inserted_chunk = chunk + // Insert a new items chunk… + .insert_next( + Chunk::new_items_leaked(self.chunk_identifier_generator.next()), + &mut self.updates, + ) + // … and insert the items. + .push_items(items, &self.chunk_identifier_generator, &mut self.updates); - ChunkContent::Items(..) => { - return Err(Error::ChunkIsItems { identifier: chunk_identifier }) - } + last_inserted_chunk.is_last_chunk().then(|| last_inserted_chunk.as_ptr()) }; new_chunk_ptr = chunk @@ -2691,6 +2730,72 @@ mod tests { Ok(()) } + #[test] + fn test_remove_gap() -> Result<(), Error> { + use super::Update::*; + + let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history(); + + // Ignore initial update. + let _ = linked_chunk.updates().unwrap().take(); + + linked_chunk.push_items_back(['a', 'b']); + linked_chunk.push_gap_back(()); + linked_chunk.push_items_back(['l', 'm']); + linked_chunk.push_gap_back(()); + assert_items_eq!(linked_chunk, ['a', 'b'] [-] ['l', 'm'] [-]); + assert_eq!( + linked_chunk.updates().unwrap().take(), + &[ + PushItems { at: Position(ChunkIdentifier(0), 0), items: vec!['a', 'b'] }, + NewGapChunk { + previous: Some(ChunkIdentifier(0)), + new: ChunkIdentifier(1), + next: None, + gap: (), + }, + NewItemsChunk { + previous: Some(ChunkIdentifier(1)), + new: ChunkIdentifier(2), + next: None, + }, + PushItems { at: Position(ChunkIdentifier(2), 0), items: vec!['l', 'm'] }, + NewGapChunk { + previous: Some(ChunkIdentifier(2)), + new: ChunkIdentifier(3), + next: None, + gap: (), + }, + ] + ); + + // Try to remove a gap that's not a gap. + let err = linked_chunk.remove_gap_at(ChunkIdentifier(0)).unwrap_err(); + assert_matches!(err, Error::ChunkIsItems { .. }); + + // Try to remove an unknown gap chunk. + let err = linked_chunk.remove_gap_at(ChunkIdentifier(42)).unwrap_err(); + assert_matches!(err, Error::InvalidChunkIdentifier { .. }); + + // Remove the gap in the middle. + let maybe_next = linked_chunk.remove_gap_at(ChunkIdentifier(1)).unwrap(); + let next = maybe_next.unwrap(); + // The next insert position at the start of the next chunk. + assert_eq!(next.chunk_identifier(), ChunkIdentifier(2)); + assert_eq!(next.index(), 0); + assert_items_eq!(linked_chunk, ['a', 'b'] ['l', 'm'] [-]); + assert_eq!(linked_chunk.updates().unwrap().take(), &[RemoveChunk(ChunkIdentifier(1))]); + + // Remove the gap at the end. + let next = linked_chunk.remove_gap_at(ChunkIdentifier(3)).unwrap(); + // It was the last chunk, so there's no next insert position. + assert!(next.is_none()); + assert_items_eq!(linked_chunk, ['a', 'b'] ['l', 'm']); + assert_eq!(linked_chunk.updates().unwrap().take(), &[RemoveChunk(ChunkIdentifier(3))]); + + Ok(()) + } + #[test] fn test_chunk_item_positions() { let mut linked_chunk = LinkedChunk::<3, char, ()>::new(); diff --git a/crates/matrix-sdk/src/event_cache/pagination.rs b/crates/matrix-sdk/src/event_cache/pagination.rs index 064acd5ae00..832398ec57c 100644 --- a/crates/matrix-sdk/src/event_cache/pagination.rs +++ b/crates/matrix-sdk/src/event_cache/pagination.rs @@ -178,12 +178,9 @@ impl RoomPagination { let insert_new_gap_pos = if let Some(gap_id) = prev_gap_id { // There is a prior gap, let's replace it by new events! trace!("replaced gap with new events from backpagination"); - Some( - room_events - .replace_gap_at(sync_events, gap_id) - .expect("gap_identifier is a valid chunk id we read previously") - .first_position(), - ) + room_events + .replace_gap_at(sync_events, gap_id) + .expect("gap_identifier is a valid chunk id we read previously") } else if let Some(pos) = first_event_pos { // No prior gap, but we had some events: assume we need to prepend events // before those. diff --git a/crates/matrix-sdk/src/event_cache/room/events.rs b/crates/matrix-sdk/src/event_cache/room/events.rs index 83fc2218972..67a65635153 100644 --- a/crates/matrix-sdk/src/event_cache/room/events.rs +++ b/crates/matrix-sdk/src/event_cache/room/events.rs @@ -145,13 +145,13 @@ impl RoomEvents { /// Because the `gap_identifier` can represent non-gap chunk, this method /// returns a `Result`. /// - /// This method returns a reference to the (first if many) newly created - /// `Chunk` that contains the `items`. + /// This method returns either the position of the first chunk that's been + /// created, or the next insert position if the chunk has been removed. pub fn replace_gap_at( &mut self, events: I, gap_identifier: ChunkIdentifier, - ) -> Result<&Chunk, Error> + ) -> Result, Error> where I: IntoIterator, { @@ -165,8 +165,14 @@ impl RoomEvents { // because of the removals. self.remove_events(duplicated_event_ids); - // Replace the gap by new events. - self.chunks.replace_gap_at(unique_events, gap_identifier) + if unique_events.is_empty() { + // There are no new events, so there's no need to create a new empty items + // chunk; instead, remove the gap. + self.chunks.remove_gap_at(gap_identifier) + } else { + // Replace the gap by new events. + Ok(Some(self.chunks.replace_gap_at(unique_events, gap_identifier)?.first_position())) + } } /// Search for a chunk, and return its identifier. @@ -711,9 +717,8 @@ mod tests { let chunk_identifier_of_gap = room_events .chunks() - .find_map(|chunk| chunk.is_gap().then_some(chunk.first_position())) - .unwrap() - .chunk_identifier(); + .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier())) + .unwrap(); room_events.replace_gap_at([event_1, event_2], chunk_identifier_of_gap).unwrap(); @@ -752,9 +757,8 @@ mod tests { let chunk_identifier_of_gap = room_events .chunks() - .find_map(|chunk| chunk.is_gap().then_some(chunk.first_position())) - .unwrap() - .chunk_identifier(); + .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier())) + .unwrap(); assert_events_eq!( room_events.events(), @@ -790,6 +794,40 @@ mod tests { } } + #[test] + fn test_replace_gap_at_with_no_new_events() { + let (_, event_0) = new_event("$ev0"); + let (_, event_1) = new_event("$ev1"); + let (_, event_2) = new_event("$ev2"); + + let mut room_events = RoomEvents::new(); + + room_events.push_events([event_0, event_1]); + room_events.push_gap(Gap { prev_token: "middle".to_owned() }); + room_events.push_events([event_2]); + room_events.push_gap(Gap { prev_token: "end".to_owned() }); + + // Remove the first gap. + let first_gap_id = room_events + .chunks() + .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier())) + .unwrap(); + + // The next insert position is the next chunk's start. + let pos = room_events.replace_gap_at([], first_gap_id).unwrap(); + assert_eq!(pos, Some(Position::new(ChunkIdentifier::new(2), 0))); + + // Remove the second gap. + let second_gap_id = room_events + .chunks() + .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier())) + .unwrap(); + + // No next insert position. + let pos = room_events.replace_gap_at([], second_gap_id).unwrap(); + assert!(pos.is_none()); + } + #[test] fn test_remove_events() { let (event_id_0, event_0) = new_event("$ev0"); diff --git a/crates/matrix-sdk/tests/integration/event_cache.rs b/crates/matrix-sdk/tests/integration/event_cache.rs index 0832fea4b5d..20541241840 100644 --- a/crates/matrix-sdk/tests/integration/event_cache.rs +++ b/crates/matrix-sdk/tests/integration/event_cache.rs @@ -14,7 +14,7 @@ use matrix_sdk::{ use matrix_sdk_test::{ async_test, event_factory::EventFactory, GlobalAccountDataTestEvent, JoinedRoomBuilder, }; -use ruma::{event_id, room_id, user_id}; +use ruma::{event_id, events::AnyTimelineEvent, room_id, serde::Raw, user_id}; use serde_json::json; use tokio::{spawn, sync::broadcast}; use wiremock::ResponseTemplate; @@ -916,3 +916,74 @@ async fn test_backpaginate_with_no_initial_events() { assert_event_matches_msg(&events[2], "world"); assert_eq!(events.len(), 3); } + +#[async_test] +async fn test_backpaginate_replace_empty_gap() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let event_cache = client.event_cache(); + + // Immediately subscribe the event cache to sync updates. + event_cache.subscribe().unwrap(); + + let room_id = room_id!("!omelette:fromage.fr"); + + let f = EventFactory::new().room(room_id).sender(user_id!("@a:b.c")); + + // Start with a room with an event, limited timeline and prev-batch token. + let room = server + .sync_room( + &client, + JoinedRoomBuilder::new(room_id) + .add_timeline_event(f.text_msg("world").event_id(event_id!("$2"))) + .set_timeline_limited() + .set_timeline_prev_batch("prev-batch".to_owned()), + ) + .await; + + let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap(); + + let (events, mut stream) = room_event_cache.subscribe().await.unwrap(); + wait_for_initial_events(events, &mut stream).await; + + // The first back-pagination will return a previous-batch token, but no events. + server + .mock_room_messages() + .ok( + "start-token-unused1".to_owned(), + Some("prev_batch".to_owned()), + Vec::>::new(), + Vec::new(), + ) + .mock_once() + .mount() + .await; + + // The second round of back-pagination will return this one. + server + .mock_room_messages() + .from("prev_batch") + .ok( + "start-token-unused2".to_owned(), + None, + vec![f.text_msg("hello").event_id(event_id!("$1"))], + Vec::new(), + ) + .mock_once() + .mount() + .await; + + let pagination = room_event_cache.pagination(); + + // Run pagination twice. + pagination.run_backwards(20, once).await.unwrap(); + pagination.run_backwards(20, once).await.unwrap(); + + // The linked chunk should contain the events in the correct order. + let (events, _stream) = room_event_cache.subscribe().await.unwrap(); + + assert_event_matches_msg(&events[0], "hello"); + assert_event_matches_msg(&events[1], "world"); + assert_eq!(events.len(), 2); +} From 5f3b56a987b7799ca42fcf4dedb2b80b8a98660e Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 17 Dec 2024 15:49:55 +0000 Subject: [PATCH 3/9] task(crypto): Accept old PreviouslyVerified value of SenderData when deserializing --- .../src/olm/group_sessions/sender_data.rs | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data.rs index df54390e580..0be07403e00 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data.rs @@ -215,6 +215,7 @@ enum SenderDataReader { legacy_session: bool, }, + #[serde(alias = "SenderUnverifiedButPreviouslyVerified")] VerificationViolation(KnownSenderData), SenderUnverified(KnownSenderData), @@ -286,7 +287,10 @@ mod tests { use vodozemac::Ed25519PublicKey; use super::SenderData; - use crate::types::{DeviceKeys, Signatures}; + use crate::{ + olm::KnownSenderData, + types::{DeviceKeys, Signatures}, + }; #[test] fn serializing_unknown_device_correctly_preserves_owner_check_failed_if_true() { @@ -360,6 +364,47 @@ mod tests { assert_let!(SenderData::SenderVerified { .. } = end); } + #[test] + fn deserializing_sender_unverified_but_previously_verified_migrates_to_verification_violation() + { + let json = r#" + { + "SenderUnverifiedButPreviouslyVerified":{ + "user_id":"@u:s.co", + "master_key":[ + 150,140,249,139,141,29,63,230,179,14,213,175,176,61,11,255, + 26,103,10,51,100,154,183,47,181,117,87,204,33,215,241,92 + ], + "master_key_verified":true + } + } + "#; + + let end: SenderData = serde_json::from_str(json).expect("Failed to parse!"); + assert_let!(SenderData::VerificationViolation(KnownSenderData { user_id, .. }) = end); + assert_eq!(user_id, owned_user_id!("@u:s.co")); + } + + #[test] + fn deserializing_verification_violation() { + let json = r#" + { + "VerificationViolation":{ + "user_id":"@u:s.co", + "master_key":[ + 150,140,249,139,141,29,63,230,179,14,213,175,176,61,11,255, + 26,103,10,51,100,154,183,47,181,117,87,204,33,215,241,92 + ], + "master_key_verified":true + } + } + "#; + + let end: SenderData = serde_json::from_str(json).expect("Failed to parse!"); + assert_let!(SenderData::VerificationViolation(KnownSenderData { user_id, .. }) = end); + assert_eq!(user_id, owned_user_id!("@u:s.co")); + } + #[test] fn equal_sessions_have_same_trust_level() { let unknown = SenderData::unknown(); From db39c6bea6ee3007fae469d5aa5e66389e6127b9 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 18 Dec 2024 09:42:49 +0000 Subject: [PATCH 4/9] task(crypto): Accept old PreviouslyVerified value of VerificationLevel when deserializing --- .../src/deserialized_responses.rs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index c63a0e42968..3ec6401a16d 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -176,6 +176,7 @@ pub enum VerificationLevel { /// The message was sent by a user identity we have not verified, but the /// user was previously verified. + #[serde(alias = "PreviouslyVerified")] VerificationViolation, /// The message was sent by a device not linked to (signed by) any user @@ -883,6 +884,40 @@ mod tests { ); } + #[test] + fn test_verification_level_deserializes() { + // Given a JSON VerificationLevel + #[derive(Deserialize)] + struct Container { + verification_level: VerificationLevel, + } + let container = json!({ "verification_level": "VerificationViolation" }); + + // When we deserialize it + let deserialized: Container = serde_json::from_value(container) + .expect("We can deserialize the old PreviouslyVerified value"); + + // Then it is populated correctly + assert_eq!(deserialized.verification_level, VerificationLevel::VerificationViolation); + } + + #[test] + fn test_verification_level_deserializes_from_old_previously_verified_value() { + // Given a JSON VerificationLevel with the old value PreviouslyVerified + #[derive(Deserialize)] + struct Container { + verification_level: VerificationLevel, + } + let container = json!({ "verification_level": "PreviouslyVerified" }); + + // When we deserialize it + let deserialized: Container = serde_json::from_value(container) + .expect("We can deserialize the old PreviouslyVerified value"); + + // Then it is migrated to the new value + assert_eq!(deserialized.verification_level, VerificationLevel::VerificationViolation); + } + #[test] fn sync_timeline_event_serialisation() { let room_event = SyncTimelineEvent { From 612ba6fa29280dfd2a5b70f14699af9728eeb022 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 18 Dec 2024 09:49:17 +0000 Subject: [PATCH 5/9] task(crypto): Accept old PreviouslyVerified value of ShieldStateCode when deserializing --- .../src/deserialized_responses.rs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 3ec6401a16d..ea41fd2f3b2 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -260,6 +260,7 @@ pub enum ShieldStateCode { /// An unencrypted event in an encrypted room. SentInClear, /// The sender was previously verified but changed their identity. + #[serde(alias = "PreviouslyVerified")] VerificationViolation, } @@ -809,7 +810,7 @@ mod tests { TimelineEventKind, UnableToDecryptInfo, UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation, VerificationState, }; - use crate::deserialized_responses::{DeviceLinkProblem, VerificationLevel}; + use crate::deserialized_responses::{DeviceLinkProblem, ShieldStateCode, VerificationLevel}; fn example_event() -> serde_json::Value { json!({ @@ -918,6 +919,40 @@ mod tests { assert_eq!(deserialized.verification_level, VerificationLevel::VerificationViolation); } + #[test] + fn test_shield_state_code_deserializes() { + // Given a JSON ShieldStateCode with value VerificationViolation + #[derive(Deserialize)] + struct Container { + shield_state_code: ShieldStateCode, + } + let container = json!({ "shield_state_code": "VerificationViolation" }); + + // When we deserialize it + let deserialized: Container = serde_json::from_value(container) + .expect("We can deserialize the old PreviouslyVerified value"); + + // Then it is populated correctly + assert_eq!(deserialized.shield_state_code, ShieldStateCode::VerificationViolation); + } + + #[test] + fn test_shield_state_code_deserializes_from_old_previously_verified_value() { + // Given a JSON ShieldStateCode with the old value PreviouslyVerified + #[derive(Deserialize)] + struct Container { + shield_state_code: ShieldStateCode, + } + let container = json!({ "shield_state_code": "PreviouslyVerified" }); + + // When we deserialize it + let deserialized: Container = serde_json::from_value(container) + .expect("We can deserialize the old PreviouslyVerified value"); + + // Then it is migrated to the new value + assert_eq!(deserialized.shield_state_code, ShieldStateCode::VerificationViolation); + } + #[test] fn sync_timeline_event_serialisation() { let room_event = SyncTimelineEvent { From b18e7d71edc13901598281aca04477874f1254e7 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 18 Dec 2024 10:16:38 +0000 Subject: [PATCH 6/9] fix(crypto): Fix error when reading VerifiedStateOrBool with old PreviouslyVerifiedButNoLonger value --- .../matrix-sdk-crypto/src/identities/user.rs | 77 ++++++++++++++----- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/identities/user.rs b/crates/matrix-sdk-crypto/src/identities/user.rs index bbfdf54c4ee..be1f54e4011 100644 --- a/crates/matrix-sdk-crypto/src/identities/user.rs +++ b/crates/matrix-sdk-crypto/src/identities/user.rs @@ -911,6 +911,7 @@ enum OwnUserIdentityVerifiedState { NeverVerified, /// We previously verified this identity, but it has changed. + #[serde(alias = "PreviouslyVerifiedButNoLonger")] VerificationViolation, /// We have verified the current identity. @@ -1530,26 +1531,10 @@ pub(crate) mod tests { /// that we can deserialize boolean values. #[test] fn test_deserialize_own_user_identity_bool_verified() { - let mut json = json!({ - "user_id": "@example:localhost", - "master_key": { - "user_id":"@example:localhost", - "usage":["master"], - "keys":{"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0":"rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0"}, - }, - "self_signing_key": { - "user_id":"@example:localhost", - "usage":["self_signing"], - "keys":{"ed25519:0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210":"0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210"} - }, - "user_signing_key": { - "user_id":"@example:localhost", - "usage":["user_signing"], - "keys":{"ed25519:DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo":"DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo"} - }, - "verified": false - }); + let mut json = own_user_identity_data(); + // Set `"verified": false` + *json.get_mut("verified").unwrap() = false.into(); let id: OwnUserIdentityData = serde_json::from_value(json.clone()).unwrap(); assert_eq!(*id.verified.read().unwrap(), OwnUserIdentityVerifiedState::NeverVerified); @@ -1559,6 +1544,38 @@ pub(crate) mod tests { assert_eq!(*id.verified.read().unwrap(), OwnUserIdentityVerifiedState::Verified); } + #[test] + fn test_own_user_identity_verified_state_verification_violation_deserializes() { + // Given data containing verified: VerificationViolation + let mut json = own_user_identity_data(); + *json.get_mut("verified").unwrap() = "VerificationViolation".into(); + + // When we deserialize + let id: OwnUserIdentityData = serde_json::from_value(json.clone()).unwrap(); + + // Then the value is correctly populated + assert_eq!( + *id.verified.read().unwrap(), + OwnUserIdentityVerifiedState::VerificationViolation + ); + } + + #[test] + fn test_own_user_identity_verified_state_previously_verified_deserializes() { + // Given data containing verified: PreviouslyVerifiedButNoLonger + let mut json = own_user_identity_data(); + *json.get_mut("verified").unwrap() = "PreviouslyVerifiedButNoLonger".into(); + + // When we deserialize + let id: OwnUserIdentityData = serde_json::from_value(json.clone()).unwrap(); + + // Then the old value is re-interpreted as VerificationViolation + assert_eq!( + *id.verified.read().unwrap(), + OwnUserIdentityVerifiedState::VerificationViolation + ); + } + #[test] fn own_identity_check_signatures() { let response = own_key_query(); @@ -1895,4 +1912,26 @@ pub(crate) mod tests { assert!(!own_identity.was_previously_verified()); assert!(!own_identity.has_verification_violation()); } + + fn own_user_identity_data() -> Value { + json!({ + "user_id": "@example:localhost", + "master_key": { + "user_id":"@example:localhost", + "usage":["master"], + "keys":{"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0":"rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0"}, + }, + "self_signing_key": { + "user_id":"@example:localhost", + "usage":["self_signing"], + "keys":{"ed25519:0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210":"0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210"} + }, + "user_signing_key": { + "user_id":"@example:localhost", + "usage":["user_signing"], + "keys":{"ed25519:DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo":"DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo"} + }, + "verified": false + }) + } } From bb70229dd884c1315f5e20f4b9e9685be7d30af7 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 18 Dec 2024 13:09:33 +0100 Subject: [PATCH 7/9] chore: Make Clippy happy. --- crates/matrix-sdk-crypto/src/verification/requests.rs | 2 +- crates/matrix-sdk/src/room/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/verification/requests.rs b/crates/matrix-sdk-crypto/src/verification/requests.rs index 4c25a969807..50960185a8f 100644 --- a/crates/matrix-sdk-crypto/src/verification/requests.rs +++ b/crates/matrix-sdk-crypto/src/verification/requests.rs @@ -658,7 +658,7 @@ impl VerificationRequest { let recip_devices: Vec = self .recipient_devices .iter() - .filter(|&d| filter_device.map_or(true, |device| **d != *device)) + .filter(|&d| filter_device.is_none_or(|device| **d != *device)) .cloned() .collect(); diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index 30a3eed4de5..6ee2995822d 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -1813,7 +1813,7 @@ impl Room { // A member has no unknown devices iff it was tracked *and* the tracking is // not considered dirty. let members_with_unknown_devices = - members.iter().filter(|member| tracked.get(*member).map_or(true, |dirty| *dirty)); + members.iter().filter(|member| tracked.get(*member).is_none_or(|dirty| *dirty)); let (req_id, request) = olm.query_keys_for_users(members_with_unknown_devices.map(|owned| owned.borrow())); From ff7077b742d845dfd35f0730e7c2c75adf06b961 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 18 Dec 2024 13:27:07 +0000 Subject: [PATCH 8/9] doc(crypto): Add changelog entry for #4424 --- crates/matrix-sdk-crypto/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/matrix-sdk-crypto/CHANGELOG.md b/crates/matrix-sdk-crypto/CHANGELOG.md index af8c51f7e33..7177a251fb8 100644 --- a/crates/matrix-sdk-crypto/CHANGELOG.md +++ b/crates/matrix-sdk-crypto/CHANGELOG.md @@ -93,6 +93,12 @@ All notable changes to this project will be documented in this file. ### Refactor +- Fix [#4424](https://github.com/matrix-org/matrix-rust-sdk/issues/4424) Failed + storage upgrade for "PreviouslyVerifiedButNoLonger". This bug caused errors to + occur when loading crypto information from storage, which typically prevented + apps from starting correctly. + ([#4430](https://github.com/matrix-org/matrix-rust-sdk/pull/4430)) + - Add new method `OlmMachine::try_decrypt_room_event`. ([#4116](https://github.com/matrix-org/matrix-rust-sdk/pull/4116)) From bb573117e102261b46da3f2d6761c4b3f51850a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 18 Dec 2024 12:52:52 +0100 Subject: [PATCH 9/9] chore: Release matrix-sdk version 0.9.0 --- Cargo.lock | 18 +++++++++--------- Cargo.toml | 18 +++++++++--------- crates/matrix-sdk-base/CHANGELOG.md | 2 ++ crates/matrix-sdk-base/Cargo.toml | 2 +- crates/matrix-sdk-common/CHANGELOG.md | 2 ++ crates/matrix-sdk-common/Cargo.toml | 2 +- crates/matrix-sdk-crypto/CHANGELOG.md | 2 ++ crates/matrix-sdk-crypto/Cargo.toml | 2 +- crates/matrix-sdk-indexeddb/CHANGELOG.md | 4 ++++ crates/matrix-sdk-indexeddb/Cargo.toml | 2 +- crates/matrix-sdk-qrcode/CHANGELOG.md | 4 ++++ crates/matrix-sdk-qrcode/Cargo.toml | 2 +- crates/matrix-sdk-sqlite/CHANGELOG.md | 2 ++ crates/matrix-sdk-sqlite/Cargo.toml | 2 +- .../matrix-sdk-store-encryption/CHANGELOG.md | 4 ++++ crates/matrix-sdk-store-encryption/Cargo.toml | 2 +- crates/matrix-sdk-ui/CHANGELOG.md | 2 ++ crates/matrix-sdk-ui/Cargo.toml | 2 +- crates/matrix-sdk/CHANGELOG.md | 2 ++ crates/matrix-sdk/Cargo.toml | 2 +- 20 files changed, 51 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36ee61dd332..f4f968755e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3048,7 +3048,7 @@ dependencies = [ [[package]] name = "matrix-sdk" -version = "0.8.0" +version = "0.9.0" dependencies = [ "anyhow", "anymap2", @@ -3124,7 +3124,7 @@ dependencies = [ [[package]] name = "matrix-sdk-base" -version = "0.8.0" +version = "0.9.0" dependencies = [ "as_variant", "assert_matches", @@ -3160,7 +3160,7 @@ dependencies = [ [[package]] name = "matrix-sdk-common" -version = "0.8.0" +version = "0.9.0" dependencies = [ "assert_matches", "async-trait", @@ -3189,7 +3189,7 @@ dependencies = [ [[package]] name = "matrix-sdk-crypto" -version = "0.8.0" +version = "0.9.0" dependencies = [ "aes", "anyhow", @@ -3313,7 +3313,7 @@ dependencies = [ [[package]] name = "matrix-sdk-indexeddb" -version = "0.8.0" +version = "0.9.0" dependencies = [ "anyhow", "assert_matches", @@ -3381,7 +3381,7 @@ dependencies = [ [[package]] name = "matrix-sdk-qrcode" -version = "0.8.0" +version = "0.9.0" dependencies = [ "byteorder", "image", @@ -3393,7 +3393,7 @@ dependencies = [ [[package]] name = "matrix-sdk-sqlite" -version = "0.8.0" +version = "0.9.0" dependencies = [ "assert_matches", "async-trait", @@ -3421,7 +3421,7 @@ dependencies = [ [[package]] name = "matrix-sdk-store-encryption" -version = "0.8.0" +version = "0.9.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -3469,7 +3469,7 @@ dependencies = [ [[package]] name = "matrix-sdk-ui" -version = "0.8.0" +version = "0.9.0" dependencies = [ "anyhow", "as_variant", diff --git a/Cargo.toml b/Cargo.toml index d9f068ae18b..5882bd8bd74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,17 +97,17 @@ web-sys = "0.3.69" wiremock = "0.6.2" zeroize = "1.8.1" -matrix-sdk = { path = "crates/matrix-sdk", version = "0.8.0", default-features = false } -matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.8.0" } -matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.8.0" } -matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.8.0" } +matrix-sdk = { path = "crates/matrix-sdk", version = "0.9.0", default-features = false } +matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.9.0" } +matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.9.0" } +matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.9.0" } matrix-sdk-ffi-macros = { path = "bindings/matrix-sdk-ffi-macros", version = "0.7.0" } -matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.8.0", default-features = false } -matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.8.0" } -matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.8.0", default-features = false } -matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.8.0" } +matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.9.0", default-features = false } +matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.9.0" } +matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.9.0", default-features = false } +matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.9.0" } matrix-sdk-test = { path = "testing/matrix-sdk-test", version = "0.7.0" } -matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.8.0", default-features = false } +matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.9.0", default-features = false } # Default release profile, select with `--release` [profile.release] diff --git a/crates/matrix-sdk-base/CHANGELOG.md b/crates/matrix-sdk-base/CHANGELOG.md index 862d8a3fa0b..988064b396a 100644 --- a/crates/matrix-sdk-base/CHANGELOG.md +++ b/crates/matrix-sdk-base/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +## [0.9.0] - 2024-12-18 + ### Features - Introduced support for diff --git a/crates/matrix-sdk-base/Cargo.toml b/crates/matrix-sdk-base/Cargo.toml index 58a9c50e64c..825c524062a 100644 --- a/crates/matrix-sdk-base/Cargo.toml +++ b/crates/matrix-sdk-base/Cargo.toml @@ -9,7 +9,7 @@ name = "matrix-sdk-base" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" rust-version = { workspace = true } -version = "0.8.0" +version = "0.9.0" [package.metadata.docs.rs] all-features = true diff --git a/crates/matrix-sdk-common/CHANGELOG.md b/crates/matrix-sdk-common/CHANGELOG.md index 683eed16852..7eedc0ab0b7 100644 --- a/crates/matrix-sdk-common/CHANGELOG.md +++ b/crates/matrix-sdk-common/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +## [0.9.0] - 2024-12-18 + ### Bug Fixes - Change the behavior of `LinkedChunk::new_with_update_history()` to emit an diff --git a/crates/matrix-sdk-common/Cargo.toml b/crates/matrix-sdk-common/Cargo.toml index 78837937e11..2aef55b7efd 100644 --- a/crates/matrix-sdk-common/Cargo.toml +++ b/crates/matrix-sdk-common/Cargo.toml @@ -9,7 +9,7 @@ name = "matrix-sdk-common" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" rust-version = { workspace = true } -version = "0.8.0" +version = "0.9.0" [package.metadata.docs.rs] default-target = "x86_64-unknown-linux-gnu" diff --git a/crates/matrix-sdk-crypto/CHANGELOG.md b/crates/matrix-sdk-crypto/CHANGELOG.md index 7177a251fb8..5ddf3a801ad 100644 --- a/crates/matrix-sdk-crypto/CHANGELOG.md +++ b/crates/matrix-sdk-crypto/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +## [0.9.0] - 2024-12-18 + - Expose new API `DehydratedDevices::get_dehydrated_device_pickle_key`, `DehydratedDevices::save_dehydrated_device_pickle_key` and `DehydratedDevices::delete_dehydrated_device_pickle_key` to store/load the dehydrated device pickle key. This allows client to automatically rotate the dehydrated device to avoid one-time-keys exhaustion and to_device accumulation. diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 060692780fe..b3e3ccd1155 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -9,7 +9,7 @@ name = "matrix-sdk-crypto" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" rust-version = { workspace = true } -version = "0.8.0" +version = "0.9.0" [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] diff --git a/crates/matrix-sdk-indexeddb/CHANGELOG.md b/crates/matrix-sdk-indexeddb/CHANGELOG.md index 4372794f5a1..fa58f5dda75 100644 --- a/crates/matrix-sdk-indexeddb/CHANGELOG.md +++ b/crates/matrix-sdk-indexeddb/CHANGELOG.md @@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +## [0.9.0] - 2024-12-18 + +No notable changes in this release. + ## [0.8.0] - 2024-11-19 ### Features diff --git a/crates/matrix-sdk-indexeddb/Cargo.toml b/crates/matrix-sdk-indexeddb/Cargo.toml index 46de1f42acf..e06e315b060 100644 --- a/crates/matrix-sdk-indexeddb/Cargo.toml +++ b/crates/matrix-sdk-indexeddb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "matrix-sdk-indexeddb" -version = "0.8.0" +version = "0.9.0" repository = "https://github.com/matrix-org/matrix-rust-sdk" description = "Web's IndexedDB Storage backend for matrix-sdk" license = "Apache-2.0" diff --git a/crates/matrix-sdk-qrcode/CHANGELOG.md b/crates/matrix-sdk-qrcode/CHANGELOG.md index 9235a4b9ff2..d3fae753d53 100644 --- a/crates/matrix-sdk-qrcode/CHANGELOG.md +++ b/crates/matrix-sdk-qrcode/CHANGELOG.md @@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +## [0.9.0] - 2024-12-18 + +No notable changes in this release. + ## [0.8.0] - 2024-11-19 No notable changes in this release. diff --git a/crates/matrix-sdk-qrcode/Cargo.toml b/crates/matrix-sdk-qrcode/Cargo.toml index 42cf72489ca..ecc95ac78cf 100644 --- a/crates/matrix-sdk-qrcode/Cargo.toml +++ b/crates/matrix-sdk-qrcode/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "matrix-sdk-qrcode" description = "Library to encode and decode QR codes for interactive verifications in Matrix land" -version = "0.8.0" +version = "0.9.0" authors = ["Damir Jelić "] edition = "2021" homepage = "https://github.com/matrix-org/matrix-rust-sdk" diff --git a/crates/matrix-sdk-sqlite/CHANGELOG.md b/crates/matrix-sdk-sqlite/CHANGELOG.md index dcf6ac6d8ad..b37a3a9a0cc 100644 --- a/crates/matrix-sdk-sqlite/CHANGELOG.md +++ b/crates/matrix-sdk-sqlite/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +## [0.9.0] - 2024-12-18 + ### Features - Add support for persisting LinkedChunks in the SQLite store. This is a step diff --git a/crates/matrix-sdk-sqlite/Cargo.toml b/crates/matrix-sdk-sqlite/Cargo.toml index c94b0f764f6..4596f4fc222 100644 --- a/crates/matrix-sdk-sqlite/Cargo.toml +++ b/crates/matrix-sdk-sqlite/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "matrix-sdk-sqlite" -version = "0.8.0" +version = "0.9.0" edition = "2021" repository = "https://github.com/matrix-org/matrix-rust-sdk" description = "Sqlite storage backend for matrix-sdk" diff --git a/crates/matrix-sdk-store-encryption/CHANGELOG.md b/crates/matrix-sdk-store-encryption/CHANGELOG.md index 9235a4b9ff2..d3fae753d53 100644 --- a/crates/matrix-sdk-store-encryption/CHANGELOG.md +++ b/crates/matrix-sdk-store-encryption/CHANGELOG.md @@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +## [0.9.0] - 2024-12-18 + +No notable changes in this release. + ## [0.8.0] - 2024-11-19 No notable changes in this release. diff --git a/crates/matrix-sdk-store-encryption/Cargo.toml b/crates/matrix-sdk-store-encryption/Cargo.toml index eda60adb6a3..e76c1f5a4f1 100644 --- a/crates/matrix-sdk-store-encryption/Cargo.toml +++ b/crates/matrix-sdk-store-encryption/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "matrix-sdk-store-encryption" -version = "0.8.0" +version = "0.9.0" edition = "2021" description = "Helpers for encrypted storage keys for the Matrix SDK" repository = "https://github.com/matrix-org/matrix-rust-sdk" diff --git a/crates/matrix-sdk-ui/CHANGELOG.md b/crates/matrix-sdk-ui/CHANGELOG.md index 0b0d578ba9a..908df65b4e1 100644 --- a/crates/matrix-sdk-ui/CHANGELOG.md +++ b/crates/matrix-sdk-ui/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +## [0.9.0] - 2024-12-18 + ### Bug Fixes - Add the `m.room.create` and the `m.room.history_visibility` state events to diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index 106220a3420..2ba3efa8db5 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "matrix-sdk-ui" description = "GUI-centric utilities on top of matrix-rust-sdk (experimental)." -version = "0.8.0" +version = "0.9.0" edition = "2021" repository = "https://github.com/matrix-org/matrix-rust-sdk" license = "Apache-2.0" diff --git a/crates/matrix-sdk/CHANGELOG.md b/crates/matrix-sdk/CHANGELOG.md index 87862d8672b..2906a2568b6 100644 --- a/crates/matrix-sdk/CHANGELOG.md +++ b/crates/matrix-sdk/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +## [0.9.0] - 2024-12-18 + ### Bug Fixes - Use the inviter's server name and the server name from the room alias as diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index 7a432a49ed1..450f008491a 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -9,7 +9,7 @@ name = "matrix-sdk" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" rust-version = { workspace = true } -version = "0.8.0" +version = "0.9.0" [package.metadata.docs.rs] features = ["docsrs"]