diff --git a/src/client.rs b/src/client.rs index 4b09094..4e812de 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,13 +4,17 @@ pub mod connection; pub mod error; pub mod response; +pub mod session; + +use std::rc::Rc; pub use connection::Connection; pub use error::Error; pub use response::Response; +pub use session::Session; use gio::{ - prelude::{IOStreamExt, OutputStreamExt, SocketClientExt, TlsConnectionExt}, + prelude::{IOStreamExt, OutputStreamExt, SocketClientExt, TlsCertificateExt, TlsConnectionExt}, Cancellable, SocketClient, SocketClientEvent, SocketProtocol, TlsCertificate, TlsClientConnection, }; @@ -19,6 +23,7 @@ use glib::{object::Cast, Bytes, Priority, Uri}; pub const DEFAULT_TIMEOUT: u32 = 10; pub struct Client { + session: Rc, pub socket: SocketClient, } @@ -49,15 +54,18 @@ impl Client { }); // Done - Self { socket } + Self { + session: Rc::new(Session::new()), + socket, + } } // Actions - /// Make async request to given [Uri](https://docs.gtk.org/glib/struct.Uri.html), + /// Make new async request to given [Uri](https://docs.gtk.org/glib/struct.Uri.html), /// callback with new `Response`on success or `Error` on failure. - /// * creates new [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html) - /// * session management by Glib TLS Backend + /// * call this method ignore default session resumption by Glib TLS backend, + /// implement certificate change ability in application runtime pub fn request_async( &self, uri: Uri, @@ -70,51 +78,95 @@ impl Client { // * guest sessions will not work without! self.socket.set_tls(certificate.is_none()); - match crate::gio::network_address::from_uri(&uri, crate::DEFAULT_PORT) { - Ok(network_address) => { - self.socket.connect_async( + // Update previous session available for this request + match self.update_session(&uri, certificate.as_ref()) { + Ok(()) => match crate::gio::network_address::from_uri(&uri, crate::DEFAULT_PORT) { + Ok(network_address) => self.socket.connect_async( &network_address.clone(), match cancellable { Some(ref cancellable) => Some(cancellable.clone()), None => None::, } .as_ref(), - move |result| match result { - Ok(connection) => { - match Connection::new_wrap( - connection, - certificate, - Some(network_address), - ) { - Ok(result) => request_async( - result, - uri.to_string(), - match priority { - Some(priority) => Some(priority), - None => Some(Priority::DEFAULT), - }, - match cancellable { - Some(ref cancellable) => Some(cancellable.clone()), - None => None::, - }, - move |result| callback(result), - ), - Err(reason) => callback(Err(Error::Connection(reason))), + { + let session = self.session.clone(); + move |result| match result { + Ok(connection) => { + match Connection::new_wrap( + connection, + certificate, + Some(network_address), + ) { + Ok(connection) => { + // Wrap connection to shared reference clone semantics + let connection = Rc::new(connection); + + // Update session record + session.update(uri.to_string(), connection.clone()); + + // Begin new request + request_async( + connection, + uri.to_string(), + match priority { + Some(priority) => Some(priority), + None => Some(Priority::DEFAULT), + }, + match cancellable { + Some(ref cancellable) => Some(cancellable.clone()), + None => None::, + }, + move |result| callback(result), + ) + } + Err(reason) => callback(Err(Error::Connection(reason))), + } } + Err(reason) => callback(Err(Error::Connect(reason))), } - Err(reason) => callback(Err(Error::Connect(reason))), }, - ); + ), + Err(reason) => callback(Err(Error::NetworkAddress(reason))), + }, + Err(reason) => callback(Err(reason)), + } + } + + /// Update existing session for given request + pub fn update_session( + &self, + uri: &Uri, + certificate: Option<&TlsCertificate>, + ) -> Result<(), Error> { + if let Some(connection) = self.session.get(&uri.to_string()) { + // Check connection contain TLS authorization + if let Some(ref tls_client_connection) = connection.tls_client_connection { + if let Some(new) = certificate { + // Get previous certificate + if let Some(ref old) = tls_client_connection.certificate() { + if !new.is_same(old) { + // Prevent session resumption + // Glib backend restore session in runtime with old certificate + // @TODO keep in mind, until better solution found for TLS 1.3 + println!("{:?}", tls_client_connection.handshake(Cancellable::NONE)); + } + } + } } - Err(reason) => callback(Err(Error::NetworkAddress(reason))), - }; + + // Close connection if active yet + if let Err(reason) = connection.close(Cancellable::NONE) { + return Err(Error::Connection(reason)); + } + } + Ok(()) } } /// Make new request for constructed `Connection` /// * callback with new `Response`on success or `Error` on failure pub fn request_async( - connection: Connection, + connection: Rc, query: String, priority: Option, cancellable: Option, diff --git a/src/client/response.rs b/src/client/response.rs index 96ab10b..b646165 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -10,9 +10,10 @@ pub use meta::Meta; use super::Connection; use gio::Cancellable; use glib::Priority; +use std::rc::Rc; pub struct Response { - pub connection: Connection, + pub connection: Rc, pub meta: Meta, } @@ -20,7 +21,7 @@ impl Response { // Constructors pub fn from_request_async( - connection: Connection, + connection: Rc, priority: Option, cancellable: Option, callback: impl FnOnce(Result) + 'static, diff --git a/src/client/session.rs b/src/client/session.rs new file mode 100644 index 0000000..6f3f1f6 --- /dev/null +++ b/src/client/session.rs @@ -0,0 +1,24 @@ +use super::Connection; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +/// Request sessions holder for `Client` object +/// * useful to keep connections open and / or validate TLS certificate updates in runtime +pub struct Session { + index: RefCell>>, +} + +impl Session { + pub fn new() -> Self { + Self { + index: RefCell::new(HashMap::new()), + } + } + + pub fn get(&self, request: &str) -> Option> { + self.index.borrow().get(request).cloned() + } + + pub fn update(&self, request: String, connection: Rc) -> Option> { + self.index.borrow_mut().insert(request, connection) + } +}