use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Contact {
pub org: String,
pub contact: String,
pub domain: String,
}
impl Contact {
pub fn new(
org: impl Into<String>,
contact: impl Into<String>,
domain: impl Into<String>,
) -> Self {
Self {
org: org.into(),
contact: contact.into(),
domain: domain.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Evidence {
pub content_type: String,
pub payload: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hash: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Category {
Messaging,
Connection,
Content,
Copyright,
Vulnerability,
Infrastructure,
Reputation,
Other(String),
}
impl Category {
pub fn as_str(&self) -> &str {
match self {
Self::Messaging => "messaging",
Self::Connection => "connection",
Self::Content => "content",
Self::Copyright => "copyright",
Self::Vulnerability => "vulnerability",
Self::Infrastructure => "infrastructure",
Self::Reputation => "reputation",
Self::Other(s) => s.as_str(),
}
}
pub fn from_str_value(s: &str) -> Self {
match s {
"messaging" => Self::Messaging,
"connection" => Self::Connection,
"content" => Self::Content,
"copyright" => Self::Copyright,
"vulnerability" => Self::Vulnerability,
"infrastructure" => Self::Infrastructure,
"reputation" => Self::Reputation,
other => Self::Other(other.to_string()),
}
}
pub fn is_known(&self) -> bool {
!matches!(self, Self::Other(_))
}
}
impl Serialize for Category {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Category {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(Self::from_str_value(&s))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Report {
pub xarf_version: String,
pub report_id: String,
pub timestamp: String,
pub reporter: Contact,
pub sender: Contact,
pub source_identifier: String,
pub category: Category,
#[serde(rename = "type")]
pub type_: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_port: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub evidence_source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub evidence: Option<Vec<Evidence>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confidence: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub legacy_version: Option<String>,
#[serde(rename = "_internal", default, skip_serializing_if = "Option::is_none")]
pub internal: Option<Map<String, Value>>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
impl Report {
pub fn strip_internal(&mut self) -> Option<Map<String, Value>> {
self.internal.take()
}
pub fn extra(&self, key: &str) -> Option<&Value> {
self.extra.get(key)
}
pub fn set_extra(&mut self, key: impl Into<String>, value: Value) -> Option<Value> {
self.extra.insert(key.into(), value)
}
pub fn to_json_value(&self) -> Value {
serde_json::to_value(self)
.expect("XARF report serialization is infallible for valid Report values")
}
}