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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
use std::collections::HashMap;
use std::fmt;
use std::future::Future;
use std::hash::Hash;
use std::num::NonZeroU64;
use std::pin::Pin;
use std::str::FromStr;

use log::*;
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use crate::error::*;

pub(crate) const DEFAULT_AGENT_STR: &str =
    concat!(env!("CARGO_PKG_NAME"), "_rs-", env!("CARGO_PKG_VERSION"));

/// uri: a string URI as defined in URIs
pub type WampUri = String;

/// id: an integer ID as defined in IDs
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct WampId(NonZeroU64);

impl fmt::Display for WampId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl From<WampId> for NonZeroU64 {
    fn from(id: WampId) -> Self {
        id.0
    }
}

impl WampId {
    /// IDs in the global scope MUST be drawn randomly from a uniform distribution over the complete
    /// range [1, 2^53]
    pub(crate) fn generate() -> Self {
        let random_id = rand::random::<u64>() & ((1 << 53) - 1);
        // Safety: since random_id is in range of [0, 2**53) and we add 1, the value is always in
        // range [1, 2^53].
        Self(unsafe { NonZeroU64::new_unchecked(random_id + 1) })
    }
}

/// integer: a non-negative integer
pub type WampInteger = usize;
/// string: a Unicode string, including the empty string
pub type WampString = String;
/// bool: a boolean value (true or false)
pub type WampBool = bool;
/// dict: a dictionary (map) where keys MUST be strings
pub type WampDict = HashMap<String, Arg>;
/// list: a list (array) where items can be of any type
pub type WampList = Vec<Arg>;
/// Arbitrary values supported by the serialization format in the payload
///
/// Implementation note: we currently use `serde_json::Value`, which is
/// suboptimal when you want to use MsgPack and pass binary data.
pub type WampPayloadValue = serde_json::Value;
/// Unnamed WAMP argument list
pub type WampArgs = Vec<WampPayloadValue>;
/// Named WAMP argument map
pub type WampKwArgs = serde_json::Map<String, WampPayloadValue>;

/// Generic enum that can hold any concrete WAMP value
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum Arg {
    /// uri: a string URI as defined in URIs
    Uri(WampUri),
    /// id: an integer ID as defined in IDs
    Id(WampId),
    /// integer: a non-negative integer
    Integer(WampInteger),
    /// string: a Unicode string, including the empty string
    String(WampString),
    /// bool: a boolean value (true or false)
    Bool(WampBool),
    /// dict: a dictionary (map) where keys MUST be strings
    Dict(WampDict),
    /// list: a list (array) where items can be again any of this enumeration
    List(WampList),
    None,
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
/// All roles a client can be
pub enum ClientRole {
    /// Client can call RPC endpoints
    Caller,
    /// Client can register RPC endpoints
    Callee,
    /// Client can publish events to topics
    Publisher,
    /// Client can register for events on topics
    Subscriber,
}
impl ClientRole {
    /// Returns the string repesentation of the role
    pub fn to_str(&self) -> &'static str {
        match self {
            ClientRole::Caller => "caller",
            ClientRole::Callee => "callee",
            ClientRole::Publisher => "publisher",
            ClientRole::Subscriber => "subscriber",
        }
    }
}

/// All the supported roles a server can have
pub enum ServerRole {
    /// Server supports RPC calls
    Router,
    /// Server supports pub/sub
    Broker,
}
impl ServerRole {
    /// Returns the string repesentation of the role
    pub fn to_str(&self) -> &'static str {
        match self {
            ServerRole::Router => "router",
            ServerRole::Broker => "broker",
        }
    }
}

/// All the supported authentication methods WAMP-proto defines.
///
/// There is no special support currently built into wamp-async-rs, so
/// "on challenge handler" will receive the raw challenge data as is, and
/// it is required to reply with the correct [`AuthenticationChallengeResponse`].
#[derive(Debug, Clone, strum::AsRefStr, strum::EnumString)]
pub enum AuthenticationMethod {
    /// No authentication challenge
    #[strum(serialize = "anonymous")]
    Anonymous,
    /// [Challenge Response Authentication]
    ///
    /// [Challenge Response Authentication]: https://wamp-proto.org/_static/gen/wamp_latest.html#wampcra
    #[strum(serialize = "wampcra")]
    WampCra,
    /// [Ticket-based Authentication]
    ///
    /// [Ticket-based Authentication]: https://wamp-proto.org/_static/gen/wamp_latest.html#ticketauth
    #[strum(serialize = "ticket")]
    Ticket,
}

impl Serialize for AuthenticationMethod {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(self.as_ref())
    }
}

impl<'de> Deserialize<'de> for AuthenticationMethod {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Self::from_str(&s).map_err(|err| serde::de::Error::custom(err.to_string()))
    }
}

/// This is what wamp-async-rs users are expected to return from `on_challenge_handler`
/// during the authentication flow.
///
/// See also [`Self::with_signature`] shortcut, and
/// [`crate::Client::join_realm_with_authentication`] for usage example.
pub struct AuthenticationChallengeResponse {
    pub signature: WampString,
    pub extra: WampDict,
}

