1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::str::FromStr;
use std::time::SystemTime;

use gethostname::gethostname;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct Heartbeat {
    /// Heartbeat metadata
    pub heartbeat_info: HeartbeatInfo,
    /// Current AgentStatus
    pub status: AgentStatus,
    /// Host information
    pub host_info: Option<HostInfo>,
    /// Build information
    pub build_info: Option<BuildInfo>,
}

impl Heartbeat {
    pub fn new(hbtype: HeartbeatType, agent_status: Option<AgentStatus>) -> Heartbeat {
        let mut status_or_undefined = AgentStatus::Undefined;
        if let Some(value) = agent_status {
            status_or_undefined = value;
        }
        match hbtype {
            HeartbeatType::Full => Heartbeat {
                heartbeat_info: HeartbeatInfo::new(hbtype),
                status: status_or_undefined,
                host_info: Some(HostInfo::new()),
                build_info: Some(BuildInfo::new()),
            },
            HeartbeatType::Compact => {
                // TODO: Needs logic to actually determine what to include or not.
                // See https://linear.app/netzwork/issue/NET-30/api-support-sending-compact-heartbeats-to-save-bandwidth
                todo!()
            }
        }
    }
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct HeartbeatInfo {
    /// Heartbeat type
    pub hbtype: HeartbeatType,
    /// UUID (v4) of this heartbeat
    pub heartbeat_uuid: Uuid,
    /// Current timestamp
    pub timestamp: SystemTime,
}
impl HeartbeatInfo {
    fn new(hbtype: HeartbeatType) -> HeartbeatInfo {
        HeartbeatInfo {
            hbtype,
            heartbeat_uuid: Uuid::new_v4(),
            timestamp: SystemTime::now(),
        }
    }
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum HeartbeatType {
    /// A full heartbeat contains information in all data structures
    Full,
    /// A compact heartbeat contains a minimal amount of information,
    /// indicating that information is unchanged compared to the previous heartbeat.
    Compact,
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct HostInfo {
    /// The hostname
    pub hostname: String,
    /// The (stable) Host UUID.
    pub host_uuid: Uuid,
    /// The host's current uptime
    pub uptime: f64,
}
impl HostInfo {
    fn new() -> HostInfo {
        HostInfo {
            hostname: gethostname().into_string().expect("Failed to get hostname"),
            host_uuid: app_machine_id::get(
                Uuid ::new_v3(&Uuid::NAMESPACE_DNS, b"netzwork.tbd")
            )
            .expect("Failed to generate app-machine-UUID"),
            uptime: uptime_lib::get()
                .expect("Failed to get uptime")
                .as_secs_f64(),
        }
    }
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum AgentStatus {
    Undefined,
    Initializing,
    AwaitingJoin,
    Running,
    Orphaned,
    Exiting,
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct BuildInfo {
    // TODO: Include API crate version here
    // See: https://linear.app/netzwork/issue/NET-31/api-include-api-crate-version-in-the-buildinfo-struct
    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")),
        }
    }
}