use std::time::{Duration, Instant};
#[macro_use]
mod error;
#[cfg(feature = "http")]
use adapter::http::HttpAdapter;
#[cfg(feature = "usb")]
use adapter::usb::UsbAdapter;
use adapter::Adapter;
use command::{close_session::CloseSessionCommand, Command};
use credentials::Credentials;
use securechannel::SessionId;
use serial_number::SerialNumber;
macro_rules! session_debug {
($session:expr, $msg:expr) => {
if let Some(session) = $session.id() {
debug!("session={} {}", session.to_u8(), $msg);
} else {
debug!("session=none {}", $msg);
}
};
($session:expr, $fmt:expr, $($arg:tt)+) => {
if let Some(session) = $session.id() {
debug!(concat!("session={} ", $fmt), session.to_u8(), $($arg)+);
} else {
debug!(concat!("session=none ", $fmt), $($arg)+);
}
};
}
macro_rules! session_error {
($session:expr, $msg:expr) => {
if let Some(session) = $session.id() {
error!("session={} {}", session.to_u8(), $msg);
} else {
error!("session=none {}", $msg);
}
};
($session:expr, $fmt:expr, $($arg:tt)+) => {
if let Some(session) = $session.id() {
error!(concat!("session={} ", $fmt), session.to_u8(), $($arg)+);
} else {
error!(concat!("session=none ", $fmt), $($arg)+);
}
};
}
pub(crate) mod connection;
mod timeout;
use self::connection::Connection;
use self::error::SessionErrorKind::*;
pub use self::{
error::{SessionError, SessionErrorKind},
timeout::SessionTimeout,
};
const TIMEOUT_SKEW_INTERVAL: Duration = Duration::from_secs(1);
#[cfg(feature = "http")]
pub type HttpSession = Session<HttpAdapter>;
#[cfg(feature = "usb")]
pub type UsbSession = Session<UsbAdapter>;
pub struct Session<A: Adapter> {
config: A::Config,
connection: Option<Connection<A>>,
credentials: Option<Credentials>,
last_command_timestamp: Instant,
timeout: SessionTimeout,
}
impl<A: Adapter> Session<A> {
pub fn create(
config: A::Config,
credentials: Credentials,
reconnect: bool,
) -> Result<Self, SessionError> {
let mut session = Self::new(config, credentials)?;
session.open()?;
if !reconnect {
session.credentials = None;
}
Ok(session)
}
pub fn new(config: A::Config, credentials: Credentials) -> Result<Self, SessionError> {
let session = Self {
config,
connection: None,
credentials: Some(credentials),
last_command_timestamp: Instant::now(),
timeout: SessionTimeout::default(),
};
Ok(session)
}
pub fn open(&mut self) -> Result<(), SessionError> {
self.connection()?;
Ok(())
}
#[inline]
pub fn id(&self) -> Option<SessionId> {
self.connection.as_ref().and_then(|ref c| c.id())
}
pub fn is_open(&self) -> bool {
if self.connection.is_none() {
return false;
}
let time_since_last_command = Instant::now().duration_since(self.last_command_timestamp);
if time_since_last_command > (self.timeout.duration() - TIMEOUT_SKEW_INTERVAL) {
session_debug!(
self,
"session timed out after {} seconds (max {})",
time_since_last_command.as_secs(),
self.timeout.duration().as_secs()
);
return false;
}
true
}
pub fn adapter(&mut self) -> Result<&A, SessionError> {
Ok(self.connection()?.adapter())
}
pub fn serial_number(&mut self) -> Result<SerialNumber, SessionError> {
Ok(self.adapter()?.serial_number()?)
}
pub(crate) fn send_command<T: Command>(
&mut self,
command: T,
) -> Result<T::ResponseType, SessionError> {
let response = self.connection()?.send_command(command)?;
self.last_command_timestamp = Instant::now();
Ok(response)
}
fn connection(&mut self) -> Result<&mut Connection<A>, SessionError> {
if self.is_open() {
return Ok(self.connection.as_mut().unwrap());
}
self.connection = None;
let connection = Connection::open(
&self.config,
self.credentials
.as_ref()
.ok_or_else(|| err!(AuthFail, "session reconnection disabled"))?,
)?;
self.connection = Some(connection);
self.last_command_timestamp = Instant::now();
Ok(self.connection.as_mut().unwrap())
}
}
impl<A: Adapter> Drop for Session<A> {
fn drop(&mut self) {
if !self.is_open() {
return;
}
session_debug!(self, "closing dropped session");
if let Err(e) = self.send_command(CloseSessionCommand {}) {
session_debug!(self, "error closing dropped session: {}", e);
}
}
}