mod a;
mod ac;
mod av;
mod c;
mod i;
mod pr;
mod s;
mod ui;
pub use self::{
a::Availability, ac::AttackComplexity, av::AttackVector, c::Confidentiality, i::Integrity,
pr::PrivilegesRequired, s::Scope, ui::UserInteraction,
};
use super::Score;
use crate::{Error, Metric, MetricType, PREFIX, Result};
use alloc::{borrow::ToOwned, vec::Vec};
use core::{fmt, str::FromStr};
#[cfg(feature = "serde")]
use {
alloc::string::{String, ToString},
serde::{Deserialize, Serialize, de, ser},
};
#[cfg(feature = "std")]
use crate::Severity;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Base {
pub minor_version: usize,
pub av: Option<AttackVector>,
pub ac: Option<AttackComplexity>,
pub pr: Option<PrivilegesRequired>,
pub ui: Option<UserInteraction>,
pub s: Option<Scope>,
pub c: Option<Confidentiality>,
pub i: Option<Integrity>,
pub a: Option<Availability>,
}
impl Base {
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn score(&self) -> Score {
let exploitability = self.exploitability().value();
let iss = self.impact().value();
let iss_scoped = if !self.is_scope_changed() {
6.42 * iss
} else {
(7.52 * (iss - 0.029)) - (3.25 * (iss - 0.02).powf(15.0))
};
let score = if iss_scoped <= 0.0 {
0.0
} else if !self.is_scope_changed() {
(iss_scoped + exploitability).min(10.0)
} else {
(1.08 * (iss_scoped + exploitability)).min(10.0)
};
Score::new(score).roundup()
}
pub fn exploitability(&self) -> Score {
let av_score = self.av.map(|av| av.score()).unwrap_or(0.0);
let ac_score = self.ac.map(|ac| ac.score()).unwrap_or(0.0);
let ui_score = self.ui.map(|ui| ui.score()).unwrap_or(0.0);
let pr_score = self
.pr
.map(|pr| pr.scoped_score(self.is_scope_changed()))
.unwrap_or(0.0);
(8.22 * av_score * ac_score * pr_score * ui_score).into()
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn impact(&self) -> Score {
let c_score = self.c.map(|c| c.score()).unwrap_or(0.0);
let i_score = self.i.map(|i| i.score()).unwrap_or(0.0);
let a_score = self.a.map(|a| a.score()).unwrap_or(0.0);
(1.0 - ((1.0 - c_score) * (1.0 - i_score) * (1.0 - a_score)).abs()).into()
}
pub fn metrics(&self) -> impl Iterator<Item = (MetricType, &dyn fmt::Debug)> {
[
(
MetricType::AV,
self.av.as_ref().map(|m| m as &dyn fmt::Debug),
),
(
MetricType::AC,
self.ac.as_ref().map(|m| m as &dyn fmt::Debug),
),
(
MetricType::PR,
self.pr.as_ref().map(|m| m as &dyn fmt::Debug),
),
(
MetricType::UI,
self.ui.as_ref().map(|m| m as &dyn fmt::Debug),
),
(MetricType::S, self.s.as_ref().map(|m| m as &dyn fmt::Debug)),
(MetricType::C, self.c.as_ref().map(|m| m as &dyn fmt::Debug)),
(MetricType::I, self.i.as_ref().map(|m| m as &dyn fmt::Debug)),
(MetricType::A, self.a.as_ref().map(|m| m as &dyn fmt::Debug)),
]
.into_iter()
.filter_map(|(name, metric)| metric.as_ref().map(|&m| (name, m)))
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn severity(&self) -> Severity {
self.score().severity()
}
fn is_scope_changed(&self) -> bool {
self.s.map(|s| s.is_changed()).unwrap_or(false)
}
}
macro_rules! write_metrics {
($f:expr, $($metric:expr),+) => {
$(
if let Some(metric) = $metric {
write!($f, "/{}", metric)?;
}
)+
};
}
impl fmt::Display for Base {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:3.{}", PREFIX, self.minor_version)?;
write_metrics!(
f, self.av, self.ac, self.pr, self.ui, self.s, self.c, self.i, self.a
);
Ok(())
}
}
impl FromStr for Base {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let component_vec = s
.split('/')
.map(|component| {
let mut parts = component.split(':');
let id = parts.next().ok_or_else(|| Error::InvalidComponent {
component: component.to_owned(),
})?;
let value = parts.next().ok_or_else(|| Error::InvalidComponent {
component: component.to_owned(),
})?;
if parts.next().is_some() {
return Err(Error::InvalidComponent {
component: component.to_owned(),
});
}
Ok((id, value))
})
.collect::<Result<Vec<_>>>()?;
let mut components = component_vec.iter();
let &(id, version_string) = components.next().ok_or(Error::InvalidPrefix {
prefix: s.to_owned(),
})?;
if id != PREFIX {
return Err(Error::InvalidPrefix {
prefix: id.to_owned(),
});
}
let mut metrics = Self {
minor_version: match version_string {
"3.0" => 0,
"3.1" => 1,
_ => {
return Err(Error::UnsupportedVersion {
version: version_string.to_owned(),
});
}
},
..Default::default()
};
for &component in components {
let id = component.0.to_ascii_uppercase();
let value = component.1.to_ascii_uppercase();
match id.parse::<MetricType>()? {
MetricType::AV => metrics.av = Some(value.parse()?),
MetricType::AC => metrics.ac = Some(value.parse()?),
MetricType::PR => metrics.pr = Some(value.parse()?),
MetricType::UI => metrics.ui = Some(value.parse()?),
MetricType::S => metrics.s = Some(value.parse()?),
MetricType::C => metrics.c = Some(value.parse()?),
MetricType::I => metrics.i = Some(value.parse()?),
MetricType::A => metrics.a = Some(value.parse()?),
}
}
Ok(metrics)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de> Deserialize<'de> for Base {
fn deserialize<D: de::Deserializer<'de>>(
deserializer: D,
) -> core::result::Result<Self, D::Error> {
String::deserialize(deserializer)?
.parse()
.map_err(de::Error::custom)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl Serialize for Base {
fn serialize<S: ser::Serializer>(
&self,
serializer: S,
) -> core::result::Result<S::Ok, S::Error> {
self.to_string().serialize(serializer)
}
}