1use crate::{Result, types::TypeDescriptionMsg};
6use sha2::{Digest, Sha256};
7
8const RIHS01_PREFIX: &str = "RIHS01_";
10
11pub fn calculate_type_hash(type_description: &TypeDescriptionMsg) -> Result<String> {
34 fn escape_json_string(s: &str) -> String {
43 s.replace('\\', "\\\\")
45 .replace('"', "\\\"")
46 .replace('\n', "\\n")
47 .replace('\r', "\\r")
48 .replace('\t', "\\t")
49 }
50
51 fn field_to_json(field: &crate::types::Field) -> String {
52 format!(
53 r#"{{"name": "{}", "type": {{"type_id": {}, "capacity": {}, "string_capacity": {}, "nested_type_name": "{}"}}}}"#,
54 escape_json_string(&field.name),
55 field.field_type.type_id,
56 field.field_type.capacity,
57 field.field_type.string_capacity,
58 escape_json_string(&field.field_type.nested_type_name)
59 )
60 }
61
62 fn type_desc_to_json(td: &crate::types::IndividualTypeDescription) -> String {
63 let fields_json: Vec<String> = td.fields.iter().map(field_to_json).collect();
64 format!(
65 r#"{{"type_name": "{}", "fields": [{}]}}"#,
66 escape_json_string(&td.type_name),
67 fields_json.join(", ")
68 )
69 }
70
71 let type_desc_json = type_desc_to_json(&type_description.type_description);
73
74 let mut sorted_refs = type_description.referenced_type_descriptions.clone();
76 sorted_refs.sort_by(|a, b| a.type_name.cmp(&b.type_name));
77
78 let ref_types_json: Vec<String> = sorted_refs.iter().map(type_desc_to_json).collect();
79
80 let hashable_repr = format!(
81 r#"{{"type_description": {}, "referenced_type_descriptions": [{}]}}"#,
82 type_desc_json,
83 ref_types_json.join(", ")
84 );
85
86 let mut hasher = Sha256::new();
88 hasher.update(hashable_repr.as_bytes());
89 let hash_result = hasher.finalize();
90
91 let hash_hex = format!("{:x}", hash_result);
93 Ok(format!("{}{}", RIHS01_PREFIX, hash_hex))
94}
95
96pub fn parse_rihs_string(rihs_str: &str) -> Result<(u32, String)> {
110 if !rihs_str.starts_with("RIHS") {
111 return Err(crate::error::InvalidRihsFormat::MissingPrefix.into());
112 }
113
114 let parts: Vec<&str> = rihs_str.split('_').collect();
115 if parts.len() != 2 {
116 return Err(crate::error::InvalidRihsFormat::InvalidStructure.into());
117 }
118
119 let version_str = parts[0]
120 .strip_prefix("RIHS")
121 .ok_or(crate::error::InvalidRihsFormat::VersionExtractionFailed)?;
122
123 let version = version_str.parse::<u32>().map_err(|_| {
124 crate::error::InvalidRihsFormat::InvalidVersionNumber {
125 version_str: version_str.to_string(),
126 }
127 })?;
128
129 let hash_value = parts[1].to_string();
130
131 Ok((version, hash_value))
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::types::{Field, FieldType, IndividualTypeDescription};
138
139 #[test]
140 fn test_parse_rihs_string() {
141 let rihs = "RIHS01_abc123def456";
142 let (version, hash) = parse_rihs_string(rihs).unwrap();
143 assert_eq!(version, 1);
144 assert_eq!(hash, "abc123def456");
145 }
146
147 #[test]
148 fn test_parse_rihs_invalid() {
149 assert!(parse_rihs_string("invalid").is_err());
150 assert!(parse_rihs_string("RIHS_nope").is_err());
151 assert!(parse_rihs_string("RIHS01").is_err());
152 }
153
154 #[test]
155 fn test_calculate_hash_format() {
156 let type_desc = IndividualTypeDescription::new(
157 "test_pkg/msg/TestMsg",
158 vec![Field::new(
159 "field1",
160 FieldType::primitive(crate::types::FIELD_TYPE_INT32),
161 )],
162 );
163
164 let msg = TypeDescriptionMsg::new(type_desc, vec![]);
165 let hash = calculate_type_hash(&msg).unwrap();
166
167 assert!(hash.starts_with(RIHS01_PREFIX));
168 assert_eq!(hash.len(), RIHS01_PREFIX.len() + 64); }
170}