Skip to content

Commit

Permalink
fix(sled): Fix broken raw redacted state events
Browse files Browse the repository at this point in the history
A fix for (de)serialization of redacted state events in Ruma made
old events undeserializable.
Bump the DB version.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
  • Loading branch information
zecakeh authored and jplatte committed Feb 11, 2023
1 parent 57c3224 commit 5ae84a9
Showing 1 changed file with 121 additions and 12 deletions.
133 changes: 121 additions & 12 deletions crates/matrix-sdk-sled/src/state_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use ruma::{
RoomVersionId, UserId,
};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
use sled::{
transaction::{ConflictableTransactionError, TransactionError},
Config, Db, Transactional, Tree,
Expand Down Expand Up @@ -115,7 +116,7 @@ impl From<SledStoreError> for StoreError {
}
}
}
const DATABASE_VERSION: u8 = 2;
const DATABASE_VERSION: u8 = 3;

const VERSION_KEY: &str = "state-store-version";

Expand Down Expand Up @@ -438,17 +439,17 @@ impl SledStateStore {

debug!(old_version, new_version = DATABASE_VERSION, "Upgrading the Sled state store");

if old_version == 1 {
if self.store_cipher.is_some() {
// we stored some fields un-encrypted. Drop them to force re-creation
return Err(SledStoreError::MigrationConflict {
path: self.path.take().expect("Path must exist for a migration to fail"),
old_version: old_version.into(),
new_version: DATABASE_VERSION.into(),
});
}
// no migration to handle
self.set_db_version(2u8)?;
if old_version == 1 && self.store_cipher.is_some() {
// we stored some fields un-encrypted. Drop them to force re-creation
return Err(SledStoreError::MigrationConflict {
path: self.path.take().expect("Path must exist for a migration to fail"),
old_version: old_version.into(),
new_version: DATABASE_VERSION.into(),
});
}

if old_version < 3 {
self.migrate_to_v3()?;
return Ok(());
}

Expand All @@ -462,6 +463,54 @@ impl SledStateStore {
})
}

fn v3_fix_tree(&self, tree: &Tree, batch: &mut sled::Batch) -> Result<()> {
fn maybe_fix_json(raw_json: &RawJsonValue) -> Result<Option<JsonValue>> {
let json = raw_json.get();

if json.contains(r#""content":null"#) {
let mut value: JsonValue = serde_json::from_str(json)?;
if let Some(content) = value.get_mut("content") {
if matches!(content, JsonValue::Null) {
*content = JsonValue::Object(Default::default());
return Ok(Some(value));
}
}
}

Ok(None)
}

for entry in tree.iter() {
let (key, value) = entry?;
let raw_json: Box<RawJsonValue> = self.deserialize_value(&value)?;

if let Some(fixed_json) = maybe_fix_json(&raw_json)? {
batch.insert(key, self.serialize_value(&fixed_json)?);
}
}

Ok(())
}

fn migrate_to_v3(&self) -> Result<()> {
let mut room_info_batch = sled::Batch::default();
self.v3_fix_tree(&self.room_info, &mut room_info_batch)?;

let mut room_state_batch = sled::Batch::default();
self.v3_fix_tree(&self.room_state, &mut room_state_batch)?;

let ret: Result<(), TransactionError<SledStoreError>> = (&self.room_info, &self.room_state)
.transaction(|(room_info, room_state)| {
room_info.apply_batch(&room_info_batch)?;
room_state.apply_batch(&room_state_batch)?;

Ok(())
});
ret?;

self.set_db_version(3u8)
}

/// Open a `SledCryptoStore` that uses the same database as this store.
///
/// The given passphrase will be used to encrypt private data.
Expand Down Expand Up @@ -1551,9 +1600,15 @@ mod encrypted_tests {
#[cfg(test)]
mod migration {
use matrix_sdk_test::async_test;
use ruma::{
events::{AnySyncStateEvent, StateEventType},
room_id,
};
use serde_json::json;
use tempfile::TempDir;

use super::{MigrationConflictStrategy, Result, SledStateStore, SledStoreError};
use crate::state_store::ROOM_STATE;

#[async_test]
pub async fn migrating_v1_to_2_plain() -> Result<()> {
Expand Down Expand Up @@ -1638,4 +1693,58 @@ mod migration {
assert_eq!(std::fs::read_dir(folder.path())?.count(), 1);
Ok(())
}

#[async_test]
pub async fn migrating_v2_to_v3() {
// An event that fails to deserialize.
let wrong_redacted_state_event = json!({
"content": null,
"event_id": "$wrongevent",
"origin_server_ts": 1673887516047_u64,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.topic",
"unsigned": {
"redacted_because": {
"type": "m.room.redaction",
"sender": "@example:localhost",
"content": {},
"redacts": "$wrongevent",
"origin_server_ts": 1673893816047_u64,
"unsigned": {},
"event_id": "$redactionevent",
},
},
});
serde_json::from_value::<AnySyncStateEvent>(wrong_redacted_state_event.clone())
.unwrap_err();

let room_id = room_id!("!some_room:localhost");
let folder = TempDir::new().unwrap();

let store = SledStateStore::builder()
.path(folder.path().to_path_buf())
.passphrase("secret".to_owned())
.build()
.unwrap();

store
.room_state
.insert(
store.encode_key(ROOM_STATE, (room_id, StateEventType::RoomTopic, "")),
store.serialize_value(&wrong_redacted_state_event).unwrap(),
)
.unwrap();
store.set_db_version(2u8).unwrap();
drop(store);

let store = SledStateStore::builder()
.path(folder.path().to_path_buf())
.passphrase("secret".to_owned())
.build()
.unwrap();
let event =
store.get_state_event(room_id, StateEventType::RoomTopic, "").await.unwrap().unwrap();
event.deserialize().unwrap();
}
}

0 comments on commit 5ae84a9

Please sign in to comment.