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