1use crate::error::{GGUFError, Result};
4use crate::format::constants::GGUF_MAX_METADATA_DECODED_SIZE;
5use crate::format::types::GGUFTensorType as TensorType;
6use crate::format::{GGUFHeader, Metadata, TensorInfo};
7use crate::tensor::{TensorData, TensorInfo as TensorInfoNew, TensorShape};
8use std::collections::HashMap;
9use std::io::{BufReader, Read};
10
11const MIN_TENSOR_TRACKING_CAPACITY: usize = 64;
12const MAX_READER_CHUNK_SIZE: usize = 1024 * 1024;
13
14#[derive(Debug)]
16pub struct GGUFStreamReader<R> {
17 reader: R,
19 header: GGUFHeader,
21 metadata: Metadata,
23 tensor_infos: Vec<TensorInfoNew>,
25 tensor_name_index: HashMap<String, usize>,
27 position: u64,
29 at_tensor_data: bool,
31 tensor_data_offset: u64,
33 tensor_data_span: u64,
35 tensor_alignment: u64,
37 tensor_buffer_size: usize,
39 tensor_order: Vec<usize>,
41 next_tensor: usize,
43}
44
45#[derive(Debug, Clone)]
47pub struct StreamReaderConfig {
48 pub buffer_size: usize,
50 pub validate_checksums: bool,
55 pub max_metadata_size: usize,
57 pub max_decoded_metadata_size: usize,
59 pub max_tensor_count: usize,
61}
62
63impl Default for StreamReaderConfig {
64 fn default() -> Self {
65 Self {
66 buffer_size: 64 * 1024, validate_checksums: false, max_metadata_size: 16 * 1024 * 1024, max_decoded_metadata_size: GGUF_MAX_METADATA_DECODED_SIZE,
70 max_tensor_count: 100_000,
71 }
72 }
73}
74
75impl<R: Read> GGUFStreamReader<R> {
76 pub fn new(reader: R) -> Result<Self> {
78 Self::with_config(reader, StreamReaderConfig::default())
79 }
80
81 pub fn with_config(mut reader: R, config: StreamReaderConfig) -> Result<Self> {
83 if config.validate_checksums {
84 return Err(GGUFError::FeatureUnavailable("stream checksum validation".to_string()));
85 }
86 let mut position = 0u64;
87
88 let header = GGUFHeader::read_from(&mut reader)?;
90 header.validate_comprehensive()?;
91 position = position
92 .checked_add(GGUFHeader::size() as u64)
93 .ok_or_else(|| GGUFError::Format("Stream position overflow".to_string()))?;
94
95 let max_tensor_count = u64::try_from(config.max_tensor_count).unwrap_or(u64::MAX);
97 if header.tensor_count > max_tensor_count {
98 return Err(GGUFError::Format(format!(
99 "Too many tensors: {} exceeds limit of {}",
100 header.tensor_count, config.max_tensor_count
101 )));
102 }
103
104 let metadata = Metadata::read_from_with_limits(
106 &mut reader,
107 header.metadata_kv_count,
108 config.max_metadata_size,
109 config.max_decoded_metadata_size,
110 )?;
111 let metadata_size = metadata.checked_serialized_size().ok_or_else(|| {
112 GGUFError::InvalidMetadata("Serialized metadata size overflows usize".to_string())
113 })?;
114 position = position
115 .checked_add(u64::try_from(metadata_size).map_err(|_| {
116 GGUFError::InvalidMetadata("Metadata size does not fit u64".to_string())
117 })?)
118 .ok_or_else(|| GGUFError::Format("Stream position overflow".to_string()))?;
119 let tensor_alignment = u64::try_from(metadata.tensor_alignment()?).map_err(|_| {
120 GGUFError::InvalidMetadata("Tensor alignment does not fit u64".to_string())
121 })?;
122
123 let tensor_capacity = usize::try_from(header.tensor_count).map_err(|_| {
125 GGUFError::InvalidTensorData("Tensor count does not fit this platform".to_string())
126 })?;
127 let mut tensor_infos = Vec::new();
128 for _ in 0..header.tensor_count {
129 let tensor_info = TensorInfo::read_from(&mut reader)?;
130 let descriptor_size = tensor_info.checked_serialized_size().ok_or_else(|| {
131 GGUFError::InvalidTensorData("Tensor descriptor size overflows usize".to_string())
132 })?;
133 position = position
134 .checked_add(u64::try_from(descriptor_size).map_err(|_| {
135 GGUFError::InvalidTensorData(
136 "Tensor descriptor size does not fit u64".to_string(),
137 )
138 })?)
139 .ok_or_else(|| GGUFError::Format("Stream position overflow".to_string()))?;
140
141 let shape = TensorShape::new(tensor_info.dimensions)?;
143 let tensor_type = TensorType::from_u32(tensor_info.tensor_type)?;
144
145 let new_tensor_info =
146 TensorInfoNew::new(tensor_info.name, shape, tensor_type, tensor_info.offset);
147 new_tensor_info.validate()?;
148 if !new_tensor_info.data_offset().is_multiple_of(tensor_alignment) {
149 return Err(GGUFError::InvalidTensorData(format!(
150 "Tensor '{}' offset {} is not aligned to {} bytes",
151 new_tensor_info.name(),
152 new_tensor_info.data_offset(),
153 tensor_alignment
154 )));
155 }
156 try_reserve_vec_slot(&mut tensor_infos, tensor_capacity, "tensor descriptor list")?;
157 tensor_infos.push(new_tensor_info);
158 }
159
160 let mut tensor_name_index = HashMap::new();
161 for (index, tensor_info) in tensor_infos.iter().enumerate() {
162 try_reserve_map_slot(&mut tensor_name_index, tensor_infos.len(), "tensor name index")?;
163 let name = try_clone_tensor_name(tensor_info.name())?;
164 if tensor_name_index.insert(name, index).is_some() {
165 return Err(GGUFError::InvalidTensorData(format!(
166 "Duplicate tensor name: {}",
167 tensor_info.name()
168 )));
169 }
170 }
171
172 let mut tensor_order = Vec::new();
173 tensor_order.try_reserve_exact(tensor_capacity).map_err(|_| {
174 GGUFError::InvalidTensorData("Unable to allocate tensor order".to_string())
175 })?;
176 tensor_order.extend(0..tensor_infos.len());
177 tensor_order.sort_unstable_by_key(|&index| (tensor_infos[index].data_offset(), index));
178
179 let tensor_data_offset = checked_align_u64(position, tensor_alignment)?;
181 let tensor_data_span =
182 validate_tensor_layout(&tensor_infos, &tensor_order, tensor_data_offset)?;
183 let padding_size = usize::try_from(tensor_data_offset - position)
184 .map_err(|_| GGUFError::Format("Alignment padding does not fit usize".to_string()))?;
185 if padding_size > 0 {
186 read_discard(&mut reader, padding_size)?;
187 position = tensor_data_offset;
188 }
189
190 Ok(Self {
191 reader,
192 header,
193 metadata,
194 tensor_infos,
195 tensor_name_index,
196 position,
197 at_tensor_data: true,
198 tensor_data_offset,
199 tensor_data_span,
200 tensor_alignment,
201 tensor_buffer_size: config.buffer_size,
202 tensor_order,
203 next_tensor: 0,
204 })
205 }
206
207 pub fn header(&self) -> &GGUFHeader {
209 &self.header
210 }
211
212 pub fn metadata(&self) -> &Metadata {
214 &self.metadata
215 }
216
217 pub fn tensor_infos(&self) -> &[TensorInfoNew] {
219 &self.tensor_infos
220 }
221
222 pub fn get_tensor_info(&self, name: &str) -> Option<&TensorInfoNew> {
224 self.tensor_name_index.get(name).and_then(|&index| self.tensor_infos.get(index))
225 }
226
227 pub fn tensor_names(&self) -> Vec<&str> {
229 self.tensor_infos.iter().map(|t| t.name()).collect()
230 }
231
232 pub fn tensor_count(&self) -> usize {
234 self.tensor_infos.len()
235 }
236
237 pub fn tensor_alignment(&self) -> u64 {
239 self.tensor_alignment
240 }
241
242 pub fn read_next_tensor(&mut self) -> Result<Option<(String, TensorData)>> {
247 if !self.at_tensor_data {
248 return Ok(None);
249 }
250
251 if let Some(&index) = self.tensor_order.get(self.next_tensor) {
252 let tensor_info = &self.tensor_infos[index];
253 let data_size_u64 = tensor_info.checked_expected_data_size()?;
254 let data_size = usize::try_from(data_size_u64).map_err(|_| {
255 GGUFError::InvalidTensorData(format!(
256 "Tensor '{}' size does not fit this platform",
257 tensor_info.name()
258 ))
259 })?;
260 let target_position =
261 self.tensor_data_offset.checked_add(tensor_info.data_offset()).ok_or_else(
262 || GGUFError::InvalidTensorData("Tensor offset overflows u64".to_string()),
263 )?;
264 let tensor_name = try_clone_tensor_name(tensor_info.name())?;
265
266 if self.position < target_position {
268 let skip_bytes =
269 usize::try_from(target_position - self.position).map_err(|_| {
270 GGUFError::InvalidTensorData("Tensor gap does not fit usize".to_string())
271 })?;
272 self.skip_bytes(skip_bytes)?;
273 } else if self.position > target_position {
274 return Err(GGUFError::InvalidTensorData(format!(
275 "Tensor '{}' starts at {}, before current stream position {}",
276 tensor_name, target_position, self.position
277 )));
278 }
279
280 let data = read_exact_owned(&mut self.reader, data_size, self.tensor_buffer_size)?;
282 self.position = self.position.checked_add(data_size_u64).ok_or_else(|| {
283 GGUFError::InvalidTensorData("Stream position overflows u64".to_string())
284 })?;
285
286 self.next_tensor = self.next_tensor.checked_add(1).ok_or_else(|| {
287 GGUFError::InvalidTensorData("Tensor stream cursor overflows usize".to_string())
288 })?;
289
290 Ok(Some((tensor_name, TensorData::new_owned(data))))
291 } else {
292 Ok(None)
293 }
294 }
295
296 pub fn read_all_tensors(&mut self) -> Result<HashMap<String, TensorData>> {
298 let mut tensors = HashMap::new();
299 let remaining = self.tensor_order.len().saturating_sub(self.next_tensor);
300 tensors.try_reserve(remaining).map_err(|_| {
301 GGUFError::InvalidTensorData("Unable to allocate tensor data map".to_string())
302 })?;
303
304 while let Some((name, data)) = self.read_next_tensor()? {
305 tensors.insert(name, data);
306 }
307
308 Ok(tensors)
309 }
310
311 fn skip_bytes(&mut self, count: usize) -> Result<()> {
313 const SKIP_BUFFER_SIZE: usize = 8192;
314 let mut buffer = [0u8; SKIP_BUFFER_SIZE];
315 let mut remaining = count;
316
317 while remaining > 0 {
318 let to_read = remaining.min(SKIP_BUFFER_SIZE);
319 self.reader.read_exact(&mut buffer[..to_read])?;
320 remaining -= to_read;
321 self.position = self
322 .position
323 .checked_add(to_read as u64)
324 .ok_or_else(|| GGUFError::Format("Stream position overflow".to_string()))?;
325 }
326
327 Ok(())
328 }
329
330 pub fn position(&self) -> u64 {
332 self.position
333 }
334
335 pub fn at_tensor_data(&self) -> bool {
337 self.at_tensor_data
338 }
339
340 pub fn tensor_iterator(self) -> TensorIterator<R> {
342 TensorIterator::new(self)
343 }
344
345 pub fn summary(&self) -> StreamReaderSummary {
347 let tensor_types: HashMap<TensorType, usize> = {
348 let mut types = HashMap::new();
349 for tensor_info in &self.tensor_infos {
350 *types.entry(tensor_info.tensor_type()).or_insert(0) += 1;
351 }
352 types
353 };
354
355 let total_tensor_size = self
356 .tensor_infos
357 .iter()
358 .fold(0u64, |total, tensor| total.saturating_add(tensor.expected_data_size()));
359
360 StreamReaderSummary {
361 header: self.header.clone(),
362 metadata_count: self.metadata.len(),
363 tensor_count: self.tensor_infos.len(),
364 total_tensor_size,
365 current_position: self.position,
366 tensor_data_offset: self.tensor_data_offset,
367 tensor_data_span: self.tensor_data_span,
368 tensor_types,
369 }
370 }
371
372 pub fn validate(&self) -> Result<()> {
374 if self.header.tensor_count as usize != self.tensor_infos.len() {
376 return Err(GGUFError::Format(
377 "Header tensor count doesn't match actual tensor count".to_string(),
378 ));
379 }
380
381 if self.header.metadata_kv_count as usize != self.metadata.len() {
382 return Err(GGUFError::Format(
383 "Header metadata count doesn't match actual metadata count".to_string(),
384 ));
385 }
386
387 for tensor_info in &self.tensor_infos {
389 tensor_info.validate()?;
390 }
391
392 Ok(())
393 }
394
395 pub fn into_inner(self) -> R {
397 self.reader
398 }
399}
400
401fn read_discard<R: Read>(reader: &mut R, mut size: usize) -> Result<()> {
402 const BUFFER_SIZE: usize = 8192;
403 let mut buffer = [0u8; BUFFER_SIZE];
404 while size > 0 {
405 let chunk = size.min(BUFFER_SIZE);
406 reader.read_exact(&mut buffer[..chunk])?;
407 size -= chunk;
408 }
409 Ok(())
410}
411
412fn read_exact_owned<R: Read>(reader: &mut R, size: usize, buffer_size: usize) -> Result<Vec<u8>> {
413 if size == 0 {
414 return Ok(Vec::new());
415 }
416 if buffer_size == 0 {
417 return Err(GGUFError::InvalidTensorData(
418 "Stream reader buffer size must be greater than zero".to_string(),
419 ));
420 }
421
422 let chunk_size = size.min(buffer_size).min(MAX_READER_CHUNK_SIZE);
423 let mut chunk = Vec::new();
424 chunk.try_reserve_exact(chunk_size).map_err(|_| {
425 GGUFError::InvalidTensorData("Unable to allocate tensor read chunk".to_string())
426 })?;
427 chunk.resize(chunk_size, 0);
428 let mut data = Vec::new();
429 let mut remaining = size;
430 while remaining > 0 {
431 let to_read = remaining.min(chunk.len());
432 reader.read_exact(&mut chunk[..to_read])?;
433 data.try_reserve(to_read).map_err(|_| {
434 GGUFError::InvalidTensorData("Unable to allocate tensor data buffer".to_string())
435 })?;
436 data.extend_from_slice(&chunk[..to_read]);
437 remaining -= to_read;
438 }
439 Ok(data)
440}
441
442fn try_clone_tensor_name(name: &str) -> Result<String> {
443 let mut owned = String::new();
444 owned
445 .try_reserve_exact(name.len())
446 .map_err(|_| GGUFError::InvalidTensorData("Unable to allocate tensor name".to_string()))?;
447 owned.push_str(name);
448 Ok(owned)
449}
450
451fn try_reserve_vec_slot<T>(values: &mut Vec<T>, total: usize, description: &str) -> Result<()> {
452 if values.len() < values.capacity() {
453 return Ok(());
454 }
455 let target = values.capacity().saturating_mul(2).max(MIN_TENSOR_TRACKING_CAPACITY).min(total);
456 values
457 .try_reserve_exact(target.saturating_sub(values.len()))
458 .map_err(|_| GGUFError::InvalidTensorData(format!("Unable to allocate {description}")))
459}
460
461fn try_reserve_map_slot<K: std::hash::Hash + Eq, V>(
462 values: &mut HashMap<K, V>,
463 total: usize,
464 description: &str,
465) -> Result<()> {
466 if values.len() < values.capacity() {
467 return Ok(());
468 }
469 let target = values.capacity().saturating_mul(2).max(MIN_TENSOR_TRACKING_CAPACITY).min(total);
470 values
471 .try_reserve(target.saturating_sub(values.len()))
472 .map_err(|_| GGUFError::InvalidTensorData(format!("Unable to allocate {description}")))
473}
474
475fn validate_tensor_layout(
476 tensor_infos: &[TensorInfoNew],
477 tensor_order: &[usize],
478 tensor_data_offset: u64,
479) -> Result<u64> {
480 let mut previous: Option<(u64, &str)> = None;
481 let mut tensor_data_span = 0u64;
482 for &index in tensor_order {
483 let tensor = tensor_infos.get(index).ok_or_else(|| {
484 GGUFError::InvalidTensorData("Tensor order index is out of bounds".to_string())
485 })?;
486 let end = tensor
487 .data_offset()
488 .checked_add(tensor.checked_expected_data_size()?)
489 .ok_or_else(|| {
490 GGUFError::InvalidTensorData(format!(
491 "Tensor '{}' range overflows u64",
492 tensor.name()
493 ))
494 })?;
495 tensor_data_offset.checked_add(end).ok_or_else(|| {
496 GGUFError::InvalidTensorData(format!(
497 "Tensor '{}' absolute range overflows u64",
498 tensor.name()
499 ))
500 })?;
501 if let Some((previous_end, previous_name)) = previous {
502 if previous_end > tensor.data_offset() {
503 return Err(GGUFError::InvalidTensorData(format!(
504 "Tensor data overlap detected: '{}' ends at {}, after '{}' starts at {}",
505 previous_name,
506 previous_end,
507 tensor.name(),
508 tensor.data_offset()
509 )));
510 }
511 }
512 tensor_data_span = tensor_data_span.max(end);
513 previous = Some((end, tensor.name()));
514 }
515 Ok(tensor_data_span)
516}
517
518pub struct TensorIterator<R> {
520 reader: GGUFStreamReader<R>,
521 finished: bool,
522}
523
524impl<R: Read> TensorIterator<R> {
525 fn new(reader: GGUFStreamReader<R>) -> Self {
526 Self { reader, finished: false }
527 }
528}
529
530impl<R: Read> Iterator for TensorIterator<R> {
531 type Item = Result<(String, TensorData)>;
532
533 fn next(&mut self) -> Option<Self::Item> {
534 if self.finished {
535 return None;
536 }
537
538 match self.reader.read_next_tensor() {
539 Ok(Some(tensor)) => Some(Ok(tensor)),
540 Ok(None) => {
541 self.finished = true;
542 None
543 }
544 Err(e) => {
545 self.finished = true;
546 Some(Err(e))
547 }
548 }
549 }
550}
551
552#[derive(Debug, Clone)]
554pub struct StreamReaderSummary {
555 pub header: GGUFHeader,
557 pub metadata_count: usize,
559 pub tensor_count: usize,
561 pub total_tensor_size: u64,
563 pub current_position: u64,
565 pub tensor_data_offset: u64,
567 pub tensor_data_span: u64,
569 pub tensor_types: HashMap<TensorType, usize>,
571}
572
573impl StreamReaderSummary {
574 pub fn progress_percentage(&self) -> f64 {
576 if self.tensor_data_span == 0 {
577 return 100.0;
578 }
579
580 let consumed = self
581 .current_position
582 .saturating_sub(self.tensor_data_offset)
583 .min(self.tensor_data_span);
584 (consumed as f64 / self.tensor_data_span as f64) * 100.0
585 }
586}
587
588pub fn stream_reader_from_read<R: Read>(reader: R) -> Result<GGUFStreamReader<R>> {
590 GGUFStreamReader::new(reader)
591}
592
593pub fn buffered_stream_reader<R: Read>(reader: R) -> Result<GGUFStreamReader<BufReader<R>>> {
595 let buf_reader = BufReader::new(reader);
596 GGUFStreamReader::new(buf_reader)
597}
598
599impl std::fmt::Display for StreamReaderSummary {
600 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
601 writeln!(f, "GGUF Stream Summary:")?;
602 writeln!(f, " Version: {}", self.header.version)?;
603 writeln!(f, " Tensors: {}", self.tensor_count)?;
604 writeln!(f, " Metadata entries: {}", self.metadata_count)?;
605 writeln!(f, " Total tensor size: {} bytes", self.total_tensor_size)?;
606 writeln!(f, " Tensor data span: {} bytes", self.tensor_data_span)?;
607 writeln!(f, " Current position: {} bytes", self.current_position)?;
608 writeln!(f, " Progress: {:.1}%", self.progress_percentage())?;
609 writeln!(f, " Tensor types:")?;
610
611 for (tensor_type, count) in &self.tensor_types {
612 writeln!(f, " {}: {}", tensor_type.name(), count)?;
613 }
614
615 Ok(())
616 }
617}
618
619fn checked_align_u64(position: u64, alignment: u64) -> Result<u64> {
620 if alignment == 0 {
621 return Err(GGUFError::InvalidMetadata("Tensor alignment cannot be zero".to_string()));
622 }
623 let remainder = position % alignment;
624 let padding = if remainder == 0 { 0 } else { alignment - remainder };
625 position
626 .checked_add(padding)
627 .ok_or_else(|| GGUFError::Format("Tensor data offset overflows u64".to_string()))
628}
629
630#[cfg(all(test, feature = "std"))]
631mod tests {
632 use super::*;
633 use crate::format::constants::*;
634 use std::io::Cursor;
635
636 fn create_stream_gguf_data() -> Vec<u8> {
637 let mut data = Vec::new();
638
639 data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
641 data.extend_from_slice(&GGUF_VERSION.to_le_bytes());
642 data.extend_from_slice(&2u64.to_le_bytes()); data.extend_from_slice(&1u64.to_le_bytes()); data.extend_from_slice(&4u64.to_le_bytes()); data.extend_from_slice(b"name"); data.extend_from_slice(&8u32.to_le_bytes()); data.extend_from_slice(&5u64.to_le_bytes()); data.extend_from_slice(b"model"); let mut offset_positions = Vec::new();
654
655 data.extend_from_slice(&8u64.to_le_bytes()); data.extend_from_slice(b"tensor_a"); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&4u64.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); offset_positions.push(data.len()); data.extend_from_slice(&0u64.to_le_bytes()); data.extend_from_slice(&8u64.to_le_bytes()); data.extend_from_slice(b"tensor_b"); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&3u64.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); offset_positions.push(data.len()); data.extend_from_slice(&0u64.to_le_bytes()); while data.len() % 32 != 0 {
675 data.push(0);
676 }
677
678 let tensor_a_pos = offset_positions[0];
680 data[tensor_a_pos..tensor_a_pos + 8].copy_from_slice(&0u64.to_le_bytes());
681
682 let tensor_b_pos = offset_positions[1];
684 data[tensor_b_pos..tensor_b_pos + 8].copy_from_slice(&32u64.to_le_bytes());
685
686 data.extend_from_slice(&[0xAA; 16]);
688
689 data.extend_from_slice(&[0u8; 16]);
691
692 data.extend_from_slice(&[0xBB; 12]);
694
695 data
696 }
697
698 fn create_out_of_order_stream_gguf_data() -> Vec<u8> {
699 let mut data = create_stream_gguf_data();
700 let tensor_a_name = data.windows(8).position(|window| window == b"tensor_a").unwrap();
701 let tensor_b_name = data.windows(8).position(|window| window == b"tensor_b").unwrap();
702 let tensor_a_start = tensor_a_name - 8;
703 let tensor_b_start = tensor_b_name - 8;
704 let descriptor_size = tensor_b_start - tensor_a_start;
705 let tensor_a = data[tensor_a_start..tensor_a_start + descriptor_size].to_vec();
706 let tensor_b = data[tensor_b_start..tensor_b_start + descriptor_size].to_vec();
707 data[tensor_a_start..tensor_a_start + descriptor_size].copy_from_slice(&tensor_b);
708 data[tensor_b_start..tensor_b_start + descriptor_size].copy_from_slice(&tensor_a);
709 data
710 }
711
712 fn set_tensor_offset(data: &mut [u8], name: &[u8; 8], offset: u64) {
713 let name_position = data.windows(name.len()).position(|window| window == name).unwrap();
714 let offset_position = name_position + name.len() + 4 + 8 + 4;
715 data[offset_position..offset_position + 8].copy_from_slice(&offset.to_le_bytes());
716 }
717
718 #[test]
719 fn test_stream_reader_creation() {
720 let data = create_stream_gguf_data();
721 let cursor = Cursor::new(data);
722
723 let reader = GGUFStreamReader::new(cursor).unwrap();
724 assert_eq!(reader.tensor_count(), 2);
725 assert_eq!(reader.metadata().len(), 1);
726 assert!(reader.at_tensor_data());
727 }
728
729 #[test]
730 fn test_stream_reader_config() {
731 let data = create_stream_gguf_data();
732 let cursor = Cursor::new(data);
733
734 let config = StreamReaderConfig {
735 buffer_size: 1024,
736 validate_checksums: false,
737 max_metadata_size: 1024,
738 max_decoded_metadata_size: 1024,
739 max_tensor_count: 10,
740 };
741
742 let reader = GGUFStreamReader::with_config(cursor, config).unwrap();
743 assert_eq!(reader.tensor_count(), 2);
744
745 let unsupported = StreamReaderConfig { validate_checksums: true, ..Default::default() };
746 assert!(matches!(
747 GGUFStreamReader::with_config(Cursor::new(create_stream_gguf_data()), unsupported),
748 Err(GGUFError::FeatureUnavailable(_))
749 ));
750 }
751
752 #[test]
753 fn test_stream_metadata_budgets_are_configurable() {
754 let serialized_error = GGUFStreamReader::with_config(
755 Cursor::new(create_stream_gguf_data()),
756 StreamReaderConfig { max_metadata_size: 1, ..Default::default() },
757 )
758 .unwrap_err();
759 assert!(serialized_error.to_string().contains("Metadata exceeds byte limit"));
760
761 let decoded_error = GGUFStreamReader::with_config(
762 Cursor::new(create_stream_gguf_data()),
763 StreamReaderConfig { max_decoded_metadata_size: 0, ..Default::default() },
764 )
765 .unwrap_err();
766 assert!(decoded_error.to_string().contains("Decoded metadata allocation exceeds budget"));
767 }
768
769 #[test]
770 fn test_read_next_tensor() {
771 let data = create_stream_gguf_data();
772 let cursor = Cursor::new(data);
773
774 let mut reader = GGUFStreamReader::new(cursor).unwrap();
775
776 let result = reader.read_next_tensor().unwrap();
778 assert!(result.is_some());
779 let (name, tensor_data) = result.unwrap();
780 assert_eq!(name, "tensor_a");
781 assert_eq!(tensor_data.len(), 16);
782
783 let result = reader.read_next_tensor().unwrap();
785 assert!(result.is_some());
786 let (name, tensor_data) = result.unwrap();
787 assert_eq!(name, "tensor_b");
788 assert_eq!(tensor_data.len(), 12);
789
790 let result = reader.read_next_tensor().unwrap();
792 assert!(result.is_none());
793 }
794
795 #[test]
796 fn test_read_next_tensor_uses_precomputed_data_order() {
797 let data = create_out_of_order_stream_gguf_data();
798 let mut reader = GGUFStreamReader::new(Cursor::new(data)).unwrap();
799
800 assert_eq!(reader.tensor_order, [1, 0]);
801 assert_eq!(reader.tensor_name_index["tensor_b"], 0);
802 assert_eq!(reader.tensor_name_index["tensor_a"], 1);
803 let (first_name, first_data) = reader.read_next_tensor().unwrap().unwrap();
804 assert_eq!(first_name, "tensor_a");
805 assert_eq!(first_data.as_slice(), &[0xAA; 16]);
806 assert_eq!(reader.next_tensor, 1);
807
808 let (second_name, second_data) = reader.read_next_tensor().unwrap().unwrap();
809 assert_eq!(second_name, "tensor_b");
810 assert_eq!(second_data.as_slice(), &[0xBB; 12]);
811 assert_eq!(reader.next_tensor, 2);
812 assert!(reader.read_next_tensor().unwrap().is_none());
813 }
814
815 #[test]
816 fn test_streamed_payload_is_not_retained_by_reader() {
817 let data = create_stream_gguf_data();
818 let mut reader = GGUFStreamReader::new(Cursor::new(data)).unwrap();
819
820 let (name, returned) = reader.read_next_tensor().unwrap().unwrap();
821 assert!(matches!(returned, TensorData::Owned(_)));
822 assert!(reader.get_tensor_info(&name).unwrap().data().is_none());
823 assert!(reader.tensor_infos().iter().all(|tensor| !tensor.has_data()));
824 }
825
826 #[test]
827 fn test_duplicate_tensor_names_are_rejected() {
828 let mut data = create_stream_gguf_data();
829 let tensor_b_name = data.windows(8).position(|window| window == b"tensor_b").unwrap();
830 data[tensor_b_name..tensor_b_name + 8].copy_from_slice(b"tensor_a");
831
832 let error = GGUFStreamReader::new(Cursor::new(data)).unwrap_err();
833 assert!(matches!(
834 error,
835 GGUFError::InvalidTensorData(message) if message.contains("Duplicate tensor name")
836 ));
837 }
838
839 #[test]
840 fn test_overlapping_tensor_ranges_are_rejected_during_construction() {
841 let mut data = create_stream_gguf_data();
842 set_tensor_offset(&mut data, b"tensor_b", 0);
843
844 let error = GGUFStreamReader::new(Cursor::new(data)).unwrap_err();
845 assert!(matches!(
846 error,
847 GGUFError::InvalidTensorData(message) if message.contains("overlap")
848 ));
849 }
850
851 #[test]
852 fn test_overflowing_tensor_range_is_rejected_during_construction() {
853 let mut data = create_stream_gguf_data();
854 set_tensor_offset(&mut data, b"tensor_a", u64::MAX - 31);
855
856 let error = GGUFStreamReader::new(Cursor::new(data)).unwrap_err();
857 assert!(matches!(
858 error,
859 GGUFError::InvalidTensorData(message) if message.contains("range overflows")
860 ));
861 }
862
863 #[test]
864 fn test_tensor_tracking_growth_is_bounded() {
865 let mut descriptors = Vec::<u8>::new();
866 try_reserve_vec_slot(&mut descriptors, usize::MAX, "test descriptors").unwrap();
867 assert!(descriptors.capacity() >= MIN_TENSOR_TRACKING_CAPACITY);
868 assert!(descriptors.capacity() < 1024);
869
870 let mut names = HashMap::<u8, usize>::new();
871 try_reserve_map_slot(&mut names, usize::MAX, "test names").unwrap();
872 assert!(names.capacity() >= MIN_TENSOR_TRACKING_CAPACITY);
873 assert!(names.capacity() < 1024);
874 }
875
876 #[test]
877 fn test_read_all_tensors() {
878 let data = create_stream_gguf_data();
879 let cursor = Cursor::new(data);
880
881 let mut reader = GGUFStreamReader::new(cursor).unwrap();
882 let tensors = reader.read_all_tensors().unwrap();
883
884 assert_eq!(tensors.len(), 2);
885 assert!(tensors.contains_key("tensor_a"));
886 assert!(tensors.contains_key("tensor_b"));
887 assert_eq!(tensors["tensor_a"].len(), 16);
888 assert_eq!(tensors["tensor_b"].len(), 12);
889 }
890
891 #[test]
892 fn test_tensor_iterator() {
893 let data = create_stream_gguf_data();
894 let cursor = Cursor::new(data);
895
896 let reader = GGUFStreamReader::new(cursor).unwrap();
897 let mut iterator = reader.tensor_iterator();
898
899 let first = iterator.next().unwrap().unwrap();
901 assert_eq!(first.0, "tensor_a");
902 assert_eq!(first.1.len(), 16);
903
904 let second = iterator.next().unwrap().unwrap();
906 assert_eq!(second.0, "tensor_b");
907 assert_eq!(second.1.len(), 12);
908
909 assert!(iterator.next().is_none());
911 }
912
913 #[test]
914 fn test_stream_summary() {
915 let data = create_stream_gguf_data();
916 let cursor = Cursor::new(data);
917
918 let mut reader = GGUFStreamReader::new(cursor).unwrap();
919 let summary = reader.summary();
920
921 assert_eq!(summary.tensor_count, 2);
922 assert_eq!(summary.metadata_count, 1);
923 assert_eq!(summary.total_tensor_size, 28); assert_eq!(summary.tensor_data_span, 44); assert_eq!(summary.current_position, summary.tensor_data_offset);
926 assert_eq!(summary.progress_percentage(), 0.0);
927 assert!(summary.tensor_types.contains_key(&TensorType::F32));
928
929 while reader.read_next_tensor().unwrap().is_some() {}
930 let completed = reader.summary();
931 assert_eq!(completed.current_position, completed.tensor_data_offset + 44);
932 assert_eq!(completed.progress_percentage(), 100.0);
933 }
934
935 #[test]
936 fn test_stream_validation() {
937 let data = create_stream_gguf_data();
938 let cursor = Cursor::new(data);
939
940 let reader = GGUFStreamReader::new(cursor).unwrap();
941 assert!(reader.validate().is_ok());
942 }
943
944 #[test]
945 fn test_limits_exceeded() {
946 let data = create_stream_gguf_data();
947 let cursor = Cursor::new(data);
948
949 let config = StreamReaderConfig {
950 max_tensor_count: 1, ..Default::default()
952 };
953
954 let result = GGUFStreamReader::with_config(cursor, config);
955 assert!(result.is_err());
956 }
957
958 #[test]
959 fn test_convenience_functions() {
960 let data = create_stream_gguf_data();
961
962 let cursor = Cursor::new(data.clone());
964 let reader = stream_reader_from_read(cursor).unwrap();
965 assert_eq!(reader.tensor_count(), 2);
966
967 let cursor = Cursor::new(data);
969 let reader = buffered_stream_reader(cursor).unwrap();
970 assert_eq!(reader.tensor_count(), 2);
971 }
972
973 #[test]
974 fn test_summary_display() {
975 let data = create_stream_gguf_data();
976 let cursor = Cursor::new(data);
977
978 let reader = GGUFStreamReader::new(cursor).unwrap();
979 let summary = reader.summary();
980 let display_str = format!("{}", summary);
981
982 assert!(display_str.contains("GGUF Stream Summary"));
983 assert!(display_str.contains("Tensors: 2"));
984 assert!(display_str.contains("F32"));
985 }
986
987 #[test]
988 fn test_into_inner() {
989 let data = create_stream_gguf_data();
990 let cursor = Cursor::new(data);
991
992 let reader = GGUFStreamReader::new(cursor).unwrap();
993 let _inner = reader.into_inner(); }
995}