observer_core/
normalize.rs1use crate::error::{ObserverError, ObserverResult};
5use crate::inventory::Inventory;
6use crate::product::ProductDefinition;
7use crate::suite::SuiteCore;
8use serde_json::{Map, Value};
9use sha2::{Digest, Sha256};
10
11pub fn normalized_inventory_sha256(inventory: &Inventory) -> ObserverResult<String> {
12 Ok(hash_hex(inventory.to_canonical_text().as_bytes()))
13}
14
15pub fn normalized_suite_sha256(suite: &SuiteCore) -> ObserverResult<String> {
16 let value = serde_json::to_value(suite).map_err(|error| ObserverError::Normalize(error.to_string()))?;
17 let value = wrap_suite_core(value);
18 let canonical = canonicalize_json(value);
19 let bytes = serde_json::to_vec(&canonical)
20 .map_err(|error| ObserverError::Normalize(error.to_string()))?;
21 Ok(hash_hex(&bytes))
22}
23
24pub fn normalized_product_sha256(product: &ProductDefinition) -> ObserverResult<String> {
25 product.validate()?;
26 let value = serde_json::to_value(product).map_err(|error| ObserverError::Normalize(error.to_string()))?;
27 let canonical = canonicalize_json(value);
28 let bytes = serde_json::to_vec(&canonical)
29 .map_err(|error| ObserverError::Normalize(error.to_string()))?;
30 Ok(hash_hex(&bytes))
31}
32
33fn hash_hex(bytes: &[u8]) -> String {
34 let digest = Sha256::digest(bytes);
35 format!("{digest:x}")
36}
37
38fn wrap_suite_core(items: Value) -> Value {
39 let mut object = Map::new();
40 object.insert("k".to_owned(), Value::String("observer_suite_core".to_owned()));
41 object.insert("v".to_owned(), Value::String("0".to_owned()));
42 match items {
43 Value::Object(mut suite) => {
44 let items = suite.remove("items").unwrap_or(Value::Array(Vec::new()));
45 object.insert("items".to_owned(), items);
46 }
47 _ => {
48 object.insert("items".to_owned(), Value::Array(Vec::new()));
49 }
50 }
51 Value::Object(object)
52}
53
54fn canonicalize_json(value: Value) -> Value {
55 match value {
56 Value::Object(object) => {
57 let mut ordered = Map::new();
58 let mut entries = object.into_iter().collect::<Vec<_>>();
59 entries.sort_by(|left, right| left.0.as_bytes().cmp(right.0.as_bytes()));
60 for (key, value) in entries {
61 ordered.insert(key, canonicalize_json(value));
62 }
63 Value::Object(ordered)
64 }
65 Value::Array(items) => Value::Array(items.into_iter().map(canonicalize_json).collect()),
66 other => other,
67 }
68}