-
Notifications
You must be signed in to change notification settings - Fork 251
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
refactor(sdk-crypto): Room key sharing, introduce extensible strategy #3571
Conversation
d584668
to
7f4d686
Compare
7f4d686
to
81a9f95
Compare
81a9f95
to
05ba146
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3571 +/- ##
==========================================
+ Coverage 83.84% 83.87% +0.03%
==========================================
Files 253 254 +1
Lines 25884 25903 +19
==========================================
+ Hits 21702 21727 +25
+ Misses 4182 4176 -6 ☔ View full report in Codecov by Sentry. |
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.
Looks good, with a few questions and comments. I'd feel more comfortable if @poljar had time to look too.
let dan_device_that_will_get_the_key = &dan_devices_shared[0]; | ||
assert_eq!(dan_device_that_will_get_the_key.device_id().as_str(), "JHPUERYQUW"); | ||
|
||
// Check withthelds for others |
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.
// Check withthelds for others | |
// Check withhelds for others |
let (_, code) = share_result | ||
.withheld_devices | ||
.iter() | ||
.find(|(d, _)| d.device_id().as_str() == "FRGNMZVOKA") |
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 we use something like KeyDistributionTestData::dan_device()
for this to avoid a magic constant?
/// conversation. A device is trusted if any of the following is true: | ||
/// - It was manually marked as trusted. | ||
/// - It was marked as verified via interactive verification. | ||
/// - It is signed by it's owner identity, and this identity has |
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.
/// - It is signed by it's owner identity, and this identity has | |
/// - It is signed by its owner identity, and this identity has |
pub withheld_devices: Vec<(ReadOnlyDevice, WithheldCode)>, | ||
} | ||
|
||
pub(crate) struct CollectRecipientsHelper {} |
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 there any need for this struct? Can't we just have collect_session_recipients
as a free function?
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.
Agree there.
// rotated for other reasons, we also need to check whether any | ||
// of the devices in the session got deleted or blacklisted in the | ||
// meantime. If so, we should also rotate the session. | ||
if !should_rotate { |
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 would prefer if the contents of this if
were a separate function - the containing function is very long.
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 would probably prefer the body of the for
loop to be a separate function, or even better the body inside the if let Some(shared)
conditional to be put into a method on the OutboundGroupSession
type.
} | ||
|
||
async fn collect_session_recipients_device_based( | ||
// &self, |
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.
Remove commented code.
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.
This PR is called a refactor but a non-trivial amount of behavioral changes has snuck in. Some of them seem to be bugs as well.
|
||
use crate::response_from_file; | ||
|
||
/// ============================================================================ |
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.
pub withheld_devices: Vec<(ReadOnlyDevice, WithheldCode)>, | ||
} | ||
|
||
pub(crate) struct CollectRecipientsHelper {} |
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.
Agree there.
} | ||
|
||
impl CollectRecipientsHelper { | ||
pub(crate) async fn collect_session_recipients( |
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.
It would have been much nicer if you first moved this function to this new file in a separate commit and only then started to modify it. It's a bit annoying to see what has changed and what was just moved.
For reference, I need to pick out the diff manually via:
git diff origin/main:crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs crates/matrix-sdk-crypto/src/olm/group_sessions/share_strategy.rs
Even with this, the diff isn't smart enough to notice what was split out where.
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 clarify? Should I drop everything and do a new PR and do that?
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.
No, please don't open a new PR, it took quite a bit effort to review it and please don't throw that work now away.
Can you clarify?
Let us introduce a simplified example, let's suppose that you have a file with this function:
// main.rs
fn greet() {
println!("Hello, world!");
}
Now you need to refactor and slightly modify this function, you need add a parameter to it, you need to do the following changes:
- Delete the function from main.rs.
- Create a new file greetings.rs.
- Add the function to greetings.rs
- Modify the function to take a parameter
// greetings.rs
fn greet(name: &str) {
println!("Hello, {}!", name);
}
If you do all 4 steps in a single commit the diff will look like:
--- main.rs
+++ main.rs
@@ -1,4 +1,0 @@
-// main.rs
-
-fn greet() {
- println!("Hello, world!");
-}
--- /dev/null
+++ greetings.rs
@@ -0,0 +1,4 @@
+// greetings.rs
+
+fn greet(name: &str) {
+ println!("Hello, {}!", name);
+}
The modifications are a bit hard to spot, aren't they? Sure the function is 3 lines long, you can easily spot them if you pay attention and compare the green and red lines carefully.
Now if you instead commit step 1-3 first and commit step 4 separately you get two diffs which look like this:
--- main.rs
+++ main.rs
@@ -1,4 +1,0 @@
-// main.rs
-
-fn greet() {
- println!("Hello, world!");
-}
--- /dev/null
+++ greetings.rs
@@ -0,0 +1,4 @@
+// greetings.rs
+
+fn greet() {
+ println!("Hello, world!");
+}
and:
--- greetings.rs
+++ greetings.rs
@@ -1,4 +1,4 @@
// greetings.rs
-fn greet() {
- println!("Hello, world!");
+fn greet(name: &str) {
+ println!("Hello, {}!", name);
}
This makes the changes to the function stand out quite clearly, the first commit can be quickly checked if the move is done correctly, something Rust
helps us with as well, and then we can focus our attention to the meaningful functional modifications.
let sharing_strategy_changed = | ||
outbound.settings().sharing_strategy != settings.sharing_strategy; | ||
|
||
let mut should_rotate = |
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.
This function seems to have received a downgrade in the documentation department, why was this comment removed:
matrix-rust-sdk/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs
Lines 373 to 381 in 2b1bddb
// To protect the room history we need to rotate the session if either: | |
// | |
// 1. Any user left the room. | |
// 2. Any of the users' devices got deleted or blacklisted. | |
// 3. The history visibility changed. | |
// 4. The encryption algorithm changed. | |
// | |
// This is calculated in the following code and stored in this variable. | |
let mut should_rotate = user_left || visibility_changed || algorithm_changed; |
let sharing_strategy_changed = | ||
outbound.settings().sharing_strategy != settings.sharing_strategy; | ||
|
||
let mut should_rotate = |
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.
This function seems to have received a downgrade in the documentation department, why was this comment removed:
matrix-rust-sdk/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs
Lines 373 to 381 in 2b1bddb
// To protect the room history we need to rotate the session if either: | |
// | |
// 1. Any user left the room. | |
// 2. Any of the users' devices got deleted or blacklisted. | |
// 3. The history visibility changed. | |
// 4. The encryption algorithm changed. | |
// | |
// This is calculated in the following code and stored in this variable. | |
let mut should_rotate = user_left || visibility_changed || algorithm_changed; |
// rotated for other reasons, we also need to check whether any | ||
// of the devices in the session got deleted or blacklisted in the | ||
// meantime. If so, we should also rotate the session. | ||
if !should_rotate { |
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 would probably prefer the body of the for
loop to be a separate function, or even better the body inside the if let Some(shared)
conditional to be put into a method on the OutboundGroupSession
type.
trace!(should_rotate, "Done calculating group session recipients"); | ||
|
||
Ok(CollectRecipientsResult { should_rotate, devices, withheld_devices }) | ||
CollectRecipientsHelper::collect_session_recipients(settings, &self.store, users, outbound) |
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.
This really feels like needless indirection. Perhaps it would make sense to make the collect_session_recipients()
a constructor of CollectRecipientsResult
if you don't like the free standing function suggestion.
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.
Thanks for the direction. I am not sure what's best but I think I will go with the free standing
}) | ||
} | ||
|
||
async fn collect_session_recipients_device_based( |
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 makes sense for this to be a free standing method or to be bound to the DeviceBasedStrategy
enum variant, though you'll have to change the variant to be a tuple variant and add a struct for it, so perhaps a free standing function again makes more sense.
Furthermore, this could get a better name? It's not a different version of collect_session_recipients()
, perhaps it could be called collect_recipient_devices()
.
} | ||
}); | ||
|
||
if !recipients.is_empty() { |
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.
Why was this conditional added? Furthermore if we think that this is useful, why was it added only for the recipients
set and not for the withheld_recipients
set? Because of the allocation on the user id and the or_default()
?
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.
It was annoying in the tests to have these empty buckets of devices. Also was not usefull because we checked earlier for removed users so no need to maintain an empty bucket when looking for left devices.
pub withheld_devices: Vec<(ReadOnlyDevice, WithheldCode)>, | ||
} | ||
|
||
struct KeySharingResult { |
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.
Perhaps a better name is RecipientDevices
?
Will close and try to re-organize that PR into a new PR, that hopefully will match expectations |
Closed in favor of #3605 |
Fixes: #3562
Extracted the logic to collect the devices that should receive a room key.
Introduce a new
CollectStrategy
that will define how to sort devices that should receive the room_key and those that should receive a witheld code.The common part of sharing (checking if the session should be rotated) is done by the
CollectRecipientsHelper
, then the actual selection is delegated depending on thCollectStrategy
There is no functional changes in this PR, a following PR will introduce an
IdentityBased
strategy that will select devices depending on if they are signed by their owner.Also created some testing data that can be reused for various tests, will be used by the new strategy also. This is isolated in the first commit.
Signed-off-by: