-
Notifications
You must be signed in to change notification settings - Fork 269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(crypto): Redecrypt non-UTD messages to remove no-longer-relevant warning shields #4644
base: main
Are you sure you want to change the base?
Conversation
9a19fcf
to
9cbfbfb
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4644 +/- ##
==========================================
- Coverage 85.82% 85.81% -0.01%
==========================================
Files 292 292
Lines 33761 33822 +61
==========================================
+ Hits 28974 29024 +50
- Misses 4787 4798 +11 ☔ View full report in Codecov by Sentry. |
741aa40
to
d83e187
Compare
…imelineItem ... meaning we attempt to redecrypt them even if they are not UTDs. The redecryption doesn't actually work - that will be fixed in the next commit.
…etry decryption In later commits, we will make this cleverer, so it can calculate which non-UTD events need updating too.
Future commits will add code to try non-UTDs too, in another function.
d83e187
to
414f03d
Compare
I swapped the Rust team reviewer to @bnjbvr since I know he has questions about including session ID in the timeline item. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems generally good to me. A few comments.
@@ -225,6 +228,45 @@ impl TimelineState { | |||
txn.commit(); | |||
} | |||
|
|||
/// Try to fetch [`EncryptionInfo`] for the events with the supplied | |||
/// indices, and update them where we succeed. | |||
pub(super) async fn retry_event_encryption_info<P: RoomDataProvider>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to say that having this here but retry_event_decryption_by_index
in the other place feels rather asymmetric. Is there a reason they need to be different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my mind, TimelineState::retry_event_encryption_info
is a partner to TimelineState::retry_event_decryption
, not to TimelineController::retry_event_decryption_by_index
. retry_event_decryption_by_index
is needed because we need to construct a complex retry_one
closure to pass to retry_event_decryption
whereas retry_event_encryption_info
can do its work directly.
if let Some(event) = item.as_event() { | ||
if let Some(remote_event) = event.as_remote() { | ||
if let Some(session_id) = &remote_event.session_id { | ||
if should_retry(session_id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uh, that's a lot of indent levels 😅
You could use .and_then
on all the Option
to chain them, as well as early continue
when you're seeing something you're not expecting:
let Some(thing) = item.as_event()
.and_then(|event| event.as_remote())
.map(/* or and_then, not sure what session_id is */|remote_event| *remote_event.session_id) else {
continue;
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After the changes I've already made, this is a little flatter, and I don't think there is a nice way to make it better?
On similar lines, I had a look at TimelineState::retry_event_encryption_info
which has a similar problem, but I couldn't see a way to make that better because it has an async
call all the way over to the right. Suggestions welcome.
This is ready for re-review. I think I would recommend looking at the full diff now, since the review changes reverted some of what I did in earlier commits. I will rebase into nice commits after review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few stylistic change requests below. On the one hand, thanks for not storing any extra data in the TimelineItemContent
itself, on the other… you know my opinion about adding more code to the timeline re-decryption. This all should be moved somewhere else, and tested in isolation there (could be in the crypto crate, the main SDK crate, inside or outside the event cache,… any place other than the timeline would make more sense).
/// Replace the timeline item at the supplied index with the supplied item. | ||
pub(super) fn replace( | ||
&mut self, | ||
timeline_item_index: usize, | ||
timeline_item: Arc<TimelineItem>, | ||
) -> Arc<TimelineItem> { | ||
self.items.replace(timeline_item_index, timeline_item) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this function useful? I would have assumed that there are many other places in the code that already replaces items from a transaction, and that they didn't need this function to do this… (Admittedly, the blackboxing of the TimelineStateTransaction
is a bit compromised already at this point.)
if let Some(event) = item.as_event() { | ||
if let TimelineItemContent::UnableToDecrypt(encrypted) = event.content() { | ||
if let Some(session_id) = encrypted.session_id() { | ||
if should_retry(session_id) { | ||
return Some(Either::Left(idx)); | ||
} | ||
} | ||
} else if let Some(remote_event) = event.as_remote() { | ||
if let Some(encryption_info) = &remote_event.encryption_info { | ||
if should_retry(&encryption_info.session_id) { | ||
return Some(Either::Right(idx)); | ||
} | ||
} | ||
} | ||
} | ||
None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style nit: this could be much less indented if you shortcut the None
s:
if let Some(event) = item.as_event() { | |
if let TimelineItemContent::UnableToDecrypt(encrypted) = event.content() { | |
if let Some(session_id) = encrypted.session_id() { | |
if should_retry(session_id) { | |
return Some(Either::Left(idx)); | |
} | |
} | |
} else if let Some(remote_event) = event.as_remote() { | |
if let Some(encryption_info) = &remote_event.encryption_info { | |
if should_retry(&encryption_info.session_id) { | |
return Some(Either::Right(idx)); | |
} | |
} | |
} | |
} | |
None | |
let event = item.as_event()?; | |
if let TimelineItemContent::UnableToDecrypt(encrypted) = event.content() { | |
let session_id = encrypted.session_id()?; | |
if should_retry(session_id) { | |
return Some(Either::Left(idx)); | |
} | |
} else { | |
let remote_event = event.as_remote()?; | |
let encryption_info = remote_event.encryption_info.as_ref()?; | |
if should_retry(&encryption_info.session_id) { | |
return Some(Either::Right(idx)); | |
} | |
} | |
None |
if retry_indices.is_empty() { | ||
return; | ||
} | ||
|
||
debug!("Retrying decryption"); | ||
|
||
let mut state = state.clone().write_owned().await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to have the caller pass the owned lock here; that shows that the lock will be taken, at the call sites.
#[serde(default)] | ||
pub session_id: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be an Option
instead of a string that defaults to "" when the data was reloaded from the store, and we didn't have it? Seems dangerous to have this floating knowledge that ""
means "didn't have this information in the store"
if let Some(item) = item { | ||
if let Some(event) = item.as_event() { | ||
let sender = event.sender.clone(); | ||
if let Some(remote) = event.as_remote() { | ||
if let Some(encryption_info) = &remote.encryption_info { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
too many indent levels. Please use either a closure that returns an option, or short-circuiting with let Some(thing) = ... else { continue; };
let mut txn = self.transaction(); | ||
for (idx, item) in new_info { | ||
txn.replace(idx, item); | ||
} | ||
txn.commit(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need a transaction if no operations between the start of the transaction and the end aren't fallible.
|
||
let new_item = item.with_kind(TimelineItemKind::Event(new_event)); | ||
|
||
new_info.push((idx, new_item)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you replace the item here, instead of storing it temporarily? Or can't because iterating on the items themselves?
|
||
// Retry fetching encryption info for events that are already decrypted | ||
let room_data_provider = self.room_data_provider.clone(); | ||
matrix_sdk::executor::spawn(async move { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From @bnjbvr
anyhow, one change that should happen now, and unnoted in this review because D4mir noticed it afterwards, was that this is spawning a task that's detached; it should be attached to the timeline, one way or another:
either as a one-off, making sure there's only one at any time
or a task that listens to a channel of commands, asking for retrying decryption of a set of tasks, and running those requests linearly
Fixes element-hq/element-meta#2697
I'm sorry it's a big change. I've tried to break it into decent commits, and I did a couple of preparatory PRs to make it less painful, but it's still a bit to get your head around.
The basic idea is that when a session is updated and we call
retry_event_decryption
, we don't only look at UTDs any more - now we also look at decrypted events, and re-request theirEncryptionInfo
, in case it has improved.