cvssrust/
common.rs

1use std::collections::HashMap;
2use std::fmt;
3
4pub const METRIC_DELIM: char = ':';
5pub const VECTOR_DELIM: char = '/';
6
7/// CVSS Score implementation: Base/Temporal/Environmental
8pub trait CVSSScore {
9    /// Calculate CVSS Base Score
10    fn base_score(&self) -> Score;
11
12    /// Calculate CVSS Temporal Score
13    fn temporal_score(&self) -> Score;
14
15    /// Calculate CVSS Environmental Score
16    fn environmental_score(&self) -> Score;
17
18    /// Calculate Impact Sub Score
19    fn impact_score(&self) -> Score;
20
21    /// Calculate Exploitability Score
22    fn expoitability_score(&self) -> Score;
23}
24
25/// Base/Temporal/Environmental CVSS Score
26#[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/// Qualitative Severity Rating Scale
53#[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/// Parsing error type
97#[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
125/// Append metric and its value to a vector string.
126pub 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
135/// Append metric and its value to a vector string if it is not undefined (for optionsl metrics).
136pub 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
142/// Parse CVSS vector and return metrics as a hash map of strings.
143pub 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}