237 lines
7.2 KiB
Rust
237 lines
7.2 KiB
Rust
use std::fmt;
|
|
use std::num::{NonZeroU16, ParseIntError};
|
|
use std::str::FromStr;
|
|
|
|
use serde::de::{DeserializeOwned, Deserializer, Error};
|
|
use serde::ser::Serializer;
|
|
use serde::{Deserialize, Serialize};
|
|
use strum::{EnumString, IntoStaticStr};
|
|
|
|
pub use toml::Value;
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
|
pub struct References(Vec<String>);
|
|
|
|
pub trait DomainModel: Send + Serialize + DeserializeOwned {
|
|
fn kind() -> DomainModelKind;
|
|
|
|
fn name(&self) -> &str;
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumString, IntoStaticStr)]
|
|
#[strum(serialize_all = "snake_case")]
|
|
pub enum DomainKind {
|
|
Source,
|
|
Threat,
|
|
Observe,
|
|
React,
|
|
Mitigate,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, EnumString, IntoStaticStr)]
|
|
pub enum DomainModelKind {
|
|
#[strum(serialize = "SIE")]
|
|
SourceIntelligence,
|
|
#[strum(serialize = "SPR")]
|
|
SourceProvider,
|
|
#[strum(serialize = "SRT")]
|
|
SourceRequirement,
|
|
#[strum(serialize = "TTC")]
|
|
ThreatTactic,
|
|
#[strum(serialize = "TTE")]
|
|
ThreatTechnique,
|
|
#[strum(serialize = "TSE")]
|
|
ThreatSoftware,
|
|
#[strum(serialize = "TSN")]
|
|
ThreatSimulation,
|
|
#[strum(serialize = "OET")]
|
|
ObserveEvent,
|
|
#[strum(serialize = "ODN")]
|
|
ObserveDetection,
|
|
#[strum(serialize = "OPR")]
|
|
ObserveProvider,
|
|
#[strum(serialize = "OCN")]
|
|
ObserveConfiguration,
|
|
#[strum(serialize = "RSE")]
|
|
ReactStage,
|
|
#[strum(serialize = "RAN")]
|
|
ReactAction,
|
|
#[strum(serialize = "RPK")]
|
|
ReactPlaybook,
|
|
#[strum(serialize = "MSY")]
|
|
MitigateStrategy,
|
|
#[strum(serialize = "MPM")]
|
|
MitigatePlatform,
|
|
#[strum(serialize = "MCN")]
|
|
MitigateConfiguration,
|
|
}
|
|
|
|
impl DomainModelKind {
|
|
pub fn domain(&self) -> DomainKind {
|
|
match self {
|
|
DomainModelKind::SourceIntelligence => DomainKind::Source,
|
|
DomainModelKind::SourceProvider => DomainKind::Source,
|
|
DomainModelKind::SourceRequirement => DomainKind::Source,
|
|
DomainModelKind::ThreatTactic => DomainKind::Threat,
|
|
DomainModelKind::ThreatTechnique => DomainKind::Threat,
|
|
DomainModelKind::ThreatSoftware => DomainKind::Threat,
|
|
DomainModelKind::ThreatSimulation => DomainKind::Threat,
|
|
DomainModelKind::ObserveEvent => DomainKind::Observe,
|
|
DomainModelKind::ObserveDetection => DomainKind::Observe,
|
|
DomainModelKind::ObserveProvider => DomainKind::Observe,
|
|
DomainModelKind::ObserveConfiguration => DomainKind::Observe,
|
|
DomainModelKind::ReactStage => DomainKind::React,
|
|
DomainModelKind::ReactAction => DomainKind::React,
|
|
DomainModelKind::ReactPlaybook => DomainKind::React,
|
|
DomainModelKind::MitigateStrategy => DomainKind::Mitigate,
|
|
DomainModelKind::MitigatePlatform => DomainKind::Mitigate,
|
|
DomainModelKind::MitigateConfiguration => DomainKind::Mitigate,
|
|
}
|
|
}
|
|
|
|
pub fn parts(self) -> (&'static str, &'static str) {
|
|
match self {
|
|
DomainModelKind::SourceIntelligence => ("source", "intelligence"),
|
|
DomainModelKind::SourceProvider => ("source", "provider"),
|
|
DomainModelKind::SourceRequirement => ("source", "requirement"),
|
|
DomainModelKind::ThreatTactic => ("threat", "tactic"),
|
|
DomainModelKind::ThreatTechnique => ("threat", "technique"),
|
|
DomainModelKind::ThreatSoftware => ("threat", "software"),
|
|
DomainModelKind::ThreatSimulation => ("threat", "simulation"),
|
|
DomainModelKind::ObserveEvent => ("observe", "event"),
|
|
DomainModelKind::ObserveDetection => ("observe", "detection"),
|
|
DomainModelKind::ObserveProvider => ("observe", "provider"),
|
|
DomainModelKind::ObserveConfiguration => ("observe", "configuration"),
|
|
DomainModelKind::ReactStage => ("react", "stage"),
|
|
DomainModelKind::ReactAction => ("react", "action"),
|
|
DomainModelKind::ReactPlaybook => ("react", "playbook"),
|
|
DomainModelKind::MitigateStrategy => ("mitigate", "strategy"),
|
|
DomainModelKind::MitigatePlatform => ("mitigate", "platform"),
|
|
DomainModelKind::MitigateConfiguration => ("mitigate", "configuration"),
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
|
pub struct ModelId {
|
|
pub major: u16,
|
|
pub minor: Option<NonZeroU16>,
|
|
}
|
|
|
|
impl ModelId {
|
|
pub fn new(major: u16) -> Self {
|
|
Self { major, minor: None }
|
|
}
|
|
|
|
pub fn is_example(self) -> bool {
|
|
self.major == 0
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ModelId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{:0>5}", self.major)?;
|
|
if let Some(minor) = self.minor {
|
|
write!(f, ".{:0>3}", minor)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl FromStr for ModelId {
|
|
type Err = ParseIntError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let mut parts = s.splitn(2, '.');
|
|
|
|
let major = parts.next().unwrap().parse()?;
|
|
let minor = parts.next().map(FromStr::from_str).transpose()?;
|
|
|
|
Ok(Self { major, minor })
|
|
}
|
|
}
|
|
|
|
impl Serialize for ModelId {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.collect_str(self)
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for ModelId {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s = <&str>::deserialize(deserializer)?;
|
|
s.parse().map_err(D::Error::custom)
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
|
pub struct DomainId {
|
|
pub kind: DomainModelKind,
|
|
pub model_id: ModelId,
|
|
}
|
|
|
|
impl DomainId {
|
|
pub fn new(kind: DomainModelKind, model_id: ModelId) -> Self {
|
|
Self { kind, model_id }
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for DomainId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_tuple("Id")
|
|
.field(&format_args!("{}", self))
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for DomainId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}{}", <&'static str>::from(&self.kind), self.model_id)
|
|
}
|
|
}
|
|
|
|
impl FromStr for DomainId {
|
|
type Err = &'static str;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
if s.len() > 3 && s.is_char_boundary(3) {
|
|
let (kind, model_id) = s.split_at(3);
|
|
if let (Ok(kind), Ok(model_id)) =
|
|
(DomainModelKind::from_str(kind), ModelId::from_str(model_id))
|
|
{
|
|
return Ok(Self { kind, model_id });
|
|
}
|
|
}
|
|
Err("invalid storm id")
|
|
}
|
|
}
|
|
|
|
impl Serialize for DomainId {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.collect_str(self)
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for DomainId {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s = <&str>::deserialize(deserializer)?;
|
|
s.parse().map_err(D::Error::custom)
|
|
}
|
|
}
|