appletheia_application/command/
default_command_hasher.rs1use std::fmt::Write;
2
3use sha2::{Digest, Sha256};
4
5use crate::command::{Command, CommandHash, CommandHasher, CommandHasherError};
6
7#[derive(Clone, Copy, Debug, Default)]
8pub struct DefaultCommandHasher;
9
10impl DefaultCommandHasher {
11 pub fn new() -> Self {
12 Self
13 }
14
15 fn canonicalize_json(value: serde_json::Value) -> serde_json::Value {
16 match value {
17 serde_json::Value::Array(array) => serde_json::Value::Array(
18 array
19 .into_iter()
20 .map(Self::canonicalize_json)
21 .collect::<Vec<_>>(),
22 ),
23 serde_json::Value::Object(map) => {
24 let mut entries: Vec<(String, serde_json::Value)> = map.into_iter().collect();
25 entries.sort_by(|(a, _), (b, _)| a.cmp(b));
26
27 let mut sorted = serde_json::Map::with_capacity(entries.len());
28 for (key, value) in entries {
29 sorted.insert(key, Self::canonicalize_json(value));
30 }
31 serde_json::Value::Object(sorted)
32 }
33 other => other,
34 }
35 }
36
37 fn to_lower_hex(bytes: &[u8]) -> String {
38 let mut out = String::with_capacity(bytes.len() * 2);
39 for &b in bytes {
40 let _ = write!(&mut out, "{:02x}", b);
41 }
42 out
43 }
44}
45
46impl CommandHasher for DefaultCommandHasher {
47 fn command_hash<C: Command>(&self, command: &C) -> Result<CommandHash, CommandHasherError> {
48 let value = serde_json::to_value(command)?;
49 let canonical = Self::canonicalize_json(value);
50 let json = serde_json::to_string(&canonical)?;
51
52 let mut hasher = Sha256::new();
53 hasher.update(json.as_bytes());
54 let hash = Self::to_lower_hex(&hasher.finalize());
55 Ok(CommandHash::new(hash)?)
56 }
57}