1use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
5use std::time::Duration;
6
7use crate::{decode, encode, FromRelay, State, ToRelay};
8
9pub struct Connection {
11 out: Sender<ToRelay>,
12 inbox: Receiver<Result<FromRelay, String>>,
13 state: State,
14}
15
16impl Connection {
17 pub fn connect(url: &str) -> Connection {
19 let (out, outgoing) = channel::<ToRelay>();
20 let (incoming, inbox) = channel();
21 let url = url.to_string();
22 std::thread::spawn(move || {
23 if let Err(e) = run(&url, outgoing, &incoming) {
24 let _ = incoming.send(Err(e));
25 }
26 });
27 Connection {
28 out,
29 inbox,
30 state: State::Connecting,
31 }
32 }
33
34 pub fn send(&mut self, message: ToRelay) {
36 let _ = self.out.send(message);
37 }
38
39 pub fn poll(&mut self) -> Vec<FromRelay> {
41 let mut out = Vec::new();
42 loop {
43 match self.inbox.try_recv() {
44 Ok(Ok(m)) => {
45 self.state = State::Open;
46 out.push(m);
47 }
48 Ok(Err(e)) => self.state = State::Closed(e),
49 Err(TryRecvError::Empty) => break,
50 Err(TryRecvError::Disconnected) => {
51 if !matches!(self.state, State::Closed(_)) {
52 self.state = State::Closed("disconnected".into());
53 }
54 break;
55 }
56 }
57 }
58 out
59 }
60
61 pub fn state(&self) -> &State {
63 &self.state
64 }
65}
66
67fn run(
68 url: &str,
69 outgoing: Receiver<ToRelay>,
70 incoming: &Sender<Result<FromRelay, String>>,
71) -> Result<(), String> {
72 use tungstenite::{stream::MaybeTlsStream, Message};
73 let (mut socket, _) = tungstenite::connect(url).map_err(|e| format!("{url}: {e}"))?;
74 if let MaybeTlsStream::Plain(s) = socket.get_ref() {
75 s.set_read_timeout(Some(Duration::from_millis(2)))
76 .map_err(|e| e.to_string())?;
77 let _ = s.set_nodelay(true);
78 }
79 loop {
80 loop {
82 match outgoing.try_recv() {
83 Ok(m) => socket
84 .send(Message::Binary(encode(&m).into()))
85 .map_err(|e| e.to_string())?,
86 Err(TryRecvError::Empty) => break,
87 Err(TryRecvError::Disconnected) => {
88 let _ = socket.close(None);
89 return Ok(());
90 }
91 }
92 }
93 match socket.read() {
94 Ok(Message::Binary(bytes)) => {
95 let m = decode::<FromRelay>(&bytes)?;
96 if incoming.send(Ok(m)).is_err() {
97 return Ok(());
98 }
99 }
100 Ok(Message::Close(_)) => return Err("the relay closed the connection".into()),
101 Ok(_) => {}
102 Err(tungstenite::Error::Io(e))
103 if matches!(
104 e.kind(),
105 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
106 ) => {}
107 Err(e) => return Err(e.to_string()),
108 }
109 }
110}