use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessState {
Running,
Sleeping,
DiskSleep,
Stopped,
Traced,
Zombie,
Dead,
Unknown(i64),
}
impl ProcessState {
pub fn from_raw(value: i64) -> Self {
match value {
0 => Self::Running,
1 => Self::Sleeping,
2 => Self::DiskSleep,
4 => Self::Stopped,
8 => Self::Traced,
16 => Self::Dead,
32 => Self::Zombie,
_ => Self::Unknown(value),
}
}
}
impl fmt::Display for ProcessState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Running => write!(f, "R (running)"),
Self::Sleeping => write!(f, "S (sleeping)"),
Self::DiskSleep => write!(f, "D (disk sleep)"),
Self::Stopped => write!(f, "T (stopped)"),
Self::Traced => write!(f, "t (traced)"),
Self::Zombie => write!(f, "Z (zombie)"),
Self::Dead => write!(f, "X (dead)"),
Self::Unknown(v) => write!(f, "? ({v})"),
}
}
}
#[derive(Debug, Clone)]
pub struct ProcessInfo {
pub pid: u64,
pub ppid: u64,
pub comm: String,
pub state: ProcessState,
pub vaddr: u64,
pub cr3: Option<u64>,
pub start_time: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Protocol {
Tcp,
Udp,
Tcp6,
Udp6,
Unix,
Raw,
}
impl fmt::Display for Protocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tcp => write!(f, "TCP"),
Self::Udp => write!(f, "UDP"),
Self::Tcp6 => write!(f, "TCP6"),
Self::Udp6 => write!(f, "UDP6"),
Self::Unix => write!(f, "UNIX"),
Self::Raw => write!(f, "RAW"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionState {
Established,
SynSent,
SynRecv,
FinWait1,
FinWait2,
TimeWait,
Close,
CloseWait,
LastAck,
Listen,
Closing,
Unknown(u8),
}
impl ConnectionState {
pub fn from_raw(value: u8) -> Self {
match value {
1 => Self::Established,
2 => Self::SynSent,
3 => Self::SynRecv,
4 => Self::FinWait1,
5 => Self::FinWait2,
6 => Self::TimeWait,
7 => Self::Close,
8 => Self::CloseWait,
9 => Self::LastAck,
10 => Self::Listen,
11 => Self::Closing,
_ => Self::Unknown(value),
}
}
}
impl fmt::Display for ConnectionState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Established => write!(f, "ESTABLISHED"),
Self::SynSent => write!(f, "SYN_SENT"),
Self::SynRecv => write!(f, "SYN_RECV"),
Self::FinWait1 => write!(f, "FIN_WAIT1"),
Self::FinWait2 => write!(f, "FIN_WAIT2"),
Self::TimeWait => write!(f, "TIME_WAIT"),
Self::Close => write!(f, "CLOSE"),
Self::CloseWait => write!(f, "CLOSE_WAIT"),
Self::LastAck => write!(f, "LAST_ACK"),
Self::Listen => write!(f, "LISTEN"),
Self::Closing => write!(f, "CLOSING"),
Self::Unknown(v) => write!(f, "UNKNOWN({v})"),
}
}
}
#[derive(Debug, Clone)]
pub struct ConnectionInfo {
pub protocol: Protocol,
pub local_addr: String,
pub local_port: u16,
pub remote_addr: String,
pub remote_port: u16,
pub state: ConnectionState,
pub pid: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleState {
Live,
Coming,
Going,
Unformed,
Unknown(u32),
}
impl ModuleState {
pub fn from_raw(value: u32) -> Self {
match value {
0 => Self::Live,
1 => Self::Coming,
2 => Self::Going,
3 => Self::Unformed,
_ => Self::Unknown(value),
}
}
}
impl fmt::Display for ModuleState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Live => write!(f, "Live"),
Self::Coming => write!(f, "Coming"),
Self::Going => write!(f, "Going"),
Self::Unformed => write!(f, "Unformed"),
Self::Unknown(v) => write!(f, "Unknown({v})"),
}
}
}
#[derive(Debug, Clone)]
pub struct ModuleInfo {
pub name: String,
pub base_addr: u64,
pub size: u64,
pub state: ModuleState,
}
#[derive(Debug, Clone)]
pub struct PsTreeEntry {
pub process: ProcessInfo,
pub depth: u32,
}
#[derive(Debug, Clone)]
pub struct ThreadInfo {
pub tgid: u64,
pub tid: u64,
pub comm: String,
pub state: ProcessState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(clippy::struct_excessive_bools)]
pub struct VmaFlags {
pub read: bool,
pub write: bool,
pub exec: bool,
pub shared: bool,
}
impl VmaFlags {
pub fn from_raw(flags: u64) -> Self {
Self {
read: flags & 0x1 != 0,
write: flags & 0x2 != 0,
exec: flags & 0x4 != 0,
shared: flags & 0x8 != 0,
}
}
}
impl fmt::Display for VmaFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}",
if self.read { 'r' } else { '-' },
if self.write { 'w' } else { '-' },
if self.exec { 'x' } else { '-' },
if self.shared { 's' } else { 'p' },
)
}
}
#[derive(Debug, Clone)]
pub struct VmaInfo {
pub pid: u64,
pub comm: String,
pub start: u64,
pub end: u64,
pub flags: VmaFlags,
pub pgoff: u64,
pub file_backed: bool,
}
#[derive(Debug, Clone)]
pub struct FileDescriptorInfo {
pub pid: u64,
pub comm: String,
pub fd: u32,
pub path: String,
pub inode: Option<u64>,
pub pos: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnvVarInfo {
pub pid: u64,
pub comm: String,
pub key: String,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CmdlineInfo {
pub pid: u64,
pub comm: String,
pub cmdline: String,
}
#[derive(Debug, Clone)]
pub struct MalfindInfo {
pub pid: u64,
pub comm: String,
pub start: u64,
pub end: u64,
pub flags: VmaFlags,
pub reason: String,
pub header_bytes: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct MountInfo {
pub dev_name: String,
pub mount_point: String,
pub fs_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BashHistoryInfo {
pub pid: u64,
pub comm: String,
pub command: String,
pub timestamp: Option<i64>,
pub index: u64,
}
#[derive(Debug, Clone)]
pub struct PsxViewInfo {
pub pid: u64,
pub comm: String,
pub in_task_list: bool,
pub in_pid_hash: bool,
}
#[derive(Debug, Clone)]
pub struct TtyCheckInfo {
pub name: String,
pub operation: String,
pub handler: u64,
pub hooked: bool,
}
#[derive(Debug, Clone)]
pub struct KernelHookInfo {
pub symbol: String,
pub address: u64,
pub hook_type: String,
pub target: Option<u64>,
pub suspicious: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElfType {
None,
Relocatable,
Executable,
SharedObject,
Core,
Unknown(u16),
}
impl ElfType {
pub fn from_raw(value: u16) -> Self {
match value {
0 => Self::None,
1 => Self::Relocatable,
2 => Self::Executable,
3 => Self::SharedObject,
4 => Self::Core,
_ => Self::Unknown(value),
}
}
}
impl fmt::Display for ElfType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "NONE"),
Self::Relocatable => write!(f, "REL"),
Self::Executable => write!(f, "EXEC"),
Self::SharedObject => write!(f, "DYN"),
Self::Core => write!(f, "CORE"),
Self::Unknown(v) => write!(f, "UNKNOWN({v})"),
}
}
}
#[derive(Debug, Clone)]
pub struct ElfInfo {
pub pid: u64,
pub comm: String,
pub vma_start: u64,
pub elf_type: ElfType,
pub machine: u16,
pub entry_point: u64,
}
#[derive(Debug, Clone)]
pub struct HiddenModuleInfo {
pub name: String,
pub base_addr: u64,
pub size: u64,
pub in_modules_list: bool,
pub in_sysfs: bool,
}
#[derive(Debug, Clone)]
pub struct SyscallInfo {
pub number: u64,
pub handler: u64,
pub hooked: bool,
pub expected_name: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BootTimeSource {
Timekeeper,
UserProvided,
}
impl std::fmt::Display for BootTimeSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Timekeeper => write!(f, "timekeeper"),
Self::UserProvided => write!(f, "user-provided"),
}
}
}
#[derive(Debug, Clone)]
pub struct BootTimeEstimate {
pub source: BootTimeSource,
pub boot_epoch_secs: i64,
}
#[derive(Debug, Clone)]
pub struct BootTimeInfo {
pub best_estimate: Option<i64>,
pub estimates: Vec<BootTimeEstimate>,
pub inconsistent: bool,
pub max_drift_secs: i64,
}
const BOOT_TIME_DRIFT_THRESHOLD: i64 = 60;
impl BootTimeInfo {
pub fn from_estimates(estimates: Vec<BootTimeEstimate>) -> Self {
let best_estimate = estimates.first().map(|e| e.boot_epoch_secs);
let mut max_drift: i64 = 0;
for i in 0..estimates.len() {
for j in (i + 1)..estimates.len() {
let drift = (estimates[i].boot_epoch_secs - estimates[j].boot_epoch_secs).abs();
if drift > max_drift {
max_drift = drift;
}
}
}
Self {
best_estimate,
estimates,
inconsistent: max_drift > BOOT_TIME_DRIFT_THRESHOLD,
max_drift_secs: max_drift,
}
}
pub fn absolute_secs(&self, boot_ns: u64) -> Option<i64> {
self.best_estimate.map(|epoch| {
let boot_secs = i64::try_from(boot_ns / 1_000_000_000).unwrap_or(i64::MAX);
epoch + boot_secs
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NeighState {
Incomplete,
Reachable,
Stale,
Delay,
Probe,
Failed,
Permanent,
Unknown(u8),
}
impl NeighState {
pub fn from_raw(value: u8) -> Self {
match value {
0x01 => Self::Incomplete,
0x02 => Self::Reachable,
0x04 => Self::Stale,
0x08 => Self::Delay,
0x10 => Self::Probe,
0x20 => Self::Failed,
0x80 => Self::Permanent,
_ => Self::Unknown(value),
}
}
}
impl std::fmt::Display for NeighState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Incomplete => write!(f, "INCOMPLETE"),
Self::Reachable => write!(f, "REACHABLE"),
Self::Stale => write!(f, "STALE"),
Self::Delay => write!(f, "DELAY"),
Self::Probe => write!(f, "PROBE"),
Self::Failed => write!(f, "FAILED"),
Self::Permanent => write!(f, "PERMANENT"),
Self::Unknown(v) => write!(f, "UNKNOWN(0x{v:02x})"),
}
}
}
#[derive(Debug, Clone)]
pub struct ArpEntryInfo {
pub ip_addr: String,
pub mac_addr: String,
pub dev_name: String,
pub state: NeighState,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct NetfilterRuleInfo {
pub table: String,
pub chain: String,
pub target: String,
pub protocol: String,
pub source: Option<String>,
pub destination: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CrontabEntry {
pub pid: u64,
pub comm: String,
pub line: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub enum SshKeyType {
Rsa,
Ed25519,
Dsa,
Ecdsa256,
Ecdsa384,
Ecdsa521,
Unknown,
}
impl SshKeyType {
pub fn from_prefix(prefix: &str) -> Self {
match prefix {
"ssh-rsa" => Self::Rsa,
"ssh-ed25519" => Self::Ed25519,
"ssh-dss" => Self::Dsa,
"ecdsa-sha2-nistp256" => Self::Ecdsa256,
"ecdsa-sha2-nistp384" => Self::Ecdsa384,
"ecdsa-sha2-nistp521" => Self::Ecdsa521,
_ => Self::Unknown,
}
}
}
impl fmt::Display for SshKeyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Rsa => write!(f, "ssh-rsa"),
Self::Ed25519 => write!(f, "ssh-ed25519"),
Self::Dsa => write!(f, "ssh-dss"),
Self::Ecdsa256 => write!(f, "ecdsa-sha2-nistp256"),
Self::Ecdsa384 => write!(f, "ecdsa-sha2-nistp384"),
Self::Ecdsa521 => write!(f, "ecdsa-sha2-nistp521"),
Self::Unknown => write!(f, "unknown"),
}
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct SshKeyInfo {
pub pid: u64,
pub key_type: SshKeyType,
pub key_data: String,
pub comment: String,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct HiddenProcessInfo {
pub pid: u64,
pub comm: String,
pub present_in_pid_ns: bool,
pub present_in_task_list: bool,
pub present_in_pid_hash: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct VdsoTamperInfo {
pub pid: u64,
pub comm: String,
pub vdso_base: u64,
pub vdso_size: u64,
pub differs_from_canonical: bool,
pub diff_byte_count: usize,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct UserNsEscalationInfo {
pub pid: u64,
pub comm: String,
pub ns_depth: u32,
pub owner_uid: u32,
pub process_uid: u32,
pub has_cap_sys_admin: bool,
pub is_suspicious: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct AuditTamperInfo {
pub audit_enabled: bool,
pub backlog_limit: u32,
pub suppressed_pids: Vec<u64>,
pub suppressed_uids: Vec<u32>,
pub audit_globally_disabled: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct CpuPinningInfo {
pub pid: u64,
pub comm: String,
pub pinned_cpu_count: u32,
pub total_cpu_count: u32,
pub sched_policy: u32,
pub cpu_time_ns: u64,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, serde::Serialize)]
pub struct ContainerEscapeCorrelateInfo {
pub pid: u64,
pub comm: String,
pub pid_ns_differs_from_cgroup_ns: bool,
pub has_host_mounts: bool,
pub cap_sys_admin: bool,
pub cap_sys_ptrace: bool,
pub in_non_init_pid_ns: bool,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub enum FdAbuseType {
TimerFd,
SignalFd,
EventFd,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct FdAbuseInfo {
pub pid: u64,
pub comm: String,
pub fd_type: FdAbuseType,
pub signal_mask: u64,
pub interval_ns: u64,
pub is_cross_process_shared: bool,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, serde::Serialize)]
pub struct SharedMemAnomalyInfo {
pub pid: u64,
pub comm: String,
pub shm_base: u64,
pub shm_size: u64,
pub is_memfd: bool,
pub is_executable: bool,
pub is_cross_uid: bool,
pub has_elf_header: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct FuseAbuseInfo {
pub pid: u64,
pub comm: String,
pub mount_point: String,
pub is_over_sensitive_path: bool,
pub daemon_is_root: bool,
pub allow_other: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn neigh_state_from_raw() {
assert_eq!(NeighState::from_raw(0x01), NeighState::Incomplete);
assert_eq!(NeighState::from_raw(0x02), NeighState::Reachable);
assert_eq!(NeighState::from_raw(0x04), NeighState::Stale);
assert_eq!(NeighState::from_raw(0x08), NeighState::Delay);
assert_eq!(NeighState::from_raw(0x10), NeighState::Probe);
assert_eq!(NeighState::from_raw(0x20), NeighState::Failed);
assert_eq!(NeighState::from_raw(0x80), NeighState::Permanent);
assert!(matches!(
NeighState::from_raw(0xFF),
NeighState::Unknown(0xFF)
));
}
#[test]
fn neigh_state_display() {
assert_eq!(NeighState::Reachable.to_string(), "REACHABLE");
assert_eq!(NeighState::Stale.to_string(), "STALE");
assert_eq!(NeighState::Permanent.to_string(), "PERMANENT");
assert_eq!(NeighState::Unknown(0x42).to_string(), "UNKNOWN(0x42)");
}
#[test]
fn vma_flags_from_raw() {
let f = VmaFlags::from_raw(0x5); assert!(f.read);
assert!(!f.write);
assert!(f.exec);
assert!(!f.shared);
}
#[test]
fn vma_flags_display() {
assert_eq!(VmaFlags::from_raw(0x7).to_string(), "rwxp"); assert_eq!(VmaFlags::from_raw(0x1).to_string(), "r--p");
assert_eq!(VmaFlags::from_raw(0xF).to_string(), "rwxs"); assert_eq!(VmaFlags::from_raw(0x0).to_string(), "---p");
}
#[test]
fn process_state_from_raw() {
assert_eq!(ProcessState::from_raw(0), ProcessState::Running);
assert_eq!(ProcessState::from_raw(1), ProcessState::Sleeping);
assert_eq!(ProcessState::from_raw(2), ProcessState::DiskSleep);
assert_eq!(ProcessState::from_raw(4), ProcessState::Stopped);
assert_eq!(ProcessState::from_raw(8), ProcessState::Traced);
assert_eq!(ProcessState::from_raw(16), ProcessState::Dead);
assert_eq!(ProcessState::from_raw(32), ProcessState::Zombie);
assert!(matches!(
ProcessState::from_raw(99),
ProcessState::Unknown(99)
));
}
#[test]
fn process_state_display() {
assert_eq!(ProcessState::Running.to_string(), "R (running)");
assert_eq!(ProcessState::Sleeping.to_string(), "S (sleeping)");
assert_eq!(ProcessState::DiskSleep.to_string(), "D (disk sleep)");
assert_eq!(ProcessState::Stopped.to_string(), "T (stopped)");
assert_eq!(ProcessState::Traced.to_string(), "t (traced)");
assert_eq!(ProcessState::Dead.to_string(), "X (dead)");
assert_eq!(ProcessState::Zombie.to_string(), "Z (zombie)");
assert_eq!(ProcessState::Unknown(42).to_string(), "? (42)");
}
#[test]
fn connection_state_from_raw() {
assert_eq!(ConnectionState::from_raw(1), ConnectionState::Established);
assert_eq!(ConnectionState::from_raw(2), ConnectionState::SynSent);
assert_eq!(ConnectionState::from_raw(3), ConnectionState::SynRecv);
assert_eq!(ConnectionState::from_raw(4), ConnectionState::FinWait1);
assert_eq!(ConnectionState::from_raw(5), ConnectionState::FinWait2);
assert_eq!(ConnectionState::from_raw(6), ConnectionState::TimeWait);
assert_eq!(ConnectionState::from_raw(7), ConnectionState::Close);
assert_eq!(ConnectionState::from_raw(8), ConnectionState::CloseWait);
assert_eq!(ConnectionState::from_raw(9), ConnectionState::LastAck);
assert_eq!(ConnectionState::from_raw(10), ConnectionState::Listen);
assert_eq!(ConnectionState::from_raw(11), ConnectionState::Closing);
assert!(matches!(
ConnectionState::from_raw(99),
ConnectionState::Unknown(99)
));
}
#[test]
fn connection_state_display() {
assert_eq!(ConnectionState::Established.to_string(), "ESTABLISHED");
assert_eq!(ConnectionState::SynSent.to_string(), "SYN_SENT");
assert_eq!(ConnectionState::SynRecv.to_string(), "SYN_RECV");
assert_eq!(ConnectionState::FinWait1.to_string(), "FIN_WAIT1");
assert_eq!(ConnectionState::FinWait2.to_string(), "FIN_WAIT2");
assert_eq!(ConnectionState::TimeWait.to_string(), "TIME_WAIT");
assert_eq!(ConnectionState::Close.to_string(), "CLOSE");
assert_eq!(ConnectionState::CloseWait.to_string(), "CLOSE_WAIT");
assert_eq!(ConnectionState::LastAck.to_string(), "LAST_ACK");
assert_eq!(ConnectionState::Listen.to_string(), "LISTEN");
assert_eq!(ConnectionState::Closing.to_string(), "CLOSING");
assert_eq!(ConnectionState::Unknown(42).to_string(), "UNKNOWN(42)");
}
#[test]
fn module_state_from_raw() {
assert_eq!(ModuleState::from_raw(0), ModuleState::Live);
assert_eq!(ModuleState::from_raw(1), ModuleState::Coming);
assert_eq!(ModuleState::from_raw(2), ModuleState::Going);
assert_eq!(ModuleState::from_raw(3), ModuleState::Unformed);
assert!(matches!(
ModuleState::from_raw(99),
ModuleState::Unknown(99)
));
}
#[test]
fn module_state_display() {
assert_eq!(ModuleState::Live.to_string(), "Live");
assert_eq!(ModuleState::Coming.to_string(), "Coming");
assert_eq!(ModuleState::Going.to_string(), "Going");
assert_eq!(ModuleState::Unformed.to_string(), "Unformed");
assert_eq!(ModuleState::Unknown(42).to_string(), "Unknown(42)");
}
#[test]
fn protocol_display() {
assert_eq!(Protocol::Tcp.to_string(), "TCP");
assert_eq!(Protocol::Udp.to_string(), "UDP");
assert_eq!(Protocol::Tcp6.to_string(), "TCP6");
assert_eq!(Protocol::Udp6.to_string(), "UDP6");
assert_eq!(Protocol::Unix.to_string(), "UNIX");
assert_eq!(Protocol::Raw.to_string(), "RAW");
}
#[test]
fn elf_type_from_raw() {
assert_eq!(ElfType::from_raw(0), ElfType::None);
assert_eq!(ElfType::from_raw(1), ElfType::Relocatable);
assert_eq!(ElfType::from_raw(2), ElfType::Executable);
assert_eq!(ElfType::from_raw(3), ElfType::SharedObject);
assert_eq!(ElfType::from_raw(4), ElfType::Core);
assert!(matches!(ElfType::from_raw(99), ElfType::Unknown(99)));
}
#[test]
fn elf_type_display() {
assert_eq!(ElfType::None.to_string(), "NONE");
assert_eq!(ElfType::Relocatable.to_string(), "REL");
assert_eq!(ElfType::Executable.to_string(), "EXEC");
assert_eq!(ElfType::SharedObject.to_string(), "DYN");
assert_eq!(ElfType::Core.to_string(), "CORE");
assert_eq!(ElfType::Unknown(42).to_string(), "UNKNOWN(42)");
}
#[test]
fn boot_time_source_display() {
assert_eq!(BootTimeSource::Timekeeper.to_string(), "timekeeper");
assert_eq!(BootTimeSource::UserProvided.to_string(), "user-provided");
}
#[test]
fn from_estimates_empty_has_no_best() {
let info = BootTimeInfo::from_estimates(vec![]);
assert_eq!(info.best_estimate, None);
assert!(!info.inconsistent);
assert_eq!(info.max_drift_secs, 0);
}
#[test]
fn from_estimates_single_source() {
let info = BootTimeInfo::from_estimates(vec![BootTimeEstimate {
source: BootTimeSource::Timekeeper,
boot_epoch_secs: 1_712_000_000,
}]);
assert_eq!(info.best_estimate, Some(1_712_000_000));
assert!(!info.inconsistent);
assert_eq!(info.max_drift_secs, 0);
}
#[test]
fn from_estimates_consistent_sources() {
let info = BootTimeInfo::from_estimates(vec![
BootTimeEstimate {
source: BootTimeSource::Timekeeper,
boot_epoch_secs: 1_712_000_000,
},
BootTimeEstimate {
source: BootTimeSource::UserProvided,
boot_epoch_secs: 1_712_000_030, },
]);
assert_eq!(info.best_estimate, Some(1_712_000_000));
assert!(!info.inconsistent);
assert_eq!(info.max_drift_secs, 30);
}
#[test]
fn from_estimates_inconsistent_sources() {
let info = BootTimeInfo::from_estimates(vec![
BootTimeEstimate {
source: BootTimeSource::Timekeeper,
boot_epoch_secs: 1_712_000_000,
},
BootTimeEstimate {
source: BootTimeSource::UserProvided,
boot_epoch_secs: 1_712_000_120, },
]);
assert_eq!(info.best_estimate, Some(1_712_000_000));
assert!(info.inconsistent);
assert_eq!(info.max_drift_secs, 120);
}
#[test]
fn absolute_secs_with_boot_epoch() {
let info = BootTimeInfo::from_estimates(vec![BootTimeEstimate {
source: BootTimeSource::UserProvided,
boot_epoch_secs: 1_712_000_000,
}]);
assert_eq!(info.absolute_secs(500_000_000), Some(1_712_000_000));
assert_eq!(info.absolute_secs(3_600_000_000_000), Some(1_712_003_600));
}
#[test]
fn absolute_secs_without_boot_epoch() {
let info = BootTimeInfo::from_estimates(vec![]);
assert_eq!(info.absolute_secs(500_000_000), None);
}
#[test]
fn ssh_key_type_from_prefix() {
assert_eq!(SshKeyType::from_prefix("ssh-rsa"), SshKeyType::Rsa);
assert_eq!(SshKeyType::from_prefix("ssh-ed25519"), SshKeyType::Ed25519);
assert_eq!(SshKeyType::from_prefix("ssh-dss"), SshKeyType::Dsa);
assert_eq!(
SshKeyType::from_prefix("ecdsa-sha2-nistp256"),
SshKeyType::Ecdsa256
);
assert_eq!(
SshKeyType::from_prefix("ecdsa-sha2-nistp384"),
SshKeyType::Ecdsa384
);
assert_eq!(
SshKeyType::from_prefix("ecdsa-sha2-nistp521"),
SshKeyType::Ecdsa521
);
assert_eq!(SshKeyType::from_prefix("bogus"), SshKeyType::Unknown);
assert_eq!(SshKeyType::from_prefix(""), SshKeyType::Unknown);
}
#[test]
fn ssh_key_type_display() {
assert_eq!(SshKeyType::Rsa.to_string(), "ssh-rsa");
assert_eq!(SshKeyType::Ed25519.to_string(), "ssh-ed25519");
assert_eq!(SshKeyType::Dsa.to_string(), "ssh-dss");
assert_eq!(SshKeyType::Ecdsa256.to_string(), "ecdsa-sha2-nistp256");
assert_eq!(SshKeyType::Ecdsa384.to_string(), "ecdsa-sha2-nistp384");
assert_eq!(SshKeyType::Ecdsa521.to_string(), "ecdsa-sha2-nistp521");
assert_eq!(SshKeyType::Unknown.to_string(), "unknown");
}
}