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