1use std::collections::HashMap;
2use std::fmt;
3
4pub const METRIC_DELIM: char = ':';
5pub const VECTOR_DELIM: char = '/';
6
7pub trait CVSSScore {
9 fn base_score(&self) -> Score;
11
12 fn temporal_score(&self) -> Score;
14
15 fn environmental_score(&self) -> Score;
17
18 fn impact_score(&self) -> Score;
20
21 fn expoitability_score(&self) -> Score;
23}
24
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)]
28pub struct Score(f64);
29
30impl Score {
31 pub fn value(self) -> f64 {
32 self.0
33 }
34
35 pub fn severity(self) -> Severity {
36 Severity::from_score(self)
37 }
38}
39
40impl From<f64> for Score {
41 fn from(score: f64) -> Score {
42 Score(score)
43 }
44}
45
46impl From<Score> for f64 {
47 fn from(score: Score) -> f64 {
48 score.value()
49 }
50}
51
52#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
55pub enum Severity {
56 None,
57 Low,
58 Medium,
59 High,
60 Critical,
61}
62
63impl Severity {
64 fn from(s: f64) -> Severity {
65 match s {
66 s if s < 0.1 => Severity::None,
67 s if s >= 0.1 && s < 4.0 => Severity::Low,
68 s if s >= 4.0 && s < 7.0 => Severity::Medium,
69 s if s >= 7.0 && s < 9.0 => Severity::High,
70 s if s >= 9.0 => Severity::Critical,
71 _ => Severity::Critical,
72 }
73 }
74
75 pub fn from_score(s: Score) -> Severity {
76 Severity::from(s.value())
77 }
78}
79
80impl fmt::Display for Severity {
81 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82 write!(
83 f,
84 "{}",
85 match self {
86 Severity::None => "None",
87 Severity::Low => "Low",
88 Severity::Medium => "Medium",
89 Severity::High => "High",
90 Severity::Critical => "Critical",
91 }
92 )
93 }
94}
95
96#[derive(Debug, Clone, PartialEq)]
98#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
99pub enum ParseError {
100 MalformedVector,
101 Missing,
102 Duplicated,
103 IncorrectValue,
104}
105
106impl fmt::Display for ParseError {
107 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108 write!(f, "CVSS parse error")
109 }
110}
111
112pub trait Optional {
113 fn is_undefined(&self) -> bool;
114}
115
116pub trait NumValue {
117 fn num_value(&self) -> f64 {
118 0.0
119 }
120 fn num_value_scoped(&self, _scope_change: bool) -> f64 {
121 0.0
122 }
123}
124
125pub fn append_metric<T: AsRef<str>>(vector: &mut String, metric: &str, value: &T) {
127 if !vector.is_empty() {
128 vector.push(VECTOR_DELIM);
129 }
130 vector.push_str(metric);
131 vector.push(METRIC_DELIM);
132 vector.push_str(value.as_ref());
133}
134
135pub fn append_metric_optional<T: AsRef<str> + Optional>(vector: &mut String, metric: &str, value: &T) {
137 if !value.is_undefined() {
138 append_metric(vector, metric, value);
139 }
140}
141
142pub fn parse_metrics(cvss_str: &str) -> Result<HashMap<&str, &str>, ParseError> {
144 let mut parsed = HashMap::new();
145
146 for vector_part in cvss_str.split(VECTOR_DELIM) {
147 let mut metric_parts = vector_part.split(METRIC_DELIM);
148 let metric = metric_parts
149 .next()
150 .ok_or_else(|| ParseError::MalformedVector)?;
151 let value = metric_parts
152 .next()
153 .ok_or_else(|| ParseError::MalformedVector)?;
154 if metric_parts.next().is_some() {
155 return Err(ParseError::MalformedVector);
156 }
157
158 if parsed.contains_key(metric) {
159 return Err(ParseError::Duplicated);
160 }
161 parsed.insert(metric, value);
162 }
163
164 Ok(parsed)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_severity_from_score() {
173 assert_eq!(Severity::from_score(Score(0.0)), Severity::None);
174 assert_eq!(Severity::from_score(Score(3.9)), Severity::Low);
175 assert_eq!(Severity::from_score(Score(3.95)), Severity::Low);
176 assert_eq!(Severity::from_score(Score(4.0)), Severity::Medium);
177 assert_eq!(Severity::from_score(Score(4.01)), Severity::Medium);
178 assert_eq!(Severity::from_score(Score(7.89)), Severity::High);
179 assert_eq!(Severity::from_score(Score(9.12)), Severity::Critical);
180 assert_eq!(Severity::from_score(Score(102.3)), Severity::Critical);
181 }
182
183 #[test]
184 fn test_score_severity() {
185 assert_eq!(Score(4.5).severity(), Severity::Medium);
186 }
187}