use crate::net::{SecretKey, UnprotectedToHexKey};
use derive_more::{Display, Error};
use std::{
env,
net::{IpAddr, SocketAddr},
ops::Deref,
path::{Path, PathBuf},
str::FromStr,
};
use tokio::{io, net::lookup_host};
#[derive(Debug, PartialEq, Eq)]
pub struct SessionInfo {
pub host: String,
pub port: u16,
pub auth_key: SecretKey,
}
#[derive(Copy, Clone, Debug, Display, Error, PartialEq, Eq)]
pub enum SessionInfoParseError {
#[display(fmt = "Prefix of string is invalid")]
BadPrefix,
#[display(fmt = "Bad hex key for session")]
BadHexKey,
#[display(fmt = "Invalid key for session")]
InvalidKey,
#[display(fmt = "Invalid port for session")]
InvalidPort,
#[display(fmt = "Missing address for session")]
MissingAddr,
#[display(fmt = "Missing key for session")]
MissingKey,
#[display(fmt = "Missing port for session")]
MissingPort,
}
impl From<SessionInfoParseError> for io::Error {
fn from(x: SessionInfoParseError) -> Self {
io::Error::new(io::ErrorKind::InvalidData, x)
}
}
impl FromStr for SessionInfo {
type Err = SessionInfoParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut tokens = s.split(' ').take(5);
if tokens.next().ok_or(SessionInfoParseError::BadPrefix)? != "DISTANT" {
return Err(SessionInfoParseError::BadPrefix);
}
if tokens.next().ok_or(SessionInfoParseError::BadPrefix)? != "DATA" {
return Err(SessionInfoParseError::BadPrefix);
}
let host = tokens
.next()
.ok_or(SessionInfoParseError::MissingAddr)?
.trim()
.to_string();
let port = tokens
.next()
.ok_or(SessionInfoParseError::MissingPort)?
.trim()
.parse::<u16>()
.map_err(|_| SessionInfoParseError::InvalidPort)?;
let auth_key = SecretKey::from_slice(
&hex::decode(
tokens
.next()
.ok_or(SessionInfoParseError::MissingKey)?
.trim(),
)
.map_err(|_| SessionInfoParseError::BadHexKey)?,
)
.map_err(|_| SessionInfoParseError::InvalidKey)?;
Ok(SessionInfo {
host,
port,
auth_key,
})
}
}
impl SessionInfo {
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()?)
}
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))
}
pub fn into_auth_key(self) -> SecretKey {
self.auth_key
}
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)
}
pub async fn to_socket_addr(&self) -> io::Result<SocketAddr> {
let addr = self.to_ip_addr().await?;
Ok(SocketAddr::from((addr, self.port)))
}
pub fn to_unprotected_string(&self) -> String {
format!(
"DISTANT DATA {} {} {}",
self.host,
self.port,
self.auth_key.unprotected_to_hex_key()
)
}
}
pub struct SessionInfoFile {
path: PathBuf,
session: SessionInfo,
}
impl AsRef<Path> for SessionInfoFile {
fn as_ref(&self) -> &Path {
self.as_path()
}
}
impl AsRef<SessionInfo> for SessionInfoFile {
fn as_ref(&self) -> &SessionInfo {
self.as_session()
}
}
impl Deref for SessionInfoFile {
type Target = SessionInfo;
fn deref(&self) -> &Self::Target {
&self.session
}
}
impl From<SessionInfoFile> for SessionInfo {
fn from(sf: SessionInfoFile) -> Self {
sf.session
}
}
impl SessionInfoFile {
pub fn new(path: impl Into<PathBuf>, session: SessionInfo) -> Self {
Self {
path: path.into(),
session,
}
}
pub fn as_path(&self) -> &Path {
self.path.as_path()
}
pub fn as_session(&self) -> &SessionInfo {
&self.session
}
pub async fn save(&self) -> io::Result<()> {
self.save_to(self.as_path(), true).await
}
pub async fn save_to(&self, path: impl AsRef<Path>, all: bool) -> io::Result<()> {
if all {
if let Some(dir) = path.as_ref().parent() {
tokio::fs::create_dir_all(dir).await?;
}
}
tokio::fs::write(path.as_ref(), self.session.to_unprotected_string()).await
}
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 {
path: path.as_ref().to_path_buf(),
session: text.parse()?,
})
}
}