Skip to main content

dtn7_plus/client/
mod.rs

1//! Simple ways to interact with dtnd
2//!
3//! # Example
4//!
5//! ```
6//! use dtn7_plus::client::DtnClient;
7//!
8//! let client = DtnClient::new();
9//!
10//! let local_node = client.local_node_id()?;
11//! client.register_application_endpoint("incoming")?;
12//!
13//! # Ok::<(), dtn7_plus::client::ClientError>(())
14//! ```
15use bp7::{CreationTimestamp, EndpointID};
16use serde::{Deserialize, Serialize};
17use std::convert::TryInto;
18use thiserror::Error;
19use tungstenite::{protocol::WebSocketConfig, WebSocket};
20
21pub use tungstenite::protocol::Message;
22
23#[derive(Error, Debug)]
24pub enum ClientError {
25    #[error("message not utf8: {0}")]
26    NonUtf8(#[from] std::string::FromUtf8Error),
27    #[error("serde cbor error: {0}")]
28    Cbor(#[from] serde_cbor::Error),
29    #[error("serde json error: {0}")]
30    Json(#[from] serde_json::Error),
31    #[error("http connection error: {0}")]
32    Http(#[from] attohttpc::Error),
33    #[error("failed to create endpoint: {0}")]
34    EndpointIdInvalid(#[from] bp7::eid::EndpointIdError),
35}
36
37/// Client for connecting to a local dtnd instance
38///
39/// Works with IPv6 and IPv4.
40#[derive(Debug, Clone, PartialEq, Default)]
41pub struct DtnClient {
42    localhost: String,
43    port: u16,
44}
45
46impl DtnClient {
47    /// Constructs a new client for `127.0.0.1` on port `3000`.
48    pub fn new() -> Self {
49        DtnClient {
50            localhost: "127.0.0.1".into(),
51            port: 3000,
52        }
53    }
54    /// New client with custom host and port
55    pub fn with_host_and_port(localhost: String, port: u16) -> Self {
56        DtnClient { localhost, port }
57    }
58    /// Return the local node ID via rest interface
59    pub fn local_node_id(&self) -> Result<EndpointID, ClientError> {
60        Ok(attohttpc::get(format!(
61            "http://{}:{}/status/nodeid",
62            self.localhost, self.port
63        ))
64        .send()?
65        .text()?
66        .try_into()?)
67    }
68    /// Get a new node-wide unique creation timestamp via rest interface
69    pub fn creation_timestamp(&self) -> Result<CreationTimestamp, ClientError> {
70        let response = attohttpc::get(format!("http://{}:{}/cts", self.localhost, self.port))
71            .send()?
72            .text()?;
73        Ok(serde_json::from_str(&response)?)
74    }
75    /// Register a new application endpoint at local node
76    pub fn register_application_endpoint(&self, path: &str) -> Result<(), ClientError> {
77        let _response = attohttpc::get(format!(
78            "http://{}:{}/register?{}",
79            self.localhost, self.port, path
80        ))
81        .send()?
82        .text()?;
83        Ok(())
84    }
85    /// Unregister an application endpoint at local node
86    pub fn unregister_application_endpoint(&self, path: &str) -> Result<(), ClientError> {
87        let _response = attohttpc::get(format!(
88            "http://{}:{}/unregister?{}",
89            self.localhost, self.port, path
90        ))
91        .send()?
92        .text()?;
93        Ok(())
94    }
95
96    /// Constructs a new websocket connection to the configured dtn7 client
97    pub fn ws(&self) -> anyhow::Result<DtnWsConnection<std::net::TcpStream>> {
98        let stream = std::net::TcpStream::connect(format!("{}:{}", self.localhost, self.port))?;
99        let ws = self.ws_custom(stream)?;
100        Ok(ws)
101    }
102    /// Constructs a new websocket connection to the configured dtn7 client with a custom WebSocketConfig
103    pub fn ws_with_config(
104        &self,
105        config: WebSocketConfig,
106    ) -> anyhow::Result<DtnWsConnection<std::net::TcpStream>> {
107        let stream = std::net::TcpStream::connect(format!("{}:{}", self.localhost, self.port))?;
108        let ws = self.ws_custom_with_config(stream, config)?;
109        Ok(ws)
110    }
111
112    /// Constructs a new websocket connection to the configured dtn7 client using a custom Stream
113    pub fn ws_custom<Stream>(&self, stream: Stream) -> anyhow::Result<DtnWsConnection<Stream>>
114    where
115        Stream: std::io::Read + std::io::Write,
116    {
117        let ws_url = url::Url::parse(&format!("ws://{}:{}/ws", self.localhost, self.port))
118            .expect("Error constructing websocket url!");
119        let (socket, _) =
120            tungstenite::client::client(&ws_url, stream).expect("Error constructing websocket!");
121        Ok(DtnWsConnection { socket })
122    }
123    /// Constructs a new websocket connection to the configured dtn7 client using a custom Stream and a custom WebSocketConfig
124    pub fn ws_custom_with_config<Stream>(
125        &self,
126        stream: Stream,
127        config: WebSocketConfig,
128    ) -> anyhow::Result<DtnWsConnection<Stream>>
129    where
130        Stream: std::io::Read + std::io::Write,
131    {
132        let ws_url = url::Url::parse(&format!("ws://{}:{}/ws", self.localhost, self.port))
133            .expect("Error constructing websocket url!");
134        let (socket, _) = tungstenite::client::client_with_config(&ws_url, stream, Some(config))
135            .expect("Error constructing websocket!");
136        Ok(DtnWsConnection { socket })
137    }
138}
139pub struct DtnWsConnection<Stream>
140where
141    Stream: std::io::Read + std::io::Write,
142{
143    socket: WebSocket<Stream>,
144}
145
146impl<Stream> DtnWsConnection<Stream>
147where
148    Stream: std::io::Read + std::io::Write,
149{
150    /// Send a text message via websocket
151    ///
152    /// accepted commands:
153    /// `/data`
154    /// `/bundle`
155    /// `/subscribe <service>`
156    pub fn write_text(&mut self, txt: &str) -> anyhow::Result<()> {
157        self.socket.send(Message::text(txt))?;
158        Ok(())
159    }
160    /// Send a binary message via websocket
161    ///
162    /// Server expects either
163    /// - a valid bundle (in bundle mode)
164    /// - a WsSendData struct as a cbor buffer (in data mode)
165    pub fn write_binary(&mut self, bin: &[u8]) -> anyhow::Result<()> {
166        self.socket.send(Message::binary(bin))?;
167        Ok(())
168    }
169
170    /// Read the next message
171    ///
172    /// Could be text, binary, ping, etc etc
173    pub fn read_message(&mut self) -> anyhow::Result<Message> {
174        Ok(self.socket.read()?)
175    }
176
177    /// Expect a text message next, returning an error on any other message type
178    pub fn read_text(&mut self) -> anyhow::Result<String> {
179        let msg = self.socket.read()?;
180        if let Message::Text(txt) = msg {
181            Ok(txt)
182        } else {
183            anyhow::bail!("Unexpected message type");
184        }
185    }
186    /// Expect a binary message next, returning an error on any other message type
187    pub fn read_binary(&mut self) -> anyhow::Result<Vec<u8>> {
188        let msg = self.socket.read()?;
189        if let Message::Binary(bin) = msg {
190            Ok(bin)
191        } else {
192            anyhow::bail!("Unexpected message type");
193        }
194    }
195}
196/// Let server construct a new bundle from the provided data
197///
198/// To be used via WebSocket connection.
199#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
200pub struct WsSendData {
201    /// source with a dtn URI scheme, e.g. dtn://node1 or ipn://23.0
202    pub src: String,
203    /// destination with a dtn URI scheme, e.g. dtn://node1/sms or ipn://23.42/
204    pub dst: String,
205    /// turn on delivery notifications
206    pub delivery_notification: bool,
207    /// lifetime for bundle in milliseconds
208    pub lifetime: u64,
209    /// payload data
210    #[serde(with = "crate::serde::base64_or_bytes")]
211    pub data: Vec<u8>,
212}
213
214/// Received bundle payload with meta data
215///
216/// To be used via WebSocket connection.
217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
218pub struct WsRecvData {
219    pub bid: String,
220    pub src: String,
221    pub dst: String,
222    pub cts: CreationTimestamp,
223    pub lifetime: u64,
224    #[serde(with = "crate::serde::base64_or_bytes")]
225    pub data: Vec<u8>,
226}