Skip to content

Commit 6a5fa81

Browse files
committed
fixup! WIP tutorial
1 parent f4ed483 commit 6a5fa81

File tree

1 file changed

+205
-25
lines changed
  • crates/matrix-sdk-crypto/src

1 file changed

+205
-25
lines changed

crates/matrix-sdk-crypto/src/lib.rs

Lines changed: 205 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -363,14 +363,18 @@ pub mod vodozemac {
363363
///
364364
/// ## Uploading identity and one-time keys.
365365
///
366-
/// The first step is to announce the support for it to other users in the
367-
/// Matrix network. This involves publishing your long-term device keys and a
368-
/// set of one-time prekeys to the homeserver. This information is used by other
369-
/// devices to encrypt messages specifically for your device.
366+
/// To enable end-to-end encryption in a Matrix client, the first step is to
367+
/// announce the support for it to other users in the network. This is done by
368+
/// publishing the client's long-term device keys and a set of one-time prekeys
369+
/// to the Matrix homeserver. The homeserver then makes this information
370+
/// available to other devices in the network.
371+
///
372+
/// The long-term device keys and one-time prekeys allow other devices to
373+
/// encrypt messages specifically for your device.
370374
///
371375
/// To achieve this, you will need to extract any requests that need to be sent
372376
/// to the homeserver from the [`OlmMachine`] and send them to the homeserver.
373-
/// The following snipped showcases how to achieve this using the
377+
/// The following snippet showcases how to achieve this using the
374378
/// [`OlmMachine::outgoing_requests()`] method:
375379
///
376380
/// ```no_run
@@ -389,42 +393,186 @@ pub mod vodozemac {
389393
/// let outgoing_requests = machine.outgoing_requests().await?;
390394
///
391395
/// // Send each request to the server and push the response into the state machine.
396+
/// // You can safely send these requests out in parallel.
392397
/// for request in outgoing_requests {
393-
/// // You can safely send out these requests out in parallel.
394398
/// let request_id = request.request_id();
399+
/// // Send the request to the server and await a response.
395400
/// let response = send_request(request).await?;
401+
/// // Push the response into the state machine.
396402
/// machine.mark_request_as_sent(&request_id, &response).await?;
397403
/// }
398404
/// # Ok(())
399405
/// # }
400406
/// ```
401407
///
402-
/// ## Receiving room keys and related changes
408+
/// It's important to note that the outgoing requests method in the
409+
/// [`OlmMachine`], while thread-safe, may return the same request multiple
410+
/// times if it is called multiple times before the request has been marked as
411+
/// sent. To prevent this issue, it is advisable to encapsulate the outgoing
412+
/// request handling logic into a separate helper method and protect it from
413+
/// being called multiple times concurrently using a lock.
414+
///
415+
/// This helps to ensure that the request is only handled once and prevents
416+
/// multiple identical requests from being sent.
403417
///
418+
/// Additionally, if an error occurs while sending a request using the
419+
/// [`OlmMachine::outgoing_requests()`] method, the request will be
420+
/// naturally retried the next time the method is called.
421+
///
422+
/// A more complete example, which uses a helper method, might look like this:
404423
/// ```no_run
405424
/// # use std::collections::BTreeMap;
425+
/// # use ruma::api::client::keys::upload_keys::v3::Response;
426+
/// # use anyhow::Result;
427+
/// # use matrix_sdk_crypto::{OlmMachine, OutgoingRequest};
428+
/// # async fn send_request(request: &OutgoingRequest) -> Result<Response> {
429+
/// # let response = unimplemented!();
430+
/// # Ok(response)
431+
/// # }
432+
/// # #[tokio::main]
433+
/// # async fn main() -> Result<()> {
434+
/// struct Client {
435+
/// outgoing_requests_lock: tokio::sync::Mutex<()>,
436+
/// olm_machine: OlmMachine,
437+
/// }
438+
///
439+
/// async fn process_outgoing_requests(client: &Client) -> Result<()> {
440+
/// // Let's acquire a lock so we know that we don't send out the same request out multiple
441+
/// // times.
442+
/// let guard = client.outgoing_requests_lock.lock().await;
443+
///
444+
/// for request in client.olm_machine.outgoing_requests().await? {
445+
/// let request_id = request.request_id();
446+
///
447+
/// match send_request(&request).await {
448+
/// Ok(response) => {
449+
/// client.olm_machine.mark_request_as_sent(&request_id, &response).await?;
450+
/// }
451+
/// Err(error) => {
452+
/// // It's OK to ignore transient HTTP errors since requests will be retried.
453+
/// eprintln!(
454+
/// "Error while sending out a end-to-end encryption \
455+
/// related request: {error:?}"
456+
/// );
457+
/// }
458+
/// }
459+
/// }
460+
///
461+
/// Ok(())
462+
/// }
463+
/// # Ok(())
464+
/// # }
465+
/// ```
466+
///
467+
/// Once we have the helper method that processes our outgoing requests we can
468+
/// structure our sync method as follows:
469+
///
470+
/// ```no_run
406471
/// # use anyhow::Result;
407472
/// # use matrix_sdk_crypto::OlmMachine;
408473
/// # #[tokio::main]
409474
/// # async fn main() -> Result<()> {
410-
/// # let to_device_events = Vec::new();
411-
/// # let changed_devices = Default::default();
412-
/// # let one_time_key_counts = BTreeMap::default();
413-
/// # let unused_fallback_keys = Some(Vec::new());
414-
/// # let machine: OlmMachine = unimplemented!();
415-
/// // Push changes that the server sent to us in a sync response.
416-
/// let decrypted_to_device = machine
417-
/// .receive_sync_changes(
418-
/// to_device_events,
419-
/// &changed_devices,
420-
/// &one_time_key_counts,
421-
/// unused_fallback_keys.as_deref(),
422-
/// )
423-
/// .await?;
475+
/// # struct Client {
476+
/// # outgoing_requests_lock: tokio::sync::Mutex<()>,
477+
/// # olm_machine: OlmMachine,
478+
/// # }
479+
/// # async fn process_outgoing_requests(client: &Client) -> Result<()> {
480+
/// # unimplemented!();
481+
/// # }
482+
/// # async fn send_out_sync_request(client: &Client) -> Result<()> {
483+
/// # unimplemented!();
484+
/// # }
485+
/// async fn sync(client: &Client) -> Result<()> {
486+
/// // This is happening at the top of the method so we advertise our
487+
/// // end-to-end encryption capabilities as soon as possible.
488+
/// process_outgoing_requests(client).await?;
489+
///
490+
/// // We can sync with the homeserver now.
491+
/// let response = send_out_sync_request(client).await?;
492+
///
493+
/// // Process the sync response here.
494+
///
495+
/// Ok(())
496+
/// }
424497
/// # Ok(())
425498
/// # }
426499
/// ```
427500
///
501+
/// ## Receiving room keys and related changes
502+
///
503+
/// The next step in our implementation is to forward messages that were sent
504+
/// directly to the client's device, and state updates about the one-time
505+
/// prekeys, to the [`OlmMachine`]. This is achieved using
506+
/// the [`OlmMachine::receive_sync_changes()`] method.
507+
///
508+
/// The method performs two tasks:
509+
///
510+
/// 1. It processes and, if necessary, decrypts each [to-device] event that was
511+
/// pushed into it, and returns the decrypted events. The original events are
512+
/// replaced with their decrypted versions.
513+
///
514+
/// 2. It produces internal state changes that may trigger the creation of new
515+
/// outgoing requests. For example, if the server informs the client that its
516+
/// one-time prekeys have been depleted, the OlmMachine will create an outgoing
517+
/// request to replenish them.
518+
///
519+
/// Our updated sync method now looks like this:
520+
///
521+
/// ```no_run
522+
/// # use anyhow::Result;
523+
/// # use matrix_sdk_crypto::OlmMachine;
524+
/// # use ruma::api::client::sync::sync_events::v3::Response;
525+
/// # #[tokio::main]
526+
/// # async fn main() -> Result<()> {
527+
/// # struct Client {
528+
/// # outgoing_requests_lock: tokio::sync::Mutex<()>,
529+
/// # olm_machine: OlmMachine,
530+
/// # }
531+
/// # async fn process_outgoing_requests(client: &Client) -> Result<()> {
532+
/// # unimplemented!();
533+
/// # }
534+
/// # async fn send_out_sync_request(client: &Client) -> Result<Response> {
535+
/// # unimplemented!();
536+
/// # }
537+
/// async fn sync(client: &Client) -> Result<()> {
538+
/// process_outgoing_requests(client).await?;
539+
///
540+
/// let response = send_out_sync_request(client).await?;
541+
///
542+
/// // Push the sync changes into the OlmMachine, make sure that this is
543+
/// // happening before the `next_batch` token of the sync is persisted.
544+
/// let to_device_events = client
545+
/// .olm_machine
546+
/// .receive_sync_changes(
547+
/// response.to_device.events,
548+
/// &response.device_lists,
549+
/// &response.device_one_time_keys_count,
550+
/// response.device_unused_fallback_key_types.as_deref(),
551+
/// )
552+
/// .await?;
553+
///
554+
/// // Send out the outgoing requests that receiving sync changes produced.
555+
/// process_outgoing_requests(client).await?;
556+
///
557+
/// // Process the rest of the sync response here.
558+
///
559+
/// Ok(())
560+
/// }
561+
/// # Ok(())
562+
/// # }
563+
/// ```
564+
///
565+
/// It is important to note that the names of the fields in the response shown
566+
/// in the example match the names of the fields specified in the [sync]
567+
/// response specification.
568+
///
569+
/// It is critical to note that due to the ephemeral nature of to-device
570+
/// events[[1]](https://spec.matrix.org/unstable/client-server-api/#server-behaviour-4),
571+
/// it is important to process these events before persisting the `next_batch`
572+
/// sync token. This is because if the `next_batch` sync token is persisted
573+
/// before processing the to-device events, some messages might be lost, leading
574+
/// to decryption failures.
575+
///
428576
/// ## Decrypting room events
429577
///
430578
/// The final step in the decryption process is to decrypt the room events that
@@ -433,8 +581,8 @@ pub mod vodozemac {
433581
/// exchanged between devices to decrypt the events. The decrypted events can
434582
/// then be processed and displayed to the user in the Matrix client.
435583
///
436-
/// Room events can be decrypted using the [`OlmMachine::decrypt_room_event()`]
437-
/// method.
584+
/// Room message [events] can be decrypted using the
585+
/// [`OlmMachine::decrypt_room_event()`] method:
438586
///
439587
/// ```no_run
440588
/// # use std::collections::BTreeMap;
@@ -451,6 +599,16 @@ pub mod vodozemac {
451599
/// # }
452600
/// ```
453601
///
602+
/// It's worth mentioning that the [`OlmMachine::decrypt_room_event()`] method
603+
/// is designed to be thread-safe and can be safely called concurrently. This
604+
/// means that room message [events] can be processed in parallel, improving the
605+
/// overall efficiency of the end-to-end encryption implementation.
606+
///
607+
/// By allowing room message [events] to be processed concurrently, the client's
608+
/// implementation can take full advantage of the capabilities of modern
609+
/// hardware and achieve better performance, especially when dealing with a
610+
/// large number of messages at once.
611+
///
454612
/// # Encryption
455613
///
456614
/// TODO
@@ -475,6 +633,20 @@ pub mod vodozemac {
475633
///
476634
/// ## Tracking users
477635
///
636+
/// [Tracking the device list for a user]
637+
///
638+
/// ```mermaid
639+
/// sequenceDiagram
640+
/// actor Alice
641+
/// participant Homeserver
642+
///
643+
/// Alice->>Homeserver: Request to sync changes
644+
/// Homeserver->>Alice: Users whose device list has changed
645+
/// Alice->>Alice: Mark user's devicel list as outdated
646+
/// Alice->>Homeserver: Ask the server for the new device list of all the outdated users
647+
/// Alice->>Alice: Update the local device list and mark the users as up-to-date
648+
/// ```
649+
///
478650
/// ```no_run
479651
/// # use std::collections::{BTreeMap, HashSet};
480652
/// # use anyhow::Result;
@@ -490,6 +662,7 @@ pub mod vodozemac {
490662
/// # }
491663
/// ```
492664
///
665+
///
493666
/// TODO
494667
///
495668
/// ## Establishing end-to-end encrypted channels
@@ -570,7 +743,7 @@ pub mod vodozemac {
570743
/// # let event = unimplemented!();
571744
/// # let machine: OlmMachine = unimplemented!();
572745
/// // Decrypt each room event you'd like to display to the user using this method.
573-
/// let decrypted = machine.decrypt_room_event(event, room_id)?;
746+
/// let decrypted = machine.decrypt_room_event(event, room_id).await?;
574747
/// # Ok(())
575748
/// # }
576749
/// ```
@@ -583,6 +756,10 @@ pub mod vodozemac {
583756
///
584757
/// TODO
585758
///
759+
/// # Room key backups
760+
///
761+
/// TODO
762+
///
586763
/// [Matrix]: https://matrix.org/
587764
/// [Olm]: https://gitlab.matrix.org/matrix-org/olm/-/blob/master/docs/olm.md
588765
/// [Diffie-Hellman]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
@@ -593,6 +770,9 @@ pub mod vodozemac {
593770
/// [client-server specification]: https://matrix.org/docs/spec/client_server/
594771
/// [forward secrecy]: https://en.wikipedia.org/wiki/Forward_secrecy
595772
/// [replay attacks]: https://en.wikipedia.org/wiki/Replay_attack
596-
///
773+
/// [Tracking the device list for a user]: https://spec.matrix.org/unstable/client-server-api/#tracking-the-device-list-for-a-user
597774
/// [X3DH]: https://signal.org/docs/specifications/x3dh/
775+
/// [to-device]: https://spec.matrix.org/unstable/client-server-api/#send-to-device-messaging
776+
/// [sync]: https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync
777+
/// [events]: https://spec.matrix.org/unstable/client-server-api/#events
598778
pub mod tutorial {}

0 commit comments

Comments
 (0)