sqlite_forensic/
case_uco.rs1use std::fmt::Write as _;
17
18use sqlite_core::Value;
19
20use crate::blob::{identify_media_type, sha256_hex};
21use crate::CarvedRecord;
22
23const CONTEXT: &str = "\"@context\":{\
25 \"kb\":\"http://example.org/kb/\",\
26 \"uco-core\":\"https://ontology.unifiedcyberontology.org/uco/core/\",\
27 \"uco-observable\":\"https://ontology.unifiedcyberontology.org/uco/observable/\",\
28 \"uco-types\":\"https://ontology.unifiedcyberontology.org/uco/types/\",\
29 \"uco-vocabulary\":\"https://ontology.unifiedcyberontology.org/uco/vocabulary/\",\
30 \"xsd\":\"http://www.w3.org/2001/XMLSchema#\"}";
31
32#[must_use]
38pub fn bundle_for_records(records: &[CarvedRecord]) -> String {
39 let blobs: Vec<&[u8]> = records
40 .iter()
41 .flat_map(|r| r.values.iter())
42 .filter_map(|v| match v {
43 Value::Blob(b) => Some(b.as_slice()),
44 _ => None,
45 })
46 .collect();
47
48 let mut graph = String::new();
49 for (i, bytes) in blobs.iter().enumerate() {
50 if i > 0 {
51 graph.push(',');
52 }
53 let hash = sha256_hex(bytes);
54 let mime = identify_media_type(bytes)
56 .map(|m| format!("\"uco-observable:mimeType\":\"{m}\","))
57 .unwrap_or_default();
58 let _ = write!(
59 graph,
60 "{{\"@id\":\"kb:observable-blob-{i}\",\
61 \"@type\":\"uco-observable:ObservableObject\",\
62 \"uco-core:hasFacet\":[{{\"@id\":\"kb:content-data-facet-{i}\",\
63 \"@type\":\"uco-observable:ContentDataFacet\",\
64 {mime}\"uco-observable:sizeInBytes\":{size},\
65 \"uco-observable:hash\":[{{\"@type\":\"uco-types:Hash\",\
66 \"uco-types:hashMethod\":{{\"@type\":\"uco-vocabulary:HashNameVocab\",\"@value\":\"SHA256\"}},\
67 \"uco-types:hashValue\":{{\"@type\":\"xsd:hexBinary\",\"@value\":\"{hash}\"}}}}]}}]}}",
68 size = bytes.len()
69 );
70 }
71
72 format!("{{{CONTEXT},\"@graph\":[{graph}]}}")
73}