1use crate::error::{Error, Result};
9use crate::types::{NcAttrValue, NcAttribute, NcDimension, NcType, NcVariable};
10use crate::NcFormat;
11
12use super::types::{nc_type_from_code, pad_to_4};
13
14const ABSENT: u32 = 0x0000_0000;
16const NC_DIMENSION: u32 = 0x0000_000A;
17const NC_VARIABLE: u32 = 0x0000_000B;
18const NC_ATTRIBUTE: u32 = 0x0000_000C;
19
20const STREAMING: u32 = 0xFFFF_FFFF;
22
23pub struct ClassicHeader {
25 pub dimensions: Vec<NcDimension>,
26 pub global_attributes: Vec<NcAttribute>,
27 pub variables: Vec<NcVariable>,
28 pub numrecs: u64,
29}
30
31struct Cursor<'a> {
33 data: &'a [u8],
34 pos: usize,
35}
36
37impl<'a> Cursor<'a> {
38 fn new(data: &'a [u8]) -> Self {
39 Cursor { data, pos: 0 }
40 }
41
42 fn remaining(&self) -> usize {
43 self.data.len().saturating_sub(self.pos)
44 }
45
46 fn ensure(&self, n: usize) -> Result<()> {
47 if self.remaining() < n {
48 Err(Error::InvalidData(format!(
49 "unexpected end of header at offset {}: need {} bytes, have {}",
50 self.pos,
51 n,
52 self.remaining()
53 )))
54 } else {
55 Ok(())
56 }
57 }
58
59 #[allow(dead_code)]
60 fn read_u8(&mut self) -> Result<u8> {
61 self.ensure(1)?;
62 let v = self.data[self.pos];
63 self.pos += 1;
64 Ok(v)
65 }
66
67 fn read_u16_be(&mut self) -> Result<u16> {
68 self.ensure(2)?;
69 let v = u16::from_be_bytes([self.data[self.pos], self.data[self.pos + 1]]);
70 self.pos += 2;
71 Ok(v)
72 }
73
74 fn read_u32_be(&mut self) -> Result<u32> {
75 self.ensure(4)?;
76 let v = u32::from_be_bytes([
77 self.data[self.pos],
78 self.data[self.pos + 1],
79 self.data[self.pos + 2],
80 self.data[self.pos + 3],
81 ]);
82 self.pos += 4;
83 Ok(v)
84 }
85
86 fn read_i32_be(&mut self) -> Result<i32> {
87 self.ensure(4)?;
88 let v = i32::from_be_bytes([
89 self.data[self.pos],
90 self.data[self.pos + 1],
91 self.data[self.pos + 2],
92 self.data[self.pos + 3],
93 ]);
94 self.pos += 4;
95 Ok(v)
96 }
97
98 fn read_u64_be(&mut self) -> Result<u64> {
99 self.ensure(8)?;
100 let v = u64::from_be_bytes([
101 self.data[self.pos],
102 self.data[self.pos + 1],
103 self.data[self.pos + 2],
104 self.data[self.pos + 3],
105 self.data[self.pos + 4],
106 self.data[self.pos + 5],
107 self.data[self.pos + 6],
108 self.data[self.pos + 7],
109 ]);
110 self.pos += 8;
111 Ok(v)
112 }
113
114 fn read_i64_be(&mut self) -> Result<i64> {
115 self.ensure(8)?;
116 let v = i64::from_be_bytes([
117 self.data[self.pos],
118 self.data[self.pos + 1],
119 self.data[self.pos + 2],
120 self.data[self.pos + 3],
121 self.data[self.pos + 4],
122 self.data[self.pos + 5],
123 self.data[self.pos + 6],
124 self.data[self.pos + 7],
125 ]);
126 self.pos += 8;
127 Ok(v)
128 }
129
130 fn read_f32_be(&mut self) -> Result<f32> {
131 self.ensure(4)?;
132 let v = f32::from_be_bytes([
133 self.data[self.pos],
134 self.data[self.pos + 1],
135 self.data[self.pos + 2],
136 self.data[self.pos + 3],
137 ]);
138 self.pos += 4;
139 Ok(v)
140 }
141
142 fn read_f64_be(&mut self) -> Result<f64> {
143 self.ensure(8)?;
144 let v = f64::from_be_bytes([
145 self.data[self.pos],
146 self.data[self.pos + 1],
147 self.data[self.pos + 2],
148 self.data[self.pos + 3],
149 self.data[self.pos + 4],
150 self.data[self.pos + 5],
151 self.data[self.pos + 6],
152 self.data[self.pos + 7],
153 ]);
154 self.pos += 8;
155 Ok(v)
156 }
157
158 fn read_bytes(&mut self, n: usize) -> Result<&'a [u8]> {
159 self.ensure(n)?;
160 let slice = &self.data[self.pos..self.pos + n];
161 self.pos += n;
162 Ok(slice)
163 }
164
165 fn skip(&mut self, n: usize) -> Result<()> {
166 self.ensure(n)?;
167 self.pos += n;
168 Ok(())
169 }
170
171 fn read_count(&mut self, format: NcFormat) -> Result<u64> {
173 match format {
174 NcFormat::Cdf5 => self.read_u64_be(),
175 _ => self.read_u32_be().map(|v| v as u64),
176 }
177 }
178
179 fn read_name(&mut self, format: NcFormat) -> Result<String> {
182 let len = self.read_count(format)? as usize;
183 let bytes = self.read_bytes(len)?;
184 let padded_len = pad_to_4(len);
185 let pad = padded_len - len;
186 if pad > 0 {
187 self.skip(pad)?;
188 }
189 String::from_utf8(bytes.to_vec())
190 .map_err(|e| Error::InvalidData(format!("invalid UTF-8 name: {}", e)))
191 }
192}
193
194pub fn parse_header(data: &[u8], format: NcFormat) -> Result<ClassicHeader> {
199 let mut cur = Cursor::new(data);
201 cur.skip(4)?;
202
203 let numrecs_raw = cur.read_count(format)?;
205 let numrecs = if format != NcFormat::Cdf5 && (numrecs_raw as u32) == STREAMING {
206 0 } else {
208 numrecs_raw
209 };
210
211 let mut dimensions = parse_dim_list(&mut cur, format)?;
213
214 let global_attributes = parse_att_list(&mut cur, format)?;
216
217 let mut variables = parse_var_list(&mut cur, format, &dimensions)?;
219
220 if numrecs > 0 {
221 apply_unlimited_dimension_size(&mut dimensions, &mut variables, numrecs);
222 }
223
224 Ok(ClassicHeader {
225 dimensions,
226 global_attributes,
227 variables,
228 numrecs,
229 })
230}
231
232fn parse_dim_list(cur: &mut Cursor<'_>, format: NcFormat) -> Result<Vec<NcDimension>> {
234 let tag = cur.read_u32_be()?;
235
236 if tag == ABSENT {
237 let _zero = cur.read_count(format)?;
239 return Ok(Vec::new());
240 }
241
242 if tag != NC_DIMENSION {
243 return Err(Error::InvalidData(format!(
244 "expected NC_DIMENSION tag (0x{:08X}), got 0x{:08X}",
245 NC_DIMENSION, tag
246 )));
247 }
248
249 let nelems = cur.read_count(format)? as usize;
250 let mut dims = Vec::with_capacity(nelems);
251
252 for _ in 0..nelems {
253 let name = cur.read_name(format)?;
254 let size = cur.read_count(format)?;
255 let is_unlimited = size == 0;
257 dims.push(NcDimension {
258 name,
259 size,
260 is_unlimited,
261 });
262 }
263
264 Ok(dims)
265}
266
267fn parse_att_list(cur: &mut Cursor<'_>, format: NcFormat) -> Result<Vec<NcAttribute>> {
269 let tag = cur.read_u32_be()?;
270
271 if tag == ABSENT {
272 let _zero = cur.read_count(format)?;
273 return Ok(Vec::new());
274 }
275
276 if tag != NC_ATTRIBUTE {
277 return Err(Error::InvalidData(format!(
278 "expected NC_ATTRIBUTE tag (0x{:08X}), got 0x{:08X}",
279 NC_ATTRIBUTE, tag
280 )));
281 }
282
283 let nelems = cur.read_count(format)? as usize;
284 let mut attrs = Vec::with_capacity(nelems);
285
286 for _ in 0..nelems {
287 let name = cur.read_name(format)?;
288 let nc_type = cur.read_u32_be()?;
289 let nvalues = cur.read_count(format)? as usize;
290 let value = read_attr_values(cur, nc_type, nvalues, format)?;
291
292 attrs.push(NcAttribute { name, value });
293 }
294
295 Ok(attrs)
296}
297
298fn read_attr_values(
301 cur: &mut Cursor<'_>,
302 nc_type: u32,
303 nvalues: usize,
304 _format: NcFormat,
305) -> Result<NcAttrValue> {
306 let typ = nc_type_from_code(nc_type)?;
307 let elem_size = typ.size();
308 let raw_bytes = nvalues * elem_size;
309 let padded = pad_to_4(raw_bytes);
310
311 match typ {
312 NcType::Byte => {
313 let bytes = cur.read_bytes(raw_bytes)?;
314 let values: Vec<i8> = bytes.iter().map(|&b| b as i8).collect();
315 cur.skip(padded - raw_bytes)?;
316 Ok(NcAttrValue::Bytes(values))
317 }
318 NcType::Char => {
319 let bytes = cur.read_bytes(raw_bytes)?;
320 let s = String::from_utf8_lossy(bytes);
322 let trimmed = s.trim_end_matches('\0').to_string();
323 cur.skip(padded - raw_bytes)?;
324 Ok(NcAttrValue::Chars(trimmed))
325 }
326 NcType::Short => {
327 let mut values = Vec::with_capacity(nvalues);
328 for _ in 0..nvalues {
329 values.push(cur.read_u16_be()? as i16);
330 }
331 let pad = padded - raw_bytes;
332 cur.skip(pad)?;
333 Ok(NcAttrValue::Shorts(values))
334 }
335 NcType::Int => {
336 let mut values = Vec::with_capacity(nvalues);
337 for _ in 0..nvalues {
338 values.push(cur.read_i32_be()?);
339 }
340 Ok(NcAttrValue::Ints(values))
341 }
342 NcType::Float => {
343 let mut values = Vec::with_capacity(nvalues);
344 for _ in 0..nvalues {
345 values.push(cur.read_f32_be()?);
346 }
347 Ok(NcAttrValue::Floats(values))
348 }
349 NcType::Double => {
350 let mut values = Vec::with_capacity(nvalues);
351 for _ in 0..nvalues {
352 values.push(cur.read_f64_be()?);
353 }
354 Ok(NcAttrValue::Doubles(values))
355 }
356 NcType::UByte => {
357 let bytes = cur.read_bytes(raw_bytes)?;
358 cur.skip(padded - raw_bytes)?;
359 Ok(NcAttrValue::UBytes(bytes.to_vec()))
360 }
361 NcType::UShort => {
362 let mut values = Vec::with_capacity(nvalues);
363 for _ in 0..nvalues {
364 values.push(cur.read_u16_be()?);
365 }
366 let pad = padded - raw_bytes;
367 cur.skip(pad)?;
368 Ok(NcAttrValue::UShorts(values))
369 }
370 NcType::UInt => {
371 let mut values = Vec::with_capacity(nvalues);
372 for _ in 0..nvalues {
373 values.push(cur.read_u32_be()?);
374 }
375 Ok(NcAttrValue::UInts(values))
376 }
377 NcType::Int64 => {
378 let mut values = Vec::with_capacity(nvalues);
379 for _ in 0..nvalues {
380 values.push(cur.read_i64_be()?);
381 }
382 Ok(NcAttrValue::Int64s(values))
383 }
384 NcType::UInt64 => {
385 let mut values = Vec::with_capacity(nvalues);
386 for _ in 0..nvalues {
387 values.push(cur.read_u64_be()?);
388 }
389 Ok(NcAttrValue::UInt64s(values))
390 }
391 NcType::String
392 | NcType::Enum { .. }
393 | NcType::Compound { .. }
394 | NcType::Opaque { .. }
395 | NcType::Array { .. }
396 | NcType::VLen { .. } => Err(Error::InvalidData(format!(
397 "{:?} is not valid in classic format attributes",
398 typ
399 ))),
400 }
401}
402
403fn parse_var_list(
405 cur: &mut Cursor<'_>,
406 format: NcFormat,
407 dims: &[NcDimension],
408) -> Result<Vec<NcVariable>> {
409 let tag = cur.read_u32_be()?;
410
411 if tag == ABSENT {
412 let _zero = cur.read_count(format)?;
413 return Ok(Vec::new());
414 }
415
416 if tag != NC_VARIABLE {
417 return Err(Error::InvalidData(format!(
418 "expected NC_VARIABLE tag (0x{:08X}), got 0x{:08X}",
419 NC_VARIABLE, tag
420 )));
421 }
422
423 let nelems = cur.read_count(format)? as usize;
424 let mut vars = Vec::with_capacity(nelems);
425
426 for _ in 0..nelems {
427 let name = cur.read_name(format)?;
428
429 let ndims = cur.read_count(format)? as usize;
431
432 let mut var_dims = Vec::with_capacity(ndims);
434 let mut is_record_var = false;
435 for _ in 0..ndims {
436 let dimid = cur.read_count(format)? as usize;
437 if dimid >= dims.len() {
438 return Err(Error::InvalidData(format!(
439 "variable '{}' references dimension index {} but only {} dimensions exist",
440 name,
441 dimid,
442 dims.len()
443 )));
444 }
445 if dims[dimid].is_unlimited {
446 is_record_var = true;
447 }
448 var_dims.push(dims[dimid].clone());
449 }
450
451 let attributes = parse_att_list(cur, format)?;
453
454 let nc_type_code = cur.read_u32_be()?;
456 let dtype = nc_type_from_code(nc_type_code)?;
457
458 let vsize = cur.read_count(format)?;
462
463 let data_offset = match format {
465 NcFormat::Classic => cur.read_u32_be()? as u64,
466 NcFormat::Offset64 | NcFormat::Cdf5 => cur.read_u64_be()?,
467 _ => unreachable!("classic parser only handles CDF-1/2/5"),
468 };
469
470 let record_size = if is_record_var { vsize } else { 0 };
472
473 let data_size = if is_record_var { 0 } else { vsize };
476
477 vars.push(NcVariable {
478 name,
479 dimensions: var_dims,
480 dtype,
481 attributes,
482 data_offset,
483 _data_size: data_size,
484 is_record_var,
485 record_size,
486 });
487 }
488
489 Ok(vars)
490}
491
492fn apply_unlimited_dimension_size(
493 dimensions: &mut [NcDimension],
494 variables: &mut [NcVariable],
495 numrecs: u64,
496) {
497 for dim in dimensions.iter_mut().filter(|dim| dim.is_unlimited) {
498 dim.size = numrecs;
499 }
500
501 for variable in variables {
502 for dim in variable
503 .dimensions
504 .iter_mut()
505 .filter(|dim| dim.is_unlimited)
506 {
507 dim.size = numrecs;
508 }
509 }
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515 use crate::NcFormat;
516
517 fn build_cdf1_header(
520 dims: &[(&str, u32)],
521 attrs: &[(&str, u32, &[u8])], vars: &[(&str, &[u32], u32, u32, u32)], numrecs: u32,
524 ) -> Vec<u8> {
525 let mut buf = Vec::new();
526
527 buf.extend_from_slice(b"CDF\x01");
529
530 buf.extend_from_slice(&numrecs.to_be_bytes());
532
533 if dims.is_empty() {
535 buf.extend_from_slice(&ABSENT.to_be_bytes());
537 buf.extend_from_slice(&0u32.to_be_bytes());
538 } else {
539 buf.extend_from_slice(&NC_DIMENSION.to_be_bytes());
540 buf.extend_from_slice(&(dims.len() as u32).to_be_bytes());
541 for (name, size) in dims {
542 write_name_cdf1(&mut buf, name);
543 buf.extend_from_slice(&size.to_be_bytes());
544 }
545 }
546
547 write_att_list_cdf1(&mut buf, attrs);
549
550 if vars.is_empty() {
552 buf.extend_from_slice(&ABSENT.to_be_bytes());
553 buf.extend_from_slice(&0u32.to_be_bytes());
554 } else {
555 buf.extend_from_slice(&NC_VARIABLE.to_be_bytes());
556 buf.extend_from_slice(&(vars.len() as u32).to_be_bytes());
557 for (name, dimids, nc_type, vsize, offset) in vars {
558 write_name_cdf1(&mut buf, name);
559 buf.extend_from_slice(&(dimids.len() as u32).to_be_bytes());
561 for &did in *dimids {
563 buf.extend_from_slice(&did.to_be_bytes());
564 }
565 buf.extend_from_slice(&ABSENT.to_be_bytes());
567 buf.extend_from_slice(&0u32.to_be_bytes());
568 buf.extend_from_slice(&nc_type.to_be_bytes());
570 buf.extend_from_slice(&vsize.to_be_bytes());
572 buf.extend_from_slice(&offset.to_be_bytes());
574 }
575 }
576
577 buf
578 }
579
580 fn write_name_cdf1(buf: &mut Vec<u8>, name: &str) {
581 let name_bytes = name.as_bytes();
582 buf.extend_from_slice(&(name_bytes.len() as u32).to_be_bytes());
583 buf.extend_from_slice(name_bytes);
584 let pad = pad_to_4(name_bytes.len()) - name_bytes.len();
585 for _ in 0..pad {
586 buf.push(0);
587 }
588 }
589
590 fn write_att_list_cdf1(buf: &mut Vec<u8>, attrs: &[(&str, u32, &[u8])]) {
591 if attrs.is_empty() {
592 buf.extend_from_slice(&ABSENT.to_be_bytes());
593 buf.extend_from_slice(&0u32.to_be_bytes());
594 return;
595 }
596 buf.extend_from_slice(&NC_ATTRIBUTE.to_be_bytes());
597 buf.extend_from_slice(&(attrs.len() as u32).to_be_bytes());
598 for (name, nc_type, value_bytes) in attrs {
599 write_name_cdf1(buf, name);
600 buf.extend_from_slice(&nc_type.to_be_bytes());
601 let elem_size = match nc_type {
603 1 => 1, 2 => 1, 3 => 2, 4 => 4, 5 => 4, 6 => 8, _ => 1,
610 };
611 let nvalues = value_bytes.len() / elem_size;
612 buf.extend_from_slice(&(nvalues as u32).to_be_bytes());
613 buf.extend_from_slice(value_bytes);
614 let pad = pad_to_4(value_bytes.len()) - value_bytes.len();
615 for _ in 0..pad {
616 buf.push(0);
617 }
618 }
619 }
620
621 fn write_count_cdf5(buf: &mut Vec<u8>, value: u64) {
622 buf.extend_from_slice(&value.to_be_bytes());
623 }
624
625 fn write_name_cdf5(buf: &mut Vec<u8>, name: &str) {
626 let name_bytes = name.as_bytes();
627 write_count_cdf5(buf, name_bytes.len() as u64);
628 buf.extend_from_slice(name_bytes);
629 let pad = pad_to_4(name_bytes.len()) - name_bytes.len();
630 for _ in 0..pad {
631 buf.push(0);
632 }
633 }
634
635 fn build_cdf5_header(
636 dims: &[(&str, u64)],
637 vars: &[(&str, &[u64], u32, u64, u64)],
638 numrecs: u64,
639 ) -> Vec<u8> {
640 let mut buf = Vec::new();
641 buf.extend_from_slice(b"CDF\x05");
642 write_count_cdf5(&mut buf, numrecs);
643
644 if dims.is_empty() {
645 buf.extend_from_slice(&ABSENT.to_be_bytes());
646 write_count_cdf5(&mut buf, 0);
647 } else {
648 buf.extend_from_slice(&NC_DIMENSION.to_be_bytes());
649 write_count_cdf5(&mut buf, dims.len() as u64);
650 for (name, size) in dims {
651 write_name_cdf5(&mut buf, name);
652 write_count_cdf5(&mut buf, *size);
653 }
654 }
655
656 buf.extend_from_slice(&ABSENT.to_be_bytes());
657 write_count_cdf5(&mut buf, 0);
658
659 if vars.is_empty() {
660 buf.extend_from_slice(&ABSENT.to_be_bytes());
661 write_count_cdf5(&mut buf, 0);
662 } else {
663 buf.extend_from_slice(&NC_VARIABLE.to_be_bytes());
664 write_count_cdf5(&mut buf, vars.len() as u64);
665 for (name, dimids, nc_type, vsize, offset) in vars {
666 write_name_cdf5(&mut buf, name);
667 write_count_cdf5(&mut buf, dimids.len() as u64);
668 for dimid in *dimids {
669 write_count_cdf5(&mut buf, *dimid);
670 }
671 buf.extend_from_slice(&ABSENT.to_be_bytes());
672 write_count_cdf5(&mut buf, 0);
673 buf.extend_from_slice(&nc_type.to_be_bytes());
674 write_count_cdf5(&mut buf, *vsize);
675 buf.extend_from_slice(&offset.to_be_bytes());
676 }
677 }
678
679 buf
680 }
681
682 #[test]
683 fn test_empty_header() {
684 let data = build_cdf1_header(&[], &[], &[], 0);
685 let header = parse_header(&data, NcFormat::Classic).unwrap();
686 assert!(header.dimensions.is_empty());
687 assert!(header.global_attributes.is_empty());
688 assert!(header.variables.is_empty());
689 assert_eq!(header.numrecs, 0);
690 }
691
692 #[test]
693 fn test_dimensions() {
694 let data = build_cdf1_header(
695 &[("x", 10), ("y", 20), ("time", 0)], &[],
697 &[],
698 5,
699 );
700 let header = parse_header(&data, NcFormat::Classic).unwrap();
701 assert_eq!(header.dimensions.len(), 3);
702
703 assert_eq!(header.dimensions[0].name, "x");
704 assert_eq!(header.dimensions[0].size, 10);
705 assert!(!header.dimensions[0].is_unlimited);
706
707 assert_eq!(header.dimensions[1].name, "y");
708 assert_eq!(header.dimensions[1].size, 20);
709 assert!(!header.dimensions[1].is_unlimited);
710
711 assert_eq!(header.dimensions[2].name, "time");
712 assert_eq!(header.dimensions[2].size, 5);
713 assert!(header.dimensions[2].is_unlimited);
714
715 assert_eq!(header.numrecs, 5);
716 }
717
718 #[test]
719 fn test_global_attributes() {
720 let value_bytes = 42i32.to_be_bytes();
722 let data = build_cdf1_header(
723 &[],
724 &[("answer", 4, &value_bytes)], &[],
726 0,
727 );
728 let header = parse_header(&data, NcFormat::Classic).unwrap();
729 assert_eq!(header.global_attributes.len(), 1);
730 assert_eq!(header.global_attributes[0].name, "answer");
731 if let NcAttrValue::Ints(ref v) = header.global_attributes[0].value {
732 assert_eq!(v, &[42]);
733 } else {
734 panic!("expected Ints attribute");
735 }
736 }
737
738 #[test]
739 fn test_char_attribute() {
740 let text = b"hello";
741 let data = build_cdf1_header(
742 &[],
743 &[("greeting", 2, text)], &[],
745 0,
746 );
747 let header = parse_header(&data, NcFormat::Classic).unwrap();
748 assert_eq!(header.global_attributes.len(), 1);
749 assert_eq!(header.global_attributes[0].name, "greeting");
750 if let NcAttrValue::Chars(ref s) = header.global_attributes[0].value {
751 assert_eq!(s, "hello");
752 } else {
753 panic!("expected Chars attribute");
754 }
755 }
756
757 #[test]
758 fn test_variables() {
759 let data = build_cdf1_header(
760 &[("x", 10), ("y", 20)],
761 &[],
762 &[
763 ("temperature", &[0, 1], 5, 800, 200), ("pressure", &[0, 1], 6, 1600, 1000), ],
766 0,
767 );
768 let header = parse_header(&data, NcFormat::Classic).unwrap();
769 assert_eq!(header.variables.len(), 2);
770
771 let temp = &header.variables[0];
772 assert_eq!(temp.name, "temperature");
773 assert_eq!(temp.dtype, NcType::Float);
774 assert_eq!(temp.dimensions.len(), 2);
775 assert_eq!(temp.dimensions[0].name, "x");
776 assert_eq!(temp.dimensions[1].name, "y");
777 assert_eq!(temp.data_offset, 200);
778 assert_eq!(temp._data_size, 800);
779 assert!(!temp.is_record_var);
780
781 let pres = &header.variables[1];
782 assert_eq!(pres.name, "pressure");
783 assert_eq!(pres.dtype, NcType::Double);
784 assert_eq!(pres.data_offset, 1000);
785 assert_eq!(pres._data_size, 1600);
786 }
787
788 #[test]
789 fn test_record_variable() {
790 let data = build_cdf1_header(
791 &[("time", 0), ("x", 5)], &[],
793 &[
794 ("values", &[0, 1], 5, 20, 100), ],
797 10, );
799 let header = parse_header(&data, NcFormat::Classic).unwrap();
800 assert_eq!(header.numrecs, 10);
801 assert_eq!(header.variables.len(), 1);
802
803 let var = &header.variables[0];
804 assert_eq!(var.name, "values");
805 assert!(var.is_record_var);
806 assert_eq!(var.record_size, 20);
807 assert_eq!(var._data_size, 0); assert_eq!(var.shape(), vec![10, 5]);
809 }
810
811 #[test]
812 fn test_cdf2_offset64() {
813 let mut buf = Vec::new();
816 buf.extend_from_slice(b"CDF\x02");
817 buf.extend_from_slice(&0u32.to_be_bytes());
819 buf.extend_from_slice(&NC_DIMENSION.to_be_bytes());
821 buf.extend_from_slice(&1u32.to_be_bytes());
822 write_name_cdf1(&mut buf, "x");
823 buf.extend_from_slice(&100u32.to_be_bytes());
824 buf.extend_from_slice(&ABSENT.to_be_bytes());
826 buf.extend_from_slice(&0u32.to_be_bytes());
827 buf.extend_from_slice(&NC_VARIABLE.to_be_bytes());
829 buf.extend_from_slice(&1u32.to_be_bytes());
830 write_name_cdf1(&mut buf, "data");
831 buf.extend_from_slice(&1u32.to_be_bytes()); buf.extend_from_slice(&0u32.to_be_bytes()); buf.extend_from_slice(&ABSENT.to_be_bytes());
835 buf.extend_from_slice(&0u32.to_be_bytes());
836 buf.extend_from_slice(&5u32.to_be_bytes());
838 buf.extend_from_slice(&400u32.to_be_bytes());
840 let offset: u64 = 0x1_0000_0000; buf.extend_from_slice(&offset.to_be_bytes());
843
844 let header = parse_header(&buf, NcFormat::Offset64).unwrap();
845 assert_eq!(header.variables.len(), 1);
846 assert_eq!(header.variables[0].data_offset, 0x1_0000_0000);
847 assert_eq!(header.variables[0]._data_size, 400);
848 }
849
850 #[test]
851 fn test_cdf5_uses_64_bit_counts_for_var_metadata() {
852 let data = build_cdf5_header(
853 &[("n", 4)],
854 &[
855 ("ubyte_var", &[0], 7, 4, 128),
856 ("int64_var", &[0], 10, 32, 256),
857 ],
858 0,
859 );
860
861 let header = parse_header(&data, NcFormat::Cdf5).unwrap();
862 assert_eq!(header.variables.len(), 2);
863 assert_eq!(header.variables[0].name, "ubyte_var");
864 assert_eq!(header.variables[0].dtype, NcType::UByte);
865 assert_eq!(header.variables[0].dimensions[0].name, "n");
866 assert_eq!(header.variables[1].name, "int64_var");
867 assert_eq!(header.variables[1].dtype, NcType::Int64);
868 assert_eq!(header.variables[1].data_offset, 256);
869 }
870
871 #[test]
872 fn test_unlimited_dimension_size_tracks_numrecs() {
873 let data = build_cdf1_header(
874 &[("time", 0), ("x", 5)],
875 &[],
876 &[("series", &[0, 1], 6, 40, 128)],
877 3,
878 );
879
880 let header = parse_header(&data, NcFormat::Classic).unwrap();
881 assert_eq!(header.dimensions[0].size, 3);
882 assert_eq!(header.variables[0].shape(), vec![3, 5]);
883 }
884
885 #[test]
886 fn test_double_attribute() {
887 let pi = std::f64::consts::PI;
888 let value_bytes = pi.to_be_bytes();
889 let data = build_cdf1_header(
890 &[],
891 &[("pi", 6, &value_bytes)], &[],
893 0,
894 );
895 let header = parse_header(&data, NcFormat::Classic).unwrap();
896 assert_eq!(header.global_attributes.len(), 1);
897 if let NcAttrValue::Doubles(ref v) = header.global_attributes[0].value {
898 assert_eq!(v.len(), 1);
899 assert!((v[0] - pi).abs() < 1e-15);
900 } else {
901 panic!("expected Doubles attribute");
902 }
903 }
904
905 #[test]
906 fn test_short_attribute_with_padding() {
907 let mut value_bytes = Vec::new();
909 value_bytes.extend_from_slice(&1i16.to_be_bytes());
910 value_bytes.extend_from_slice(&2i16.to_be_bytes());
911 value_bytes.extend_from_slice(&3i16.to_be_bytes());
912 let mut buf = Vec::new();
915 buf.extend_from_slice(b"CDF\x01");
916 buf.extend_from_slice(&0u32.to_be_bytes()); buf.extend_from_slice(&ABSENT.to_be_bytes());
919 buf.extend_from_slice(&0u32.to_be_bytes());
920 buf.extend_from_slice(&NC_ATTRIBUTE.to_be_bytes());
922 buf.extend_from_slice(&1u32.to_be_bytes());
923 write_name_cdf1(&mut buf, "vals");
924 buf.extend_from_slice(&3u32.to_be_bytes()); buf.extend_from_slice(&3u32.to_be_bytes()); buf.extend_from_slice(&value_bytes);
927 buf.extend_from_slice(&[0, 0]);
929 buf.extend_from_slice(&ABSENT.to_be_bytes());
931 buf.extend_from_slice(&0u32.to_be_bytes());
932
933 let header = parse_header(&buf, NcFormat::Classic).unwrap();
934 if let NcAttrValue::Shorts(ref v) = header.global_attributes[0].value {
935 assert_eq!(v, &[1, 2, 3]);
936 } else {
937 panic!("expected Shorts attribute");
938 }
939 }
940
941 #[test]
942 fn test_name_padding() {
943 let data = build_cdf1_header(
945 &[("a", 1), ("ab", 2), ("abc", 3), ("abcd", 4), ("abcde", 5)],
946 &[],
947 &[],
948 0,
949 );
950 let header = parse_header(&data, NcFormat::Classic).unwrap();
951 assert_eq!(header.dimensions.len(), 5);
952 assert_eq!(header.dimensions[0].name, "a");
953 assert_eq!(header.dimensions[1].name, "ab");
954 assert_eq!(header.dimensions[2].name, "abc");
955 assert_eq!(header.dimensions[3].name, "abcd");
956 assert_eq!(header.dimensions[4].name, "abcde");
957 }
958
959 #[test]
960 fn test_invalid_dimension_reference() {
961 let data = build_cdf1_header(
963 &[("x", 10)], &[],
965 &[("bad_var", &[5], 4, 40, 100)], 0,
967 );
968 let result = parse_header(&data, NcFormat::Classic);
969 assert!(result.is_err());
970 }
971
972 #[test]
973 fn test_byte_attribute() {
974 let value_bytes: &[u8] = &[0xFF]; let data = build_cdf1_header(
976 &[],
977 &[("flag", 1, value_bytes)], &[],
979 0,
980 );
981 let header = parse_header(&data, NcFormat::Classic).unwrap();
982 if let NcAttrValue::Bytes(ref v) = header.global_attributes[0].value {
983 assert_eq!(v, &[-1i8]);
984 } else {
985 panic!("expected Bytes attribute");
986 }
987 }
988
989 #[test]
990 fn test_float_attribute() {
991 let val = std::f32::consts::PI;
992 let value_bytes = val.to_be_bytes();
993 let data = build_cdf1_header(
994 &[],
995 &[("pi_approx", 5, &value_bytes)], &[],
997 0,
998 );
999 let header = parse_header(&data, NcFormat::Classic).unwrap();
1000 if let NcAttrValue::Floats(ref v) = header.global_attributes[0].value {
1001 assert_eq!(v.len(), 1);
1002 assert!((v[0] - std::f32::consts::PI).abs() < 1e-6);
1003 } else {
1004 panic!("expected Floats attribute");
1005 }
1006 }
1007
1008 #[test]
1009 fn test_multiple_global_attributes() {
1010 let int_val = 100i32.to_be_bytes();
1011 let float_val = 2.5f32.to_be_bytes();
1012 let data = build_cdf1_header(
1013 &[],
1014 &[("count", 4, &int_val), ("scale", 5, &float_val)],
1015 &[],
1016 0,
1017 );
1018 let header = parse_header(&data, NcFormat::Classic).unwrap();
1019 assert_eq!(header.global_attributes.len(), 2);
1020 assert_eq!(header.global_attributes[0].name, "count");
1021 assert_eq!(header.global_attributes[1].name, "scale");
1022 }
1023}