use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::collections::HashMap;
use crate::enums::*;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Document {
pub oatf: String,
#[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
pub schema: Option<String>,
pub attack: Attack,
#[serde(skip)]
pub oatf_is_first_key: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Attack {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<Status>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub modified: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub grace_period: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub severity: Option<Severity>,
#[serde(skip_serializing_if = "Option::is_none")]
pub impact: Option<Vec<Impact>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub classification: Option<Classification>,
#[serde(skip_serializing_if = "Option::is_none")]
pub references: Option<Vec<Reference>>,
pub execution: Execution,
#[serde(skip_serializing_if = "Option::is_none")]
pub indicators: Option<Vec<Indicator>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub correlation: Option<Correlation>,
#[serde(flatten)]
pub extensions: IndexMap<String, Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Correlation {
#[serde(skip_serializing_if = "Option::is_none")]
pub logic: Option<CorrelationLogic>,
}
#[derive(Clone, Debug)]
pub enum Severity {
Scalar(SeverityLevel),
Object {
level: SeverityLevel,
confidence: Option<i64>,
},
}
impl Serialize for Severity {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
match self {
Severity::Scalar(level) => level.serialize(serializer),
Severity::Object { level, confidence } => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("level", level)?;
if let Some(c) = confidence {
map.serialize_entry("confidence", c)?;
}
map.end()
}
}
}
}
impl<'de> Deserialize<'de> for Severity {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = Value::deserialize(deserializer)?;
match &value {
Value::String(s) => {
let level: SeverityLevel = serde_json::from_value(Value::String(s.clone()))
.map_err(serde::de::Error::custom)?;
Ok(Severity::Scalar(level))
}
Value::Object(map) => {
let level_val = map.get("level").ok_or_else(|| {
serde::de::Error::custom("severity object must have 'level' field")
})?;
let level: SeverityLevel =
serde_json::from_value(level_val.clone()).map_err(serde::de::Error::custom)?;
let confidence = match map.get("confidence") {
Some(v) if v.is_null() => None,
Some(v) => Some(v.as_i64().ok_or_else(|| {
serde::de::Error::custom(format!(
"severity.confidence must be an integer, got {}",
v
))
})?),
None => None,
};
Ok(Severity::Object { level, confidence })
}
_ => Err(serde::de::Error::custom(
"severity must be a string or object",
)),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Classification {
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<Category>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mappings: Option<Vec<FrameworkMapping>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Execution {
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phases: Option<Vec<Phase>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actors: Option<Vec<Actor>>,
#[serde(flatten)]
pub extensions: IndexMap<String, Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Actor {
pub name: String,
pub mode: String,
pub phases: Vec<Phase>,
#[serde(flatten)]
pub extensions: IndexMap<String, Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Phase {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extractors: Option<Vec<Extractor>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub on_enter: Option<Vec<Action>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trigger: Option<Trigger>,
#[serde(flatten)]
pub extensions: IndexMap<String, Value>,
}
#[derive(Clone, Debug)]
pub enum Action {
Send {
method: String,
params: Option<Value>,
extensions: IndexMap<String, Value>,
},
Log {
message: String,
level: Option<LogLevel>,
extensions: IndexMap<String, Value>,
},
BindingSpecific {
key: String,
value: Value,
extensions: IndexMap<String, Value>,
},
}
impl Serialize for Action {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
match self {
Action::Send {
method,
params,
extensions,
..
} => {
let mut outer = serializer.serialize_map(None)?;
let mut inner = serde_json::Map::new();
inner.insert("method".to_string(), Value::String(method.clone()));
if let Some(p) = params {
inner.insert("params".to_string(), p.clone());
}
outer.serialize_entry("send", &Value::Object(inner))?;
for (k, v) in extensions {
outer.serialize_entry(k, v)?;
}
outer.end()
}
Action::Log {
message,
level,
extensions,
..
} => {
let mut outer = serializer.serialize_map(None)?;
let mut inner = serde_json::Map::new();
inner.insert("message".to_string(), Value::String(message.clone()));
if let Some(l) = level {
inner.insert(
"level".to_string(),
serde_json::to_value(l).unwrap_or(Value::Null),
);
}
outer.serialize_entry("log", &Value::Object(inner))?;
for (k, v) in extensions {
outer.serialize_entry(k, v)?;
}
outer.end()
}
Action::BindingSpecific {
key,
value,
extensions,
..
} => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry(key, value)?;
for (k, v) in extensions {
map.serialize_entry(k, v)?;
}
map.end()
}
}
}
}
impl<'de> Deserialize<'de> for Action {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let map: serde_json::Map<String, Value> = serde_json::Map::deserialize(deserializer)?;
let mut extensions = IndexMap::new();
let mut action_key = None;
let mut action_value = None;
let mut non_ext_key_count = 0usize;
for (k, v) in &map {
if k.starts_with("x-") {
extensions.insert(k.clone(), v.clone());
} else {
non_ext_key_count += 1;
if action_key.is_none() {
action_key = Some(k.clone());
action_value = Some(v.clone());
}
}
}
if non_ext_key_count != 1 {
return Err(serde::de::Error::custom(format!(
"action must have exactly 1 non-extension key, found {}",
non_ext_key_count
)));
}
let key = action_key
.ok_or_else(|| serde::de::Error::custom("action object must have at least one key"))?;
let value = action_value
.ok_or_else(|| serde::de::Error::custom("action object must have a value"))?;
match key.as_str() {
"send" => {
let obj = value
.as_object()
.ok_or_else(|| serde::de::Error::custom("send must be an object"))?;
for field in obj.keys() {
if field != "method" && field != "params" {
return Err(serde::de::Error::custom(format!(
"send has unknown field '{}'",
field
)));
}
}
let method = obj
.get("method")
.and_then(|v| v.as_str())
.ok_or_else(|| serde::de::Error::custom("send requires 'method'"))?
.to_string();
let params = obj.get("params").cloned();
Ok(Action::Send {
method,
params,
extensions,
})
}
"log" => {
let obj = value
.as_object()
.ok_or_else(|| serde::de::Error::custom("log must be an object"))?;
for field in obj.keys() {
if field != "message" && field != "level" {
return Err(serde::de::Error::custom(format!(
"log has unknown field '{}'",
field
)));
}
}
let message = obj
.get("message")
.and_then(|v| v.as_str())
.ok_or_else(|| serde::de::Error::custom("log requires 'message'"))?
.to_string();
let level = obj
.get("level")
.map(|v| serde_json::from_value(v.clone()))
.transpose()
.map_err(serde::de::Error::custom)?;
Ok(Action::Log {
message,
level,
extensions,
})
}
_ => Ok(Action::BindingSpecific {
key,
value,
extensions,
}),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Trigger {
#[serde(skip_serializing_if = "Option::is_none")]
pub event: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<i64>,
#[serde(rename = "match", skip_serializing_if = "Option::is_none")]
pub match_predicate: Option<MatchPredicate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProtocolEvent {
pub event_type: String,
pub content: Value,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TriggerResult {
Advanced {
reason: AdvanceReason,
},
NotAdvanced,
}
#[derive(Clone, Debug, Default)]
pub struct TriggerState {
pub event_count: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Extractor {
pub name: String,
pub source: ExtractorSource,
#[serde(rename = "type")]
pub extractor_type: ExtractorType,
pub selector: String,
}
pub type MatchPredicate = HashMap<String, MatchEntry>;
#[derive(Clone, Debug)]
pub enum MatchEntry {
Scalar(Value),
Condition(MatchCondition),
}
impl Serialize for MatchEntry {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
MatchEntry::Scalar(v) => v.serialize(serializer),
MatchEntry::Condition(c) => c.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for MatchEntry {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = Value::deserialize(deserializer)?;
match &value {
Value::Object(map) => {
if map
.keys()
.any(|k| MATCH_OPERATOR_KEYS.contains(&k.as_str()))
{
let cond: MatchCondition =
serde_json::from_value(value).map_err(serde::de::Error::custom)?;
Ok(MatchEntry::Condition(cond))
} else {
Ok(MatchEntry::Scalar(value))
}
}
_ => Ok(MatchEntry::Scalar(value)),
}
}
}
pub static MATCH_OPERATOR_KEYS: &[&str] = &[
"contains",
"starts_with",
"ends_with",
"regex",
"any_of",
"gt",
"lt",
"gte",
"lte",
"exists",
];
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MatchCondition {
#[serde(skip_serializing_if = "Option::is_none")]
pub contains: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub starts_with: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ends_with: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub regex: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub any_of: Option<Vec<Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gt: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lt: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gte: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lte: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exists: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Indicator {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub surface: Option<String>,
pub target: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub actor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub direction: Option<Direction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<IndicatorMethod>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<PatternMatch>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expression: Option<ExpressionMatch>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic: Option<SemanticMatch>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub confidence: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub severity: Option<SeverityLevel>,
#[serde(skip_serializing_if = "Option::is_none")]
pub false_positives: Option<Vec<String>>,
#[serde(flatten)]
pub extensions: IndexMap<String, Value>,
}
#[derive(Clone, Debug)]
pub struct PatternMatch {
pub target: Option<String>,
pub condition: Option<Condition>,
pub contains: Option<String>,
pub starts_with: Option<String>,
pub ends_with: Option<String>,
pub regex: Option<String>,
pub any_of: Option<Vec<Value>>,
pub gt: Option<f64>,
pub lt: Option<f64>,
pub gte: Option<f64>,
pub lte: Option<f64>,
}
impl PatternMatch {
pub fn is_shorthand(&self) -> bool {
self.condition.is_none() && self.is_shorthand_fields_present()
}
pub fn is_shorthand_fields_present(&self) -> bool {
self.contains.is_some()
|| self.starts_with.is_some()
|| self.ends_with.is_some()
|| self.regex.is_some()
|| self.any_of.is_some()
|| self.gt.is_some()
|| self.lt.is_some()
|| self.gte.is_some()
|| self.lte.is_some()
}
}
impl Serialize for PatternMatch {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(None)?;
if let Some(ref t) = self.target {
map.serialize_entry("target", t)?;
}
if let Some(ref c) = self.condition {
map.serialize_entry("condition", c)?;
}
if let Some(ref v) = self.contains {
map.serialize_entry("contains", v)?;
}
if let Some(ref v) = self.starts_with {
map.serialize_entry("starts_with", v)?;
}
if let Some(ref v) = self.ends_with {
map.serialize_entry("ends_with", v)?;
}
if let Some(ref v) = self.regex {
map.serialize_entry("regex", v)?;
}
if let Some(ref v) = self.any_of {
map.serialize_entry("any_of", v)?;
}
if let Some(v) = self.gt {
map.serialize_entry("gt", &v)?;
}
if let Some(v) = self.lt {
map.serialize_entry("lt", &v)?;
}
if let Some(v) = self.gte {
map.serialize_entry("gte", &v)?;
}
if let Some(v) = self.lte {
map.serialize_entry("lte", &v)?;
}
map.end()
}
}
impl<'de> Deserialize<'de> for PatternMatch {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = Value::deserialize(deserializer)?;
let map = value
.as_object()
.ok_or_else(|| serde::de::Error::custom("pattern must be an object"))?;
let parse_opt_string = |key: &str| -> Result<Option<String>, D::Error> {
match map.get(key) {
None | Some(Value::Null) => Ok(None),
Some(Value::String(s)) => Ok(Some(s.clone())),
Some(v) => Err(serde::de::Error::custom(format!(
"pattern.{} must be a string, got {}",
key, v
))),
}
};
let parse_opt_number = |key: &str| -> Result<Option<f64>, D::Error> {
match map.get(key) {
None | Some(Value::Null) => Ok(None),
Some(v) => v.as_f64().map(Some).ok_or_else(|| {
serde::de::Error::custom(format!("pattern.{} must be a number, got {}", key, v))
}),
}
};
let target = match map.get("target") {
None | Some(Value::Null) => None,
Some(Value::String(s)) => Some(s.clone()),
Some(v) => {
return Err(serde::de::Error::custom(format!(
"pattern.target must be a string, got {}",
v
)));
}
};
let condition = match map.get("condition") {
Some(v) => Some(Condition::from_value(v.clone()).map_err(serde::de::Error::custom)?),
None => None,
};
let contains = parse_opt_string("contains")?;
let starts_with = parse_opt_string("starts_with")?;
let ends_with = parse_opt_string("ends_with")?;
let regex = parse_opt_string("regex")?;
let any_of = match map.get("any_of") {
None | Some(Value::Null) => None,
Some(Value::Array(arr)) => Some(arr.clone()),
Some(v) => {
return Err(serde::de::Error::custom(format!(
"pattern.any_of must be an array, got {}",
v
)));
}
};
let gt = parse_opt_number("gt")?;
let lt = parse_opt_number("lt")?;
let gte = parse_opt_number("gte")?;
let lte = parse_opt_number("lte")?;
Ok(PatternMatch {
target,
condition,
contains,
starts_with,
ends_with,
regex,
any_of,
gt,
lt,
gte,
lte,
})
}
}
#[derive(Clone, Debug)]
pub enum Condition {
Equality(Value),
Operators(MatchCondition),
}
impl Condition {
pub fn from_value(v: Value) -> Result<Self, String> {
match &v {
Value::Object(map) => {
if map
.keys()
.any(|k| MATCH_OPERATOR_KEYS.contains(&k.as_str()))
{
let cond: MatchCondition = serde_json::from_value(v)
.map_err(|e| format!("invalid pattern.condition object: {}", e))?;
Ok(Condition::Operators(cond))
} else {
Ok(Condition::Equality(v))
}
}
_ => Ok(Condition::Equality(v)),
}
}
}
impl Serialize for Condition {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Condition::Equality(v) => v.serialize(serializer),
Condition::Operators(c) => c.serialize(serializer),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExpressionMatch {
pub cel: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub variables: Option<HashMap<String, String>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SemanticMatch {
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
pub intent: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub intent_class: Option<SemanticIntentClass>,
#[serde(skip_serializing_if = "Option::is_none")]
pub threshold: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub examples: Option<SemanticExamples>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SemanticExamples {
#[serde(skip_serializing_if = "Option::is_none")]
pub positive: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub negative: Option<Vec<String>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Reference {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FrameworkMapping {
pub framework: String,
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relationship: Option<Relationship>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IndicatorVerdict {
pub indicator_id: String,
pub result: IndicatorResult,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub evidence: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AttackVerdict {
#[serde(skip_serializing_if = "Option::is_none")]
pub attack_id: Option<String>,
pub result: AttackResult,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tier: Option<Tier>,
pub indicator_verdicts: Vec<IndicatorVerdict>,
pub evaluation_summary: EvaluationSummary,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EvaluationSummary {
pub matched: i64,
pub not_matched: i64,
pub error: i64,
pub skipped: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SynthesizeBlock {
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ResponseEntry {
#[serde(rename = "when", skip_serializing_if = "Option::is_none")]
pub when: Option<MatchPredicate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub synthesize: Option<SynthesizeBlock>,
#[serde(flatten)]
pub extra: IndexMap<String, Value>,
}