mirror of https://github.com/chipsenkbeil/distant
Refactor codebase with breaking changes
1. Add --session argument to launch and action subcommands that accepts file or pipe for launch and environment, file, or pipe for action 2. Unify session string as "DISTANT DATA <host> <port> <auth key>" 3. Rename utils to session 4. Split out Session file functionality to SessionFile 5. Remove SessionError in favor of io::Error 6. Bump version to 0.4.0 in preparation for that releasepull/38/head
parent
93532480d7
commit
a7dd0eb435
@ -0,0 +1,222 @@
|
||||
use crate::{PROJECT_DIRS, SESSION_PATH};
|
||||
use derive_more::{Display, Error};
|
||||
use orion::aead::SecretKey;
|
||||
use std::{
|
||||
env,
|
||||
net::{IpAddr, SocketAddr},
|
||||
ops::Deref,
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
};
|
||||
use tokio::{io, net::lookup_host};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Session {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub auth_key: SecretKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Display, Error, PartialEq, Eq)]
|
||||
pub enum SessionParseError {
|
||||
#[display(fmt = "Prefix of string is invalid")]
|
||||
BadPrefix,
|
||||
|
||||
#[display(fmt = "Bad hex key for session")]
|
||||
BadSessionHexKey,
|
||||
|
||||
#[display(fmt = "Invalid key for session")]
|
||||
InvalidSessionKey,
|
||||
|
||||
#[display(fmt = "Invalid port for session")]
|
||||
InvalidSessionPort,
|
||||
|
||||
#[display(fmt = "Missing address for session")]
|
||||
MissingSessionAddr,
|
||||
|
||||
#[display(fmt = "Missing key for session")]
|
||||
MissingSessionKey,
|
||||
|
||||
#[display(fmt = "Missing port for session")]
|
||||
MissingSessionPort,
|
||||
}
|
||||
|
||||
impl From<SessionParseError> for io::Error {
|
||||
fn from(x: SessionParseError) -> Self {
|
||||
io::Error::new(io::ErrorKind::InvalidData, x)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Session {
|
||||
type Err = SessionParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut tokens = s.split(' ').take(5);
|
||||
|
||||
// First, validate that we have the appropriate prefix
|
||||
if tokens.next().ok_or(SessionParseError::BadPrefix)? != "DISTANT" {
|
||||
return Err(SessionParseError::BadPrefix);
|
||||
}
|
||||
if tokens.next().ok_or(SessionParseError::BadPrefix)? != "DATA" {
|
||||
return Err(SessionParseError::BadPrefix);
|
||||
}
|
||||
|
||||
// Second, load up the address without parsing it
|
||||
let host = tokens
|
||||
.next()
|
||||
.ok_or(SessionParseError::MissingSessionAddr)?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
// Third, load up the port and parse it into a number
|
||||
let port = tokens
|
||||
.next()
|
||||
.ok_or(SessionParseError::MissingSessionPort)?
|
||||
.trim()
|
||||
.parse::<u16>()
|
||||
.map_err(|_| SessionParseError::InvalidSessionPort)?;
|
||||
|
||||
// Fourth, load up the key and convert it back into a secret key from a hex slice
|
||||
let auth_key = SecretKey::from_slice(
|
||||
&hex::decode(
|
||||
tokens
|
||||
.next()
|
||||
.ok_or(SessionParseError::MissingSessionKey)?
|
||||
.trim(),
|
||||
)
|
||||
.map_err(|_| SessionParseError::BadSessionHexKey)?,
|
||||
)
|
||||
.map_err(|_| SessionParseError::InvalidSessionKey)?;
|
||||
|
||||
Ok(Session {
|
||||
host,
|
||||
port,
|
||||
auth_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Loads session from environment variables
|
||||
pub fn from_environment() -> io::Result<Self> {
|
||||
fn to_err(x: env::VarError) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::InvalidInput, x)
|
||||
}
|
||||
|
||||
let host = env::var("DISTANT_HOST").map_err(to_err)?;
|
||||
let port = env::var("DISTANT_PORT").map_err(to_err)?;
|
||||
let auth_key = env::var("DISTANT_AUTH_KEY").map_err(to_err)?;
|
||||
Ok(format!("DISTANT DATA {} {} {}", host, port, auth_key).parse()?)
|
||||
}
|
||||
|
||||
/// Loads session from the next line available in this program's stdin
|
||||
pub fn from_stdin() -> io::Result<Self> {
|
||||
let mut line = String::new();
|
||||
std::io::stdin().read_line(&mut line)?;
|
||||
line.parse()
|
||||
.map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x))
|
||||
}
|
||||
|
||||
/// Returns the ip address associated with the session based on the host
|
||||
pub async fn to_ip_addr(&self) -> io::Result<IpAddr> {
|
||||
let addr = match self.host.parse::<IpAddr>() {
|
||||
Ok(addr) => addr,
|
||||
Err(_) => lookup_host((self.host.as_str(), self.port))
|
||||
.await?
|
||||
.next()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Failed to lookup_host"))?
|
||||
.ip(),
|
||||
};
|
||||
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
/// Returns socket address associated with the session
|
||||
pub async fn to_socket_addr(&self) -> io::Result<SocketAddr> {
|
||||
let addr = self.to_ip_addr().await?;
|
||||
Ok(SocketAddr::from((addr, self.port)))
|
||||
}
|
||||
|
||||
/// Returns a string representing the auth key as hex
|
||||
pub fn to_unprotected_hex_auth_key(&self) -> String {
|
||||
hex::encode(self.auth_key.unprotected_as_bytes())
|
||||
}
|
||||
|
||||
/// Converts to unprotected string that exposes the auth key in the form of
|
||||
/// `DISTANT DATA <addr> <port> <auth key>`
|
||||
pub async fn to_unprotected_string(&self) -> io::Result<String> {
|
||||
Ok(format!(
|
||||
"DISTANT DATA {} {} {}",
|
||||
self.to_ip_addr().await?,
|
||||
self.port,
|
||||
self.to_unprotected_hex_auth_key()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides operations related to working with a session that is disk-based
|
||||
pub struct SessionFile(Session);
|
||||
|
||||
impl AsRef<Session> for SessionFile {
|
||||
fn as_ref(&self) -> &Session {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SessionFile {
|
||||
type Target = Session;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SessionFile> for Session {
|
||||
fn from(sf: SessionFile) -> Self {
|
||||
sf.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Session> for SessionFile {
|
||||
fn from(session: Session) -> Self {
|
||||
Self(session)
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionFile {
|
||||
/// Clears the global session file
|
||||
pub async fn clear() -> io::Result<()> {
|
||||
tokio::fs::remove_file(SESSION_PATH.as_path()).await
|
||||
}
|
||||
|
||||
/// Returns true if the global session file exists
|
||||
pub fn exists() -> bool {
|
||||
SESSION_PATH.exists()
|
||||
}
|
||||
|
||||
/// Saves a session to the global session file
|
||||
pub async fn save(&self) -> io::Result<()> {
|
||||
// Ensure our cache directory exists
|
||||
let cache_dir = PROJECT_DIRS.cache_dir();
|
||||
tokio::fs::create_dir_all(cache_dir).await?;
|
||||
|
||||
self.save_to(SESSION_PATH.as_path()).await
|
||||
}
|
||||
|
||||
/// Saves a session to to a file at the specified path
|
||||
pub async fn save_to(&self, path: impl AsRef<Path>) -> io::Result<()> {
|
||||
tokio::fs::write(path.as_ref(), self.0.to_unprotected_string().await?).await
|
||||
}
|
||||
|
||||
/// Loads a session from the global session file
|
||||
pub async fn load() -> io::Result<Self> {
|
||||
Self::load_from(SESSION_PATH.as_path()).await
|
||||
}
|
||||
|
||||
/// Loads a session from a file at the specified path
|
||||
pub async fn load_from(path: impl AsRef<Path>) -> io::Result<Self> {
|
||||
let text = tokio::fs::read_to_string(path.as_ref()).await?;
|
||||
|
||||
Ok(Self(text.parse()?))
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
use crate::{PROJECT_DIRS, SESSION_PATH};
|
||||
use derive_more::{Display, Error, From};
|
||||
use orion::aead::SecretKey;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use tokio::{io, net::lookup_host};
|
||||
|
||||
#[derive(Debug, Display, Error, From)]
|
||||
pub enum SessionError {
|
||||
#[display(fmt = "Bad hex key for session")]
|
||||
BadSessionHexKey,
|
||||
|
||||
#[display(fmt = "Invalid address for session")]
|
||||
InvalidSessionAddr,
|
||||
|
||||
#[display(fmt = "Invalid key for session")]
|
||||
InvalidSessionKey,
|
||||
|
||||
#[display(fmt = "Invalid port for session")]
|
||||
InvalidSessionPort,
|
||||
|
||||
IoError(io::Error),
|
||||
|
||||
#[display(fmt = "Missing address for session")]
|
||||
MissingSessionAddr,
|
||||
|
||||
#[display(fmt = "Missing key for session")]
|
||||
MissingSessionKey,
|
||||
|
||||
#[display(fmt = "Missing port for session")]
|
||||
MissingSessionPort,
|
||||
|
||||
#[display(fmt = "No session file: {:?}", SESSION_PATH.as_path())]
|
||||
NoSessionFile,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Session {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub auth_key: SecretKey,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Returns a string representing the secret key as hex
|
||||
pub fn to_unprotected_hex_auth_key(&self) -> String {
|
||||
hex::encode(self.auth_key.unprotected_as_bytes())
|
||||
}
|
||||
|
||||
/// Returns the ip address associated with the session based on the host
|
||||
pub async fn to_ip_addr(&self) -> io::Result<IpAddr> {
|
||||
let addr = match self.host.parse::<IpAddr>() {
|
||||
Ok(addr) => addr,
|
||||
Err(_) => lookup_host((self.host.as_str(), self.port))
|
||||
.await?
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::NotFound, SessionError::InvalidSessionAddr)
|
||||
})?
|
||||
.ip(),
|
||||
};
|
||||
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
/// Returns socket address associated with the session
|
||||
pub async fn to_socket_addr(&self) -> io::Result<SocketAddr> {
|
||||
let addr = self.to_ip_addr().await?;
|
||||
Ok(SocketAddr::from((addr, self.port)))
|
||||
}
|
||||
|
||||
/// Clears the global session file
|
||||
pub async fn clear() -> io::Result<()> {
|
||||
tokio::fs::remove_file(SESSION_PATH.as_path()).await
|
||||
}
|
||||
|
||||
/// Returns true if a session is available
|
||||
pub fn exists() -> bool {
|
||||
SESSION_PATH.exists()
|
||||
}
|
||||
|
||||
/// Saves a session to disk
|
||||
pub async fn save(&self) -> io::Result<()> {
|
||||
let key_hex_str = self.to_unprotected_hex_auth_key();
|
||||
|
||||
// Ensure our cache directory exists
|
||||
let cache_dir = PROJECT_DIRS.cache_dir();
|
||||
tokio::fs::create_dir_all(cache_dir).await?;
|
||||
|
||||
// Write our session file
|
||||
let addr = self.to_ip_addr().await?;
|
||||
tokio::fs::write(
|
||||
SESSION_PATH.as_path(),
|
||||
format!("{} {} {}", addr, self.port, key_hex_str),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads a session's information into memory
|
||||
pub async fn load() -> Result<Self, SessionError> {
|
||||
let text = tokio::fs::read_to_string(SESSION_PATH.as_path())
|
||||
.await
|
||||
.map_err(|_| SessionError::NoSessionFile)?;
|
||||
let mut tokens = text.split(' ').take(3);
|
||||
|
||||
// First, load up the address without parsing it
|
||||
let host = tokens
|
||||
.next()
|
||||
.ok_or(SessionError::MissingSessionAddr)?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
// Second, load up the port and parse it into a number
|
||||
let port = tokens
|
||||
.next()
|
||||
.ok_or(SessionError::MissingSessionPort)?
|
||||
.trim()
|
||||
.parse::<u16>()
|
||||
.map_err(|_| SessionError::InvalidSessionPort)?;
|
||||
|
||||
// Third, load up the key and convert it back into a secret key from a hex slice
|
||||
let auth_key = SecretKey::from_slice(
|
||||
&hex::decode(tokens.next().ok_or(SessionError::MissingSessionKey)?.trim())
|
||||
.map_err(|_| SessionError::BadSessionHexKey)?,
|
||||
)
|
||||
.map_err(|_| SessionError::InvalidSessionKey)?;
|
||||
|
||||
Ok(Session {
|
||||
host,
|
||||
port,
|
||||
auth_key,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue