pub mod error;
pub mod host;
use std::collections::HashSet;
use std::fmt;
use std::net::{IpAddr, SocketAddr};
use std::time::{Duration, SystemTime};
use chrono::DateTime;
use error::NetzworkApiError;
use hmac_sha256::Hash;
use host::id::get_secure_machine_id;
use local_ip_address::list_afinet_netifas;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::host::HostInfo;
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct Heartbeat {
pub hb_type: HeartbeatType,
pub timestamp: DateTime<chrono::Utc>,
pub hb_uuid: Uuid,
pub payload: Vec<HeartbeatPayload>,
}
impl Heartbeat {
pub fn new(
hb_type: HeartbeatType,
name: String,
agent_status: Option<AgentStatus>,
) -> Heartbeat {
let status = match agent_status {
Some(s) => s,
None => AgentStatus::Undefined,
};
Heartbeat {
hb_type,
timestamp: DateTime::from(SystemTime::now()),
hb_uuid: Uuid::new_v4(),
payload: vec![
HeartbeatPayload::AgentInfo(AgentInfo { status, name }),
HeartbeatPayload::HostInfo(HostInfo::new()),
HeartbeatPayload::BuildInfo(BuildInfo::new()),
HeartbeatPayload::NetInterfaceInfo(
NetInterfaceInfo::from_network_interfaces()
.expect("Could not generate network interface info"),
),
],
}
}
pub fn compact(payload: Vec<HeartbeatPayloadType>) -> Heartbeat {
let mut hb = Heartbeat {
hb_type: HeartbeatType::Compact,
timestamp: DateTime::from(SystemTime::now()),
hb_uuid: Uuid::new_v4(),
payload: vec![],
};
for p in payload.iter() {
hb.payload.push(p.to_payload())
}
hb
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub enum HeartbeatPayloadType {
AgentInfo(AgentStatus, String),
HostInfo,
BuildInfo,
NetInterfaceInfo,
}
impl HeartbeatPayloadType {
pub fn to_payload(&self) -> HeartbeatPayload {
match self {
HeartbeatPayloadType::AgentInfo(status, name) => {
HeartbeatPayload::AgentInfo(AgentInfo {
status: status.to_owned(),
name: name.to_string(),
})
}
HeartbeatPayloadType::HostInfo => HeartbeatPayload::HostInfo(HostInfo::new()),
HeartbeatPayloadType::BuildInfo => HeartbeatPayload::BuildInfo(BuildInfo::new()),
HeartbeatPayloadType::NetInterfaceInfo => HeartbeatPayload::NetInterfaceInfo(
NetInterfaceInfo::from_network_interfaces()
.expect("Could not generate network interface info"),
),
}
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub enum HeartbeatPayload {
AgentInfo(AgentInfo),
HostInfo(HostInfo),
BuildInfo(BuildInfo),
NetInterfaceInfo(NetInterfaceInfo),
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub enum HeartbeatType {
Full,
Compact,
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct AgentInfo {
pub status: AgentStatus,
pub name: String,
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub enum AgentStatus {
Undefined,
Initializing,
AwaitingJoin,
Running,
Orphaned,
Exiting,
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct BuildInfo {
build_timestamp: String,
build_date: String,
git_branch: String,
git_timestamp: String,
git_date: String,
git_hash: String,
git_describe: String,
rustc_host_triple: String,
rustc_version: String,
cargo_target_triple: String,
}
impl BuildInfo {
fn new() -> BuildInfo {
BuildInfo {
build_timestamp: String::from(env!("VERGEN_BUILD_TIMESTAMP")),
build_date: String::from(env!("VERGEN_BUILD_DATE")),
git_branch: String::from(env!("VERGEN_GIT_BRANCH")),
git_timestamp: String::from(env!("VERGEN_GIT_COMMIT_TIMESTAMP")),
git_date: String::from(env!("VERGEN_GIT_COMMIT_DATE")),
git_hash: String::from(env!("VERGEN_GIT_SHA")),
git_describe: String::from(env!("VERGEN_GIT_DESCRIBE")),
rustc_host_triple: String::from(env!("VERGEN_RUSTC_HOST_TRIPLE")),
rustc_version: String::from(env!("VERGEN_RUSTC_SEMVER")),
cargo_target_triple: String::from(env!("VERGEN_CARGO_TARGET_TRIPLE")),
}
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct NetInterfaceId {
machine_id: Uuid,
if_name: String,
ip_addr: Vec<IpAddr>,
}
impl fmt::Display for NetInterfaceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let parts: Vec<String> = vec![
self.machine_id.to_string(),
self.if_name.clone(),
self.ip_addr
.iter()
.map(|ip| ip.to_string())
.collect::<Vec<String>>()
.join(", "),
];
write!(f, "{}", parts.join("_"))
}
}
impl NetInterfaceId {
pub fn fingerprint(&self, ip_addr: &IpAddr) -> [u8; 32] {
let mut hash = Hash::new();
hash.update(self.machine_id);
hash.update(self.if_name.clone());
hash.update(ip_addr.to_string());
hash.finalize()
}
pub fn fingerprint_set(&self) -> HashSet<[u8; 32]> {
let mut set = HashSet::new();
for addr in self.ip_addr.iter() {
set.insert(self.fingerprint(addr));
}
set
}
}
pub fn identify_interface(
known_interfaces: &[NetInterfaceId],
remote_socket: &SocketAddr,
fingerprint: [u8; 32],
) -> Result<NetInterfaceId, NetzworkApiError> {
for iface in known_interfaces.iter() {
if iface.ip_addr.contains(&remote_socket.ip())
&& iface.fingerprint(&remote_socket.ip()) == fingerprint
{
return Ok(iface.to_owned());
};
}
Err(NetzworkApiError::UnknownNetInterfaceId(format!(
"{:?} with fingerprint {:?}",
remote_socket, fingerprint
)))
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct ConnectivityMeasurementSample {
timestamp: SystemTime,
rtt: Duration,
fingerprint: [u8; 32],
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct NetInterfaceConnectivityMeasurement {
interface: NetInterfaceId,
remote_socket: SocketAddr,
samples: Vec<ConnectivityMeasurementSample>,
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
pub struct NetInterfaceInfo {
pub interfaces: Vec<NetInterfaceId>,
pub measurements: Vec<NetInterfaceConnectivityMeasurement>,
}
impl NetInterfaceInfo {
pub fn new() -> NetInterfaceInfo {
NetInterfaceInfo {
interfaces: vec![],
measurements: vec![],
}
}
pub fn from_network_interfaces() -> Result<NetInterfaceInfo, local_ip_address::Error> {
let network_interfaces = list_afinet_netifas()?;
let mut ni_info = NetInterfaceInfo::new();
for (if_name, ip_addr) in network_interfaces.iter() {
let mut found_if = false;
for ifs in ni_info.interfaces.iter_mut() {
if ifs.if_name.eq(if_name) {
found_if = true;
if !ifs.ip_addr.contains(ip_addr) {
ifs.ip_addr.push(*ip_addr);
}
};
}
if !found_if {
ni_info.interfaces.push(NetInterfaceId {
machine_id: get_secure_machine_id(None).unwrap(),
if_name: if_name.clone(),
ip_addr: vec![*ip_addr],
})
}
}
Ok(ni_info)
}
}
impl Default for NetInterfaceInfo {
fn default() -> Self {
Self::new()
}
}