use std::net::SocketAddr;
use serde::{Deserialize, Serialize};
use crate::build_info;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerIdentity {
pub version: String,
pub git_commit: Option<String>,
pub git_dirty: Option<bool>,
#[serde(default)]
pub profile: Option<String>,
#[serde(default)]
pub rustc: Option<String>,
#[serde(default)]
pub build_timestamp: Option<String>,
pub hostname: Option<String>,
pub os: String,
pub arch: String,
}
impl PeerIdentity {
pub fn local() -> Self {
Self {
version: build_info::VERSION.to_string(),
git_commit: build_info::GIT_COMMIT.map(str::to_string),
git_dirty: build_info::git_dirty(),
profile: Some(build_info::PROFILE.to_string()),
rustc: Some(build_info::RUSTC.to_string()),
build_timestamp: Some(build_info::build_timestamp()),
hostname: read_hostname(),
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
}
}
}
fn read_hostname() -> Option<String> {
if let Ok(s) = std::fs::read_to_string("/etc/hostname") {
let trimmed = s.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_string());
}
}
std::env::var("HOSTNAME").ok().filter(|s| !s.is_empty())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocalView {
pub identity: PeerIdentity,
pub local_addr: Option<SocketAddr>,
pub remote_addr: Option<SocketAddr>,
}
impl LocalView {
pub fn new() -> Self {
Self {
identity: PeerIdentity::local(),
local_addr: None,
remote_addr: None,
}
}
}
impl Default for LocalView {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RemoteView {
pub identity: Option<PeerIdentity>,
pub server_local_addr: Option<SocketAddr>,
pub observed_client_addr: Option<SocketAddr>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Peers {
pub client: LocalView,
pub server: RemoteView,
}
impl Peers {
pub fn local_only() -> Self {
Self {
client: LocalView::new(),
server: RemoteView::default(),
}
}
}
impl Default for Peers {
fn default() -> Self {
Self::local_only()
}
}
use std::fmt::{self, Display, Formatter};
impl Display for PeerIdentity {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "speed-cli {}", self.version)?;
let mut tags: Vec<String> = Vec::new();
if let Some(commit) = &self.git_commit {
let short: String = commit.chars().take(12).collect();
let dirty = match self.git_dirty {
Some(true) => "+dirty",
Some(false) => "",
None => "?",
};
tags.push(format!("{short}{dirty}"));
}
if let Some(profile) = &self.profile {
tags.push(profile.clone());
}
if !tags.is_empty() {
write!(f, " ({})", tags.join(", "))?;
}
write!(
f,
" on {} {}/{}",
self.hostname.as_deref().unwrap_or("?"),
self.os,
self.arch
)
}
}
impl Display for Peers {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, " Client: {}", self.client.identity)?;
if let Some(addr) = &self.client.local_addr {
writeln!(f, " local: {addr}")?;
}
if let Some(addr) = &self.client.remote_addr {
writeln!(f, " remote: {addr}")?;
}
match &self.server.identity {
Some(id) => writeln!(f, " Server: {id}")?,
None => writeln!(f, " Server: (no handshake)")?,
}
if let Some(addr) = &self.server.server_local_addr {
writeln!(f, " bound: {addr}")?;
}
if let Some(addr) = &self.server.observed_client_addr {
writeln!(f, " sees us: {addr}")?;
}
Ok(())
}
}