use bp7::{CreationTimestamp, EndpointID};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use thiserror::Error;
use tungstenite::{protocol::WebSocketConfig, WebSocket};
pub use tungstenite::protocol::Message;
#[derive(Error, Debug)]
pub enum ClientError {
#[error("message not utf8: {0}")]
NonUtf8(#[from] std::string::FromUtf8Error),
#[error("serde cbor error: {0}")]
Cbor(#[from] serde_cbor::Error),
#[error("serde json error: {0}")]
Json(#[from] serde_json::Error),
#[error("http connection error: {0}")]
Http(#[from] attohttpc::Error),
#[error("failed to create endpoint: {0}")]
EndpointIdInvalid(#[from] bp7::eid::EndpointIdError),
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct DtnClient {
localhost: String,
port: u16,
}
impl DtnClient {
pub fn new() -> Self {
DtnClient {
localhost: "127.0.0.1".into(),
port: 3000,
}
}
pub fn with_host_and_port(localhost: String, port: u16) -> Self {
DtnClient { localhost, port }
}
pub fn local_node_id(&self) -> Result<EndpointID, ClientError> {
Ok(attohttpc::get(format!(
"http://{}:{}/status/nodeid",
self.localhost, self.port
))
.send()?
.text()?
.try_into()?)
}
pub fn creation_timestamp(&self) -> Result<CreationTimestamp, ClientError> {
let response = attohttpc::get(format!("http://{}:{}/cts", self.localhost, self.port))
.send()?
.text()?;
Ok(serde_json::from_str(&response)?)
}
pub fn register_application_endpoint(&self, path: &str) -> Result<(), ClientError> {
let _response = attohttpc::get(format!(
"http://{}:{}/register?{}",
self.localhost, self.port, path
))
.send()?
.text()?;
Ok(())
}
pub fn unregister_application_endpoint(&self, path: &str) -> Result<(), ClientError> {
let _response = attohttpc::get(format!(
"http://{}:{}/unregister?{}",
self.localhost, self.port, path
))
.send()?
.text()?;
Ok(())
}
pub fn ws(&self) -> anyhow::Result<DtnWsConnection<std::net::TcpStream>> {
let stream = std::net::TcpStream::connect(format!("{}:{}", self.localhost, self.port))?;
let ws = self.ws_custom(stream)?;
Ok(ws)
}
pub fn ws_with_config(
&self,
config: WebSocketConfig,
) -> anyhow::Result<DtnWsConnection<std::net::TcpStream>> {
let stream = std::net::TcpStream::connect(format!("{}:{}", self.localhost, self.port))?;
let ws = self.ws_custom_with_config(stream, config)?;
Ok(ws)
}
pub fn ws_custom<Stream>(&self, stream: Stream) -> anyhow::Result<DtnWsConnection<Stream>>
where
Stream: std::io::Read + std::io::Write,
{
let ws_url = url::Url::parse(&format!("ws://{}:{}/ws", self.localhost, self.port))
.expect("Error constructing websocket url!");
let (socket, _) =
tungstenite::client::client(&ws_url, stream).expect("Error constructing websocket!");
Ok(DtnWsConnection { socket })
}
pub fn ws_custom_with_config<Stream>(
&self,
stream: Stream,
config: WebSocketConfig,
) -> anyhow::Result<DtnWsConnection<Stream>>
where
Stream: std::io::Read + std::io::Write,
{
let ws_url = url::Url::parse(&format!("ws://{}:{}/ws", self.localhost, self.port))
.expect("Error constructing websocket url!");
let (socket, _) = tungstenite::client::client_with_config(&ws_url, stream, Some(config))
.expect("Error constructing websocket!");
Ok(DtnWsConnection { socket })
}
}
pub struct DtnWsConnection<Stream>
where
Stream: std::io::Read + std::io::Write,
{
socket: WebSocket<Stream>,
}
impl<Stream> DtnWsConnection<Stream>
where
Stream: std::io::Read + std::io::Write,
{
pub fn write_text(&mut self, txt: &str) -> anyhow::Result<()> {
self.socket.send(Message::text(txt))?;
Ok(())
}
pub fn write_binary(&mut self, bin: &[u8]) -> anyhow::Result<()> {
self.socket.send(Message::binary(bin))?;
Ok(())
}
pub fn read_message(&mut self) -> anyhow::Result<Message> {
Ok(self.socket.read()?)
}
pub fn read_text(&mut self) -> anyhow::Result<String> {
let msg = self.socket.read()?;
if let Message::Text(txt) = msg {
Ok(txt)
} else {
anyhow::bail!("Unexpected message type");
}
}
pub fn read_binary(&mut self) -> anyhow::Result<Vec<u8>> {
let msg = self.socket.read()?;
if let Message::Binary(bin) = msg {
Ok(bin)
} else {
anyhow::bail!("Unexpected message type");
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WsSendData {
pub src: String,
pub dst: String,
pub delivery_notification: bool,
pub lifetime: u64,
#[serde(with = "crate::serde::base64_or_bytes")]
pub data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WsRecvData {
pub bid: String,
pub src: String,
pub dst: String,
pub cts: CreationTimestamp,
pub lifetime: u64,
#[serde(with = "crate::serde::base64_or_bytes")]
pub data: Vec<u8>,
}