From ab9c7f44004334e9eba5ff768d533a491a092538 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sat, 30 Nov 2024 03:15:57 +0200 Subject: [PATCH] move session update method to struct implementation --- src/client.rs | 74 ++++++++----------------------------- src/client/error.rs | 28 ++++++++------ src/client/session.rs | 64 ++++++++++++++++++++++++++++++-- src/client/session/error.rs | 16 ++++++++ 4 files changed, 109 insertions(+), 73 deletions(-) create mode 100644 src/client/session/error.rs diff --git a/src/client.rs b/src/client.rs index 3b27cf3..b071614 100644 --- a/src/client.rs +++ b/src/client.rs @@ -14,7 +14,7 @@ pub use response::Response; pub use session::Session; use gio::{ - prelude::{IOStreamExt, OutputStreamExt, SocketClientExt, TlsCertificateExt, TlsConnectionExt}, + prelude::{IOStreamExt, OutputStreamExt, SocketClientExt, TlsConnectionExt}, Cancellable, SocketClient, SocketClientEvent, SocketProtocol, TlsCertificate, TlsClientConnection, }; @@ -84,9 +84,11 @@ impl Client { // * guest sessions will not work without! self.socket.set_tls(certificate.is_none()); - // Update previous session available for this request - match self.update_session(&uri, certificate.as_ref()) { + // Update previous session if available for this `uri`, force rehandshake on certificate change + match self.session.update(&uri, certificate.as_ref()) { // Begin new connection + // * [NetworkAddress](https://docs.gtk.org/gio/class.NetworkAddress.html) required for valid + // [SNI](https://geminiprotocol.net/docs/protocol-specification.gmi#server-name-indication) Ok(()) => match crate::gio::network_address::from_uri(&uri, crate::DEFAULT_PORT) { Ok(network_address) => self.socket.connect_async( &network_address.clone(), @@ -99,17 +101,18 @@ impl Client { let session = self.session.clone(); move |result| match result { Ok(connection) => { + // Wrap required connection dependencies into the struct holder match Connection::new_wrap( connection, certificate, Some(network_address), ) { Ok(connection) => { - // Wrap connection to shared reference clone semantics + // Wrap to shared reference support clone semantics let connection = Rc::new(connection); - // Update session - session.update(uri.to_string(), connection.clone()); + // Renew session + session.set(uri.to_string(), connection.clone()); // Begin new request request_async( @@ -123,66 +126,21 @@ impl Client { Some(ref cancellable) => Some(cancellable.clone()), None => None::, }, - callback, + callback, // callback with response ) } - Err(reason) => callback(Err(Error::Connection(reason))), + Err(e) => callback(Err(Error::Connection(e))), } } - Err(reason) => callback(Err(Error::Connect(reason))), + Err(e) => callback(Err(Error::Connect(e))), } }, ), - Err(reason) => callback(Err(Error::NetworkAddress(reason))), + Err(e) => callback(Err(Error::NetworkAddress(e))), }, - Err(reason) => callback(Err(reason)), + Err(e) => callback(Err(Error::Session(e))), } } - - /// 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 - match connection.tls_client_connection { - Some(ref tls_client_connection) => { - match certificate { - Some(new) => { - // Get previous certificate - if let Some(ref old) = tls_client_connection.certificate() { - // User -> User - 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!("{:?}", connection.rehandshake()); - } - } - } - None => { - // User -> Guest - println!("{:?}", connection.rehandshake()); - } - } - } - None => { - // Guest -> User - if certificate.is_some() { - println!("{:?}", connection.rehandshake()); - } - } - } - - // 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` @@ -210,11 +168,11 @@ pub fn request_async( Response::from_request_async(connection, priority, cancellable, move |result| { callback(match result { Ok(response) => Ok(response), - Err(reason) => Err(Error::Response(reason)), + Err(e) => Err(Error::Response(e)), }) }) } - Err(reason) => callback(Err(Error::OutputStream(reason))), + Err(e) => callback(Err(Error::OutputStream(e))), }, ); } diff --git a/src/client/error.rs b/src/client/error.rs index b6df128..bbb159a 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -9,6 +9,7 @@ pub enum Error { OutputStream(glib::Error), Request(glib::Error), Response(crate::client::response::Error), + Session(crate::client::session::Error), } impl Display for Error { @@ -17,23 +18,26 @@ impl Display for Error { Self::Connectable(uri) => { write!(f, "Could not create connectable address for {uri}") } - Self::Connection(reason) => { - write!(f, "Connection error: {reason}") + Self::Connection(e) => { + write!(f, "Connection error: {e}") } - Self::Connect(reason) => { - write!(f, "Connect error: {reason}") + Self::Connect(e) => { + write!(f, "Connect error: {e}") } - Self::NetworkAddress(reason) => { - write!(f, "Network address error: {reason}") + Self::NetworkAddress(e) => { + write!(f, "Network address error: {e}") } - Self::OutputStream(reason) => { - write!(f, "Output stream error: {reason}") + Self::OutputStream(e) => { + write!(f, "Output stream error: {e}") } - Self::Request(reason) => { - write!(f, "Request error: {reason}") + Self::Request(e) => { + write!(f, "Request error: {e}") } - Self::Response(reason) => { - write!(f, "Response error: {reason}") + Self::Response(e) => { + write!(f, "Response error: {e}") + } + Self::Session(e) => { + write!(f, "Session error: {e}") } } } diff --git a/src/client/session.rs b/src/client/session.rs index 1f7c422..c7d93ba 100644 --- a/src/client/session.rs +++ b/src/client/session.rs @@ -1,8 +1,16 @@ +mod error; +pub use error::Error; + use super::Connection; +use gio::{ + prelude::{TlsCertificateExt, TlsConnectionExt}, + Cancellable, TlsCertificate, +}; +use glib::Uri; 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 +/// Request sessions holder +/// * useful to keep connections open in async context and / or validate TLS certificate updates in runtime pub struct Session { index: RefCell>>, } @@ -24,7 +32,57 @@ impl Session { self.index.borrow().get(request).cloned() } - pub fn update(&self, request: String, connection: Rc) -> Option> { + pub fn set(&self, request: String, connection: Rc) -> Option> { self.index.borrow_mut().insert(request, connection) } + + /// Update existing session for given [Uri](https://docs.gtk.org/glib/struct.Uri.html) + /// and [TlsCertificate](https://docs.gtk.org/gio/class.TlsCertificate.html) + /// * force rehandshake on user certificate was changed in runtime (ignore default session resumption by Glib TLS backend implementation) + /// * close previous connection match `Uri` if not closed yet + pub fn update(&self, uri: &Uri, certificate: Option<&TlsCertificate>) -> Result<(), Error> { + if let Some(connection) = self.get(&uri.to_string()) { + match connection.tls_client_connection { + // User certificate session + Some(ref tls_client_connection) => { + match certificate { + Some(new) => { + // Get previous certificate + if let Some(ref old) = tls_client_connection.certificate() { + // User -> User + if !new.is_same(old) { + rehandshake(connection.as_ref()); + } + } + } + // User -> Guest + None => rehandshake(connection.as_ref()), + } + } + // Guest + None => { + // Guest -> User + if certificate.is_some() { + rehandshake(connection.as_ref()) + } + } + } + + // Close connection if active yet + if let Err(reason) = connection.close(Cancellable::NONE) { + return Err(Error::Connection(reason)); + } + } + Ok(()) + } +} + +// Tools + +// Applies re-handshake to connection to prevent default session resumption +// on user certificate change in runtime +pub fn rehandshake(connection: &Connection) { + if let Err(e) = connection.rehandshake() { + println!("warning: {e}"); // @TODO keep in mind until solution for TLS 1.3 + } } diff --git a/src/client/session/error.rs b/src/client/session/error.rs new file mode 100644 index 0000000..a974d1a --- /dev/null +++ b/src/client/session/error.rs @@ -0,0 +1,16 @@ +use std::fmt::{Display, Formatter, Result}; + +#[derive(Debug)] +pub enum Error { + Connection(crate::client::connection::Error), +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + Self::Connection(e) => { + write!(f, "Connection error: {e}") + } + } + } +}