#![feature(conservative_impl_trait)]
extern crate base64;
extern crate blake2;
extern crate byteorder;
extern crate futures;
extern crate gtk;
extern crate rand;
#[macro_use]
extern crate relm;
#[macro_use]
extern crate relm_derive;
extern crate tokio_core;
extern crate tokio_proto;
extern crate tokio_service;
extern crate twist;
use std::net::ToSocketAddrs;
use base64::encode;
use blake2::{Blake2b, Digest};
use byteorder::{BigEndian, WriteBytesExt};
use futures::Future;
use gtk::{
Button,
ButtonExt,
ContainerExt,
Entry,
EntryExt,
Inhibit,
Label,
WidgetExt,
Window,
WindowType,
};
use gtk::Orientation::Vertical;
use rand::Rng;
use relm::{Handle, Relm, RemoteRelm, Widget};
use tokio_core::net::TcpStream;
use tokio_proto::TcpClient;
use tokio_proto::pipeline::ClientService;
use tokio_service::Service;
use twist::client::{BaseFrame, HandshakeRequestFrame, OpCode, WebSocketFrame, WebSocketProtocol};
use self::Msg::*;
type WSService = ClientService<TcpStream, WebSocketProtocol>;
#[derive(Clone)]
struct Model {
service: Option<WSService>,
text: String,
}
#[derive(Msg)]
enum Msg {
Connected(WSService),
Message(String),
Send(String),
Quit,
}
#[derive(Clone)]
struct Win {
entry: Entry,
label: Label,
window: Window,
}
impl Widget for Win {
type Model = Model;
type ModelParam = ();
type Msg = Msg;
type Root = Window;
fn model(_: ()) -> Model {
Model {
service: None,
text: String::new(),
}
}
fn root(&self) -> &Self::Root {
&self.window
}
fn subscriptions(relm: &Relm<Msg>) {
let handshake_future = ws_handshake(relm.handle());
let future = relm.connect_ignore_err(handshake_future, Connected);
relm.exec(future);
}
fn update(&mut self, event: Msg, model: &mut Model) {
match event {
Connected(service) => {
model.service = Some(service);
},
Message(message) => {
model.text.push_str(&format!("{}\n", message));
self.label.set_text(&model.text);
},
Send(_) => {
self.entry.set_text("");
self.entry.grab_focus();
},
Quit => gtk::main_quit(),
}
}
fn update_command(relm: &Relm<Msg>, event: Msg, model: &mut Model) {
if let Send(message) = event {
if let Some(ref service) = model.service {
let send_future = ws_send(service, &message);
relm.connect_exec_ignore_err(send_future, Message);
}
}
}
fn view(relm: &RemoteRelm<Self>, _model: &Self::Model) -> Self {
let vbox = gtk::Box::new(Vertical, 0);
let label = Label::new(None);
vbox.add(&label);
let entry = Entry::new();
vbox.add(&entry);
let button = Button::new_with_label("Send");
vbox.add(&button);
let window = Window::new(WindowType::Toplevel);
window.add(&vbox);
window.show_all();
{
let entry2 = entry.clone();
connect!(relm, entry, connect_activate(_), {
let message = entry2.get_text().unwrap_or_else(String::new);
Send(message)
});
}
{
let entry = entry.clone();
connect!(relm, button, connect_clicked(_), {
let message = entry.get_text().unwrap_or_else(String::new);
Send(message)
});
}
connect!(relm, window, connect_delete_event(_, _) (Some(Quit), Inhibit(false)));
Win {
entry: entry,
label: label,
window: window,
}
}
}
fn gen_nonce() -> String {
let mut rng = rand::thread_rng();
let mut nonce_vec = Vec::with_capacity(2);
let nonce = rng.gen::<u16>();
let mut hasher = Blake2b::default();
if nonce_vec.write_u16::<BigEndian>(nonce).is_ok() {
hasher.input(&nonce_vec);
encode(&hasher.result())
} else {
nonce_vec.clear();
nonce_vec.push(rng.gen::<u8>());
nonce_vec.push(rng.gen::<u8>());
hasher.input(&nonce_vec);
encode(&hasher.result())
}
}
fn ws_handshake(handle: &Handle) -> impl Future<Item=WSService> {
let mut protocol = WebSocketProtocol::default();
protocol.client(true);
let client = TcpClient::new(protocol);
let url = "echo.websocket.org:80";
client.connect(&url.to_socket_addrs().unwrap().next().unwrap(), handle)
.and_then(|service| {
let nonce = gen_nonce();
let mut handshake_frame = WebSocketFrame::default();
let mut handshake = HandshakeRequestFrame::default();
handshake.set_user_agent("twisty 0.1.0".to_string());
handshake.set_path("/".to_string());
handshake.set_origin("http://www.websocket.org".to_string());
handshake.set_host("echo.websocket.org".to_string());
handshake.set_sec_websocket_key(nonce.to_string());
handshake_frame.set_clientside_handshake_request(handshake);
service.call(handshake_frame)
.map(|_socket| service)
})
}
fn ws_send(service: &WSService, message: &str) -> impl Future<Item=String> {
let mut frame = WebSocketFrame::default();
let mut base = BaseFrame::default();
base.set_fin(true);
base.set_masked(true);
base.set_mask(0);
base.set_opcode(OpCode::Text);
base.set_payload_length(message.len() as u64);
base.set_application_data(Some(message.as_bytes().to_vec()));
frame.set_base(base);
service.call(frame)
.and_then(|socket| {
let bytes = socket.base().unwrap().application_data().unwrap();
let string = String::from_utf8_lossy(bytes).to_string();
Ok(string)
})
}
fn main() {
Win::run(()).unwrap();
}