matrix_sdk/authentication/oauth/qrcode/rendezvous_channel/mod.rs
1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use tracing::instrument;
16use url::Url;
17
18use crate::{
19 HttpError, authentication::oauth::qrcode::SecureChannelError, http_client::HttpClient,
20};
21
22mod msc_4108;
23
24/// The result of the [`RendezvousChannel::create_inbound()`] method.
25pub(super) struct InboundChannelCreationResult {
26 /// The connected [`RendezvousChannel`].
27 pub channel: RendezvousChannel,
28 /// The initial message we received when we connected to the
29 /// [`RendezvousChannel`].
30 ///
31 /// This is currently unused, but left in for completeness sake.
32 #[allow(dead_code)]
33 pub initial_message: Vec<u8>,
34}
35
36#[derive(Debug, PartialEq, Eq)]
37pub(super) enum RendezvousInfo<'a> {
38 Msc4108 { rendezvous_url: &'a Url },
39}
40
41pub(super) enum RendezvousChannel {
42 Msc4108(msc_4108::Channel),
43}
44
45impl RendezvousChannel {
46 /// Create a new outbound [`RendezvousChannel`].
47 ///
48 /// By outbound we mean that we're going to tell the Matrix server to create
49 /// a new rendezvous session. We're going to send an initial empty message
50 /// through the channel.
51 pub(super) async fn create_outbound(
52 client: HttpClient,
53 rendezvous_server: &Url,
54 ) -> Result<Self, HttpError> {
55 Ok(Self::Msc4108(msc_4108::Channel::create_outbound(client, rendezvous_server).await?))
56 }
57
58 /// Create a new inbound [`RendezvousChannel`].
59 ///
60 /// By inbound we mean that we're going to attempt to read an initial
61 /// message from the rendezvous session on the given [`rendezvous_url`].
62 pub(super) async fn create_inbound(
63 client: HttpClient,
64 rendezvous_url: &Url,
65 ) -> Result<InboundChannelCreationResult, HttpError> {
66 let msc_4108::InboundChannelCreationResult { channel, initial_message } =
67 msc_4108::Channel::create_inbound(client, rendezvous_url).await?;
68
69 Ok(InboundChannelCreationResult { channel: Self::Msc4108(channel), initial_message })
70 }
71
72 /// Get MSC-specific information about the rendezvous session we're using to
73 /// exchange messages through the channel.
74 pub(super) fn rendezvous_info(&self) -> RendezvousInfo<'_> {
75 match self {
76 RendezvousChannel::Msc4108(channel) => {
77 RendezvousInfo::Msc4108 { rendezvous_url: channel.rendezvous_url() }
78 }
79 }
80 }
81
82 /// Send the given `message` through the [`RendezvousChannel`] to the other
83 /// device.
84 ///
85 /// The message must be of the `text/plain` content type.
86 #[instrument(skip_all)]
87 pub(super) async fn send(&mut self, message: String) -> Result<(), HttpError> {
88 match self {
89 RendezvousChannel::Msc4108(channel) => channel.send(message.into_bytes()).await,
90 }
91 }
92
93 /// Attempt to receive a message from the [`RendezvousChannel`] from the
94 /// other device.
95 ///
96 /// The content should be of the `text/plain` content type but the parsing
97 /// and verification of this fact is left up to the caller.
98 ///
99 /// This method will wait in a loop for the channel to give us a new
100 /// message.
101 pub(super) async fn receive(&mut self) -> Result<String, SecureChannelError> {
102 let message = match self {
103 RendezvousChannel::Msc4108(channel) => channel.receive().await?,
104 };
105
106 Ok(String::from_utf8(message).map_err(|e| e.utf8_error())?)
107 }
108}