cyclonedx_rust/
metadata.rs

1use std::time::SystemTime;
2
3use chrono::{DateTime, Utc};
4use derive_builder::Builder;
5use serde::{Deserialize, Serialize};
6use yaserde_derive::{YaDeserialize, YaSerialize};
7
8use crate::common::organization::{OrganizationalContact, OrganizationalEntity};
9use crate::component::Component;
10use crate::metadata::tool_type::ToolTypes;
11
12pub mod tool_type;
13
14#[derive(Clone, Builder, PartialEq, Debug, Serialize, Deserialize, YaSerialize, YaDeserialize)]
15#[yaserde(
16    prefix = "ns",
17    default_namespace = "ns",
18    namespace = "ns: http://cyclonedx.org/schema/bom/1.2"
19)]
20pub struct Metadata {
21    #[serde(rename = "timestamp")]
22    #[yaserde(rename = "timestamp", prefix = "ns")]
23    pub time_stamp: String,
24    pub tools: Option<ToolTypes>,
25    pub authors: Option<Authors>,
26    pub component: Option<Component>,
27    pub manufacture: Vec<OrganizationalEntity>,
28    pub supplier: Vec<OrganizationalEntity>,
29}
30
31impl Metadata {
32    pub fn new(
33        tools: Option<ToolTypes>,
34        authors: Option<Authors>,
35        component: Option<Component>,
36        manufacture: Vec<OrganizationalEntity>,
37        supplier: Vec<OrganizationalEntity>,
38    ) -> Metadata {
39        let time_stamp: DateTime<Utc> = SystemTime::now().into();
40        Metadata {
41            time_stamp: time_stamp.to_rfc3339(),
42            tools,
43            authors,
44            component,
45            manufacture,
46            supplier,
47        }
48    }
49}
50
51#[derive(Clone, Builder, PartialEq, Debug, Serialize, Deserialize, YaSerialize, YaDeserialize)]
52pub struct Authors {
53    author: Vec<OrganizationalContact>,
54}
55
56impl Authors {
57    pub fn new(author: Vec<OrganizationalContact>) -> Authors {
58        Authors { author }
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use crate::common::attached_text::BomEncoding;
65    use crate::common::hash_type::HashAlg::*;
66    use crate::common::hash_type::*;
67    use crate::common::organization::*;
68    use crate::metadata::tool_type::*;
69    use crate::metadata::Metadata;
70    use std::fs::File;
71    use std::io::BufReader;
72    use std::path::PathBuf;
73
74    #[test]
75    fn tool_builder() {
76        let mut tool_builder = ToolTypeBuilder::default();
77        let tool: ToolType = tool_builder
78            .name("name".to_string())
79            .version("version".to_string())
80            .vendor("vendor".to_string())
81            .hashes(Option::from(Hashes::new(vec![
82                HashType::new(Sha1, "1234567890".to_string()),
83                HashType::new(Sha256, "0987654321".to_string()),
84            ])))
85            .build()
86            .unwrap();
87
88        assert_eq!(tool.name, "name");
89        assert_eq!(tool.version, "version");
90        assert_eq!(tool.vendor, "vendor");
91        let vec = tool.hashes.unwrap().hash;
92        assert_eq!(vec.len(), 2);
93        assert_eq!(vec[0].alg, Sha1);
94        assert_eq!(vec[0].value, "1234567890".to_string());
95        assert_eq!(vec[1].alg, Sha256);
96        assert_eq!(vec[1].value, "0987654321".to_string());
97    }
98
99    #[test]
100    fn author_builder() {
101        let author: OrganizationalContact = OrganizationalContactBuilder::default()
102            .name(Some("name".to_owned()))
103            .phone(["phone".to_owned()].to_vec())
104            .email(["email".to_owned()].to_vec())
105            .build()
106            .unwrap();
107
108        assert_eq!(author.name, Some(String::from("name")));
109        assert_eq!(author.email, [String::from("email")].to_vec());
110        assert_eq!(author.phone, [String::from("phone")].to_vec());
111    }
112
113    #[test]
114    pub fn can_decode() {
115        let reader = setup("metadata-1.2.xml");
116
117        let response: Metadata = yaserde::de::from_reader(reader).unwrap();
118
119        assert_eq!(response.time_stamp, "2020-04-07T07:01:00Z");
120
121        let tool_types = response.tools.unwrap().tool;
122        assert_eq!(tool_types.len(), 1);
123        assert_eq!(tool_types[0].vendor, "Awesome Vendor");
124        assert_eq!(tool_types[0].name, "Awesome Tool");
125        assert_eq!(tool_types[0].version, "9.1.2");
126
127        let hashes = tool_types[0].hashes.clone().unwrap().hash;
128        assert_eq!(hashes.len(), 2);
129        assert_eq!(hashes[0].alg, HashAlg::Sha1);
130        assert_eq!(hashes[0].value, "25ed8e31b995bb927966616df2a42b979a2717f0");
131        assert_eq!(hashes[1].alg, HashAlg::Sha256);
132        assert_eq!(
133            hashes[1].value,
134            "a74f733635a19aefb1f73e5947cef59cd7440c6952ef0f03d09d974274cbd6df"
135        );
136
137        let authors = response.authors.unwrap().author;
138        assert_eq!(authors.len(), 1);
139        assert_eq!(authors[0].name.as_ref().unwrap(), "Samantha Wright");
140        assert_eq!(authors[0].email[0], "samantha.wright@example.com");
141        assert_eq!(authors[0].phone[0], "800-555-1212");
142
143        let component = response.component.unwrap();
144        assert_eq!(component.name.unwrap(), "Acme Application");
145        assert_eq!(component.version.unwrap(), "9.1.1");
146        let swid = component.swid.unwrap();
147        assert_eq!(
148            swid.tag_id,
149            "swidgen-242eb18a-503e-ca37-393b-cf156ef09691_9.1.1"
150        );
151        assert_eq!(swid.name, "Acme Application");
152        assert_eq!(swid.version.unwrap(), "9.1.1");
153        let text_type = swid.text.unwrap();
154        assert_eq!(text_type.content_type.unwrap(), "text/xml");
155        assert_eq!(text_type.encoding.unwrap(), BomEncoding::Base64);
156        assert_eq!(text_type.value, "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiID8+CjxTb2Z0d2FyZUlkZW50aXR5IHhtbDpsYW5nPSJFTiIgbmFtZT0iQWNtZSBBcHBsaWNhdGlvbiIgdmVyc2lvbj0iOS4xLjEiIAogdmVyc2lvblNjaGVtZT0ibXVsdGlwYXJ0bnVtZXJpYyIgCiB0YWdJZD0ic3dpZGdlbi1iNTk1MWFjOS00MmMwLWYzODItM2YxZS1iYzdhMmE0NDk3Y2JfOS4xLjEiIAogeG1sbnM9Imh0dHA6Ly9zdGFuZGFyZHMuaXNvLm9yZy9pc28vMTk3NzAvLTIvMjAxNS9zY2hlbWEueHNkIj4gCiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiAKIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL3N0YW5kYXJkcy5pc28ub3JnL2lzby8xOTc3MC8tMi8yMDE1LWN1cnJlbnQvc2NoZW1hLnhzZCBzY2hlbWEueHNkIiA+CiAgPE1ldGEgZ2VuZXJhdG9yPSJTV0lEIFRhZyBPbmxpbmUgR2VuZXJhdG9yIHYwLjEiIC8+IAogIDxFbnRpdHkgbmFtZT0iQWNtZSwgSW5jLiIgcmVnaWQ9ImV4YW1wbGUuY29tIiByb2xlPSJ0YWdDcmVhdG9yIiAvPiAKPC9Tb2Z0d2FyZUlkZW50aXR5Pg==");
157
158        assert_eq!(response.manufacture.len(), 1);
159        let manufacturer = response.manufacture[0].clone();
160        assert_eq!(manufacturer.name.as_ref().unwrap(), "Acme, Inc.");
161        assert_eq!(manufacturer.url.len(), 2);
162        assert_eq!(manufacturer.url[0], "https://example.com");
163        assert_eq!(manufacturer.url[1], "https://example2.com");
164        assert_eq!(manufacturer.contact.len(), 1);
165        assert_eq!(
166            manufacturer.contact[0].name.as_ref().unwrap(),
167            "Acme Professional Services"
168        );
169        assert_eq!(
170            manufacturer.contact[0].email[0],
171            "professional.services@example.com"
172        );
173
174        assert_eq!(response.supplier.len(), 1);
175        let manufacturer = response.supplier[0].clone();
176        assert_eq!(manufacturer.name.as_ref().unwrap(), "Acme, Inc.");
177        assert_eq!(manufacturer.url.len(), 1);
178        assert_eq!(manufacturer.url[0], "https://example.com");
179        assert_eq!(manufacturer.contact.len(), 1);
180        assert_eq!(
181            manufacturer.contact[0].name.as_ref().unwrap(),
182            "Acme Distribution"
183        );
184        assert_eq!(manufacturer.contact[0].email[0], "distribution@example.com");
185    }
186
187    fn setup(file: &str) -> BufReader<File> {
188        let mut test_folder = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
189        test_folder.push("resources/test/".to_owned() + file);
190        let file = File::open(test_folder);
191        let reader = BufReader::new(file.unwrap());
192        reader
193    }
194}