1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use_prelude!();
use std::sync::{Arc, OnceLock};

use super::{presence::JsonObject, ConnectionType};

/// Contains information about a remote peer that has requested a connection.
///
/// Connection requests and their authorization are scoped to a specific Ditto
/// peer and connection type.
///
/// Information about a peer Ditto is currently trying to connect to. This struct is used by the
/// [`Presence::set_connection_request_handler()`] callback to decide whether the Ditto client
/// should connect to a given peer or not.
pub struct ConnectionRequest {
    raw: Arc<repr_c::Box<ffi_sdk::FfiConnectionRequest>>,
    // An optional map of peer-provided information, signed by the peer.
    cached_peer_metadata: OnceLock<Arc<JsonObject>>,
    // An optional map of identity service-provided information, signed by the identity
    // service.
    cached_identity_service_metadata: OnceLock<Arc<JsonObject>>,
    // The connecting peer's (pub) key (can be empty for legacy peers).
    cached_peer_key: OnceLock<Arc<str>>,
}

impl ConnectionRequest {
    pub(crate) fn new(raw: repr_c::Box<ffi_sdk::FfiConnectionRequest>) -> Self {
        Self {
            raw: Arc::new(raw),
            cached_peer_metadata: <_>::default(),
            cached_identity_service_metadata: <_>::default(),
            cached_peer_key: <_>::default(),
        }
    }

    pub(crate) fn raw(&self) -> Arc<repr_c::Box<ffi_sdk::FfiConnectionRequest>> {
        self.raw.retain()
    }

    /// JSON-encoded metadata associated with the remote peer.
    ///
    /// JSON string representing an empty dictionary if the remote peer has not
    /// set any metadata or when this request is for a WebSocket connection.
    ///
    /// Set peer metadata for the local peer using
    /// [`Presence::set_peer_metadata()`] or
    /// [`Presence::set_peer_metadata_json_str()`].
    pub fn peer_metadata_json_str(&self) -> String {
        String::from_utf8(
            ffi_sdk::dittoffi_connection_request_peer_metadata_json(&*self.raw).to_vec(),
        )
        .expect("UTF-8")
    }

    /// [`DeserializeOwned`] convenience wrapper around [`Self::peer_metadata_json_str()`].
    pub fn peer_metadata_serde<T: DeserializeOwned>(&self) -> Result<T> {
        let value = ::serde_json::from_str(&self.peer_metadata_json_str())?;
        Ok(value)
    }

    /// Metadata associated with the remote peer.
    ///
    /// Empty dictionary if the remote peer has not set any metadata, or
    /// when this request is for a WebSocket connection (which does not
    /// currently support peer metadata).
    ///
    /// Set peer metadata for the local peer using
    /// [`Presence::set_peer_metadata()`] or
    /// [`Presence::set_peer_metadata_json_str()`].
    ///
    /// Convenience property that wraps [`Self::peer_metadata_json_str()`].
    pub fn peer_metadata(&self) -> Arc<JsonObject> {
        self.cached_peer_metadata
            .get_or_init(|| {
                Arc::new(
                    self.peer_metadata_serde()
                        .expect("incorrect json from `dittoffi`"),
                )
            })
            .retain()
    }

    /// JSON-encoded metadata for the remote peer that is provided by the
    /// identity service.
    ///
    /// Use an authentication webhook to set this value. See Ditto's online
    /// documentation for more information on how to configure an authentication
    /// webhook.
    pub fn identity_service_metadata_json_str(&self) -> String {
        String::from_utf8(
            ffi_sdk::dittoffi_connection_request_identity_service_metadata_json(&*self.raw)
                .to_vec(),
        )
        .expect("UTF-8")
    }

    /// [`DeserializeOwned`] convenience wrapper around
    /// [`Self::identity_service_metadata_json_str()`].
    pub fn identity_service_metadata_serde<T: DeserializeOwned>(&self) -> Result<T> {
        let value = ::serde_json::from_str(&self.identity_service_metadata_json_str())?;
        Ok(value)
    }

    /// Metadata for the remote peer that is provided by the identity service.
    ///
    /// Use an authentication webhook to set this value. See Ditto's online
    /// documentation for more information on how to configure an authentication
    /// webhook.
    ///
    /// Convenience property that wraps [`Self::identity_service_metadata_json_str()`].
    pub fn identity_service_metadata(&self) -> Arc<JsonObject> {
        self.cached_identity_service_metadata
            .get_or_init(|| {
                Arc::new(
                    self.identity_service_metadata_serde()
                        .expect("incorrect json from `dittoffi`"),
                )
            })
            .retain()
    }

    /// The unique peer key of the remote peer.
    ///
    /// - See also: [`crate::transport::Peer::peer_key_string`] for more information on peer keys.
    pub fn peer_key_string(&self) -> Arc<str> {
        self.cached_peer_key
            .get_or_init(|| {
                ffi_sdk::dittoffi_connection_request_peer_key_string(&*self.raw)
                    .to_str()
                    .into()
            })
            .retain()
    }

    /// The network transport of this connection request.
    ///
    /// Expect to receive separate connection requests for each network
    /// transport that connects the local and remote peer.
    pub fn connection_type(&self) -> ConnectionType {
        ConnectionType::from_ffi(::ffi_sdk::dittoffi_connection_request_connection_type(
            &*self.raw,
        ))
    }
}

#[derive(Debug)]
/// Private, to future-proof the API (_e.g._, imagine `Deny { reason: … }` or whatnot).
pub(crate) enum ConnectionRequestAuthorizationInner {
    Allow { _future_fields: () },
    Deny { _future_fields: () },
}

/// Indicates whether a connection request should be authorized.
#[derive(Debug)]
pub struct ConnectionRequestAuthorization(pub(crate) ConnectionRequestAuthorizationInner);

impl ConnectionRequestAuthorization {
    #![allow(non_upper_case_globals)]

    /// The connection request will be allowed.
    pub const Allow: Self = Self(ConnectionRequestAuthorizationInner::Allow { _future_fields: () });

    /// The connection request will be denied.
    pub const Deny: Self = Self(ConnectionRequestAuthorizationInner::Deny { _future_fields: () });

    pub(crate) fn to_ffi(self, raw: &ffi_sdk::FfiConnectionRequest) {
        let ffi_authorization = match self.0 {
            ConnectionRequestAuthorizationInner::Allow { _future_fields: () } => {
                ffi_sdk::ConnectionRequestAuthorization::Allow
            }
            ConnectionRequestAuthorizationInner::Deny { _future_fields: () } => {
                ffi_sdk::ConnectionRequestAuthorization::Deny
            }
        };
        ffi_sdk::dittoffi_connection_request_authorize(&raw, ffi_authorization);
    }
}