Skip to main content

sbom_tools/diff/
cost.rs

1//! Cost model for diff operations.
2
3use serde::{Deserialize, Serialize};
4
5/// Cost model configuration for semantic diff operations.
6///
7/// Costs are used to determine the minimum-cost alignment between two SBOMs.
8/// Higher costs indicate more significant changes.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CostModel {
11    /// Cost for adding a new component
12    pub component_added: u32,
13    /// Cost for removing a component
14    pub component_removed: u32,
15    /// Cost for patch version change
16    pub version_patch: u32,
17    /// Cost for minor version change
18    pub version_minor: u32,
19    /// Cost for major version change
20    pub version_major: u32,
21    /// Cost for license change
22    pub license_changed: u32,
23    /// Cost for supplier change
24    pub supplier_changed: u32,
25    /// Cost for introducing a vulnerability
26    pub vulnerability_introduced: u32,
27    /// Reward (negative cost) for resolving a vulnerability
28    pub vulnerability_resolved: i32,
29    /// Cost for adding a dependency
30    pub dependency_added: u32,
31    /// Cost for removing a dependency
32    pub dependency_removed: u32,
33    /// Cost for hash mismatch (integrity concern)
34    pub hash_mismatch: u32,
35}
36
37impl Default for CostModel {
38    fn default() -> Self {
39        Self {
40            component_added: 10,
41            component_removed: 10,
42            version_patch: 2,
43            version_minor: 4,
44            version_major: 7,
45            license_changed: 6,
46            supplier_changed: 4,
47            vulnerability_introduced: 15,
48            vulnerability_resolved: -3,
49            dependency_added: 5,
50            dependency_removed: 5,
51            hash_mismatch: 8,
52        }
53    }
54}
55
56impl CostModel {
57    /// Create a security-focused cost model
58    pub fn security_focused() -> Self {
59        Self {
60            vulnerability_introduced: 25,
61            vulnerability_resolved: -5,
62            hash_mismatch: 15,
63            supplier_changed: 8,
64            ..Default::default()
65        }
66    }
67
68    /// Create a compliance-focused cost model
69    pub fn compliance_focused() -> Self {
70        Self {
71            license_changed: 12,
72            supplier_changed: 8,
73            ..Default::default()
74        }
75    }
76
77    /// Get cost for version change based on semver
78    pub fn version_change_cost(
79        &self,
80        old: &Option<semver::Version>,
81        new: &Option<semver::Version>,
82    ) -> u32 {
83        match (old, new) {
84            (Some(old_ver), Some(new_ver)) => {
85                if old_ver.major != new_ver.major {
86                    self.version_major
87                } else if old_ver.minor != new_ver.minor {
88                    self.version_minor
89                } else if old_ver.patch != new_ver.patch {
90                    self.version_patch
91                } else {
92                    0
93                }
94            }
95            (None, Some(_)) | (Some(_), None) => self.version_minor,
96            (None, None) => 0,
97        }
98    }
99
100    /// Calculate total semantic score from change counts
101    #[allow(clippy::too_many_arguments)]
102    pub fn calculate_semantic_score(
103        &self,
104        components_added: usize,
105        components_removed: usize,
106        version_changes: usize,
107        license_changes: usize,
108        vulns_introduced: usize,
109        vulns_resolved: usize,
110        deps_added: usize,
111        deps_removed: usize,
112    ) -> f64 {
113        let score = (components_added as u32 * self.component_added)
114            + (components_removed as u32 * self.component_removed)
115            + (version_changes as u32 * self.version_minor)
116            + (license_changes as u32 * self.license_changed)
117            + (vulns_introduced as u32 * self.vulnerability_introduced)
118            + (deps_added as u32 * self.dependency_added)
119            + (deps_removed as u32 * self.dependency_removed);
120
121        let reward = vulns_resolved as i32 * self.vulnerability_resolved;
122
123        (score as i32 + reward) as f64
124    }
125}