impl AuthenticationChallengeResponse {
    /// This is a shortcut for a simple authentication flow like [Ticket-based Authentication].
    ///
    /// You may return a shared-secret as following:
    ///
    /// ```
    /// # use wamp_async::AuthenticationChallengeResponse;
    /// # fn test() -> AuthenticationChallengeResponse {
    /// AuthenticationChallengeResponse::with_signature("shared-secret".into())
    /// # }
    /// ```
    ///
    /// [Ticket-based Authentication]: https://wamp-proto.org/_static/gen/wamp_latest.html#ticketauth
    pub fn with_signature(signature: WampString) -> Self {
        Self {
            signature,
            extra: WampDict::default(),
        }
    }
}

/// Convert WampPayloadValue into any serde-deserializable object
pub fn try_from_any_value<'a, T: DeserializeOwned>(
    value: WampPayloadValue,
) -> Result<T, WampError> {
    serde_json::from_value(value).map_err(|e| {
        WampError::SerializationError(crate::serializer::SerializerError::Deserialization(
            e.to_string(),
        ))
    })
}

/// Convert WampArgs into any serde-deserializable object
pub fn try_from_args<'a, T: DeserializeOwned>(value: WampArgs) -> Result<T, WampError> {
    try_from_any_value(value.into())
}

/// Convert WampArgs into any serde-deserializable object
pub fn try_from_kwargs<'a, T: DeserializeOwned>(value: WampKwArgs) -> Result<T, WampError> {
    try_from_any_value(value.into())
}

/// Convert any serde-serializable object into WampPayloadValue
pub fn try_into_any_value<T: Serialize>(value: T) -> Result<WampPayloadValue, WampError> {
    serde_json::to_value(value).map_err(|e| {
        WampError::SerializationError(crate::serializer::SerializerError::Serialization(
            e.to_string(),
        ))
    })
}

/// Convert any serde-serializable object into WampArgs
pub fn try_into_args<T: Serialize>(value: T) -> Result<WampArgs, WampError> {
    match serde_json::to_value(value).unwrap() {
        serde_json::value::Value::Array(array) => Ok(array),
        value => Err(WampError::SerializationError(
            crate::serializer::SerializerError::Serialization(format!(
                "failed to serialize {:?} into positional arguments",
                value
            )),
        )),
    }
}

/// Convert any serde-serializable object into WampKwArgs
pub fn try_into_kwargs<T: Serialize>(value: T) -> Result<WampKwArgs, WampError> {
    match serde_json::to_value(value).unwrap() {
        serde_json::value::Value::Object(object) => Ok(object),
        value => Err(WampError::SerializationError(
            crate::serializer::SerializerError::Serialization(format!(
                "failed to serialize {:?} into keyword arguments",
                value
            )),
        )),
    }
}

/// Returns whether a uri is valid or not (using strict rules)
pub fn is_valid_strict_uri<T: AsRef<str>>(in_uri: T) -> bool {
    let uri: &str = in_uri.as_ref();
    let mut num_chars_token: usize = 0;
    if uri.starts_with("wamp.") {
        warn!("URI '{}' cannot start with 'wamp'", uri);
        return false;
    }

    for (i, c) in uri.chars().enumerate() {
        if c == '.' {
            if num_chars_token == 0 {
                warn!(
                    "URI '{}' contains a zero length token ending @ index {}",
                    uri, i
                );
                return false;
            }
            num_chars_token = 0;
        } else {
            num_chars_token += 1;
        }

        if c == '_' {
            continue;
        }

        if !c.is_lowercase() {
            warn!(
                "URI '{}' contains a non lower case character @ index {}",
                uri, i
            );
            return false;
        }
        if !c.is_alphanumeric() {
            warn!("URI '{}' contains an invalid character @ index {}", uri, i);
            return false;
        }
    }

    true
}

/// Future that can return success or an error
pub type GenericFuture<'a> = Pin<Box<dyn Future<Output = Result<(), WampError>> + Send + 'a>>;
/// Type returned by RPC functions
pub type RpcFuture<'a> = std::pin::Pin<
    Box<
        dyn std::future::Future<Output = Result<(Option<WampArgs>, Option<WampKwArgs>), WampError>>
            + Send
            + 'a,
    >,
>;
/// Generic function that can receive RPC calls
pub type RpcFunc<'a> =
    Box<dyn Fn(Option<WampArgs>, Option<WampKwArgs>) -> RpcFuture<'a> + Send + Sync + 'a>;

/// Authentication Challenge function that should handle a CHALLENGE request during authentication flow.
/// See more details in [`crate::Client::join_realm_with_authentication`]
pub type AuthenticationChallengeHandler<'a> = Box<
    dyn Fn(
            AuthenticationMethod,
            WampDict,
        ) -> std::pin::Pin<
            Box<
                dyn std::future::Future<Output = Result<AuthenticationChallengeResponse, WampError>>
                    + Send
                    + 'a,
            >,
        > + Send
        + Sync
        + 'a,
>;