1use 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#[derive(Debug, Clone, PartialEq, Default)]
41pub struct DtnClient {
42 localhost: String,
43 port: u16,
44}
45
46impl DtnClient {
47 pub fn new() -> Self {
49 DtnClient {
50 localhost: "127.0.0.1".into(),
51 port: 3000,
52 }
53 }
54 pub fn with_host_and_port(localhost: String, port: u16) -> Self {
56 DtnClient { localhost, port }
57 }
58 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 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 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 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 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 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 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 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 pub fn write_text(&mut self, txt: &str) -> anyhow::Result<()> {
157 self.socket.send(Message::text(txt))?;
158 Ok(())
159 }
160 pub fn write_binary(&mut self, bin: &[u8]) -> anyhow::Result<()> {
166 self.socket.send(Message::binary(bin))?;
167 Ok(())
168 }
169
170 pub fn read_message(&mut self) -> anyhow::Result<Message> {
174 Ok(self.socket.read()?)
175 }
176
177 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 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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
200pub struct WsSendData {
201 pub src: String,
203 pub dst: String,
205 pub delivery_notification: bool,
207 pub lifetime: u64,
209 #[serde(with = "crate::serde::base64_or_bytes")]
211 pub data: Vec<u8>,
212}
213
214#[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}