1#[cfg(feature = "std")]
4use crate::v4::scoring::ScoringVector;
5use crate::{Error, severity::Severity, v4::Vector};
6use alloc::borrow::ToOwned;
7#[cfg(feature = "serde")]
8use alloc::string::String;
9#[cfg(feature = "serde")]
10use alloc::string::ToString;
11use core::{fmt, fmt::Display, str::FromStr};
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize, de, ser};
14
15#[derive(Clone, Debug, PartialEq)]
25pub struct Score {
26 value: f64,
27 nomenclature: Nomenclature,
28}
29
30#[derive(Clone, Debug, PartialEq)]
37pub enum Nomenclature {
38 CvssB,
40 CvssBE,
42 CvssBT,
44 CvssBTE,
46}
47
48impl Display for Nomenclature {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::CvssB => write!(f, "CVSS-B"),
52 Self::CvssBE => write!(f, "CVSS-BE"),
53 Self::CvssBT => write!(f, "CVSS-BT"),
54 Self::CvssBTE => write!(f, "CVSS-BTE"),
55 }
56 }
57}
58
59impl FromStr for Nomenclature {
60 type Err = Error;
61
62 fn from_str(s: &str) -> crate::Result<Self> {
63 match s {
64 "CVSS-B" => Ok(Self::CvssB),
65 "CVSS-BE" => Ok(Self::CvssBE),
66 "CVSS-BT" => Ok(Self::CvssBT),
67 "CVSS-BTE" => Ok(Self::CvssBTE),
68 _ => Err(Error::InvalidNomenclatureV4 {
69 nomenclature: s.to_owned(),
70 }),
71 }
72 }
73}
74
75impl From<&Vector> for Nomenclature {
76 fn from(vector: &Vector) -> Self {
77 let has_threat = vector.e.is_some();
78 let has_environmental = vector.ar.is_some()
79 || vector.cr.is_some()
80 || vector.ir.is_some()
81 || vector.mac.is_some()
82 || vector.mat.is_some()
83 || vector.mav.is_some()
84 || vector.mpr.is_some()
85 || vector.msa.is_some()
86 || vector.msc.is_some()
87 || vector.msi.is_some()
88 || vector.mui.is_some()
89 || vector.mva.is_some()
90 || vector.mvc.is_some()
91 || vector.mvi.is_some();
92
93 match (has_threat, has_environmental) {
94 (true, true) => Nomenclature::CvssBTE,
95 (true, false) => Nomenclature::CvssBT,
96 (false, true) => Nomenclature::CvssBE,
97 (false, false) => Nomenclature::CvssB,
98 }
99 }
100}
101
102#[cfg(feature = "serde")]
103#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
104impl<'de> Deserialize<'de> for Nomenclature {
105 fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
106 String::deserialize(deserializer)?
107 .parse()
108 .map_err(de::Error::custom)
109 }
110}
111
112#[cfg(feature = "serde")]
113#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
114impl Serialize for Nomenclature {
115 fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
116 self.to_string().serialize(serializer)
117 }
118}
119
120#[cfg(feature = "std")]
121impl From<&Vector> for Score {
122 fn from(vector: &Vector) -> Self {
123 let nomenclature = Nomenclature::from(vector);
124 let scoring = ScoringVector::from(vector);
125 let value = Self::round_v4(scoring.score());
126
127 Self {
128 value,
129 nomenclature,
130 }
131 }
132}
133
134impl Score {
135 pub fn new(value: f64, nomenclature: Nomenclature) -> Self {
137 Self {
138 value,
139 nomenclature,
140 }
141 }
142
143 #[cfg(feature = "std")]
154 pub(crate) fn round_v4(value: f64) -> f64 {
155 let value = f64::clamp(value, 0.0, 10.0);
156 const EPSILON: f64 = 10e-6;
157 ((value + EPSILON) * 10.).round() / 10.
158 }
159
160 pub fn value(self) -> f64 {
162 self.value
163 }
164
165 pub fn severity(self) -> Severity {
167 if self.value < 0.1 {
168 Severity::None
169 } else if self.value < 4.0 {
170 Severity::Low
171 } else if self.value < 7.0 {
172 Severity::Medium
173 } else if self.value < 9.0 {
174 Severity::High
175 } else {
176 Severity::Critical
177 }
178 }
179}
180
181impl Display for Score {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 write!(f, "{:.1} ({})", self.value, self.nomenclature)
188 }
189}
190
191impl From<Score> for f64 {
192 fn from(score: Score) -> f64 {
193 score.value()
194 }
195}
196
197impl From<Score> for Severity {
198 fn from(score: Score) -> Severity {
199 score.severity()
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use alloc::string::ToString;
207
208 #[test]
209 fn new_score() {
210 let score = Score::new(5.5, Nomenclature::CvssB);
211 assert_eq!(score.value(), 5.5);
212 }
213
214 #[test]
215 #[cfg(feature = "std")]
216 fn round_v4_round() {
217 assert_eq!(Score::round_v4(8.6 - 7.15), 1.5);
219 assert_eq!(Score::round_v4(5.12345), 5.1);
220 }
221
222 #[test]
223 fn into_severity() {
224 let score = Score::new(5.0, Nomenclature::CvssB);
225 let severity: Severity = score.into();
226 assert_eq!(severity, Severity::Medium);
227 }
228
229 #[test]
230 fn display_score() {
231 let score = Score::new(4.5, Nomenclature::CvssB);
232 assert_eq!(score.to_string(), "4.5 (CVSS-B)");
233 }
234}