Skip to main content

starla_common/
types.rs

1//! Core types for Starla measurements
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::net::IpAddr;
6
7/// Probe identifier
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct ProbeId(pub u32);
10
11impl fmt::Display for ProbeId {
12    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13        write!(f, "{}", self.0)
14    }
15}
16
17/// Measurement identifier
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub struct MeasurementId(pub u64);
20
21impl fmt::Display for MeasurementId {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        write!(f, "{}", self.0)
24    }
25}
26
27/// Unix timestamp
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
29pub struct Timestamp(pub i64);
30
31impl Timestamp {
32    pub fn now() -> Self {
33        Self(chrono::Utc::now().timestamp())
34    }
35}
36
37impl fmt::Display for Timestamp {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(f, "{}", self.0)
40    }
41}
42
43/// Measurement type
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(rename_all = "lowercase")]
46pub enum MeasurementType {
47    Ping,
48    Traceroute,
49    Dns,
50    Http,
51    Tls,
52    Ntp,
53}
54
55impl fmt::Display for MeasurementType {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            MeasurementType::Ping => write!(f, "ping"),
59            MeasurementType::Traceroute => write!(f, "traceroute"),
60            MeasurementType::Dns => write!(f, "dns"),
61            MeasurementType::Http => write!(f, "http"),
62            MeasurementType::Tls => write!(f, "tls"),
63            MeasurementType::Ntp => write!(f, "ntp"),
64        }
65    }
66}
67
68/// Measurement result envelope
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct MeasurementResult {
71    /// Firmware version
72    pub fw: u32,
73
74    /// Measurement type
75    #[serde(rename = "type")]
76    pub measurement_type: MeasurementType,
77
78    /// Probe ID
79    pub prb_id: ProbeId,
80
81    /// Measurement ID
82    pub msm_id: MeasurementId,
83
84    /// Timestamp
85    pub timestamp: Timestamp,
86
87    /// Address family (4 or 6)
88    pub af: u8,
89
90    /// Destination address
91    pub dst_addr: IpAddr,
92
93    /// Destination name (hostname, defaults to dst_addr if not specified)
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub dst_name: Option<String>,
96
97    /// Source address (optional)
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub src_addr: Option<IpAddr>,
100
101    /// Protocol (ICMP, UDP, TCP)
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub proto: Option<String>,
104
105    /// TTL (for ping/traceroute)
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub ttl: Option<u8>,
108
109    /// Packet size (for ping/traceroute)
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub size: Option<u16>,
112
113    /// Protocol-specific result data
114    pub data: MeasurementData,
115}
116
117/// Protocol-specific measurement data
118#[derive(Debug, Clone, Serialize, Deserialize)]
119#[serde(tag = "type", content = "data")]
120pub enum MeasurementData {
121    /// Structured data (serialized via serde_json::Value)
122    Generic(serde_json::Value),
123    /// Pre-formatted "result" field value matching exact C probe output.
124    /// Used when precise formatting (e.g., float decimal places) matters.
125    PreFormatted(String),
126    /// Complete pre-formatted RESULT line body (everything inside `RESULT { ...
127    /// }`). Used for measurement types (TLS, NTP) that have non-standard
128    /// envelope layouts.
129    FullLine(String),
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_probe_id_serde() {
138        let id = ProbeId(12345);
139        let json = serde_json::to_string(&id).unwrap();
140        assert_eq!(json, "12345");
141        let parsed: ProbeId = serde_json::from_str(&json).unwrap();
142        assert_eq!(id, parsed);
143    }
144
145    #[test]
146    fn test_measurement_type_serde() {
147        let mt = MeasurementType::Ping;
148        let json = serde_json::to_string(&mt).unwrap();
149        assert_eq!(json, "\"ping\"");
150    }
151}