use crate::enums::*;
use crate::event_registry::infer_execution_protocol;
use crate::types::*;
pub fn normalize(mut doc: Document) -> Document {
n006_single_phase_to_multi_actor(&mut doc);
n007_multi_phase_to_multi_actor(&mut doc);
n001_defaults(&mut doc);
n002_severity_expansion(&mut doc);
n003_auto_generate_indicator_ids(&mut doc);
n004_resolve_targets(&mut doc);
n005_expand_pattern_shorthand(&mut doc);
n008_normalize_tags(&mut doc);
doc
}
fn n001_defaults(doc: &mut Document) {
let attack = &mut doc.attack;
if attack.name.is_none() {
attack.name = Some("Untitled".to_string());
}
if attack.version.is_none() {
attack.version = Some(1);
}
if attack.status.is_none() {
attack.status = Some(Status::Draft);
}
if let Some(ref mut severity) = attack.severity {
match severity {
Severity::Object { confidence: c, .. } => {
if c.is_none() {
*c = Some(50);
}
}
Severity::Scalar(_) => {
}
}
}
if let Some(actors) = &mut attack.execution.actors {
for actor in actors.iter_mut() {
for (i, phase) in actor.phases.iter_mut().enumerate() {
if phase.name.is_none() {
phase.name = Some(format!("phase-{}", i + 1));
}
if let Some(ref mut trigger) = phase.trigger
&& trigger.event.is_some()
&& trigger.count.is_none()
{
trigger.count = Some(1);
}
}
}
}
if let Some(indicators) = &mut attack.indicators {
let default_protocol = infer_execution_protocol(&attack.execution);
for ind in indicators.iter_mut() {
if ind.protocol.is_none()
&& let Some(ref proto) = default_protocol
{
ind.protocol = Some(proto.clone());
}
}
}
if attack.indicators.is_some() {
if attack.correlation.is_none() {
attack.correlation = Some(Correlation {
logic: Some(CorrelationLogic::Any),
});
} else if let Some(ref mut corr) = attack.correlation
&& corr.logic.is_none()
{
corr.logic = Some(CorrelationLogic::Any);
}
}
if let Some(ref mut classification) = attack.classification
&& let Some(ref mut mappings) = classification.mappings
{
for mapping in mappings.iter_mut() {
if mapping.relationship.is_none() {
mapping.relationship = Some(Relationship::Primary);
}
}
}
}
fn n002_severity_expansion(doc: &mut Document) {
if let Some(Severity::Scalar(level)) = &doc.attack.severity {
let level = level.clone();
doc.attack.severity = Some(Severity::Object {
level,
confidence: Some(50),
});
}
}
fn n003_auto_generate_indicator_ids(doc: &mut Document) {
if let Some(indicators) = &mut doc.attack.indicators {
for (i, ind) in indicators.iter_mut().enumerate() {
if ind.id.is_none() {
let id = if let Some(attack_id) = &doc.attack.id {
format!("{}-{:02}", attack_id, i + 1)
} else {
format!("indicator-{:02}", i + 1)
};
ind.id = Some(id);
}
}
}
}
fn n004_resolve_targets(doc: &mut Document) {
if let Some(indicators) = &mut doc.attack.indicators {
for ind in indicators.iter_mut() {
if let Some(ref mut pattern) = ind.pattern
&& pattern.target.is_none()
{
pattern.target = Some(ind.target.clone());
}
if let Some(ref mut semantic) = ind.semantic
&& semantic.target.is_none()
{
semantic.target = Some(ind.target.clone());
}
}
}
}
fn n005_expand_pattern_shorthand(doc: &mut Document) {
if let Some(indicators) = &mut doc.attack.indicators {
for ind in indicators.iter_mut() {
if let Some(ref mut pattern) = ind.pattern
&& pattern.is_shorthand()
{
let cond = MatchCondition {
contains: pattern.contains.take(),
starts_with: pattern.starts_with.take(),
ends_with: pattern.ends_with.take(),
regex: pattern.regex.take(),
any_of: pattern.any_of.take(),
gt: pattern.gt.take(),
lt: pattern.lt.take(),
gte: pattern.gte.take(),
lte: pattern.lte.take(),
exists: None,
};
pattern.condition = Some(Condition::Operators(cond));
}
}
}
}
fn n008_normalize_tags(doc: &mut Document) {
if let Some(ref mut classification) = doc.attack.classification
&& let Some(ref mut tags) = classification.tags
{
for tag in tags.iter_mut() {
*tag = tag.to_lowercase().replace(['_', ' '], "-");
}
}
}
fn n006_single_phase_to_multi_actor(doc: &mut Document) {
let exec = &doc.attack.execution;
if exec.state.is_some() && exec.phases.is_none() && exec.actors.is_none() {
let mode = exec.mode.clone().unwrap_or_default();
let state = exec.state.clone();
let phase = Phase {
name: Some("phase-1".to_string()),
description: None,
mode: None,
state,
extractors: None,
on_enter: None,
trigger: None,
extensions: indexmap::IndexMap::new(),
};
let actor = Actor {
name: "default".to_string(),
mode: mode.clone(),
phases: vec![phase],
extensions: indexmap::IndexMap::new(),
};
doc.attack.execution.actors = Some(vec![actor]);
doc.attack.execution.state = None;
doc.attack.execution.mode = None;
}
}
fn n007_multi_phase_to_multi_actor(doc: &mut Document) {
let exec = &doc.attack.execution;
if exec.phases.is_some() && exec.actors.is_none() {
let Some(phases) = exec.phases.clone() else {
return;
};
let mode = exec
.mode
.clone()
.or_else(|| {
phases.first().and_then(|p| p.mode.clone())
})
.unwrap_or_default();
let actor = Actor {
name: "default".to_string(),
mode,
phases,
extensions: indexmap::IndexMap::new(),
};
doc.attack.execution.actors = Some(vec![actor]);
doc.attack.execution.phases = None;
doc.attack.execution.mode = None;
}
}