1use std::collections::HashMap;
2use std::fs::File;
3use std::io::{BufReader, Read, Seek, SeekFrom};
4use std::path::Path;
5
6use flate2::read::ZlibDecoder;
7
8use crate::chunk::{read_chunk, read_chunkmap, ChunkMap};
9use crate::constants::{JP2_MAGIC, ND2_CHUNK_MAGIC, ND2_FILE_SIGNATURE};
10use crate::error::{Nd2Error, Result};
11use crate::meta_parse::{parse_attributes, parse_experiment, parse_text_info};
12use crate::parse::ClxLiteParser;
13use crate::types::{Attributes, CompressionType, ExpLoop, TextInfo};
14
15const AXIS_T: &str = "T";
17const AXIS_P: &str = "P";
18const AXIS_C: &str = "C";
19const AXIS_Z: &str = "Z";
20const AXIS_Y: &str = "Y";
21const AXIS_X: &str = "X";
22
23pub struct Nd2File {
25 reader: BufReader<File>,
26 version: (u32, u32),
27 chunkmap: ChunkMap,
28 attributes: Option<Attributes>,
30 experiment: Option<Vec<ExpLoop>>,
31 text_info: Option<TextInfo>,
32}
33
34impl Nd2File {
35 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
37 let file = File::open(path)?;
38 let mut reader = BufReader::new(file);
39
40 let version = Self::read_version(&mut reader)?;
42
43 if version.0 < 2 || version.0 > 3 {
45 return Err(Nd2Error::unsupported_version(version.0, version.1));
46 }
47
48 let chunkmap = read_chunkmap(&mut reader)?;
50
51 Ok(Self {
52 reader,
53 version,
54 chunkmap,
55 attributes: None,
56 experiment: None,
57 text_info: None,
58 })
59 }
60
61 pub fn version(&self) -> (u32, u32) {
63 self.version
64 }
65
66 pub fn attributes(&mut self) -> Result<&Attributes> {
68 if self.attributes.is_none() {
69 let chunk_name: &[u8] = if self.version.0 >= 3 {
70 b"ImageAttributesLV!"
71 } else {
72 b"ImageAttributes!"
73 };
74 let data = read_chunk(&mut self.reader, &self.chunkmap, chunk_name)?;
75 let parser = ClxLiteParser::new(false);
76 let clx = parser.parse(&data)?;
77 self.attributes = Some(parse_attributes(clx)?);
78 }
79 Ok(self.attributes.as_ref().unwrap())
80 }
81
82 pub fn experiment(&mut self) -> Result<&Vec<ExpLoop>> {
84 if self.experiment.is_none() {
85 let chunk_name: &[u8] = if self.version.0 >= 3 {
86 b"ImageMetadataLV!"
87 } else {
88 b"ImageMetadata!"
89 };
90
91 if !self.chunkmap.contains_key(chunk_name) {
92 self.experiment = Some(Vec::new());
93 } else {
94 let data = read_chunk(&mut self.reader, &self.chunkmap, chunk_name)?;
95 let parser = ClxLiteParser::new(false);
96 let clx = parser.parse(&data)?;
97 let to_parse = if self.version.0 >= 3 {
99 match clx.as_object().and_then(|o| o.get("SLxExperiment")) {
100 Some(inner) if inner.as_object().is_some() => inner.clone(),
101 _ => clx.clone(),
102 }
103 } else {
104 clx.clone()
105 };
106 let mut exp = parse_experiment(to_parse).unwrap_or_default();
107 if exp.is_empty() && self.version.0 >= 3 {
109 exp = parse_experiment(clx).unwrap_or_default();
110 }
111 self.experiment = Some(exp);
112 }
113 }
114 Ok(self.experiment.as_ref().unwrap())
115 }
116
117 pub fn text_info(&mut self) -> Result<&TextInfo> {
119 if self.text_info.is_none() {
120 let chunk_name: &[u8] = if self.version.0 >= 3 {
121 b"ImageTextInfoLV!"
122 } else {
123 b"ImageTextInfo!"
124 };
125
126 if !self.chunkmap.contains_key(chunk_name) {
127 self.text_info = Some(TextInfo::default());
128 } else {
129 let data = read_chunk(&mut self.reader, &self.chunkmap, chunk_name)?;
130 let parser = ClxLiteParser::new(false);
131 let clx = parser.parse(&data)?;
132 self.text_info = Some(parse_text_info(clx)?);
133 }
134 }
135 Ok(self.text_info.as_ref().unwrap())
136 }
137
138 pub fn chunk_names(&self) -> Vec<String> {
140 self.chunkmap
141 .keys()
142 .filter_map(|k| String::from_utf8(k.clone()).ok())
143 .collect()
144 }
145
146 pub fn read_raw_chunk(&mut self, name: &[u8]) -> Result<Vec<u8>> {
148 read_chunk(&mut self.reader, &self.chunkmap, name)
149 }
150
151 pub fn sizes(&mut self) -> Result<HashMap<String, usize>> {
154 let attrs = self.attributes()?.clone();
155 let exp = self.experiment()?.clone();
156
157 let n_chan = attrs.channel_count.unwrap_or(attrs.component_count);
158 let height = attrs.height_px as usize;
159 let width = attrs
160 .width_px
161 .or(attrs.width_bytes.map(|w| {
162 let bpp = attrs.bits_per_component_in_memory / 8;
163 w / (bpp * attrs.component_count)
164 }))
165 .unwrap_or(0) as usize;
166
167 let mut sizes: HashMap<String, usize> = HashMap::new();
168
169 if exp.is_empty() {
170 let total = attrs.sequence_count as usize;
172 let n_z: usize = 1;
173 let n_pos: usize = 1;
174 let n_chan_usize = n_chan as usize;
175 let n_time = total / (n_pos * n_chan_usize * n_z).max(1);
176 sizes.insert(AXIS_P.to_string(), n_pos);
177 sizes.insert(AXIS_T.to_string(), n_time);
178 sizes.insert(AXIS_C.to_string(), n_chan_usize);
179 sizes.insert(AXIS_Z.to_string(), n_z);
180 } else {
181 for loop_ in exp {
182 match loop_ {
183 ExpLoop::TimeLoop(t) => {
184 sizes.insert(AXIS_T.to_string(), t.count as usize);
185 }
186 ExpLoop::XYPosLoop(xy) => {
187 sizes.insert(AXIS_P.to_string(), xy.count as usize);
188 }
189 ExpLoop::ZStackLoop(z) => {
190 sizes.insert(AXIS_Z.to_string(), z.count as usize);
191 }
192 ExpLoop::NETimeLoop(n) => {
193 sizes.insert(AXIS_T.to_string(), n.count as usize);
194 }
195 ExpLoop::CustomLoop(_) => {}
196 }
197 }
198 if !sizes.contains_key(AXIS_C) {
199 sizes.insert(AXIS_C.to_string(), n_chan as usize);
200 }
201 if !sizes.contains_key(AXIS_P) {
202 sizes.insert(AXIS_P.to_string(), 1);
203 }
204 if !sizes.contains_key(AXIS_T) {
205 sizes.insert(AXIS_T.to_string(), 1);
206 }
207 if !sizes.contains_key(AXIS_Z) {
208 sizes.insert(AXIS_Z.to_string(), 1);
209 }
210 }
211
212 sizes.insert(AXIS_Y.to_string(), height);
213 sizes.insert(AXIS_X.to_string(), width);
214
215 Ok(sizes)
216 }
217
218 pub fn loop_indices(&mut self) -> Result<Vec<HashMap<String, usize>>> {
221 let (axis_order, coord_shape) = self.coord_axis_order()?;
222 let total: usize = coord_shape.iter().product();
223
224 let mut out = Vec::with_capacity(total);
225 let n = axis_order.len();
226
227 for seq in 0..total {
228 let mut idx = seq;
229 let mut m = HashMap::new();
230 for i in (0..n).rev() {
232 let coord = idx % coord_shape[i];
233 idx /= coord_shape[i];
234 m.insert(axis_order[i].to_string(), coord);
235 }
236 out.push(m);
237 }
238
239 Ok(out)
240 }
241
242 pub fn read_frame(&mut self, index: usize) -> Result<Vec<u16>> {
244 let attrs = self.attributes()?.clone();
245 let max_seq = attrs.sequence_count as usize;
246 let chunk_name = format!("ImageDataSeq|{}!", index);
247 let chunk_key = chunk_name.as_bytes();
248
249 let h = attrs.height_px as usize;
250 let w = attrs.width_px.unwrap_or(0) as usize;
251 let (n_c, n_comp) = match attrs.channel_count {
252 Some(ch) if ch > 0 => (ch as usize, (attrs.component_count / ch) as usize),
253 _ => (attrs.component_count as usize, 1),
254 };
255 let bytes_per_pixel = (attrs.bits_per_component_in_memory / 8) as usize;
256 if bytes_per_pixel == 0 {
257 return Err(Nd2Error::file_invalid_format(
258 "Invalid bits_per_component_in_memory".to_string(),
259 ));
260 }
261 let raw_row_bytes = attrs.width_bytes.map(|w| w as usize).unwrap_or_else(|| {
262 w.saturating_mul(n_c)
263 .saturating_mul(n_comp)
264 .saturating_mul(bytes_per_pixel)
265 });
266 if raw_row_bytes == 0 {
267 return Err(Nd2Error::file_invalid_format(
268 "Invalid frame row stride".to_string(),
269 ));
270 }
271 if raw_row_bytes % bytes_per_pixel != 0 {
272 return Err(Nd2Error::file_invalid_format(format!(
273 "Frame row stride {} is not divisible by bytes per pixel {}",
274 raw_row_bytes, bytes_per_pixel
275 )));
276 }
277 let raw_row_pixels = raw_row_bytes / bytes_per_pixel;
278
279 let frame_size = h
280 .checked_mul(w)
281 .and_then(|v| v.checked_mul(n_c))
282 .and_then(|v| v.checked_mul(n_comp))
283 .ok_or_else(|| {
284 Nd2Error::file_invalid_format("Frame dimensions overflow".to_string())
285 })?;
286 let expected_raw = h
287 .checked_mul(raw_row_bytes)
288 .ok_or_else(|| Nd2Error::file_invalid_format("Frame byte size overflow".to_string()))?;
289 let frame_area = h
290 .checked_mul(w)
291 .ok_or_else(|| Nd2Error::file_invalid_format("Frame area overflow".to_string()))?;
292 let n_c_n_comp = n_c.checked_mul(n_comp).ok_or_else(|| {
293 Nd2Error::file_invalid_format("Frame channel/component overflow".to_string())
294 })?;
295 if raw_row_pixels < n_c_n_comp.saturating_mul(w) {
296 return Err(Nd2Error::file_invalid_format(format!(
297 "Frame row stride {} pixels is smaller than required width {}",
298 raw_row_pixels,
299 n_c_n_comp.saturating_mul(w)
300 )));
301 }
302
303 let pixel_bytes = match attrs.compression_type {
304 Some(CompressionType::Lossless) => {
305 let data = match self.read_raw_chunk(chunk_key) {
306 Ok(data) => data,
307 Err(err) => {
308 if matches!(
309 err,
310 Nd2Error::File {
311 source: crate::error::FileError::ChunkNotFound { .. },
312 }
313 ) {
314 return Err(Nd2Error::input_out_of_range(
315 "sequence index",
316 index,
317 max_seq,
318 ));
319 }
320 return Err(err);
321 }
322 };
323
324 if data.len() < 8 {
325 return Err(Nd2Error::file_invalid_format(format!(
326 "Frame {} compressed chunk too short ({} bytes)",
327 index,
328 data.len()
329 )));
330 }
331 let mut decoder = ZlibDecoder::new(&data[8..]);
332 let mut decompressed = Vec::new();
333 decoder.read_to_end(&mut decompressed)?;
334 decompressed
335 }
336 _ => match self.read_uncompressed_frame_bytes(chunk_key, expected_raw) {
337 Ok(data) => data,
338 Err(err) => {
339 if matches!(
340 err,
341 Nd2Error::File {
342 source: crate::error::FileError::ChunkNotFound { .. },
343 }
344 ) {
345 return Err(Nd2Error::input_out_of_range(
346 "sequence index",
347 index,
348 max_seq,
349 ));
350 }
351 return Err(err);
352 }
353 },
354 };
355
356 if pixel_bytes.len() % 2 != 0 {
357 return Err(Nd2Error::file_invalid_format(format!(
358 "Frame {}: pixel data length {} is not divisible by 2",
359 index,
360 pixel_bytes.len()
361 )));
362 }
363
364 if pixel_bytes.len() / 2 < frame_size {
365 return Err(Nd2Error::file_invalid_format(format!(
366 "Frame {}: expected {} pixels ({} bytes), got {} bytes",
367 index,
368 frame_size,
369 frame_size * 2,
370 pixel_bytes.len()
371 )));
372 }
373
374 let mut pixels: Vec<u16> = vec![0; pixel_bytes.len() / 2];
375 for (i, chunk) in pixel_bytes.chunks_exact(2).enumerate() {
376 pixels[i] = u16::from_le_bytes([chunk[0], chunk[1]]);
377 }
378
379 if pixels.len() < frame_size {
380 return Err(Nd2Error::file_invalid_format(format!(
381 "Frame {}: pixel count {} < expected {}",
382 index,
383 pixels.len(),
384 frame_size
385 )));
386 }
387
388 let mut out = vec![0u16; frame_size];
389 let row_pixels = raw_row_pixels;
390
391 for y in 0..h {
392 let y_offset = y.checked_mul(row_pixels).ok_or_else(|| {
393 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
394 })?;
395 let y_plane_offset = y.checked_mul(w).ok_or_else(|| {
396 Nd2Error::file_invalid_format("Frame plane offset overflow".to_string())
397 })?;
398 for x in 0..w {
399 let x_offset = x.checked_mul(n_c_n_comp).ok_or_else(|| {
400 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
401 })?;
402 for c in 0..n_c {
403 let c_offset = c.checked_mul(n_comp).ok_or_else(|| {
404 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
405 })?;
406 for comp in 0..n_comp {
407 let src_idx = y_offset
408 .checked_add(x_offset)
409 .and_then(|v| v.checked_add(c_offset))
410 .and_then(|v| v.checked_add(comp))
411 .ok_or_else(|| {
412 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
413 })?;
414 let dst_x = y_plane_offset.checked_add(x).ok_or_else(|| {
415 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
416 })?;
417 let c_plane = c_offset.checked_add(comp).ok_or_else(|| {
418 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
419 })?;
420 let dst_idx = c_plane
421 .checked_mul(frame_area)
422 .and_then(|v| v.checked_add(dst_x))
423 .ok_or_else(|| {
424 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
425 })?;
426 out[dst_idx] = pixels[src_idx];
427 }
428 }
429 }
430 }
431
432 Ok(out)
433 }
434
435 fn read_uncompressed_frame_bytes(
436 &mut self,
437 chunk_key: &[u8],
438 expected_raw: usize,
439 ) -> Result<Vec<u8>> {
440 let file_size = self.reader.seek(SeekFrom::End(0))?;
441 let offset = self
442 .chunkmap
443 .get(chunk_key)
444 .map(|(offset, _)| *offset)
445 .ok_or_else(|| Nd2Error::file_chunk_not_found(String::from_utf8_lossy(chunk_key)))?;
446
447 let pixel_offset = match self.read_image_chunk_payload_offset(offset)? {
448 Some(payload_offset) => payload_offset.checked_add(8).ok_or_else(|| {
449 Nd2Error::file_invalid_format("Frame payload offset overflow".to_string())
450 })?,
451 None => offset.checked_add(4096).ok_or_else(|| {
452 Nd2Error::file_invalid_format("Frame fallback offset overflow".to_string())
453 })?,
454 };
455
456 let pixel_end = pixel_offset
457 .checked_add(expected_raw as u64)
458 .ok_or_else(|| Nd2Error::file_invalid_format("Frame bounds overflow".to_string()))?;
459 if pixel_end > file_size {
460 return Err(Nd2Error::file_invalid_format(format!(
461 "Frame chunk '{}' exceeds file bounds",
462 String::from_utf8_lossy(chunk_key)
463 )));
464 }
465
466 self.reader.seek(SeekFrom::Start(pixel_offset))?;
467 let mut pixel_bytes = vec![0u8; expected_raw];
468 self.reader.read_exact(&mut pixel_bytes)?;
469 Ok(pixel_bytes)
470 }
471
472 fn coord_axis_order(&mut self) -> Result<(Vec<&'static str>, Vec<usize>)> {
477 let attrs = self.attributes()?.clone();
478 let exp = self.experiment()?.clone();
479 let n_chan = attrs.channel_count.unwrap_or(attrs.component_count) as usize;
480 let seq_count = attrs.sequence_count as usize;
481
482 let mut axis_order: Vec<&'static str> = Vec::new();
483 let mut coord_shape: Vec<usize> = Vec::new();
484
485 if exp.is_empty() {
486 let n_z = 1;
488 let n_pos = 1;
489 let n_time = seq_count / (n_pos * n_chan * n_z).max(1);
490 axis_order.extend([AXIS_P, AXIS_T, AXIS_C, AXIS_Z]);
491 coord_shape.extend([n_pos, n_time, n_chan, n_z]);
492 } else {
493 for loop_ in &exp {
494 match loop_ {
495 crate::types::ExpLoop::TimeLoop(t) => {
496 axis_order.push(AXIS_T);
497 coord_shape.push(t.count as usize);
498 }
499 crate::types::ExpLoop::NETimeLoop(n) => {
500 axis_order.push(AXIS_T);
501 coord_shape.push(n.count as usize);
502 }
503 crate::types::ExpLoop::XYPosLoop(xy) => {
504 axis_order.push(AXIS_P);
505 coord_shape.push(xy.count as usize);
506 }
507 crate::types::ExpLoop::ZStackLoop(z) => {
508 axis_order.push(AXIS_Z);
509 coord_shape.push(z.count as usize);
510 }
511 crate::types::ExpLoop::CustomLoop(_) => {}
512 }
513 }
514 if !axis_order.contains(&AXIS_P) {
516 axis_order.push(AXIS_P);
517 coord_shape.push(1);
518 }
519 if !axis_order.contains(&AXIS_T) {
520 axis_order.push(AXIS_T);
521 coord_shape.push(1);
522 }
523 if !axis_order.contains(&AXIS_Z) {
524 axis_order.push(AXIS_Z);
525 coord_shape.push(1);
526 }
527 let exp_product: usize = coord_shape.iter().product();
529 if exp_product > 0 && exp_product * n_chan <= seq_count {
530 axis_order.push(AXIS_C);
531 coord_shape.push(n_chan);
532 }
533 if !axis_order.contains(&AXIS_Z) {
534 axis_order.push(AXIS_Z);
535 coord_shape.push(1);
536 }
537 }
538
539 Ok((axis_order, coord_shape))
540 }
541
542 fn seq_index_from_coords(&mut self, p: usize, t: usize, c: usize, z: usize) -> Result<usize> {
544 let (axis_order, coord_shape) = self.coord_axis_order()?;
545 let coords: Vec<usize> = axis_order
546 .iter()
547 .map(|&ax| match ax {
548 AXIS_P => p,
549 AXIS_T => t,
550 AXIS_C => c,
551 AXIS_Z => z,
552 _ => 0,
553 })
554 .collect();
555
556 if coords.len() != coord_shape.len() {
557 return Err(Nd2Error::file_invalid_format(
558 "Coord/axis length mismatch".to_string(),
559 ));
560 }
561
562 for (idx, (&coord, &shape)) in coords.iter().zip(coord_shape.iter()).enumerate() {
563 if shape == 0 {
564 return Err(Nd2Error::file_invalid_format(format!(
565 "Invalid axis length: {} has size 0",
566 axis_order[idx]
567 )));
568 }
569 if coord >= shape {
570 return Err(Nd2Error::input_out_of_range(
571 format!("axis {}", axis_order[idx]),
572 coord,
573 shape,
574 ));
575 }
576 }
577
578 let mut seq = 0usize;
579 let mut stride = 1;
580 for i in (0..coords.len()).rev() {
581 let next = coords[i]
582 .checked_mul(stride)
583 .ok_or_else(|| Nd2Error::internal_overflow("sequence index multiply"))?;
584 seq = seq
585 .checked_add(next)
586 .ok_or_else(|| Nd2Error::internal_overflow("sequence index add"))?;
587 stride = stride
588 .checked_mul(coord_shape[i])
589 .ok_or_else(|| Nd2Error::internal_overflow("sequence stride multiply"))?;
590 }
591 Ok(seq)
592 }
593
594 fn read_image_chunk_payload_offset(&mut self, offset: u64) -> Result<Option<u64>> {
595 self.reader.seek(SeekFrom::Start(offset))?;
596
597 let header = match crate::chunk::ChunkHeader::read(&mut self.reader) {
598 Ok(header) => header,
599 Err(_) => return Ok(None),
600 };
601
602 if header.magic != ND2_CHUNK_MAGIC {
603 return Ok(None);
604 }
605
606 let payload_offset = offset
607 .checked_add(16)
608 .and_then(|v| v.checked_add(header.name_length as u64))
609 .ok_or_else(|| {
610 Nd2Error::file_invalid_format("Frame payload offset overflow".to_string())
611 })?;
612
613 Ok(Some(payload_offset))
614 }
615
616 pub fn read_frame_2d(&mut self, p: usize, t: usize, c: usize, z: usize) -> Result<Vec<u16>> {
618 let sizes = self.sizes()?;
619 let height = *sizes.get(AXIS_Y).ok_or_else(|| {
620 Nd2Error::file_invalid_format("Missing height (Y) dimension".to_string())
621 })?;
622 let width = *sizes.get(AXIS_X).ok_or_else(|| {
623 Nd2Error::file_invalid_format("Missing width (X) dimension".to_string())
624 })?;
625 let n_pos = *sizes.get(AXIS_P).ok_or_else(|| {
626 Nd2Error::file_invalid_format("Missing position (P) dimension".to_string())
627 })?;
628 let n_time = *sizes.get(AXIS_T).ok_or_else(|| {
629 Nd2Error::file_invalid_format("Missing time (T) dimension".to_string())
630 })?;
631 let n_chan = *sizes.get(AXIS_C).ok_or_else(|| {
632 Nd2Error::file_invalid_format("Missing channel (C) dimension".to_string())
633 })?;
634 let n_z = *sizes
635 .get(AXIS_Z)
636 .ok_or_else(|| Nd2Error::file_invalid_format("Missing Z dimension".to_string()))?;
637
638 if p >= n_pos {
639 return Err(Nd2Error::input_out_of_range("position index", p, n_pos));
640 }
641 if t >= n_time {
642 return Err(Nd2Error::input_out_of_range("time index", t, n_time));
643 }
644 if c >= n_chan {
645 return Err(Nd2Error::input_out_of_range("channel index", c, n_chan));
646 }
647 if z >= n_z {
648 return Err(Nd2Error::input_out_of_range("z index", z, n_z));
649 }
650
651 let seq_index = self.seq_index_from_coords(p, t, c, z)?;
652
653 let frame = self.read_frame(seq_index)?;
654 let len = height.checked_mul(width).ok_or_else(|| {
655 Nd2Error::file_invalid_format("Frame dimensions overflow".to_string())
656 })?;
657
658 let start = c.checked_mul(len).ok_or_else(|| {
660 Nd2Error::file_invalid_format("Frame slice start overflow".to_string())
661 })?;
662 let end = (c + 1)
663 .checked_mul(len)
664 .ok_or_else(|| Nd2Error::file_invalid_format("Frame slice end overflow".to_string()))?;
665 if end > frame.len() {
666 return Err(Nd2Error::file_invalid_format(format!(
667 "Frame data too short for requested channel: frame {} < {}",
668 frame.len(),
669 end
670 )));
671 }
672 Ok(frame[start..end].to_vec())
673 }
674
675 fn read_version<R: Read + Seek>(reader: &mut R) -> Result<(u32, u32)> {
676 reader.seek(SeekFrom::Start(0))?;
677
678 let mut header = [0u8; 112]; reader.read_exact(&mut header).map_err(|e| {
680 Nd2Error::file_invalid_format(format!(
681 "Failed to read file header (expected 112 bytes): {}",
682 e
683 ))
684 })?;
685
686 let magic = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
687
688 if magic == JP2_MAGIC {
689 return Ok((1, 0)); }
691
692 if magic != ND2_CHUNK_MAGIC {
693 return Err(Nd2Error::file_invalid_magic(ND2_CHUNK_MAGIC, magic));
694 }
695
696 let name_length = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
697 let data_length = u64::from_le_bytes([
698 header[8], header[9], header[10], header[11], header[12], header[13], header[14],
699 header[15],
700 ]);
701
702 if name_length != 32 || data_length != 64 {
704 return Err(Nd2Error::file_invalid_format(
705 "Corrupt file header".to_string(),
706 ));
707 }
708
709 let name = &header[16..48];
711 if name != ND2_FILE_SIGNATURE {
712 return Err(Nd2Error::file_invalid_format(
713 "Invalid file signature".to_string(),
714 ));
715 }
716
717 let data = &header[48..112];
719 let major = (data[3] as char).to_digit(10).unwrap_or(0);
720 let minor = (data[5] as char).to_digit(10).unwrap_or(0);
721
722 Ok((major, minor))
723 }
724}
725
726impl Drop for Nd2File {
727 fn drop(&mut self) {
728 }
730}