1use zerodds_cdr::{BufferWriter, EncodeError, Endianness};
24use zerodds_foundation::md5;
25
26use crate::type_identifier::kinds::{EK_COMPLETE, EK_MINIMAL, EQUIVALENCE_HASH_LEN};
27use crate::type_identifier::{EquivalenceHash, TypeIdentifier};
28use crate::type_object::{CompleteTypeObject, MinimalTypeObject, TypeObject};
29
30pub fn compute_hash(to: &TypeObject) -> Result<EquivalenceHash, EncodeError> {
38 let bytes = to.to_bytes_le()?;
39 Ok(hash_bytes(&bytes))
40}
41
42pub fn compute_minimal_hash(t: &MinimalTypeObject) -> Result<EquivalenceHash, EncodeError> {
49 let mut w = BufferWriter::new(Endianness::Little);
50 w.write_u8(EK_MINIMAL)?;
51 t.encode_into(&mut w)?;
52 Ok(hash_bytes(&w.into_bytes()))
53}
54
55pub fn compute_complete_hash(t: &CompleteTypeObject) -> Result<EquivalenceHash, EncodeError> {
60 let mut w = BufferWriter::new(Endianness::Little);
61 w.write_u8(EK_COMPLETE)?;
62 t.encode_into(&mut w)?;
63 Ok(hash_bytes(&w.into_bytes()))
64}
65
66#[must_use]
72pub fn hash_bytes(data: &[u8]) -> EquivalenceHash {
73 let digest = md5(data);
74 let mut out = [0u8; EQUIVALENCE_HASH_LEN];
75 out.copy_from_slice(&digest[..EQUIVALENCE_HASH_LEN]);
76 EquivalenceHash(out)
77}
78
79pub fn to_hashed_type_identifier(to: &TypeObject) -> Result<TypeIdentifier, EncodeError> {
86 let h = compute_hash(to)?;
87 Ok(match to {
88 TypeObject::Minimal(_) => TypeIdentifier::EquivalenceHashMinimal(h),
89 TypeObject::Complete(_) => TypeIdentifier::EquivalenceHashComplete(h),
90 })
91}
92
93#[cfg(test)]
98#[allow(clippy::unwrap_used)]
99mod tests {
100 use super::*;
101
102 use crate::type_identifier::PrimitiveKind;
103 use crate::type_object::common::{CommonStructMember, NameHash};
104 use crate::type_object::flags::{StructMemberFlag, StructTypeFlag};
105 use crate::type_object::minimal::{
106 MinimalStructHeader, MinimalStructMember, MinimalStructType,
107 };
108
109 fn sample_minimal_struct(field_count: u32) -> MinimalTypeObject {
110 MinimalTypeObject::Struct(MinimalStructType {
111 struct_flags: StructTypeFlag(StructTypeFlag::IS_APPENDABLE),
112 header: MinimalStructHeader {
113 base_type: TypeIdentifier::None,
114 },
115 member_seq: (0..field_count)
116 .map(|i| MinimalStructMember {
117 common: CommonStructMember {
118 member_id: i + 1,
119 member_flags: StructMemberFlag::default(),
120 member_type_id: TypeIdentifier::Primitive(PrimitiveKind::Int64),
121 },
122 detail: NameHash([i as u8; 4]),
123 })
124 .collect(),
125 })
126 }
127
128 #[test]
129 fn hash_is_14_bytes_and_deterministic() {
130 let t = sample_minimal_struct(3);
131 let h1 = compute_minimal_hash(&t).unwrap();
132 let h2 = compute_minimal_hash(&t).unwrap();
133 assert_eq!(h1, h2);
134 assert_eq!(h1.0.len(), 14);
135 }
136
137 #[test]
138 fn different_type_objects_have_different_hashes() {
139 let t1 = sample_minimal_struct(3);
140 let t2 = sample_minimal_struct(4);
141 let h1 = compute_minimal_hash(&t1).unwrap();
142 let h2 = compute_minimal_hash(&t2).unwrap();
143 assert_ne!(h1, h2);
144 }
145
146 #[test]
147 fn minimal_and_complete_same_semantic_differ_in_hash() {
148 use crate::type_object::common::{
152 AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteMemberDetail,
153 CompleteTypeDetail, OptionalAppliedAnnotationSeq,
154 };
155 use crate::type_object::complete::{
156 CompleteStructHeader, CompleteStructMember, CompleteStructType,
157 };
158
159 let minimal = sample_minimal_struct(1);
160 let complete = CompleteStructType {
161 struct_flags: StructTypeFlag(StructTypeFlag::IS_APPENDABLE),
162 header: CompleteStructHeader {
163 base_type: TypeIdentifier::None,
164 detail: CompleteTypeDetail {
165 ann_builtin: AppliedBuiltinTypeAnnotations::default(),
166 ann_custom: OptionalAppliedAnnotationSeq::default(),
167 type_name: alloc::string::String::from("::Sample"),
168 },
169 },
170 member_seq: alloc::vec![CompleteStructMember {
171 common: CommonStructMember {
172 member_id: 1,
173 member_flags: StructMemberFlag::default(),
174 member_type_id: TypeIdentifier::Primitive(PrimitiveKind::Int64),
175 },
176 detail: CompleteMemberDetail {
177 name: alloc::string::String::from("x"),
178 ann_builtin: AppliedBuiltinMemberAnnotations::default(),
179 ann_custom: OptionalAppliedAnnotationSeq::default(),
180 },
181 }],
182 };
183
184 let hm = compute_minimal_hash(&minimal).unwrap();
185 let complete_wrapped = CompleteTypeObject::Struct(complete);
186 let hc = compute_complete_hash(&complete_wrapped).unwrap();
187 assert_ne!(hm, hc, "minimal and complete must hash to different values");
188 }
189
190 #[test]
191 fn to_hashed_type_identifier_picks_correct_kind() {
192 let minimal = sample_minimal_struct(2);
193 let ti = to_hashed_type_identifier(&TypeObject::Minimal(minimal.clone())).unwrap();
194 assert!(matches!(ti, TypeIdentifier::EquivalenceHashMinimal(_)));
195
196 use crate::type_object::common::{
200 AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteTypeDetail,
201 OptionalAppliedAnnotationSeq,
202 };
203 use crate::type_object::complete::{CompleteCollectionElement, CompleteSequenceType};
204 use crate::type_object::flags::{CollectionElementFlag, CollectionTypeFlag};
205 use crate::type_object::minimal::CommonCollectionElement;
206
207 let complete_seq = CompleteSequenceType {
208 collection_flag: CollectionTypeFlag::default(),
209 bound: 10,
210 detail: CompleteTypeDetail {
211 ann_builtin: AppliedBuiltinTypeAnnotations::default(),
212 ann_custom: OptionalAppliedAnnotationSeq::default(),
213 type_name: alloc::string::String::from("::Seq"),
214 },
215 element: CompleteCollectionElement {
216 common: CommonCollectionElement {
217 element_flags: CollectionElementFlag::default(),
218 type_id: TypeIdentifier::Primitive(PrimitiveKind::Int32),
219 },
220 ann_builtin: AppliedBuiltinMemberAnnotations::default(),
221 ann_custom: OptionalAppliedAnnotationSeq::default(),
222 },
223 };
224 let ti2 = to_hashed_type_identifier(&TypeObject::Complete(CompleteTypeObject::Sequence(
225 complete_seq,
226 )))
227 .unwrap();
228 assert!(matches!(ti2, TypeIdentifier::EquivalenceHashComplete(_)));
229 }
230
231 #[test]
232 fn hash_bytes_matches_md5_truncated_reference() {
233 let h = hash_bytes(b"");
236 let expected: [u8; 14] = [
237 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8,
238 ];
239 assert_eq!(h.0, expected);
240 }
241
242 #[test]
243 fn hash_bytes_deterministic_for_known_input() {
244 let h1 = hash_bytes(b"ZeroDDS");
245 let h2 = hash_bytes(b"ZeroDDS");
246 assert_eq!(h1, h2);
247 let h3 = hash_bytes(b"ZeroDDs"); assert_ne!(h1, h3);
249 }
250}