Skip to main content

sbom_tools/model/
metadata.rs

1//! Metadata structures for SBOM documents and components.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6/// SBOM format type
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub enum SbomFormat {
9    CycloneDx,
10    Spdx,
11}
12
13impl std::fmt::Display for SbomFormat {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        match self {
16            Self::CycloneDx => write!(f, "CycloneDX"),
17            Self::Spdx => write!(f, "SPDX"),
18        }
19    }
20}
21
22/// Document-level metadata
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct DocumentMetadata {
25    /// SBOM format type
26    pub format: SbomFormat,
27    /// Format version (e.g., "1.5" for `CycloneDX`)
28    pub format_version: String,
29    /// Specification version
30    pub spec_version: String,
31    /// Serial number or document namespace
32    pub serial_number: Option<String>,
33    /// Creation timestamp
34    pub created: DateTime<Utc>,
35    /// Creators/authors
36    pub creators: Vec<Creator>,
37    /// Document name
38    pub name: Option<String>,
39    /// Security contact for vulnerability disclosure (CRA requirement)
40    pub security_contact: Option<String>,
41    /// URL for vulnerability disclosure policy/portal
42    pub vulnerability_disclosure_url: Option<String>,
43    /// Support/end-of-life date for security updates
44    pub support_end_date: Option<DateTime<Utc>>,
45    /// SBOM lifecycle phase (e.g., "build", "pre-build", "operations")
46    pub lifecycle_phase: Option<String>,
47    /// Self-declared completeness level (from CycloneDX compositions)
48    pub completeness_declaration: CompletenessDeclaration,
49    /// Digital signature information (from CycloneDX signature field)
50    pub signature: Option<SignatureInfo>,
51}
52
53/// Self-declared completeness level of the SBOM
54///
55/// Derived from CycloneDX compositions aggregate field, which declares
56/// whether the SBOM inventory is complete, incomplete, or unknown.
57#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
58#[non_exhaustive]
59pub enum CompletenessDeclaration {
60    /// SBOM author declares the inventory is complete
61    Complete,
62    /// SBOM author declares the inventory includes only first-party components
63    IncompleteFirstPartyOnly,
64    /// SBOM author declares the inventory includes only third-party components
65    IncompleteThirdPartyOnly,
66    /// SBOM author declares the inventory is incomplete
67    Incomplete,
68    /// No completeness declaration or explicitly unknown
69    #[default]
70    Unknown,
71    /// Completeness was declared but with an unrecognized value
72    NotSpecified,
73}
74
75impl std::fmt::Display for CompletenessDeclaration {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            Self::Complete => write!(f, "complete"),
79            Self::IncompleteFirstPartyOnly => write!(f, "incomplete (first-party only)"),
80            Self::IncompleteThirdPartyOnly => write!(f, "incomplete (third-party only)"),
81            Self::Incomplete => write!(f, "incomplete"),
82            Self::Unknown => write!(f, "unknown"),
83            Self::NotSpecified => write!(f, "not specified"),
84        }
85    }
86}
87
88/// Digital signature information for the SBOM document
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct SignatureInfo {
91    /// Signature algorithm (e.g., "ES256", "RS256", "Ed25519")
92    pub algorithm: String,
93    /// Whether the signature appears structurally valid (has algorithm + value)
94    pub has_value: bool,
95}
96
97impl Default for DocumentMetadata {
98    fn default() -> Self {
99        Self {
100            format: SbomFormat::CycloneDx,
101            format_version: String::new(),
102            spec_version: String::new(),
103            serial_number: None,
104            created: Utc::now(),
105            creators: Vec::new(),
106            name: None,
107            security_contact: None,
108            vulnerability_disclosure_url: None,
109            support_end_date: None,
110            lifecycle_phase: None,
111            completeness_declaration: CompletenessDeclaration::default(),
112            signature: None,
113        }
114    }
115}
116
117/// Creator information
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct Creator {
120    /// Creator type
121    pub creator_type: CreatorType,
122    /// Creator name or identifier
123    pub name: String,
124    /// Optional email
125    pub email: Option<String>,
126}
127
128/// Type of creator
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
130pub enum CreatorType {
131    Person,
132    Organization,
133    Tool,
134}
135
136/// Organization/supplier information
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138pub struct Organization {
139    /// Organization name
140    pub name: String,
141    /// Contact URLs
142    pub urls: Vec<String>,
143    /// Contact emails
144    pub contacts: Vec<Contact>,
145}
146
147impl Organization {
148    /// Create a new organization with just a name
149    #[must_use]
150    pub const fn new(name: String) -> Self {
151        Self {
152            name,
153            urls: Vec::new(),
154            contacts: Vec::new(),
155        }
156    }
157}
158
159/// Contact information
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct Contact {
162    /// Contact name
163    pub name: Option<String>,
164    /// Email address
165    pub email: Option<String>,
166    /// Phone number
167    pub phone: Option<String>,
168}
169
170/// Component type classification
171#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
172#[non_exhaustive]
173pub enum ComponentType {
174    Application,
175    Framework,
176    #[default]
177    Library,
178    Container,
179    OperatingSystem,
180    Device,
181    Firmware,
182    File,
183    Data,
184    MachineLearningModel,
185    Platform,
186    DeviceDriver,
187    Cryptographic,
188    Other(String),
189}
190
191impl std::fmt::Display for ComponentType {
192    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        match self {
194            Self::Application => write!(f, "application"),
195            Self::Framework => write!(f, "framework"),
196            Self::Library => write!(f, "library"),
197            Self::Container => write!(f, "container"),
198            Self::OperatingSystem => write!(f, "operating-system"),
199            Self::Device => write!(f, "device"),
200            Self::Firmware => write!(f, "firmware"),
201            Self::File => write!(f, "file"),
202            Self::Data => write!(f, "data"),
203            Self::MachineLearningModel => write!(f, "machine-learning-model"),
204            Self::Platform => write!(f, "platform"),
205            Self::DeviceDriver => write!(f, "device-driver"),
206            Self::Cryptographic => write!(f, "cryptographic"),
207            Self::Other(s) => write!(f, "{s}"),
208        }
209    }
210}
211
212/// Cryptographic hash
213#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
214pub struct Hash {
215    /// Hash algorithm
216    pub algorithm: HashAlgorithm,
217    /// Hash value (hex encoded)
218    pub value: String,
219}
220
221impl Hash {
222    /// Create a new hash
223    #[must_use]
224    pub const fn new(algorithm: HashAlgorithm, value: String) -> Self {
225        Self { algorithm, value }
226    }
227}
228
229/// Hash algorithm types
230#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
231pub enum HashAlgorithm {
232    Md5,
233    Sha1,
234    Sha256,
235    Sha384,
236    Sha512,
237    Sha3_256,
238    Sha3_384,
239    Sha3_512,
240    Blake2b256,
241    Blake2b384,
242    Blake2b512,
243    Blake3,
244    Other(String),
245}
246
247impl std::fmt::Display for HashAlgorithm {
248    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249        match self {
250            Self::Md5 => write!(f, "MD5"),
251            Self::Sha1 => write!(f, "SHA-1"),
252            Self::Sha256 => write!(f, "SHA-256"),
253            Self::Sha384 => write!(f, "SHA-384"),
254            Self::Sha512 => write!(f, "SHA-512"),
255            Self::Sha3_256 => write!(f, "SHA3-256"),
256            Self::Sha3_384 => write!(f, "SHA3-384"),
257            Self::Sha3_512 => write!(f, "SHA3-512"),
258            Self::Blake2b256 => write!(f, "BLAKE2b-256"),
259            Self::Blake2b384 => write!(f, "BLAKE2b-384"),
260            Self::Blake2b512 => write!(f, "BLAKE2b-512"),
261            Self::Blake3 => write!(f, "BLAKE3"),
262            Self::Other(s) => write!(f, "{s}"),
263        }
264    }
265}
266
267/// External reference
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct ExternalReference {
270    /// Reference type
271    pub ref_type: ExternalRefType,
272    /// URL or locator
273    pub url: String,
274    /// Comment or description
275    pub comment: Option<String>,
276    /// Hash of the referenced content
277    pub hashes: Vec<Hash>,
278}
279
280/// External reference types
281#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
282pub enum ExternalRefType {
283    Vcs,
284    IssueTracker,
285    Website,
286    Advisories,
287    Bom,
288    MailingList,
289    Social,
290    Chat,
291    Documentation,
292    Support,
293    SourceDistribution,
294    BinaryDistribution,
295    License,
296    BuildMeta,
297    BuildSystem,
298    ReleaseNotes,
299    SecurityContact,
300    ModelCard,
301    Log,
302    Configuration,
303    Evidence,
304    Formulation,
305    Attestation,
306    ThreatModel,
307    AdversaryModel,
308    RiskAssessment,
309    VulnerabilityAssertion,
310    ExploitabilityStatement,
311    Pentest,
312    StaticAnalysis,
313    DynamicAnalysis,
314    RuntimeAnalysis,
315    ComponentAnalysis,
316    Maturity,
317    Certification,
318    QualityMetrics,
319    Codified,
320    Other(String),
321}
322
323impl std::fmt::Display for ExternalRefType {
324    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325        match self {
326            Self::Vcs => write!(f, "vcs"),
327            Self::IssueTracker => write!(f, "issue-tracker"),
328            Self::Website => write!(f, "website"),
329            Self::Advisories => write!(f, "advisories"),
330            Self::Bom => write!(f, "bom"),
331            Self::MailingList => write!(f, "mailing-list"),
332            Self::Social => write!(f, "social"),
333            Self::Chat => write!(f, "chat"),
334            Self::Documentation => write!(f, "documentation"),
335            Self::Support => write!(f, "support"),
336            Self::SourceDistribution => write!(f, "distribution"),
337            Self::BinaryDistribution => write!(f, "distribution-intake"),
338            Self::License => write!(f, "license"),
339            Self::BuildMeta => write!(f, "build-meta"),
340            Self::BuildSystem => write!(f, "build-system"),
341            Self::ReleaseNotes => write!(f, "release-notes"),
342            Self::SecurityContact => write!(f, "security-contact"),
343            Self::ModelCard => write!(f, "model-card"),
344            Self::Log => write!(f, "log"),
345            Self::Configuration => write!(f, "configuration"),
346            Self::Evidence => write!(f, "evidence"),
347            Self::Formulation => write!(f, "formulation"),
348            Self::Attestation => write!(f, "attestation"),
349            Self::ThreatModel => write!(f, "threat-model"),
350            Self::AdversaryModel => write!(f, "adversary-model"),
351            Self::RiskAssessment => write!(f, "risk-assessment"),
352            Self::VulnerabilityAssertion => write!(f, "vulnerability-assertion"),
353            Self::ExploitabilityStatement => write!(f, "exploitability-statement"),
354            Self::Pentest => write!(f, "pentest-report"),
355            Self::StaticAnalysis => write!(f, "static-analysis-report"),
356            Self::DynamicAnalysis => write!(f, "dynamic-analysis-report"),
357            Self::RuntimeAnalysis => write!(f, "runtime-analysis-report"),
358            Self::ComponentAnalysis => write!(f, "component-analysis-report"),
359            Self::Maturity => write!(f, "maturity-report"),
360            Self::Certification => write!(f, "certification-report"),
361            Self::QualityMetrics => write!(f, "quality-metrics"),
362            Self::Codified => write!(f, "codified"),
363            Self::Other(s) => write!(f, "{s}"),
364        }
365    }
366}
367
368/// Dependency relationship type
369#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
370pub enum DependencyType {
371    /// Direct dependency
372    DependsOn,
373    /// Optional dependency
374    OptionalDependsOn,
375    /// Development dependency
376    DevDependsOn,
377    /// Build dependency
378    BuildDependsOn,
379    /// Test dependency
380    TestDependsOn,
381    /// Runtime dependency
382    RuntimeDependsOn,
383    /// Provided dependency (e.g., Java provided scope)
384    ProvidedDependsOn,
385    /// Describes relationship (SPDX)
386    Describes,
387    /// Generates relationship
388    Generates,
389    /// Contains relationship
390    Contains,
391    /// Ancestor of
392    AncestorOf,
393    /// Variant of
394    VariantOf,
395    /// Distribution artifact
396    DistributionArtifact,
397    /// Patch for
398    PatchFor,
399    /// Copy of
400    CopyOf,
401    /// File added
402    FileAdded,
403    /// File deleted
404    FileDeleted,
405    /// File modified
406    FileModified,
407    /// Dynamic link
408    DynamicLink,
409    /// Static link
410    StaticLink,
411    /// Other relationship
412    Other(String),
413}
414
415impl std::fmt::Display for DependencyType {
416    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417        match self {
418            Self::DependsOn => write!(f, "depends-on"),
419            Self::OptionalDependsOn => write!(f, "optional-depends-on"),
420            Self::DevDependsOn => write!(f, "dev-depends-on"),
421            Self::BuildDependsOn => write!(f, "build-depends-on"),
422            Self::TestDependsOn => write!(f, "test-depends-on"),
423            Self::RuntimeDependsOn => write!(f, "runtime-depends-on"),
424            Self::ProvidedDependsOn => write!(f, "provided-depends-on"),
425            Self::Describes => write!(f, "describes"),
426            Self::Generates => write!(f, "generates"),
427            Self::Contains => write!(f, "contains"),
428            Self::AncestorOf => write!(f, "ancestor-of"),
429            Self::VariantOf => write!(f, "variant-of"),
430            Self::DistributionArtifact => write!(f, "distribution-artifact"),
431            Self::PatchFor => write!(f, "patch-for"),
432            Self::CopyOf => write!(f, "copy-of"),
433            Self::FileAdded => write!(f, "file-added"),
434            Self::FileDeleted => write!(f, "file-deleted"),
435            Self::FileModified => write!(f, "file-modified"),
436            Self::DynamicLink => write!(f, "dynamic-link"),
437            Self::StaticLink => write!(f, "static-link"),
438            Self::Other(s) => write!(f, "{s}"),
439        }
440    }
441}
442
443/// Dependency scope
444#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
445pub enum DependencyScope {
446    #[default]
447    Required,
448    Optional,
449    Excluded,
450}
451
452impl std::fmt::Display for DependencyScope {
453    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454        match self {
455            Self::Required => write!(f, "required"),
456            Self::Optional => write!(f, "optional"),
457            Self::Excluded => write!(f, "excluded"),
458        }
459    }
460}
461
462/// Format-specific extensions that don't map to the canonical model
463#[derive(Debug, Clone, Default, Serialize, Deserialize)]
464pub struct FormatExtensions {
465    /// CycloneDX-specific extensions
466    pub cyclonedx: Option<serde_json::Value>,
467    /// SPDX-specific extensions
468    pub spdx: Option<serde_json::Value>,
469}
470
471/// Component-level extensions
472#[derive(Debug, Clone, Default, Serialize, Deserialize)]
473pub struct ComponentExtensions {
474    /// Properties from `CycloneDX`
475    pub properties: Vec<Property>,
476    /// Annotations from SPDX
477    pub annotations: Vec<Annotation>,
478    /// Raw extension data
479    pub raw: Option<serde_json::Value>,
480}
481
482/// Key-value property
483#[derive(Debug, Clone, Serialize, Deserialize)]
484pub struct Property {
485    pub name: String,
486    pub value: String,
487}
488
489/// Annotation/comment
490#[derive(Debug, Clone, Serialize, Deserialize)]
491pub struct Annotation {
492    pub annotator: String,
493    pub annotation_date: DateTime<Utc>,
494    pub annotation_type: String,
495    pub comment: String,
496}