1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::HashMap;
5use uuid::Uuid;
6fn default_now() -> DateTime<Utc> { Utc::now() }
7
8pub trait StixObject {
10 fn id(&self) -> &str;
11 fn type_(&self) -> &str;
12 fn created(&self) -> DateTime<Utc>;
13}
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, )]
17#[serde(rename_all = "snake_case")]
18pub struct GranularMarking {
19 pub marking_ref: Option<String>,
20 pub selectors: Vec<String>,
21 pub lang: Option<String>,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, )]
26#[serde(rename_all = "snake_case")]
27pub struct CommonProperties {
28 #[serde(rename = "type")]
29 pub r#type: String,
30
31 pub id: String,
32
33 #[serde(rename = "spec_version")]
34 pub spec_version: Option<String>,
35
36 #[serde(default = "default_now")]
37 pub created: DateTime<Utc>,
38
39 #[serde(default = "default_now")]
40 pub modified: DateTime<Utc>,
41
42 pub created_by_ref: Option<String>,
43
44 pub revoked: Option<bool>,
45
46 pub labels: Option<Vec<String>>,
47
48 pub confidence: Option<u8>,
49
50 pub lang: Option<String>,
51
52 pub external_references: Option<Vec<ExternalReference>>,
53
54 pub object_marking_refs: Option<Vec<String>>,
55
56 pub granular_markings: Option<Vec<GranularMarking>>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub extensions: Option<HashMap<String, Value>>,
60
61 #[serde(flatten)]
62 pub custom_properties: HashMap<String, Value>,
63}
64
65impl Default for CommonProperties {
66 fn default() -> Self {
67 let now = Utc::now();
68 Self {
69 r#type: String::new(),
70 id: generate_stix_id("object"),
71 spec_version: Some("2.1".to_string()),
72 created: now,
73 modified: now,
74 created_by_ref: None,
75 revoked: None,
76 labels: None,
77 confidence: None,
78 lang: None,
79 external_references: None,
80 object_marking_refs: None,
81 granular_markings: None,
82 extensions: None,
83 custom_properties: HashMap::new(),
84 }
85 }
86}
87
88impl CommonProperties {
89 pub fn new(object_type: impl Into<String>, created_by_ref: Option<String>) -> Self {
90 let object_type = object_type.into();
91 let mut cp = Self::default();
92 cp.r#type = object_type.clone();
93 cp.id = generate_stix_id(&object_type);
94 cp.created_by_ref = created_by_ref;
95 cp
96 }
97
98 pub fn new_version(&mut self) {
122 self.modified = Utc::now();
123 }
124}
125
126impl StixObject for CommonProperties {
127 fn id(&self) -> &str {
128 &self.id
129 }
130
131 fn type_(&self) -> &str {
132 &self.r#type
133 }
134
135 fn created(&self) -> DateTime<Utc> {
136 self.created
137 }
138}
139
140pub fn generate_stix_id(object_type: &str) -> String {
141 format!("{}--{}", object_type, Uuid::new_v4())
142}
143
144pub fn is_valid_stix_id(id: &str) -> bool {
159 let parts: Vec<&str> = id.split("--").collect();
160 if parts.len() != 2 {
161 return false;
162 }
163
164 Uuid::parse_str(parts[1]).is_ok()
166}
167
168pub fn extract_type_from_id(id: &str) -> Option<&str> {
182 let parts: Vec<&str> = id.split("--").collect();
183 if parts.len() == 2 && Uuid::parse_str(parts[1]).is_ok() {
184 Some(parts[0])
185 } else {
186 None
187 }
188}
189
190pub fn is_valid_ref_for_type(id: &str, expected_type: &str) -> bool {
207 extract_type_from_id(id).map(|t| t == expected_type).unwrap_or(false)
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, )]
214#[serde(rename_all = "snake_case")]
215pub struct ExternalReference {
216 pub source_name: String,
217 pub description: Option<String>,
218 pub url: Option<String>,
219 pub external_id: Option<String>,
220 pub hashes: Option<HashMap<String, String>>,
221}
222
223impl ExternalReference {
224 pub fn new(source_name: impl Into<String>) -> Self {
225 Self {
226 source_name: source_name.into(),
227 description: None,
228 url: None,
229 external_id: None,
230 hashes: None,
231 }
232 }
233
234 pub fn builder() -> ExternalReferenceBuilder {
235 ExternalReferenceBuilder::default()
236 }
237}
238
239#[derive(Debug, Default)]
240pub struct ExternalReferenceBuilder {
241 source_name: Option<String>,
242 description: Option<String>,
243 url: Option<String>,
244 external_id: Option<String>,
245 hashes: Option<HashMap<String, String>>,
246}
247
248impl ExternalReferenceBuilder {
249 pub fn source_name(mut self, name: impl Into<String>) -> Self {
250 self.source_name = Some(name.into());
251 self
252 }
253
254 pub fn description(mut self, desc: impl Into<String>) -> Self {
255 self.description = Some(desc.into());
256 self
257 }
258
259 pub fn url(mut self, url: impl Into<String>) -> Self {
260 self.url = Some(url.into());
261 self
262 }
263
264 pub fn external_id(mut self, id: impl Into<String>) -> Self {
265 self.external_id = Some(id.into());
266 self
267 }
268
269 pub fn hashes(mut self, hashes: HashMap<String, String>) -> Self {
270 self.hashes = Some(hashes);
271 self
272 }
273
274 pub fn build(self) -> Result<ExternalReference, &'static str> {
275 let source_name = self.source_name.ok_or("missing source_name")?;
276 Ok(ExternalReference {
277 source_name,
278 description: self.description,
279 url: self.url,
280 external_id: self.external_id,
281 hashes: self.hashes,
282 })
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, )]
288#[serde(rename_all = "snake_case")]
289pub struct MarkingDefinition {
290 #[serde(flatten)]
291 pub common: CommonProperties,
292
293 pub definition_type: String,
294 pub definition: serde_json::Value,
295 pub name: Option<String>,
296}
297
298impl MarkingDefinition {
299 pub fn new(definition_type: impl Into<String>, definition: serde_json::Value) -> Self {
300 Self {
301 common: CommonProperties::new("marking-definition", None),
302 definition_type: definition_type.into(),
303 definition,
304 name: None,
305 }
306 }
307
308 pub fn builder() -> MarkingDefinitionBuilder {
309 MarkingDefinitionBuilder::default()
310 }
311
312 pub fn tlp(level: impl Into<String>) -> Self {
314 let level = level.into();
315 let definition = serde_json::json!({
316 "tlp": level.to_lowercase()
317 });
318 Self {
319 common: CommonProperties::new("marking-definition", None),
320 definition_type: "tlp".to_string(),
321 definition,
322 name: Some(format!("TLP:{}", level.to_uppercase())),
323 }
324 }
325}
326
327#[derive(Debug, Default)]
328pub struct MarkingDefinitionBuilder {
329 definition_type: Option<String>,
330 definition: Option<serde_json::Value>,
331 name: Option<String>,
332 created_by_ref: Option<String>,
333}
334
335impl MarkingDefinitionBuilder {
336 pub fn definition_type(mut self, dt: impl Into<String>) -> Self {
337 self.definition_type = Some(dt.into());
338 self
339 }
340
341 pub fn definition(mut self, def: serde_json::Value) -> Self {
342 self.definition = Some(def);
343 self
344 }
345
346 pub fn name(mut self, name: impl Into<String>) -> Self {
347 self.name = Some(name.into());
348 self
349 }
350
351 pub fn created_by_ref(mut self, r: impl Into<String>) -> Self {
352 self.created_by_ref = Some(r.into());
353 self
354 }
355
356 pub fn build(self) -> Result<MarkingDefinition, &'static str> {
357 let definition_type = self.definition_type.ok_or("missing definition_type")?;
358 let definition = self.definition.ok_or("missing definition")?;
359 Ok(MarkingDefinition {
360 common: CommonProperties::new("marking-definition", self.created_by_ref),
361 definition_type,
362 definition,
363 name: self.name,
364 })
365 }
366}
367
368impl StixObject for MarkingDefinition {
369 fn id(&self) -> &str {
370 &self.common.id
371 }
372
373 fn type_(&self) -> &str {
374 &self.common.r#type
375 }
376
377 fn created(&self) -> DateTime<Utc> {
378 self.common.created
379 }
380}
381
382#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, )]
384#[serde(rename_all = "snake_case")]
385pub struct LanguageContent {
386 #[serde(flatten)]
387 pub common: CommonProperties,
388
389 pub object_ref: String,
390 pub object_modified: DateTime<Utc>,
391 pub contents: HashMap<String, HashMap<String, String>>,
392}
393
394impl LanguageContent {
395 pub fn builder() -> LanguageContentBuilder {
396 LanguageContentBuilder::default()
397 }
398}
399
400#[derive(Debug, Default)]
401pub struct LanguageContentBuilder {
402 object_ref: Option<String>,
403 object_modified: Option<DateTime<Utc>>,
404 contents: Option<HashMap<String, HashMap<String, String>>>,
405 created_by_ref: Option<String>,
406}
407
408impl LanguageContentBuilder {
409 pub fn object_ref(mut self, r: impl Into<String>) -> Self {
410 self.object_ref = Some(r.into());
411 self
412 }
413
414 pub fn object_modified(mut self, t: DateTime<Utc>) -> Self {
415 self.object_modified = Some(t);
416 self
417 }
418
419 pub fn contents(mut self, c: HashMap<String, HashMap<String, String>>) -> Self {
420 self.contents = Some(c);
421 self
422 }
423
424 pub fn created_by_ref(mut self, r: impl Into<String>) -> Self {
425 self.created_by_ref = Some(r.into());
426 self
427 }
428
429 pub fn build(self) -> Result<LanguageContent, &'static str> {
430 let object_ref = self.object_ref.ok_or("missing object_ref")?;
431 let object_modified = self.object_modified.ok_or("missing object_modified")?;
432 let contents = self.contents.ok_or("missing contents")?;
433 Ok(LanguageContent {
434 common: CommonProperties::new("language-content", self.created_by_ref),
435 object_ref,
436 object_modified,
437 contents,
438 })
439 }
440}
441
442impl StixObject for LanguageContent {
443 fn id(&self) -> &str {
444 &self.common.id
445 }
446
447 fn type_(&self) -> &str {
448 &self.common.r#type
449 }
450
451 fn created(&self) -> DateTime<Utc> {
452 self.common.created
453 }
454}
455
456#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, )]
458#[serde(rename_all = "snake_case")]
459pub struct ExtensionDefinition {
460 #[serde(flatten)]
461 pub common: CommonProperties,
462
463 pub name: String,
464 pub description: Option<String>,
465 pub schema: String,
466 pub version: String,
467 pub extension_types: Vec<String>,
468}
469
470impl ExtensionDefinition {
471 pub fn builder() -> ExtensionDefinitionBuilder {
472 ExtensionDefinitionBuilder::default()
473 }
474}
475
476#[derive(Debug, Default)]
477pub struct ExtensionDefinitionBuilder {
478 name: Option<String>,
479 description: Option<String>,
480 schema: Option<String>,
481 version: Option<String>,
482 extension_types: Option<Vec<String>>,
483 created_by_ref: Option<String>,
484}
485
486impl ExtensionDefinitionBuilder {
487 pub fn name(mut self, n: impl Into<String>) -> Self {
488 self.name = Some(n.into());
489 self
490 }
491
492 pub fn description(mut self, d: impl Into<String>) -> Self {
493 self.description = Some(d.into());
494 self
495 }
496
497 pub fn schema(mut self, s: impl Into<String>) -> Self {
498 self.schema = Some(s.into());
499 self
500 }
501
502 pub fn version(mut self, v: impl Into<String>) -> Self {
503 self.version = Some(v.into());
504 self
505 }
506
507 pub fn extension_types(mut self, t: Vec<String>) -> Self {
508 self.extension_types = Some(t);
509 self
510 }
511
512 pub fn created_by_ref(mut self, r: impl Into<String>) -> Self {
513 self.created_by_ref = Some(r.into());
514 self
515 }
516
517 pub fn build(self) -> Result<ExtensionDefinition, &'static str> {
518 let name = self.name.ok_or("missing name")?;
519 let schema = self.schema.ok_or("missing schema")?;
520 let version = self.version.ok_or("missing version")?;
521 let extension_types = self.extension_types.ok_or("missing extension_types")?;
522 Ok(ExtensionDefinition {
523 common: CommonProperties::new("extension-definition", self.created_by_ref),
524 name,
525 description: self.description,
526 schema,
527 version,
528 extension_types,
529 })
530 }
531}
532
533impl StixObject for ExtensionDefinition {
534 fn id(&self) -> &str {
535 &self.common.id
536 }
537
538 fn type_(&self) -> &str {
539 &self.common.r#type
540 }
541
542 fn created(&self) -> DateTime<Utc> {
543 self.common.created
544 }
545}