use std::io::{self, ErrorKind, Read, Write};
use std::net::{Shutdown, TcpStream};
use std::sync::{Arc, Mutex};
use thrift::transport::{ReadHalf, TIoChannel, WriteHalf};
use thrift::{new_transport_error, TransportErrorKind};
use rustls::StreamOwned as RusTLSStream;
use rustls::{ClientSession, RootCertStore, ServerSession, Session};
use webpki;
use super::X509Credentials;
pub type TLSStream<S> = Arc<Mutex<RusTLSStream<S, TcpStream>>>;
pub struct TLSTTcpChannel<S>
where
S: Session,
{
stream: Option<TLSStream<S>>,
shutdown: Shutdown,
}
impl<S> TLSTTcpChannel<S>
where
S: Session,
{
pub fn new() -> TLSTTcpChannel<S> {
TLSTTcpChannel {
stream: None,
shutdown: Shutdown::Both,
}
}
pub fn close(&mut self) -> thrift::Result<()> {
let shutdown_direction = self.shutdown;
self.if_set(|s| s.get_mut().shutdown(shutdown_direction))
.map_err(From::from)
}
fn if_set<F, T>(&mut self, mut stream_operation: F) -> io::Result<T>
where
F: FnMut(&mut RusTLSStream<S, TcpStream>) -> io::Result<T>,
{
if let Some(ref mut s) = self.stream {
stream_operation(&mut s.lock().unwrap())
} else {
Err(io::Error::new(
ErrorKind::NotConnected,
"tcp endpoint not connected",
))
}
}
}
impl TLSTTcpChannel<ServerSession> {
pub fn with_stream(stream: TLSStream<ServerSession>) -> TLSTTcpChannel<ServerSession> {
TLSTTcpChannel {
stream: Some(stream),
shutdown: Shutdown::Both,
}
}
}
impl TLSTTcpChannel<ClientSession> {
pub fn open(
&mut self,
remote_address: &str,
key_pair: Option<X509Credentials>,
root_cert_store: Option<RootCertStore>,
) -> thrift::Result<()> {
if self.stream.is_some() {
Err(new_transport_error(
TransportErrorKind::AlreadyOpen,
"TLS session connection previously opened",
))
} else {
let tsap: Vec<&str> = remote_address.rsplit(':').collect();
if tsap.len() != 2 {
return Err(new_transport_error(
TransportErrorKind::Unknown,
format!("Invalid remote address: '{}'", remote_address),
));
}
let dns_name = match webpki::DNSNameRef::try_from_ascii_str(tsap[1]) {
Ok(dns_nameref) => dns_nameref,
Err(e) => {
return Err(new_transport_error(
TransportErrorKind::Unknown,
format!("Invalid DNS name: '{}'", e),
))
}
};
let config = super::make_tls_client_config(key_pair, root_cert_store);
let sess = ClientSession::new(&config, dns_name);
let sock = TcpStream::connect(remote_address).unwrap();
self.stream = Some(Arc::new(Mutex::new(RusTLSStream::new(sess, sock))));
Ok(())
}
}
}
impl<S> TIoChannel for TLSTTcpChannel<S>
where
S: Session,
{
fn split(self) -> thrift::Result<(ReadHalf<Self>, WriteHalf<Self>)>
where
Self: Sized,
{
if let Some(stream) = self.stream {
let read_half = ReadHalf::new(TLSTTcpChannel {
stream: Some(stream.clone()),
shutdown: Shutdown::Read,
});
let write_half = WriteHalf::new(TLSTTcpChannel {
stream: Some(stream),
shutdown: Shutdown::Write,
});
Ok((read_half, write_half))
} else {
Err(new_transport_error(
TransportErrorKind::Unknown,
"cannot clone underlying tcp stream",
))
}
}
}
impl<S> Read for TLSTTcpChannel<S>
where
S: Session,
{
fn read(&mut self, b: &mut [u8]) -> io::Result<usize> {
self.if_set(|s| s.read(b))
}
}
impl<S> Write for TLSTTcpChannel<S>
where
S: Session,
{
fn write(&mut self, b: &[u8]) -> io::Result<usize> {
self.if_set(|s| s.write(b))
}
fn flush(&mut self) -> io::Result<()> {
self.if_set(|s| s.flush())
}
}