1#[cfg(not(feature = "std"))]
4use alloc::{string::String, vec::Vec};
5
6use crate::datatype::CharacterSet;
7use crate::error::FormatError;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum LinkTarget {
12 Hard { object_header_address: u64 },
14 Soft { target_path: String },
16 External {
18 filename: String,
19 object_path: String,
20 },
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub struct LinkMessage {
26 pub name: String,
28 pub link_target: LinkTarget,
30 pub creation_order: Option<u64>,
32 pub charset: CharacterSet,
34}
35
36fn read_offset(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
37 let s = size as usize;
38 if pos + s > data.len() {
39 return Err(FormatError::UnexpectedEof {
40 expected: pos + s,
41 available: data.len(),
42 });
43 }
44 Ok(match size {
45 1 => data[pos] as u64,
46 2 => u16::from_le_bytes([data[pos], data[pos + 1]]) as u64,
47 4 => u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as u64,
48 8 => u64::from_le_bytes([
49 data[pos],
50 data[pos + 1],
51 data[pos + 2],
52 data[pos + 3],
53 data[pos + 4],
54 data[pos + 5],
55 data[pos + 6],
56 data[pos + 7],
57 ]),
58 _ => {
59 return Err(FormatError::InvalidOffsetSize(size));
60 }
61 })
62}
63
64fn ensure_len(data: &[u8], pos: usize, needed: usize) -> Result<(), FormatError> {
65 match pos.checked_add(needed) {
66 Some(end) if end <= data.len() => Ok(()),
67 _ => Err(FormatError::UnexpectedEof {
68 expected: pos.saturating_add(needed),
69 available: data.len(),
70 }),
71 }
72}
73
74impl LinkMessage {
75 pub fn serialize(&self, offset_size: u8) -> Vec<u8> {
77 let mut buf = Vec::new();
78 buf.push(1); let name_bytes = self.name.as_bytes();
81 let name_len = name_bytes.len();
82 let name_size_width: u8 = if name_len <= 0xFF { 1 }
83 else if name_len <= 0xFFFF { 2 }
84 else { 4 };
85
86 let is_hard = matches!(self.link_target, LinkTarget::Hard { .. });
87 let has_link_type = !is_hard;
88 let has_creation_order = self.creation_order.is_some();
89 let has_charset = self.charset != CharacterSet::Ascii;
90
91 let mut flags: u8 = 0;
92 let size_bits = match name_size_width { 1 => 0u8, 2 => 1, 4 => 2, _ => 3 };
94 flags |= size_bits;
95 if has_creation_order { flags |= 0x04; }
97 if has_link_type { flags |= 0x08; }
99 if has_charset { flags |= 0x10; }
101 buf.push(flags);
102
103 if has_link_type {
104 match &self.link_target {
105 LinkTarget::Soft { .. } => buf.push(1),
106 LinkTarget::External { .. } => buf.push(64),
107 _ => {}
108 }
109 }
110
111 if let Some(co) = self.creation_order {
112 buf.extend_from_slice(&co.to_le_bytes());
113 }
114
115 if has_charset {
116 buf.push(match self.charset {
117 CharacterSet::Ascii => 0,
118 CharacterSet::Utf8 => 1,
119 });
120 }
121
122 match name_size_width {
123 1 => buf.push(name_len as u8),
124 2 => buf.extend_from_slice(&(name_len as u16).to_le_bytes()),
125 4 => buf.extend_from_slice(&(name_len as u32).to_le_bytes()),
126 _ => buf.extend_from_slice(&(name_len as u64).to_le_bytes()),
127 }
128 buf.extend_from_slice(name_bytes);
129
130 match &self.link_target {
131 LinkTarget::Hard { object_header_address } => {
132 match offset_size {
133 2 => buf.extend_from_slice(&(*object_header_address as u16).to_le_bytes()),
134 4 => buf.extend_from_slice(&(*object_header_address as u32).to_le_bytes()),
135 8 => buf.extend_from_slice(&object_header_address.to_le_bytes()),
136 _ => {}
137 }
138 }
139 LinkTarget::Soft { target_path } => {
140 let path_bytes = target_path.as_bytes();
141 buf.extend_from_slice(&(path_bytes.len() as u16).to_le_bytes());
142 buf.extend_from_slice(path_bytes);
143 }
144 LinkTarget::External { filename, object_path } => {
145 let mut ext_data = Vec::new();
146 ext_data.push(0); ext_data.extend_from_slice(filename.as_bytes());
148 ext_data.push(0);
149 ext_data.extend_from_slice(object_path.as_bytes());
150 ext_data.push(0);
151 buf.extend_from_slice(&(ext_data.len() as u16).to_le_bytes());
152 buf.extend_from_slice(&ext_data);
153 }
154 }
155
156 buf
157 }
158
159 pub fn parse(data: &[u8], offset_size: u8) -> Result<LinkMessage, FormatError> {
163 ensure_len(data, 0, 2)?;
164
165 let version = data[0];
166 if version != 1 {
167 return Err(FormatError::InvalidLinkVersion(version));
168 }
169
170 let flags = data[1];
171 let name_size_field_width = match flags & 0x03 {
173 0 => 1u8,
174 1 => 2,
175 2 => 4,
176 3 => 8,
177 _ => unreachable!(),
178 };
179 let has_creation_order = flags & 0x04 != 0;
181 let has_link_type = flags & 0x08 != 0;
183 let has_charset = flags & 0x10 != 0;
185
186 let mut pos = 2;
187
188 let link_type_code = if has_link_type {
190 ensure_len(data, pos, 1)?;
191 let v = data[pos];
192 pos += 1;
193 v
194 } else {
195 0 };
197
198 let creation_order = if has_creation_order {
200 ensure_len(data, pos, 8)?;
201 let co = u64::from_le_bytes([
202 data[pos],
203 data[pos + 1],
204 data[pos + 2],
205 data[pos + 3],
206 data[pos + 4],
207 data[pos + 5],
208 data[pos + 6],
209 data[pos + 7],
210 ]);
211 pos += 8;
212 Some(co)
213 } else {
214 None
215 };
216
217 let charset = if has_charset {
219 ensure_len(data, pos, 1)?;
220 let cs = data[pos];
221 pos += 1;
222 match cs {
223 0 => CharacterSet::Ascii,
224 1 => CharacterSet::Utf8,
225 _ => return Err(FormatError::InvalidCharacterSet(cs)),
226 }
227 } else {
228 CharacterSet::Ascii
229 };
230
231 let name_len = read_offset(data, pos, name_size_field_width)? as usize;
233 pos += name_size_field_width as usize;
234
235 ensure_len(data, pos, name_len)?;
237 let name = String::from_utf8_lossy(&data[pos..pos + name_len]).into_owned();
238 pos += name_len;
239
240 let link_target = match link_type_code {
242 0 => {
243 let addr = read_offset(data, pos, offset_size)?;
245 LinkTarget::Hard {
246 object_header_address: addr,
247 }
248 }
249 1 => {
250 ensure_len(data, pos, 2)?;
252 let soft_len =
253 u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
254 pos += 2;
255 ensure_len(data, pos, soft_len)?;
256 let target_path =
257 String::from_utf8_lossy(&data[pos..pos + soft_len]).into_owned();
258 LinkTarget::Soft { target_path }
259 }
260 64 => {
261 ensure_len(data, pos, 2)?;
263 let ext_len =
264 u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
265 pos += 2;
266 ensure_len(data, pos, ext_len)?;
267 let ext_data = &data[pos..pos + ext_len];
268 let start = if !ext_data.is_empty() { 1 } else { 0 };
271 let rest = &ext_data[start..];
272 let null1 = rest.iter().position(|&b| b == 0).unwrap_or(rest.len());
273 let filename =
274 String::from_utf8_lossy(&rest[..null1]).into_owned();
275 let after_null1 = if null1 + 1 < rest.len() {
276 null1 + 1
277 } else {
278 rest.len()
279 };
280 let rest2 = &rest[after_null1..];
281 let null2 = rest2.iter().position(|&b| b == 0).unwrap_or(rest2.len());
282 let object_path =
283 String::from_utf8_lossy(&rest2[..null2]).into_owned();
284 LinkTarget::External {
285 filename,
286 object_path,
287 }
288 }
289 other => return Err(FormatError::InvalidLinkType(other)),
290 };
291
292 Ok(LinkMessage {
293 name,
294 link_target,
295 creation_order,
296 charset,
297 })
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn build_hard_link(
307 name: &str,
308 addr: u64,
309 offset_size: u8,
310 creation_order: Option<u64>,
311 charset: Option<u8>,
312 name_size_width: u8, ) -> Vec<u8> {
314 let mut buf = Vec::new();
315 buf.push(1); let mut flags: u8 = 0;
318 let size_bits = match name_size_width {
320 1 => 0u8,
321 2 => 1,
322 4 => 2,
323 8 => 3,
324 _ => 0,
325 };
326 flags |= size_bits;
327 if creation_order.is_some() {
329 flags |= 0x04;
330 }
331 if charset.is_some() {
334 flags |= 0x10;
335 }
336 buf.push(flags);
337
338 if let Some(co) = creation_order {
341 buf.extend_from_slice(&co.to_le_bytes());
342 }
343
344 if let Some(cs) = charset {
345 buf.push(cs);
346 }
347
348 let name_len = name.len();
350 match name_size_width {
351 1 => buf.push(name_len as u8),
352 2 => buf.extend_from_slice(&(name_len as u16).to_le_bytes()),
353 4 => buf.extend_from_slice(&(name_len as u32).to_le_bytes()),
354 8 => buf.extend_from_slice(&(name_len as u64).to_le_bytes()),
355 _ => {}
356 }
357
358 buf.extend_from_slice(name.as_bytes());
359
360 match offset_size {
362 4 => buf.extend_from_slice(&(addr as u32).to_le_bytes()),
363 8 => buf.extend_from_slice(&addr.to_le_bytes()),
364 _ => {}
365 }
366
367 buf
368 }
369
370 #[test]
371 fn hard_link_ascii_no_creation_order() {
372 let data = build_hard_link("mydata", 0x1000, 8, None, None, 1);
373 let msg = LinkMessage::parse(&data, 8).unwrap();
374 assert_eq!(msg.name, "mydata");
375 assert_eq!(
376 msg.link_target,
377 LinkTarget::Hard {
378 object_header_address: 0x1000
379 }
380 );
381 assert_eq!(msg.creation_order, None);
382 assert_eq!(msg.charset, CharacterSet::Ascii);
383 }
384
385 #[test]
386 fn hard_link_utf8_with_creation_order() {
387 let data = build_hard_link("données", 0x2000, 8, Some(42), Some(1), 1);
388 let msg = LinkMessage::parse(&data, 8).unwrap();
389 assert_eq!(msg.name, "données");
390 assert_eq!(
391 msg.link_target,
392 LinkTarget::Hard {
393 object_header_address: 0x2000
394 }
395 );
396 assert_eq!(msg.creation_order, Some(42));
397 assert_eq!(msg.charset, CharacterSet::Utf8);
398 }
399
400 #[test]
401 fn soft_link() {
402 let target = "/group1/dataset";
403 let mut data = Vec::new();
404 data.push(1); data.push(0x08); data.push(1); data.push(4); data.extend_from_slice(b"link");
409 data.extend_from_slice(&(target.len() as u16).to_le_bytes());
410 data.extend_from_slice(target.as_bytes());
411
412 let msg = LinkMessage::parse(&data, 8).unwrap();
413 assert_eq!(msg.name, "link");
414 assert_eq!(
415 msg.link_target,
416 LinkTarget::Soft {
417 target_path: target.to_string()
418 }
419 );
420 }
421
422 #[test]
423 fn name_length_2bytes() {
424 let data = build_hard_link("test", 0x500, 8, None, None, 2);
425 let msg = LinkMessage::parse(&data, 8).unwrap();
426 assert_eq!(msg.name, "test");
427 }
428
429 #[test]
430 fn name_length_4bytes() {
431 let data = build_hard_link("abcd", 0x600, 8, None, None, 4);
432 let msg = LinkMessage::parse(&data, 8).unwrap();
433 assert_eq!(msg.name, "abcd");
434 }
435
436 #[test]
437 fn invalid_version() {
438 let data = vec![2, 0, 0, 0]; let err = LinkMessage::parse(&data, 8).unwrap_err();
440 assert_eq!(err, FormatError::InvalidLinkVersion(2));
441 }
442
443 #[test]
444 fn invalid_link_type() {
445 let mut data = Vec::new();
446 data.push(1); data.push(0x08); data.push(99); data.push(1); data.push(b'x');
451 let err = LinkMessage::parse(&data, 8).unwrap_err();
452 assert_eq!(err, FormatError::InvalidLinkType(99));
453 }
454}