1use ndarray::{ArrayD, IxDyn};
11
12use crate::error::{Error, Result};
13use crate::types::{NcType, NcVariable};
14
15pub trait NcReadType: Clone + Default + Send + 'static {
17 fn nc_type() -> NcType;
19
20 fn from_be_bytes(bytes: &[u8]) -> Result<Self>;
22
23 fn element_size() -> usize;
25
26 fn decode_bulk_be(raw: &[u8], count: usize) -> Result<Vec<Self>> {
33 let elem_size = Self::element_size();
34 let needed = count.checked_mul(elem_size).ok_or_else(|| {
35 Error::InvalidData("classic decode byte count exceeds platform usize".to_string())
36 })?;
37 if raw.len() < needed {
38 return Err(Error::InvalidData(format!(
39 "need {} bytes for {} elements, got {}",
40 needed,
41 count,
42 raw.len()
43 )));
44 }
45 let mut values = Vec::with_capacity(count);
46 for i in 0..count {
47 let start = i * elem_size;
48 values.push(Self::from_be_bytes(&raw[start..start + elem_size])?);
49 }
50 Ok(values)
51 }
52
53 fn decode_bulk_be_into(raw: &[u8], dst: &mut [Self]) -> Result<()> {
56 let elem_size = Self::element_size();
57 let needed = dst.len().checked_mul(elem_size).ok_or_else(|| {
58 Error::InvalidData("classic decode byte count exceeds platform usize".to_string())
59 })?;
60 if raw.len() < needed {
61 return Err(Error::InvalidData(format!(
62 "need {} bytes for {} elements, got {}",
63 needed,
64 dst.len(),
65 raw.len()
66 )));
67 }
68 for (out, chunk) in dst.iter_mut().zip(raw[..needed].chunks_exact(elem_size)) {
69 *out = Self::from_be_bytes(chunk)?;
70 }
71 Ok(())
72 }
73}
74
75macro_rules! impl_nc_read_type {
76 ($ty:ty, $nc_type:expr, $size:expr) => {
77 impl NcReadType for $ty {
78 fn nc_type() -> NcType {
79 $nc_type
80 }
81
82 fn from_be_bytes(bytes: &[u8]) -> Result<Self> {
83 if bytes.len() < $size {
84 return Err(Error::InvalidData(format!(
85 "need {} bytes for {}, got {}",
86 $size,
87 stringify!($ty),
88 bytes.len()
89 )));
90 }
91 let mut arr = [0u8; $size];
92 arr.copy_from_slice(&bytes[..$size]);
93 Ok(<$ty>::from_be_bytes(arr))
94 }
95
96 fn element_size() -> usize {
97 $size
98 }
99
100 fn decode_bulk_be(raw: &[u8], count: usize) -> Result<Vec<Self>> {
101 let total_bytes = count.checked_mul($size).ok_or_else(|| {
102 Error::InvalidData(
103 "classic decode byte count exceeds platform usize".to_string(),
104 )
105 })?;
106 if raw.len() < total_bytes {
107 return Err(Error::InvalidData(format!(
108 "need {} bytes for {} elements of {}, got {}",
109 total_bytes,
110 count,
111 stringify!($ty),
112 raw.len()
113 )));
114 }
115 let bytes = &raw[..total_bytes];
116 #[cfg(target_endian = "big")]
117 {
118 let mut values = Vec::<$ty>::with_capacity(count);
120 unsafe {
121 std::ptr::copy_nonoverlapping(
122 bytes.as_ptr(),
123 values.as_mut_ptr() as *mut u8,
124 total_bytes,
125 );
126 values.set_len(count);
127 }
128 Ok(values)
129 }
130 #[cfg(target_endian = "little")]
131 {
132 Ok(bytes
134 .chunks_exact($size)
135 .map(|chunk| {
136 let mut arr = [0u8; $size];
137 arr.copy_from_slice(chunk);
138 <$ty>::from_be_bytes(arr)
139 })
140 .collect())
141 }
142 }
143
144 fn decode_bulk_be_into(raw: &[u8], dst: &mut [Self]) -> Result<()> {
145 let total_bytes = dst.len().checked_mul($size).ok_or_else(|| {
146 Error::InvalidData(
147 "classic decode byte count exceeds platform usize".to_string(),
148 )
149 })?;
150 if raw.len() < total_bytes {
151 return Err(Error::InvalidData(format!(
152 "need {} bytes for {} elements of {}, got {}",
153 total_bytes,
154 dst.len(),
155 stringify!($ty),
156 raw.len()
157 )));
158 }
159 let bytes = &raw[..total_bytes];
160 #[cfg(target_endian = "big")]
161 {
162 unsafe {
163 std::ptr::copy_nonoverlapping(
164 bytes.as_ptr(),
165 dst.as_mut_ptr() as *mut u8,
166 total_bytes,
167 );
168 }
169 Ok(())
170 }
171 #[cfg(target_endian = "little")]
172 {
173 for (out, chunk) in dst.iter_mut().zip(bytes.chunks_exact($size)) {
174 let mut arr = [0u8; $size];
175 arr.copy_from_slice(chunk);
176 *out = <$ty>::from_be_bytes(arr);
177 }
178 Ok(())
179 }
180 }
181 }
182 };
183}
184
185impl_nc_read_type!(i8, NcType::Byte, 1);
186impl_nc_read_type!(i16, NcType::Short, 2);
187impl_nc_read_type!(i32, NcType::Int, 4);
188impl_nc_read_type!(f32, NcType::Float, 4);
189impl_nc_read_type!(f64, NcType::Double, 8);
190impl_nc_read_type!(u8, NcType::UByte, 1);
191impl_nc_read_type!(u16, NcType::UShort, 2);
192impl_nc_read_type!(u32, NcType::UInt, 4);
193impl_nc_read_type!(i64, NcType::Int64, 8);
194impl_nc_read_type!(u64, NcType::UInt64, 8);
195
196pub fn read_non_record_variable<T: NcReadType>(
201 file_data: &[u8],
202 var: &NcVariable,
203) -> Result<ArrayD<T>> {
204 if var.is_record_var {
205 return Err(Error::InvalidData(
206 "use read_record_variable for record variables".to_string(),
207 ));
208 }
209
210 let offset = crate::types::checked_usize_from_u64(var.data_offset, "variable data offset")?;
211 let total_elements = checked_non_record_element_count(var)?;
212 let elem_size = T::element_size();
213 let total_bytes = total_elements.checked_mul(elem_size).ok_or_else(|| {
214 Error::InvalidData(format!(
215 "variable '{}' size in bytes exceeds platform usize",
216 var.name
217 ))
218 })?;
219
220 let end = offset.checked_add(total_bytes).ok_or_else(|| {
221 Error::InvalidData(format!(
222 "variable '{}' byte range exceeds platform usize",
223 var.name
224 ))
225 })?;
226 if end > file_data.len() {
227 return Err(Error::InvalidData(format!(
228 "variable '{}' data extends beyond file: offset={}, size={}, file_len={}",
229 var.name,
230 offset,
231 total_bytes,
232 file_data.len()
233 )));
234 }
235
236 let data_slice = &file_data[offset..end];
237 let values = T::decode_bulk_be(data_slice, total_elements)?;
238
239 let shape: Vec<usize> = var
240 .shape()
241 .iter()
242 .map(|&s| crate::types::checked_usize_from_u64(s, "variable dimension"))
243 .collect::<Result<Vec<_>>>()?;
244 if shape.is_empty() {
245 ArrayD::from_shape_vec(IxDyn(&[]), values)
247 } else {
248 ArrayD::from_shape_vec(IxDyn(&shape), values)
249 }
250 .map_err(|e| Error::InvalidData(format!("failed to create array: {}", e)))
251}
252
253pub fn read_non_record_variable_into<T: NcReadType>(
255 file_data: &[u8],
256 var: &NcVariable,
257 dst: &mut [T],
258) -> Result<()> {
259 if var.is_record_var {
260 return Err(Error::InvalidData(
261 "use read_record_variable_into for record variables".to_string(),
262 ));
263 }
264
265 let total_elements = checked_non_record_element_count(var)?;
266 if dst.len() != total_elements {
267 return Err(Error::InvalidData(format!(
268 "destination has {} elements, variable '{}' requires {}",
269 dst.len(),
270 var.name,
271 total_elements
272 )));
273 }
274
275 let offset = crate::types::checked_usize_from_u64(var.data_offset, "variable data offset")?;
276 let elem_size = T::element_size();
277 let total_bytes = total_elements.checked_mul(elem_size).ok_or_else(|| {
278 Error::InvalidData(format!(
279 "variable '{}' size in bytes exceeds platform usize",
280 var.name
281 ))
282 })?;
283
284 let end = offset.checked_add(total_bytes).ok_or_else(|| {
285 Error::InvalidData(format!(
286 "variable '{}' byte range exceeds platform usize",
287 var.name
288 ))
289 })?;
290 if end > file_data.len() {
291 return Err(Error::InvalidData(format!(
292 "variable '{}' data extends beyond file: offset={}, size={}, file_len={}",
293 var.name,
294 offset,
295 total_bytes,
296 file_data.len()
297 )));
298 }
299
300 T::decode_bulk_be_into(&file_data[offset..end], dst)
301}
302
303pub fn read_record_variable<T: NcReadType>(
315 file_data: &[u8],
316 var: &NcVariable,
317 numrecs: u64,
318 record_stride: u64,
319) -> Result<ArrayD<T>> {
320 if !var.is_record_var {
321 return Err(Error::InvalidData(
322 "use read_non_record_variable for non-record variables".to_string(),
323 ));
324 }
325
326 let elem_size = T::element_size();
327 let base_offset =
328 crate::types::checked_usize_from_u64(var.data_offset, "record variable data offset")?;
329 let numrecs_usize = crate::types::checked_usize_from_u64(numrecs, "record count")?;
330 let record_stride_usize = crate::types::checked_usize_from_u64(record_stride, "record stride")?;
331
332 let mut shape: Vec<usize> = var
334 .shape()
335 .iter()
336 .map(|&s| crate::types::checked_usize_from_u64(s, "record variable dimension"))
337 .collect::<Result<Vec<_>>>()?;
338 if shape.is_empty() {
339 return Err(Error::InvalidData(
340 "record variable must have at least one dimension".to_string(),
341 ));
342 }
343 shape[0] = numrecs_usize;
344
345 let elements_per_record: usize = shape[1..].iter().product::<usize>().max(1);
347 let bytes_per_record = elements_per_record.checked_mul(elem_size).ok_or_else(|| {
348 Error::InvalidData(format!(
349 "record variable '{}' bytes per record exceed platform usize",
350 var.name
351 ))
352 })?;
353 let total_elements = numrecs_usize
354 .checked_mul(elements_per_record)
355 .ok_or_else(|| {
356 Error::InvalidData(format!(
357 "record variable '{}' element count exceeds platform usize",
358 var.name
359 ))
360 })?;
361
362 let mut values = Vec::with_capacity(total_elements);
363
364 for rec in 0..numrecs_usize {
365 let rec_offset = base_offset
366 .checked_add(rec.checked_mul(record_stride_usize).ok_or_else(|| {
367 Error::InvalidData(format!(
368 "record variable '{}' byte offset exceeds platform usize",
369 var.name
370 ))
371 })?)
372 .ok_or_else(|| {
373 Error::InvalidData(format!(
374 "record variable '{}' byte offset exceeds platform usize",
375 var.name
376 ))
377 })?;
378 let rec_end = rec_offset.checked_add(bytes_per_record).ok_or_else(|| {
379 Error::InvalidData(format!(
380 "record variable '{}' record range exceeds platform usize",
381 var.name
382 ))
383 })?;
384 if rec_end > file_data.len() {
385 return Err(Error::InvalidData(format!(
386 "record {} for variable '{}' extends beyond file",
387 rec, var.name
388 )));
389 }
390 let rec_slice = &file_data[rec_offset..rec_end];
391 let rec_values = T::decode_bulk_be(rec_slice, elements_per_record)?;
392 values.extend(rec_values);
393 }
394
395 ArrayD::from_shape_vec(IxDyn(&shape), values)
396 .map_err(|e| Error::InvalidData(format!("failed to create array: {}", e)))
397}
398
399pub fn read_record_variable_into<T: NcReadType>(
401 file_data: &[u8],
402 var: &NcVariable,
403 numrecs: u64,
404 record_stride: u64,
405 dst: &mut [T],
406) -> Result<()> {
407 if !var.is_record_var {
408 return Err(Error::InvalidData(
409 "use read_non_record_variable_into for non-record variables".to_string(),
410 ));
411 }
412
413 let elem_size = T::element_size();
414 let base_offset =
415 crate::types::checked_usize_from_u64(var.data_offset, "record variable data offset")?;
416 let numrecs_usize = crate::types::checked_usize_from_u64(numrecs, "record count")?;
417 let record_stride_usize = crate::types::checked_usize_from_u64(record_stride, "record stride")?;
418
419 if var.dimensions.is_empty() {
420 return Err(Error::InvalidData(
421 "record variable must have at least one dimension".to_string(),
422 ));
423 }
424
425 let elements_per_record = checked_record_elements_per_record(var)?;
426 let bytes_per_record = elements_per_record.checked_mul(elem_size).ok_or_else(|| {
427 Error::InvalidData(format!(
428 "record variable '{}' bytes per record exceed platform usize",
429 var.name
430 ))
431 })?;
432 let total_elements = numrecs_usize
433 .checked_mul(elements_per_record)
434 .ok_or_else(|| {
435 Error::InvalidData(format!(
436 "record variable '{}' element count exceeds platform usize",
437 var.name
438 ))
439 })?;
440 if dst.len() != total_elements {
441 return Err(Error::InvalidData(format!(
442 "destination has {} elements, variable '{}' requires {}",
443 dst.len(),
444 var.name,
445 total_elements
446 )));
447 }
448
449 for rec in 0..numrecs_usize {
450 let rec_offset = base_offset
451 .checked_add(rec.checked_mul(record_stride_usize).ok_or_else(|| {
452 Error::InvalidData(format!(
453 "record variable '{}' byte offset exceeds platform usize",
454 var.name
455 ))
456 })?)
457 .ok_or_else(|| {
458 Error::InvalidData(format!(
459 "record variable '{}' byte offset exceeds platform usize",
460 var.name
461 ))
462 })?;
463 let rec_end = rec_offset.checked_add(bytes_per_record).ok_or_else(|| {
464 Error::InvalidData(format!(
465 "record variable '{}' record range exceeds platform usize",
466 var.name
467 ))
468 })?;
469 if rec_end > file_data.len() {
470 return Err(Error::InvalidData(format!(
471 "record {} for variable '{}' extends beyond file",
472 rec, var.name
473 )));
474 }
475
476 let dst_start = rec.checked_mul(elements_per_record).ok_or_else(|| {
477 Error::InvalidData(format!(
478 "record variable '{}' destination offset exceeds platform usize",
479 var.name
480 ))
481 })?;
482 let dst_end = dst_start.checked_add(elements_per_record).ok_or_else(|| {
483 Error::InvalidData(format!(
484 "record variable '{}' destination range exceeds platform usize",
485 var.name
486 ))
487 })?;
488 T::decode_bulk_be_into(
489 &file_data[rec_offset..rec_end],
490 &mut dst[dst_start..dst_end],
491 )?;
492 }
493
494 Ok(())
495}
496
497pub fn compute_record_stride(variables: &[NcVariable]) -> u64 {
502 variables
503 .iter()
504 .filter(|v| v.is_record_var)
505 .map(|v| {
506 let size = v.record_size;
507 let rem = size % 4;
509 if rem == 0 {
510 size
511 } else {
512 size + (4 - rem)
513 }
514 })
515 .sum()
516}
517
518fn checked_non_record_element_count(var: &NcVariable) -> Result<usize> {
519 let mut total = 1u64;
520 for dim in &var.dimensions {
521 total = total.checked_mul(dim.size).ok_or_else(|| {
522 Error::InvalidData("variable element count overflows u64".to_string())
523 })?;
524 }
525 crate::types::checked_usize_from_u64(total, "variable element count")
526}
527
528fn checked_record_elements_per_record(var: &NcVariable) -> Result<usize> {
529 let mut elements = 1usize;
530 for dim in var.dimensions.iter().skip(1) {
531 let size = crate::types::checked_usize_from_u64(dim.size, "record variable dimension")?;
532 elements = elements.checked_mul(size).ok_or_else(|| {
533 Error::InvalidData(format!(
534 "record variable '{}' elements per record exceed platform usize",
535 var.name
536 ))
537 })?;
538 }
539 Ok(elements)
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545 use crate::types::NcDimension;
546
547 #[test]
548 fn test_read_non_record_1d_float() {
549 let mut file_data = vec![0u8; 200];
551 let values = [1.0f32, 2.0f32, 3.0f32];
552 for (i, &v) in values.iter().enumerate() {
553 let bytes = v.to_be_bytes();
554 file_data[100 + i * 4..100 + i * 4 + 4].copy_from_slice(&bytes);
555 }
556
557 let var = NcVariable {
558 name: "temp".to_string(),
559 dimensions: vec![NcDimension {
560 name: "x".to_string(),
561 size: 3,
562 is_unlimited: false,
563 }],
564 dtype: NcType::Float,
565 attributes: vec![],
566 data_offset: 100,
567 _data_size: 12,
568 is_record_var: false,
569 record_size: 0,
570 };
571
572 let arr: ArrayD<f32> = read_non_record_variable(&file_data, &var).unwrap();
573 assert_eq!(arr.shape(), &[3]);
574 assert_eq!(arr[[0]], 1.0f32);
575 assert_eq!(arr[[1]], 2.0f32);
576 assert_eq!(arr[[2]], 3.0f32);
577 }
578
579 #[test]
580 fn test_read_non_record_variable_into() {
581 let mut file_data = vec![0u8; 200];
582 let values = [1.0f32, 2.0f32, 3.0f32];
583 for (i, &v) in values.iter().enumerate() {
584 file_data[100 + i * 4..100 + i * 4 + 4].copy_from_slice(&v.to_be_bytes());
585 }
586
587 let var = NcVariable {
588 name: "temp".to_string(),
589 dimensions: vec![NcDimension {
590 name: "x".to_string(),
591 size: 3,
592 is_unlimited: false,
593 }],
594 dtype: NcType::Float,
595 attributes: vec![],
596 data_offset: 100,
597 _data_size: 12,
598 is_record_var: false,
599 record_size: 0,
600 };
601
602 let mut dst = [0.0f32; 3];
603 read_non_record_variable_into(&file_data, &var, &mut dst).unwrap();
604 assert_eq!(dst, values);
605 }
606
607 #[test]
608 fn test_read_non_record_2d_int() {
609 let values: Vec<i32> = vec![10, 20, 30, 40, 50, 60];
611 let mut file_data = Vec::new();
612 for &v in &values {
613 file_data.extend_from_slice(&v.to_be_bytes());
614 }
615
616 let var = NcVariable {
617 name: "grid".to_string(),
618 dimensions: vec![
619 NcDimension {
620 name: "y".to_string(),
621 size: 2,
622 is_unlimited: false,
623 },
624 NcDimension {
625 name: "x".to_string(),
626 size: 3,
627 is_unlimited: false,
628 },
629 ],
630 dtype: NcType::Int,
631 attributes: vec![],
632 data_offset: 0,
633 _data_size: 24,
634 is_record_var: false,
635 record_size: 0,
636 };
637
638 let arr: ArrayD<i32> = read_non_record_variable(&file_data, &var).unwrap();
639 assert_eq!(arr.shape(), &[2, 3]);
640 assert_eq!(arr[[0, 0]], 10);
641 assert_eq!(arr[[0, 2]], 30);
642 assert_eq!(arr[[1, 0]], 40);
643 assert_eq!(arr[[1, 2]], 60);
644 }
645
646 #[test]
647 fn test_read_non_record_variable_into_rejects_wrong_destination_len() {
648 let var = NcVariable {
649 name: "grid".to_string(),
650 dimensions: vec![NcDimension {
651 name: "x".to_string(),
652 size: 3,
653 is_unlimited: false,
654 }],
655 dtype: NcType::Float,
656 attributes: vec![],
657 data_offset: 0,
658 _data_size: 12,
659 is_record_var: false,
660 record_size: 0,
661 };
662
663 let mut dst = [0.0f32; 2];
664 let err = read_non_record_variable_into(&[0; 12], &var, &mut dst).unwrap_err();
665 assert!(matches!(err, Error::InvalidData(_)));
666 }
667
668 #[test]
669 fn test_compute_record_stride() {
670 let vars = vec![
671 NcVariable {
672 name: "a".to_string(),
673 dimensions: vec![],
674 dtype: NcType::Float,
675 attributes: vec![],
676 data_offset: 0,
677 _data_size: 0,
678 is_record_var: true,
679 record_size: 20, },
681 NcVariable {
682 name: "b".to_string(),
683 dimensions: vec![],
684 dtype: NcType::Short,
685 attributes: vec![],
686 data_offset: 0,
687 _data_size: 0,
688 is_record_var: true,
689 record_size: 6, },
691 NcVariable {
692 name: "c".to_string(),
693 dimensions: vec![],
694 dtype: NcType::Double,
695 attributes: vec![],
696 data_offset: 0,
697 _data_size: 100,
698 is_record_var: false, record_size: 0,
700 },
701 ];
702 assert_eq!(compute_record_stride(&vars), 28);
704 }
705
706 #[test]
707 fn test_read_record_variable() {
708 let mut file_data = vec![0u8; 200];
712 let base = 100usize;
713 let record_values: Vec<Vec<f32>> = vec![vec![1.0, 2.0], vec![3.0, 4.0], vec![5.0, 6.0]];
714 for (rec, vals) in record_values.iter().enumerate() {
715 for (i, &v) in vals.iter().enumerate() {
716 let offset = base + rec * 8 + i * 4;
717 file_data[offset..offset + 4].copy_from_slice(&v.to_be_bytes());
718 }
719 }
720
721 let var = NcVariable {
722 name: "temp".to_string(),
723 dimensions: vec![
724 NcDimension {
725 name: "time".to_string(),
726 size: 0, is_unlimited: true,
728 },
729 NcDimension {
730 name: "x".to_string(),
731 size: 2,
732 is_unlimited: false,
733 },
734 ],
735 dtype: NcType::Float,
736 attributes: vec![],
737 data_offset: 100,
738 _data_size: 0,
739 is_record_var: true,
740 record_size: 8,
741 };
742
743 let arr: ArrayD<f32> = read_record_variable(&file_data, &var, 3, 8).unwrap();
744 assert_eq!(arr.shape(), &[3, 2]);
745 assert_eq!(arr[[0, 0]], 1.0);
746 assert_eq!(arr[[0, 1]], 2.0);
747 assert_eq!(arr[[1, 0]], 3.0);
748 assert_eq!(arr[[2, 1]], 6.0);
749 }
750
751 #[test]
752 fn test_read_record_variable_into() {
753 let mut file_data = vec![0u8; 200];
754 let base = 100usize;
755 let record_values: Vec<Vec<f32>> = vec![vec![1.0, 2.0], vec![3.0, 4.0], vec![5.0, 6.0]];
756 for (rec, vals) in record_values.iter().enumerate() {
757 for (i, &v) in vals.iter().enumerate() {
758 let offset = base + rec * 8 + i * 4;
759 file_data[offset..offset + 4].copy_from_slice(&v.to_be_bytes());
760 }
761 }
762
763 let var = NcVariable {
764 name: "temp".to_string(),
765 dimensions: vec![
766 NcDimension {
767 name: "time".to_string(),
768 size: 0,
769 is_unlimited: true,
770 },
771 NcDimension {
772 name: "x".to_string(),
773 size: 2,
774 is_unlimited: false,
775 },
776 ],
777 dtype: NcType::Float,
778 attributes: vec![],
779 data_offset: 100,
780 _data_size: 0,
781 is_record_var: true,
782 record_size: 8,
783 };
784
785 let mut dst = [0.0f32; 6];
786 read_record_variable_into(&file_data, &var, 3, 8, &mut dst).unwrap();
787 assert_eq!(dst, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
788 }
789
790 #[test]
791 fn test_read_record_variable_into_rejects_wrong_destination_len() {
792 let var = NcVariable {
793 name: "temp".to_string(),
794 dimensions: vec![
795 NcDimension {
796 name: "time".to_string(),
797 size: 0,
798 is_unlimited: true,
799 },
800 NcDimension {
801 name: "x".to_string(),
802 size: 2,
803 is_unlimited: false,
804 },
805 ],
806 dtype: NcType::Float,
807 attributes: vec![],
808 data_offset: 0,
809 _data_size: 0,
810 is_record_var: true,
811 record_size: 8,
812 };
813
814 let mut dst = [0.0f32; 5];
815 let err = read_record_variable_into(&[0; 24], &var, 3, 8, &mut dst).unwrap_err();
816 assert!(matches!(err, Error::InvalidData(_)));
817 }
818
819 #[test]
820 fn test_read_non_record_variable_rejects_element_count_overflow() {
821 let var = NcVariable {
822 name: "huge".to_string(),
823 dimensions: vec![
824 NcDimension {
825 name: "y".to_string(),
826 size: u64::MAX,
827 is_unlimited: false,
828 },
829 NcDimension {
830 name: "x".to_string(),
831 size: 2,
832 is_unlimited: false,
833 },
834 ],
835 dtype: NcType::Float,
836 attributes: vec![],
837 data_offset: 0,
838 _data_size: 0,
839 is_record_var: false,
840 record_size: 0,
841 };
842
843 let err = read_non_record_variable::<f32>(&[], &var).unwrap_err();
844 assert!(matches!(err, Error::InvalidData(_)));
845 }
846
847 #[test]
848 fn test_read_record_variable_rejects_record_offset_overflow() {
849 let var = NcVariable {
850 name: "huge_record".to_string(),
851 dimensions: vec![
852 NcDimension {
853 name: "time".to_string(),
854 size: 0,
855 is_unlimited: true,
856 },
857 NcDimension {
858 name: "x".to_string(),
859 size: 1,
860 is_unlimited: false,
861 },
862 ],
863 dtype: NcType::Float,
864 attributes: vec![],
865 data_offset: u64::MAX,
866 _data_size: 0,
867 is_record_var: true,
868 record_size: 4,
869 };
870
871 let err = read_record_variable::<f32>(&[], &var, 1, 4).unwrap_err();
872 assert!(matches!(err, Error::InvalidData(_)));
873 }
874}