zerodds_flatdata/
locator.rs1extern crate alloc;
22use alloc::string::String;
23use alloc::vec::Vec;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct ShmLocator {
29 pub hostname_hash: u32,
31 pub uid: u32,
34 pub slot_count: u32,
36 pub slot_size: u32,
38 pub segment_path: String,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum LocatorError {
45 TruncatedHeader,
47 TruncatedString,
49 InvalidUtf8,
51 PathTooLong,
53}
54
55impl core::fmt::Display for LocatorError {
56 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
57 match self {
58 Self::TruncatedHeader => f.write_str("ShmLocator: truncated 16-byte header"),
59 Self::TruncatedString => f.write_str("ShmLocator: string length out of buffer"),
60 Self::InvalidUtf8 => f.write_str("ShmLocator: segment_path is not UTF-8"),
61 Self::PathTooLong => f.write_str("ShmLocator: segment_path > 256 bytes"),
62 }
63 }
64}
65
66#[cfg(feature = "std")]
67impl std::error::Error for LocatorError {}
68
69const MAX_PATH_LEN: usize = 256;
71
72impl ShmLocator {
73 pub fn to_bytes_le(&self) -> Result<Vec<u8>, LocatorError> {
78 let path_bytes = self.segment_path.as_bytes();
79 if path_bytes.len() > MAX_PATH_LEN {
80 return Err(LocatorError::PathTooLong);
81 }
82 let str_len = u32::try_from(path_bytes.len() + 1).unwrap_or(u32::MAX);
84 let mut out = Vec::with_capacity(16 + 4 + path_bytes.len() + 4);
85 out.extend_from_slice(&self.hostname_hash.to_le_bytes());
86 out.extend_from_slice(&self.uid.to_le_bytes());
87 out.extend_from_slice(&self.slot_count.to_le_bytes());
88 out.extend_from_slice(&self.slot_size.to_le_bytes());
89 out.extend_from_slice(&str_len.to_le_bytes());
90 out.extend_from_slice(path_bytes);
91 out.push(0);
92 while out.len() % 4 != 0 {
93 out.push(0);
94 }
95 Ok(out)
96 }
97
98 pub fn from_bytes_le(bytes: &[u8]) -> Result<Self, LocatorError> {
103 if bytes.len() < 20 {
104 return Err(LocatorError::TruncatedHeader);
105 }
106 let hostname_hash = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
107 let uid = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
108 let slot_count = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
109 let slot_size = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
110 let str_len = u32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]) as usize;
111 if str_len > MAX_PATH_LEN + 1 {
112 return Err(LocatorError::PathTooLong);
113 }
114 if 20 + str_len > bytes.len() {
115 return Err(LocatorError::TruncatedString);
116 }
117 let raw = &bytes[20..20 + str_len];
119 let str_no_null = if raw.last() == Some(&0) {
120 &raw[..raw.len() - 1]
121 } else {
122 raw
123 };
124 let segment_path =
125 core::str::from_utf8(str_no_null).map_err(|_| LocatorError::InvalidUtf8)?;
126 Ok(Self {
127 hostname_hash,
128 uid,
129 slot_count,
130 slot_size,
131 segment_path: segment_path.into(),
132 })
133 }
134}
135
136#[must_use]
139pub fn fnv1a_32(bytes: &[u8]) -> u32 {
140 const OFFSET: u32 = 0x811c_9dc5;
141 const PRIME: u32 = 0x0100_0193;
142 let mut h = OFFSET;
143 for b in bytes {
144 h ^= u32::from(*b);
145 h = h.wrapping_mul(PRIME);
146 }
147 h
148}
149
150#[must_use]
154pub fn is_same_host(local_hostname: &str, local_uid: u32, locator: &ShmLocator) -> bool {
155 let local_hash = fnv1a_32(local_hostname.as_bytes());
156 local_hash == locator.hostname_hash && local_uid == locator.uid
157}
158
159#[cfg(test)]
160#[allow(clippy::expect_used, clippy::unwrap_used)]
161mod tests {
162 use super::*;
163
164 fn sample() -> ShmLocator {
165 ShmLocator {
166 hostname_hash: fnv1a_32(b"node1.local"),
167 uid: 1000,
168 slot_count: 16,
169 slot_size: 128,
170 segment_path: "/zddspub_AB12CD".into(),
171 }
172 }
173
174 #[test]
175 fn roundtrip_le() {
176 let l = sample();
177 let bytes = l.to_bytes_le().expect("encode");
178 let l2 = ShmLocator::from_bytes_le(&bytes).expect("decode");
179 assert_eq!(l, l2);
180 }
181
182 #[test]
183 fn truncated_header_errors() {
184 assert_eq!(
185 ShmLocator::from_bytes_le(&[0u8; 19]),
186 Err(LocatorError::TruncatedHeader)
187 );
188 }
189
190 #[test]
191 fn path_too_long_errors() {
192 let mut l = sample();
193 l.segment_path = "x".repeat(MAX_PATH_LEN + 1);
194 assert_eq!(l.to_bytes_le(), Err(LocatorError::PathTooLong));
195 }
196
197 #[test]
198 fn fnv1a_known_value() {
199 assert_eq!(fnv1a_32(b"hello"), 0x4f9f_2cab);
201 }
202
203 #[test]
204 fn same_host_match_positive() {
205 let l = sample();
206 assert!(is_same_host("node1.local", 1000, &l));
207 }
208
209 #[test]
210 fn same_host_mismatch_uid() {
211 let l = sample();
212 assert!(!is_same_host("node1.local", 999, &l));
213 }
214
215 #[test]
216 fn same_host_mismatch_hostname() {
217 let l = sample();
218 assert!(!is_same_host("node2.local", 1000, &l));
219 }
220}