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
//! Methods used to establish connections to DDMW Core servers' client
//! interfaces.

use futures::sink::SinkExt;

use tokio::io::{AsyncRead, AsyncWrite};

use tokio_stream::StreamExt;

use tokio_util::codec::Framed;

use blather::{codec, Telegram};

use crate::auth::Auth;

use crate::err::Error;

pub use protwrap::tokio::Stream;
pub use protwrap::ProtAddr;


pub type Frm = Framed<protwrap::tokio::Stream, blather::Codec>;


/// Connect to one of the DDMW core's client interfaces and optionally attempt
/// to authenticate.
///
/// If `ProtAddr::Tcp()` is passed into the `pa` argument an TCP/IP connection
/// will be attempted.  If `ProtAddr::Uds()` (currently only available on
/// unix-like platforms) is used a unix local domain socket connection will be
/// attempted.
///
/// If `auth` has `Some` value, an authentication will be attempted after
/// successful connection.  If the authentication fails the entire connection
/// will fail.  To be able to keep the connection up in case the authentication
/// fails, pass `None` to the `auth` argument and manually authenticate in the
/// application.
///
/// # Examples
/// ```no_run
/// use ddmw_client::conn;
/// async fn test() {
///   let pa = protwrap::ProtAddr::Tcp("127.0.0.1:8777".to_string());
///   let conn = conn::connect(&pa, None).await.expect("Unable to connect");
/// }
/// ```
pub async fn connect(
  pa: &ProtAddr,
  auth: Option<&Auth>
) -> Result<Framed<protwrap::tokio::Stream, blather::Codec>, Error> {
  let strm = protwrap::tokio::connect(pa).await?;

  let mut framed = Framed::new(strm, blather::Codec::new());

  if let Some(auth) = auth {
    auth.authenticate(&mut framed).await?;
  }

  Ok(framed)
}


/// Send a telegram then wait for and return the server's reply.
/// If the server returns a `Fail`, it will be returned as
/// `Err(Error::ServerError)`.
pub async fn sendrecv<T>(
  conn: &mut Framed<T, blather::Codec>,
  tg: &Telegram
) -> Result<blather::Params, Error>
where
  T: AsyncRead + AsyncWrite + Unpin
{
  conn.send(tg).await?;
  expect_okfail(conn).await
}


/// Waits for a message and ensures that it's Ok or Fail.
/// Converts Fail state to an Error::ServerError.
/// Returns a Params buffer containig the Ok parameters on success.
pub async fn expect_okfail<T>(
  conn: &mut Framed<T, blather::Codec>
) -> Result<blather::Params, Error>
where
  T: AsyncRead + Unpin
{
  if let Some(o) = conn.next().await {
    let o = o?;
    match o {
      codec::Input::Telegram(tg) => {
        if let Some(topic) = tg.get_topic() {
          if topic == "Ok" {
            return Ok(tg.into_params());
          } else if topic == "Fail" {
            return Err(Error::ServerError(tg.into_params()));
          }
        }
      }
      _ => {
        println!("unexpected reply");
      }
    }
    return Err(Error::BadState("Unexpected reply from server.".to_string()));
  }

  Err(Error::Disconnected)
}


#[derive(Debug)]
pub struct WhoAmI {
  pub id: i64,
  pub name: String
}

/// Return the current owner of a connection.
///
/// # Examples
/// ```no_run
/// use ddmw_client::{conn, auth};
/// async fn test() {
///   let pa = protwrap::ProtAddr::Tcp("127.0.0.1:8777".to_string());
///   let auth = auth::Builder::new()
///     .name("elena")
///     .pass("secret")
///     .build().expect("Unable to build Auth buffer");
///   let mut frm = conn::connect(&pa, Some(&auth)).await
///     .expect("Connection failed");
///
///   let w = conn::whoami(&mut frm).await.expect("whoami failed");
///   assert_eq!(&w.name, "elena");
/// }
/// ```
pub async fn whoami<T>(
  conn: &mut Framed<T, blather::Codec>
) -> Result<WhoAmI, Error>
where
  T: AsyncRead + AsyncWrite + Unpin
{
  let tg = Telegram::new_topic("WhoAmI")?;
  let params = sendrecv(conn, &tg).await?;
  let id = params.get_param::<i64>("Id")?;
  let name = params.get_param("Name")?;
  Ok(WhoAmI { id, name })
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :