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)]
388pub enum DependencyType {
389    /// Direct dependency
390    DependsOn,
391    /// Optional dependency
392    OptionalDependsOn,
393    /// Development dependency
394    DevDependsOn,
395    /// Build dependency
396    BuildDependsOn,
397    /// Test dependency
398    TestDependsOn,
399    /// Runtime dependency
400    RuntimeDependsOn,
401    /// Provided dependency (e.g., Java provided scope)
402    ProvidedDependsOn,
403    /// Describes relationship (SPDX)
404    Describes,
405    /// Generates relationship
406    Generates,
407    /// Contains relationship
408    Contains,
409    /// Ancestor of
410    AncestorOf,
411    /// Variant of
412    VariantOf,
413    /// Distribution artifact
414    DistributionArtifact,
415    /// Patch for
416    PatchFor,
417    /// Copy of
418    CopyOf,
419    /// File added
420    FileAdded,
421    /// File deleted
422    FileDeleted,
423    /// File modified
424    FileModified,
425    /// Dynamic link
426    DynamicLink,
427    /// Static link
428    StaticLink,
429    /// Other relationship
430    Other(String),
431}
432
433impl std::fmt::Display for DependencyType {
434    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
435        match self {
436            Self::DependsOn => write!(f, "depends-on"),
437            Self::OptionalDependsOn => write!(f, "optional-depends-on"),
438            Self::DevDependsOn => write!(f, "dev-depends-on"),
439            Self::BuildDependsOn => write!(f, "build-depends-on"),
440            Self::TestDependsOn => write!(f, "test-depends-on"),
441            Self::RuntimeDependsOn => write!(f, "runtime-depends-on"),
442            Self::ProvidedDependsOn => write!(f, "provided-depends-on"),
443            Self::Describes => write!(f, "describes"),
444            Self::Generates => write!(f, "generates"),
445            Self::Contains => write!(f, "contains"),
446            Self::AncestorOf => write!(f, "ancestor-of"),
447            Self::VariantOf => write!(f, "variant-of"),
448            Self::DistributionArtifact => write!(f, "distribution-artifact"),
449            Self::PatchFor => write!(f, "patch-for"),
450            Self::CopyOf => write!(f, "copy-of"),
451            Self::FileAdded => write!(f, "file-added"),
452            Self::FileDeleted => write!(f, "file-deleted"),
453            Self::FileModified => write!(f, "file-modified"),
454            Self::DynamicLink => write!(f, "dynamic-link"),
455            Self::StaticLink => write!(f, "static-link"),
456            Self::Other(s) => write!(f, "{s}"),
457        }
458    }
459}
460
461/// Dependency scope
462#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
463pub enum DependencyScope {
464    #[default]
465    Required,
466    Optional,
467    Excluded,
468}
469
470impl std::fmt::Display for DependencyScope {
471    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472        match self {
473            Self::Required => write!(f, "required"),
474            Self::Optional => write!(f, "optional"),
475            Self::Excluded => write!(f, "excluded"),
476        }
477    }
478}
479
480/// Format-specific extensions that don't map to the canonical model
481#[derive(Debug, Clone, Default, Serialize, Deserialize)]
482pub struct FormatExtensions {
483    /// CycloneDX-specific extensions
484    pub cyclonedx: Option<serde_json::Value>,
485    /// SPDX-specific extensions
486    pub spdx: Option<serde_json::Value>,
487}
488
489/// Component-level extensions
490#[derive(Debug, Clone, Default, Serialize, Deserialize)]
491pub struct ComponentExtensions {
492    /// Properties from `CycloneDX`
493    pub properties: Vec<Property>,
494    /// Annotations from SPDX
495    pub annotations: Vec<Annotation>,
496    /// Raw extension data
497    pub raw: Option<serde_json::Value>,
498}
499
500/// Key-value property
501#[derive(Debug, Clone, Serialize, Deserialize)]
502pub struct Property {
503    pub name: String,
504    pub value: String,
505}
506
507/// Annotation/comment
508#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct Annotation {
510    pub annotator: String,
511    pub annotation_date: DateTime<Utc>,
512    pub annotation_type: String,
513    pub comment: String,
514}