use serde::{Deserialize, Serialize};
use std::net::IpAddr;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum ValidationState {
Speculative,
Confirmed,
Rejected,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct OriginCandidate {
pub ip: IpAddr,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
pub method: String,
pub confidence: u8,
pub validated: ValidationState,
}
impl OriginCandidate {
pub fn new(ip: IpAddr, method: impl Into<String>, confidence: u8) -> Self {
Self {
ip,
port: None,
method: method.into(),
confidence,
validated: ValidationState::Speculative,
}
}
pub fn new_with_port(ip: IpAddr, port: u16, method: impl Into<String>, confidence: u8) -> Self {
Self {
ip,
port: Some(port),
method: method.into(),
confidence,
validated: ValidationState::Speculative,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use proptest::prelude::*;
#[test]
fn origin_candidate_orders_by_fields() {
let low = OriginCandidate {
ip: "192.0.2.1".parse().unwrap(),
port: None,
method: "dns".into(),
confidence: 20,
validated: ValidationState::Speculative,
};
let high = OriginCandidate {
ip: "192.0.2.2".parse().unwrap(),
port: None,
method: "ssl".into(),
confidence: 90,
validated: ValidationState::Confirmed,
};
assert!(high > low);
}
#[test]
fn origin_candidate_serializes_cleanly() {
let candidate = OriginCandidate::new("192.0.2.10".parse().unwrap(), "http_header", 75);
let value = serde_json::to_value(candidate).unwrap();
assert_eq!(value["ip"], json!("192.0.2.10"));
assert_eq!(value["method"], json!("http_header"));
assert_eq!(value["confidence"], json!(75));
assert_eq!(value["validated"], json!("Speculative"));
}
proptest! {
#[test]
fn origin_candidate_new_never_panics(
ip_octets in any::<[u8; 4]>(),
method in ".*",
confidence in any::<u8>(),
) {
let ip = IpAddr::V4(std::net::Ipv4Addr::new(ip_octets[0], ip_octets[1], ip_octets[2], ip_octets[3]));
let cand = OriginCandidate::new(ip, &method, confidence);
prop_assert_eq!(cand.ip, ip);
prop_assert_eq!(cand.method, method);
prop_assert_eq!(cand.confidence, confidence);
prop_assert_eq!(cand.validated, ValidationState::Speculative);
prop_assert!(cand.port.is_none());
}
#[test]
fn origin_candidate_new_with_port_never_panics(
ip_octets in any::<[u8; 4]>(),
port in any::<u16>(),
method in ".*",
confidence in any::<u8>(),
) {
let ip = IpAddr::V4(std::net::Ipv4Addr::new(ip_octets[0], ip_octets[1], ip_octets[2], ip_octets[3]));
let cand = OriginCandidate::new_with_port(ip, port, &method, confidence);
prop_assert_eq!(cand.ip, ip);
prop_assert_eq!(cand.port, Some(port));
prop_assert_eq!(cand.method, method);
prop_assert_eq!(cand.confidence, confidence);
}
}
}