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}