ion_binary_rs/ion_hash.rs
1use crate::ion_hash_encoder::encode_value;
2use crate::IonValue;
3use digest::Digest;
4use sha2::Sha256;
5use std::cmp::{Ordering, PartialEq};
6use std::marker::PhantomData;
7
8/// Ion Hash implementation. Once the hasher is initialized you can add new values to it
9/// and it will perform the dot operation internally. Once you added everything you want
10/// to add just call `get()` and it will provide you with a &[u8] slice containing the
11/// hash.
12///
13/// You can use the method digest if you want to hash only one IonValue.
14///
15/// ```rust,no_run
16/// use sha2::Sha256;
17/// use ion_binary_rs::{IonHash, IonValue};
18/// use std::collections::HashMap;
19///
20/// let mut ion_struct = HashMap::new();
21///
22/// ion_struct.insert("Model".to_string(), IonValue::String("CLK 350".to_string()));
23/// ion_struct.insert("Type".to_string(), IonValue::String("Sedan".to_string()));
24/// ion_struct.insert("Color".to_string(), IonValue::String("White".to_string()));
25/// ion_struct.insert(
26/// "VIN".to_string(),
27/// IonValue::String("1C4RJFAG0FC625797".to_string()),
28/// );
29/// ion_struct.insert("Make".to_string(), IonValue::String("Mercedes".to_string()));
30/// ion_struct.insert("Year".to_string(), IonValue::Integer(2019));
31///
32/// let ion_value = IonValue::Struct(ion_struct);
33///
34/// let hash = IonHash::digest::<Sha256>(&ion_value);
35///
36/// println!("{:X?}", hash);
37/// ```
38///
39#[derive(Debug)]
40pub struct IonHash<D: Digest = Sha256> {
41 buffer: Vec<u8>,
42 hasher_type: PhantomData<D>,
43}
44
45impl<D: Digest> IonHash<D> {
46 /// Hashes the bytes and perform a dot operation with
47 /// current version of the IonHash hash.
48 pub fn add_bytes(&mut self, value: &[u8]) {
49 let value = IonHash::from_bytes::<D>(value);
50
51 self.dot(value);
52 }
53
54 /// Assumes that the bytes are already hashed and performs
55 /// the dot operation with current version of the IonHash
56 /// hash.
57 pub fn add_hashed_bytes(&mut self, value: &[u8]) {
58 let value = IonHash::from_hashes_bytes::<D>(value);
59
60 self.dot(value);
61 }
62
63 /// Serializes and hashes the Ion Value and performs
64 /// the dot operation with current version of the IonHash
65 /// hash.
66 pub fn add_ion_value(&mut self, value: &IonValue) {
67 let buffer = encode_value::<D>(value);
68
69 let value = IonHash::from_bytes::<D>(&buffer);
70
71 self.dot(value);
72 }
73
74 /// performs the dot operation with current version of the
75 /// IonHash hash.
76 pub fn dot(&mut self, value: IonHash<D>) -> &mut Self {
77 if value.buffer.is_empty() {
78 return self;
79 }
80
81 if self.buffer.is_empty() {
82 self.buffer = value.buffer;
83 return self;
84 }
85
86 let mut buffer: Vec<u8> = vec![];
87
88 if *self < value {
89 buffer.extend(self.get());
90 buffer.extend(value.get());
91 } else {
92 buffer.extend(value.get());
93 buffer.extend(self.get());
94 }
95
96 self.buffer = D::digest(&buffer).to_vec();
97
98 self
99 }
100
101 /// Gets the current hash. Useful for when you need to
102 /// extract the final result after several operations.
103 pub fn get(&self) -> &[u8] {
104 &self.buffer
105 }
106}
107
108impl IonHash {
109 /// Creates an empty Ion Hash with the default hasher: Sha256
110 pub fn new() -> IonHash {
111 IonHash {
112 buffer: vec![],
113 hasher_type: PhantomData,
114 }
115 }
116
117 /// Creates a hasher with some starting bytes which will
118 /// be first hashed
119 pub fn from_bytes<D: Digest>(buf: &[u8]) -> IonHash<D> {
120 let hased_bytes = D::digest(buf);
121 IonHash::from_hashes_bytes(&hased_bytes)
122 }
123
124 /// Creates a hasher with some starting hash
125 pub fn from_hashes_bytes<D: Digest>(buf: &[u8]) -> IonHash<D> {
126 IonHash {
127 buffer: buf.to_vec(),
128 hasher_type: PhantomData,
129 }
130 }
131
132 /// Creates a hasher with some starting Ion Value which will
133 /// be first serialized and hashed
134 pub fn from_ion_value<D: Digest>(value: &IonValue) -> IonHash<D> {
135 let mut hash = IonHash::with_hasher::<D>();
136
137 hash.add_ion_value(value);
138
139 hash
140 }
141
142 /// Creates an empty hasher using the provided hasher
143 pub fn with_hasher<D: Digest>() -> IonHash<D> {
144 IonHash {
145 buffer: vec![],
146 hasher_type: PhantomData,
147 }
148 }
149
150 /// Shorthand method for hashing an Ion Value in one step.
151 pub fn digest<D: Digest>(value: &IonValue) -> Vec<u8> {
152 IonHash::from_ion_value::<D>(value).get().to_vec()
153 }
154
155 /// Shorthand method for hashing an Ion Value in one step.
156 /// It uses the default hasher: Sha256
157 pub fn default_digest(value: &IonValue) -> Vec<u8> {
158 IonHash::from_ion_value::<Sha256>(value).get().to_vec()
159 }
160}
161
162impl Default for IonHash {
163 fn default() -> IonHash<Sha256> {
164 IonHash::with_hasher::<Sha256>()
165 }
166}
167
168impl<D: Digest> PartialEq for IonHash<D> {
169 fn eq(&self, _: &IonHash<D>) -> bool {
170 self.buffer == self.get()
171 }
172}
173
174impl<D: Digest> PartialOrd for IonHash<D> {
175 fn partial_cmp(&self, value: &IonHash<D>) -> Option<Ordering> {
176 self.buffer
177 .iter()
178 .rev()
179 .map(|byte| *byte as i8)
180 .partial_cmp(value.get().iter().rev().map(|byte| *byte as i8))
181 }
182}