1use crate::error::{GGUFError, Result};
4use crate::format::constants::*;
5use crate::format::types::GGUFValueType;
6
7#[cfg(feature = "std")]
8use crate::format::endian::{
9 read_f32, read_f64, read_i16, read_i32, read_i64, read_i8, read_u16, read_u32, read_u64,
10 read_u8, write_f32, write_f64, write_i16, write_i32, write_i64, write_i8, write_u16, write_u32,
11 write_u64, write_u8,
12};
13#[cfg(feature = "std")]
14use std::collections::HashMap;
15#[cfg(feature = "std")]
16use std::io::{Read, Write};
17
18#[cfg(feature = "std")]
19use serde::{Deserialize, Serialize};
20
21#[cfg(all(not(feature = "std"), feature = "alloc"))]
22extern crate alloc;
23#[cfg(all(not(feature = "std"), feature = "alloc"))]
24use alloc::{
25 boxed::Box,
26 format,
27 string::{String, ToString},
28 vec::Vec,
29};
30#[cfg(all(not(feature = "std"), feature = "alloc"))]
31use hashbrown::HashMap;
32
33#[cfg(feature = "std")]
36const METADATA_BYTE_READ_CHUNK: usize = 8 * 1024;
37#[cfg(feature = "std")]
38const METADATA_ARRAY_RESERVE_CHUNK: usize = 1024;
39
40#[cfg(feature = "std")]
41#[derive(Debug)]
42struct MetadataDecodeBudget {
43 remaining: usize,
44 limit: usize,
45}
46
47#[cfg(feature = "std")]
48impl MetadataDecodeBudget {
49 fn new(limit: usize) -> Self {
50 Self { remaining: limit, limit }
51 }
52
53 fn charge(&mut self, bytes: usize, description: &str) -> Result<()> {
54 self.remaining = self.remaining.checked_sub(bytes).ok_or_else(|| {
55 GGUFError::InvalidMetadata(format!(
56 "Decoded metadata allocation exceeds budget of {} bytes while reading {}",
57 self.limit, description
58 ))
59 })?;
60 Ok(())
61 }
62}
63
64#[cfg(not(feature = "std"))]
66use core::{fmt, slice};
67
68#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
70#[derive(Debug, Clone, PartialEq)]
71pub enum MetadataValue {
72 U8(u8),
74 I8(i8),
76 U16(u16),
78 I16(i16),
80 U32(u32),
82 I32(i32),
84 F32(f32),
86 Bool(bool),
88 String(String),
90 Array(Box<MetadataArray>),
92 U64(u64),
94 I64(i64),
96 F64(f64),
98}
99
100#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
102#[derive(Debug, Clone, PartialEq)]
103pub struct MetadataArray {
104 pub element_type: GGUFValueType,
106 pub length: u64,
108 pub values: Vec<MetadataValue>,
110}
111
112#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
114#[derive(Debug, Clone)]
115pub struct Metadata {
116 #[cfg(any(feature = "std", feature = "alloc"))]
118 pub data: HashMap<String, MetadataValue>,
119 #[cfg(not(any(feature = "std", feature = "alloc")))]
120 pub data: (), }
122
123#[cfg(any(feature = "std", feature = "alloc"))]
124impl Default for Metadata {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130#[cfg(not(any(feature = "std", feature = "alloc")))]
131impl Default for Metadata {
132 fn default() -> Self {
133 Self { data: () }
134 }
135}
136
137impl MetadataValue {
138 pub fn value_type(&self) -> GGUFValueType {
140 match self {
141 MetadataValue::U8(_) => GGUFValueType::U8,
142 MetadataValue::I8(_) => GGUFValueType::I8,
143 MetadataValue::U16(_) => GGUFValueType::U16,
144 MetadataValue::I16(_) => GGUFValueType::I16,
145 MetadataValue::U32(_) => GGUFValueType::U32,
146 MetadataValue::I32(_) => GGUFValueType::I32,
147 MetadataValue::F32(_) => GGUFValueType::F32,
148 MetadataValue::Bool(_) => GGUFValueType::Bool,
149 MetadataValue::String(_) => GGUFValueType::String,
150 MetadataValue::Array(_) => GGUFValueType::Array,
151 MetadataValue::U64(_) => GGUFValueType::U64,
152 MetadataValue::I64(_) => GGUFValueType::I64,
153 MetadataValue::F64(_) => GGUFValueType::F64,
154 }
155 }
156
157 pub fn serialized_size(&self) -> usize {
159 self.checked_serialized_size().unwrap_or(usize::MAX)
160 }
161
162 pub fn checked_serialized_size(&self) -> Option<usize> {
164 match self {
165 MetadataValue::U8(_) | MetadataValue::I8(_) | MetadataValue::Bool(_) => Some(1),
166 MetadataValue::U16(_) | MetadataValue::I16(_) => Some(2),
167 MetadataValue::U32(_) | MetadataValue::I32(_) | MetadataValue::F32(_) => Some(4),
168 MetadataValue::String(s) => 8usize.checked_add(s.len()),
169 MetadataValue::Array(arr) => arr.as_ref().checked_serialized_size(),
170 MetadataValue::U64(_) | MetadataValue::I64(_) | MetadataValue::F64(_) => Some(8),
171 }
172 }
173
174 pub fn validate(&self) -> Result<()> {
176 self.validate_at_depth(0)
177 }
178
179 fn validate_at_depth(&self, depth: usize) -> Result<()> {
180 match self {
181 MetadataValue::Array(array) => array.validate_at_depth(depth + 1),
182 _ => Ok(()),
183 }
184 }
185
186 #[cfg(feature = "std")]
188 pub fn read_from<R: Read>(reader: &mut R, value_type: GGUFValueType) -> Result<Self> {
189 let mut budget = MetadataDecodeBudget::new(GGUF_MAX_METADATA_DECODED_SIZE);
190 Self::read_from_at_depth(reader, value_type, 0, &mut budget)
191 }
192
193 #[cfg(feature = "std")]
194 fn read_from_at_depth<R: Read>(
195 reader: &mut R,
196 value_type: GGUFValueType,
197 depth: usize,
198 budget: &mut MetadataDecodeBudget,
199 ) -> Result<Self> {
200 let value = match value_type {
201 GGUFValueType::U8 => MetadataValue::U8(read_u8(reader)?),
202 GGUFValueType::I8 => MetadataValue::I8(read_i8(reader)?),
203 GGUFValueType::U16 => MetadataValue::U16(read_u16(reader)?),
204 GGUFValueType::I16 => MetadataValue::I16(read_i16(reader)?),
205 GGUFValueType::U32 => MetadataValue::U32(read_u32(reader)?),
206 GGUFValueType::I32 => MetadataValue::I32(read_i32(reader)?),
207 GGUFValueType::F32 => MetadataValue::F32(read_f32(reader)?),
208 GGUFValueType::Bool => match read_u8(reader)? {
209 0 => MetadataValue::Bool(false),
210 1 => MetadataValue::Bool(true),
211 value => {
212 return Err(GGUFError::InvalidMetadata(format!(
213 "Invalid boolean value: {} (expected 0 or 1)",
214 value
215 )));
216 }
217 },
218 GGUFValueType::String => {
219 let len = read_u64(reader)?;
220 let bytes = read_variable_bytes(reader, len, "string", budget)?;
221 let string = String::from_utf8(bytes)
222 .map_err(|e| GGUFError::Format(format!("Invalid UTF-8 string: {}", e)))?;
223 MetadataValue::String(string)
224 }
225 GGUFValueType::Array => {
226 if depth >= GGUF_MAX_METADATA_NESTING_DEPTH {
227 return Err(GGUFError::InvalidMetadata(format!(
228 "Metadata array nesting exceeds limit of {}",
229 GGUF_MAX_METADATA_NESTING_DEPTH
230 )));
231 }
232 budget.charge(core::mem::size_of::<MetadataArray>(), "nested array")?;
233 let array = MetadataArray::read_from_at_depth(reader, depth + 1, budget)?;
234 MetadataValue::Array(Box::new(array))
235 }
236 GGUFValueType::U64 => MetadataValue::U64(read_u64(reader)?),
237 GGUFValueType::I64 => MetadataValue::I64(read_i64(reader)?),
238 GGUFValueType::F64 => MetadataValue::F64(read_f64(reader)?),
239 };
240
241 Ok(value)
242 }
243
244 #[cfg(feature = "std")]
246 pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
247 self.validate()?;
248 match self {
249 MetadataValue::U8(v) => write_u8(writer, *v)?,
250 MetadataValue::I8(v) => write_i8(writer, *v)?,
251 MetadataValue::U16(v) => write_u16(writer, *v)?,
252 MetadataValue::I16(v) => write_i16(writer, *v)?,
253 MetadataValue::U32(v) => write_u32(writer, *v)?,
254 MetadataValue::I32(v) => write_i32(writer, *v)?,
255 MetadataValue::F32(v) => write_f32(writer, *v)?,
256 MetadataValue::Bool(v) => write_u8(writer, if *v { 1 } else { 0 })?,
257 MetadataValue::String(s) => {
258 let length = u64::try_from(s.len()).map_err(|_| {
259 GGUFError::InvalidMetadata(
260 "String length does not fit the GGUF u64 length field".to_string(),
261 )
262 })?;
263 write_u64(writer, length)?;
264 writer.write_all(s.as_bytes())?;
265 }
266 MetadataValue::Array(arr) => arr.as_ref().write_to(writer)?,
267 MetadataValue::U64(v) => write_u64(writer, *v)?,
268 MetadataValue::I64(v) => write_i64(writer, *v)?,
269 MetadataValue::F64(v) => write_f64(writer, *v)?,
270 }
271 Ok(())
272 }
273
274 #[cfg(any(feature = "std", feature = "alloc"))]
276 pub fn to_string_representation(&self) -> String {
277 match self {
278 MetadataValue::U8(v) => v.to_string(),
279 MetadataValue::I8(v) => v.to_string(),
280 MetadataValue::U16(v) => v.to_string(),
281 MetadataValue::I16(v) => v.to_string(),
282 MetadataValue::U32(v) => v.to_string(),
283 MetadataValue::I32(v) => v.to_string(),
284 MetadataValue::F32(v) => v.to_string(),
285 MetadataValue::Bool(v) => v.to_string(),
286 MetadataValue::String(s) => s.clone(),
287 MetadataValue::Array(arr) => format!("Array[{}; {}]", arr.element_type, arr.length),
288 MetadataValue::U64(v) => v.to_string(),
289 MetadataValue::I64(v) => v.to_string(),
290 MetadataValue::F64(v) => v.to_string(),
291 }
292 }
293
294 pub fn as_str(&self) -> Option<&str> {
296 match self {
297 MetadataValue::String(s) => Some(s),
298 _ => None,
299 }
300 }
301
302 pub fn as_u64(&self) -> Option<u64> {
303 match self {
304 MetadataValue::U8(v) => Some(*v as u64),
305 MetadataValue::U16(v) => Some(*v as u64),
306 MetadataValue::U32(v) => Some(*v as u64),
307 MetadataValue::U64(v) => Some(*v),
308 _ => None,
309 }
310 }
311
312 pub fn as_i64(&self) -> Option<i64> {
313 match self {
314 MetadataValue::I8(v) => Some(*v as i64),
315 MetadataValue::I16(v) => Some(*v as i64),
316 MetadataValue::I32(v) => Some(*v as i64),
317 MetadataValue::I64(v) => Some(*v),
318 _ => None,
319 }
320 }
321
322 pub fn as_f64(&self) -> Option<f64> {
323 match self {
324 MetadataValue::F32(v) => Some(*v as f64),
325 MetadataValue::F64(v) => Some(*v),
326 _ => None,
327 }
328 }
329
330 pub fn as_bool(&self) -> Option<bool> {
331 match self {
332 MetadataValue::Bool(v) => Some(*v),
333 _ => None,
334 }
335 }
336}
337
338impl MetadataArray {
339 #[cfg(any(feature = "std", feature = "alloc"))]
341 pub fn new(element_type: GGUFValueType, values: Vec<MetadataValue>) -> Result<Self> {
342 for value in &values {
344 if value.value_type() != element_type {
345 return Err(GGUFError::InvalidMetadata(format!(
346 "Array element type mismatch: expected {}, got {}",
347 element_type,
348 value.value_type()
349 )));
350 }
351 }
352
353 let length = u64::try_from(values.len()).map_err(|_| {
354 GGUFError::InvalidMetadata(
355 "Array length does not fit the GGUF u64 length field".to_string(),
356 )
357 })?;
358
359 let array = Self { element_type, length, values };
360 array.validate()?;
361 Ok(array)
362 }
363
364 #[cfg(not(any(feature = "std", feature = "alloc")))]
365 pub fn new(_element_type: GGUFValueType, _values: &[MetadataValue]) -> Result<Self> {
366 Err(GGUFError::AllocationRequired)
367 }
368
369 pub fn serialized_size(&self) -> usize {
371 self.checked_serialized_size().unwrap_or(usize::MAX)
372 }
373
374 pub fn checked_serialized_size(&self) -> Option<usize> {
376 self.values
377 .iter()
378 .try_fold(12usize, |size, value| size.checked_add(value.checked_serialized_size()?))
379 }
380
381 pub fn validate(&self) -> Result<()> {
383 self.validate_at_depth(1)
384 }
385
386 fn validate_at_depth(&self, depth: usize) -> Result<()> {
387 if depth > GGUF_MAX_METADATA_NESTING_DEPTH {
388 return Err(GGUFError::InvalidMetadata(format!(
389 "Metadata array nesting exceeds limit of {}",
390 GGUF_MAX_METADATA_NESTING_DEPTH
391 )));
392 }
393 let actual_length = u64::try_from(self.values.len()).map_err(|_| {
394 GGUFError::InvalidMetadata(
395 "Array length does not fit the GGUF u64 length field".to_string(),
396 )
397 })?;
398 if self.length != actual_length {
399 return Err(GGUFError::InvalidMetadata(format!(
400 "Array length field {} does not match {} values",
401 self.length,
402 self.values.len()
403 )));
404 }
405 for value in &self.values {
406 if value.value_type() != self.element_type {
407 return Err(GGUFError::InvalidMetadata(format!(
408 "Array element type mismatch: expected {}, got {}",
409 self.element_type,
410 value.value_type()
411 )));
412 }
413 value.validate_at_depth(depth)?;
414 }
415 Ok(())
416 }
417
418 #[cfg(feature = "std")]
420 pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
421 let mut budget = MetadataDecodeBudget::new(GGUF_MAX_METADATA_DECODED_SIZE);
422 Self::read_from_at_depth(reader, 1, &mut budget)
423 }
424
425 #[cfg(feature = "std")]
426 fn read_from_at_depth<R: Read>(
427 reader: &mut R,
428 depth: usize,
429 budget: &mut MetadataDecodeBudget,
430 ) -> Result<Self> {
431 if depth > GGUF_MAX_METADATA_NESTING_DEPTH {
432 return Err(GGUFError::InvalidMetadata(format!(
433 "Metadata array nesting exceeds limit of {}",
434 GGUF_MAX_METADATA_NESTING_DEPTH
435 )));
436 }
437 let element_type_raw = read_u32(reader)?;
438 let element_type = GGUFValueType::from_u32(element_type_raw)?;
439 let length = read_u64(reader)?;
440
441 let platform_length = usize::try_from(length).map_err(|_| {
442 GGUFError::InvalidMetadata("Array length does not fit usize".to_string())
443 })?;
444 let decoded_slots = platform_length
445 .checked_mul(core::mem::size_of::<MetadataValue>())
446 .ok_or_else(|| {
447 GGUFError::InvalidMetadata(
448 "Decoded metadata array allocation overflows usize".to_string(),
449 )
450 })?;
451 budget.charge(decoded_slots, "array elements")?;
452 let mut values = Vec::new();
453 while values.len() < platform_length {
454 if values.len() == values.capacity() {
455 let remaining = platform_length - values.len();
456 let additional = values.capacity().max(METADATA_ARRAY_RESERVE_CHUNK).min(remaining);
457 values.try_reserve_exact(additional).map_err(|_| {
458 GGUFError::InvalidMetadata(format!(
459 "Unable to allocate storage for metadata array of {} elements",
460 length
461 ))
462 })?;
463 }
464 let batch_end = values
465 .len()
466 .saturating_add(
467 (values.capacity() - values.len()).min(METADATA_ARRAY_RESERVE_CHUNK),
468 )
469 .min(platform_length);
470 while values.len() < batch_end {
471 let value = MetadataValue::read_from_at_depth(reader, element_type, depth, budget)?;
472 values.push(value);
473 }
474 }
475
476 Ok(Self { element_type, length, values })
477 }
478
479 #[cfg(feature = "std")]
481 pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
482 self.validate()?;
483 write_u32(writer, self.element_type as u32)?;
484 write_u64(writer, self.length)?;
485
486 for value in &self.values {
487 value.write_to(writer)?;
488 }
489
490 Ok(())
491 }
492
493 pub fn len(&self) -> usize {
495 self.values.len()
496 }
497
498 pub fn is_empty(&self) -> bool {
500 self.values.is_empty()
501 }
502
503 pub fn get(&self, index: usize) -> Option<&MetadataValue> {
505 self.values.get(index)
506 }
507
508 #[cfg(feature = "std")]
510 pub fn iter(&self) -> std::slice::Iter<'_, MetadataValue> {
511 self.values.iter()
512 }
513
514 #[cfg(not(feature = "std"))]
515 pub fn iter(&self) -> slice::Iter<'_, MetadataValue> {
516 self.values.iter()
517 }
518}
519
520#[cfg(feature = "std")]
521fn read_variable_bytes<R: Read>(
522 reader: &mut R,
523 length: u64,
524 kind: &str,
525 budget: &mut MetadataDecodeBudget,
526) -> Result<Vec<u8>> {
527 let length = usize::try_from(length)
528 .map_err(|_| GGUFError::Format(format!("{} length does not fit usize", kind)))?;
529 budget.charge(length, kind)?;
530 let mut bytes = Vec::new();
531 let mut chunk = [0u8; METADATA_BYTE_READ_CHUNK];
532 while bytes.len() < length {
533 if bytes.len() == bytes.capacity() {
534 let remaining = length - bytes.len();
535 let additional = bytes.capacity().max(METADATA_BYTE_READ_CHUNK).min(remaining);
536 bytes.try_reserve_exact(additional).map_err(|_| {
537 GGUFError::InvalidMetadata(format!(
538 "Unable to allocate storage for metadata {} of {} bytes",
539 kind, length
540 ))
541 })?;
542 }
543 let chunk_len = (length - bytes.len()).min(chunk.len());
544 reader.read_exact(&mut chunk[..chunk_len])?;
545 bytes.extend_from_slice(&chunk[..chunk_len]);
546 }
547 Ok(bytes)
548}
549
550#[cfg(any(feature = "std", feature = "alloc"))]
551impl Metadata {
552 pub fn new() -> Self {
554 Self { data: HashMap::new() }
555 }
556
557 pub fn insert(&mut self, key: String, value: MetadataValue) {
559 self.data.insert(key, value);
560 }
561
562 pub fn get(&self, key: &str) -> Option<&MetadataValue> {
564 self.data.get(key)
565 }
566
567 pub fn remove(&mut self, key: &str) -> Option<MetadataValue> {
569 self.data.remove(key)
570 }
571
572 pub fn contains_key(&self, key: &str) -> bool {
574 self.data.contains_key(key)
575 }
576
577 pub fn len(&self) -> usize {
579 self.data.len()
580 }
581
582 pub fn is_empty(&self) -> bool {
584 self.data.is_empty()
585 }
586
587 #[cfg(feature = "std")]
589 pub fn iter(&self) -> std::collections::hash_map::Iter<'_, String, MetadataValue> {
590 self.data.iter()
591 }
592
593 #[cfg(all(not(feature = "std"), feature = "alloc"))]
594 pub fn iter(&self) -> hashbrown::hash_map::Iter<'_, String, MetadataValue> {
595 self.data.iter()
596 }
597
598 #[cfg(feature = "std")]
600 pub fn keys(&self) -> std::collections::hash_map::Keys<'_, String, MetadataValue> {
601 self.data.keys()
602 }
603
604 #[cfg(all(not(feature = "std"), feature = "alloc"))]
605 pub fn keys(&self) -> hashbrown::hash_map::Keys<'_, String, MetadataValue> {
606 self.data.keys()
607 }
608
609 #[cfg(feature = "std")]
611 pub fn values(&self) -> std::collections::hash_map::Values<'_, String, MetadataValue> {
612 self.data.values()
613 }
614
615 #[cfg(all(not(feature = "std"), feature = "alloc"))]
616 pub fn values(&self) -> hashbrown::hash_map::Values<'_, String, MetadataValue> {
617 self.data.values()
618 }
619
620 pub fn validate(&self) -> Result<()> {
622 if self.data.len() as u64 > GGUF_MAX_METADATA_COUNT {
623 return Err(GGUFError::InvalidMetadata(format!(
624 "Too many metadata entries: {} exceeds limit of {}",
625 self.data.len(),
626 GGUF_MAX_METADATA_COUNT
627 )));
628 }
629 for (key, value) in &self.data {
630 if key.is_empty() {
631 return Err(GGUFError::InvalidMetadata("Metadata key cannot be empty".to_string()));
632 }
633 if key.len() > GGUF_MAX_METADATA_KEY_LENGTH {
634 return Err(GGUFError::InvalidMetadata(format!(
635 "Metadata key is {} bytes; maximum is {}",
636 key.len(),
637 GGUF_MAX_METADATA_KEY_LENGTH
638 )));
639 }
640 if !is_valid_metadata_key(key) {
641 return Err(GGUFError::InvalidMetadata(format!(
642 "Metadata key '{}' must be dot-separated lower_snake_case ASCII",
643 key
644 )));
645 }
646 value.validate()?;
647 }
648 self.tensor_alignment()?;
649 Ok(())
650 }
651
652 pub fn tensor_alignment(&self) -> Result<usize> {
654 let alignment = match self.get("general.alignment") {
655 None => GGUF_DEFAULT_ALIGNMENT,
656 Some(MetadataValue::U32(value)) => usize::try_from(*value).map_err(|_| {
657 GGUFError::InvalidMetadata(
658 "general.alignment does not fit this platform".to_string(),
659 )
660 })?,
661 Some(value) => {
662 return Err(GGUFError::InvalidMetadata(format!(
663 "general.alignment must be u32, got {}",
664 value.value_type()
665 )));
666 }
667 };
668 if alignment == 0 || alignment % 8 != 0 {
669 return Err(GGUFError::InvalidMetadata(format!(
670 "general.alignment must be a non-zero multiple of 8, got {}",
671 alignment
672 )));
673 }
674 Ok(alignment)
675 }
676}
677
678#[cfg(any(feature = "std", feature = "alloc"))]
679fn is_valid_metadata_key(key: &str) -> bool {
680 key.is_ascii()
681 && key.split('.').all(|component| {
682 !component.is_empty()
683 && component.split('_').all(|word| {
684 !word.is_empty()
685 && word
686 .bytes()
687 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit())
688 })
689 })
690}
691
692#[cfg(not(any(feature = "std", feature = "alloc")))]
693impl Metadata {
694 pub fn new() -> Self {
696 Self { data: () }
697 }
698
699 pub fn insert(&mut self, _key: String, _value: MetadataValue) {
701 }
703
704 pub fn get(&self, _key: &str) -> Option<&MetadataValue> {
706 None
707 }
708
709 pub fn remove(&mut self, _key: &str) -> Option<MetadataValue> {
711 None
712 }
713
714 pub fn contains_key(&self, _key: &str) -> bool {
716 false
717 }
718
719 pub fn len(&self) -> usize {
721 0
722 }
723
724 pub fn is_empty(&self) -> bool {
726 true
727 }
728}
729
730#[cfg(any(feature = "std", feature = "alloc"))]
731impl Metadata {
732 #[cfg(feature = "std")]
734 pub fn read_from<R: Read>(reader: &mut R, count: u64) -> Result<Self> {
735 let mut budget = MetadataDecodeBudget::new(GGUF_MAX_METADATA_DECODED_SIZE);
736 Self::read_from_with_decode_budget(reader, count, &mut budget)
737 }
738
739 #[cfg(feature = "std")]
740 fn read_from_with_decode_budget<R: Read>(
741 reader: &mut R,
742 count: u64,
743 budget: &mut MetadataDecodeBudget,
744 ) -> Result<Self> {
745 if count > GGUF_MAX_METADATA_COUNT {
746 return Err(GGUFError::InvalidMetadata(format!(
747 "Metadata count {} exceeds limit of {}",
748 count, GGUF_MAX_METADATA_COUNT
749 )));
750 }
751 let capacity = usize::try_from(count).map_err(|_| {
752 GGUFError::InvalidMetadata("Metadata count does not fit usize".to_string())
753 })?;
754 let decoded_entries = capacity
755 .checked_mul(
756 core::mem::size_of::<(String, MetadataValue)>()
757 .saturating_add(2 * core::mem::size_of::<usize>()),
758 )
759 .ok_or_else(|| {
760 GGUFError::InvalidMetadata(
761 "Decoded metadata entry allocation overflows usize".to_string(),
762 )
763 })?;
764 budget.charge(decoded_entries, "metadata entries")?;
765 let mut data = HashMap::new();
766 data.try_reserve(capacity).map_err(|_| {
767 GGUFError::InvalidMetadata("Unable to allocate metadata map".to_string())
768 })?;
769
770 for _ in 0..count {
771 let key_len = read_u64(reader)?;
773 if key_len > GGUF_MAX_METADATA_KEY_LENGTH as u64 {
774 return Err(GGUFError::Format(format!("Metadata key too long: {} bytes", key_len)));
775 }
776 let key_len = usize::try_from(key_len).map_err(|_| {
777 GGUFError::Format("Metadata key length does not fit usize".to_string())
778 })?;
779 budget.charge(key_len, "metadata key")?;
780
781 let mut key_bytes = vec![0u8; key_len];
782 reader.read_exact(&mut key_bytes)?;
783 let key = String::from_utf8(key_bytes)
784 .map_err(|e| GGUFError::Format(format!("Invalid UTF-8 in metadata key: {}", e)))?;
785 if key.is_empty() {
786 return Err(GGUFError::InvalidMetadata("Metadata key cannot be empty".to_string()));
787 }
788 if data.contains_key(&key) {
789 return Err(GGUFError::InvalidMetadata(format!("Duplicate metadata key: {}", key)));
790 }
791
792 let value_type_raw = read_u32(reader)?;
794 let value_type = GGUFValueType::from_u32(value_type_raw)?;
795 let value = MetadataValue::read_from_at_depth(reader, value_type, 0, budget)?;
796
797 data.insert(key, value);
798 }
799
800 let metadata = Self { data };
801 metadata.validate()?;
802 Ok(metadata)
803 }
804
805 #[cfg(feature = "std")]
807 pub fn read_from_with_limit<R: Read>(
808 reader: &mut R,
809 count: u64,
810 max_bytes: usize,
811 ) -> Result<Self> {
812 Self::read_from_with_limits(reader, count, max_bytes, GGUF_MAX_METADATA_DECODED_SIZE)
813 }
814
815 #[cfg(feature = "std")]
817 pub fn read_from_with_limits<R: Read>(
818 reader: &mut R,
819 count: u64,
820 max_bytes: usize,
821 max_decoded_bytes: usize,
822 ) -> Result<Self> {
823 let limit = u64::try_from(max_bytes).unwrap_or(u64::MAX);
824 let mut limited = reader.take(limit);
825 let mut budget = MetadataDecodeBudget::new(max_decoded_bytes);
826 match Self::read_from_with_decode_budget(&mut limited, count, &mut budget) {
827 Ok(metadata) => Ok(metadata),
828 Err(_) if limited.limit() == 0 => Err(GGUFError::InvalidMetadata(format!(
829 "Metadata exceeds byte limit of {}",
830 max_bytes
831 ))),
832 Err(error) => Err(error),
833 }
834 }
835
836 #[cfg(feature = "std")]
838 pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
839 self.validate()?;
840 let mut entries = Vec::new();
841 entries.try_reserve_exact(self.data.len()).map_err(|_| {
842 GGUFError::InvalidMetadata(format!(
843 "Unable to allocate deterministic metadata index for {} entries",
844 self.data.len()
845 ))
846 })?;
847 entries.extend(self.data.iter());
848 entries.sort_unstable_by_key(|(key, _)| *key);
849 for (key, value) in entries {
850 let key_len = u64::try_from(key.len()).map_err(|_| {
852 GGUFError::InvalidMetadata(format!(
853 "Metadata key length does not fit the GGUF u64 length field: {} bytes",
854 key.len()
855 ))
856 })?;
857 write_u64(writer, key_len)?;
858 writer.write_all(key.as_bytes())?;
859
860 write_u32(writer, value.value_type() as u32)?;
862 value.write_to(writer)?;
863 }
864
865 Ok(())
866 }
867
868 pub fn serialized_size(&self) -> usize {
870 self.checked_serialized_size().unwrap_or(usize::MAX)
871 }
872
873 pub fn checked_serialized_size(&self) -> Option<usize> {
875 self.data.iter().try_fold(0usize, |size, (key, value)| {
876 size.checked_add(8)?
877 .checked_add(key.len())?
878 .checked_add(4)?
879 .checked_add(value.checked_serialized_size()?)
880 })
881 }
882
883 pub fn get_string(&self, key: &str) -> Option<&str> {
885 self.get(key)?.as_str()
886 }
887
888 pub fn get_u64(&self, key: &str) -> Option<u64> {
890 self.get(key)?.as_u64()
891 }
892
893 pub fn get_i64(&self, key: &str) -> Option<i64> {
895 self.get(key)?.as_i64()
896 }
897
898 pub fn get_f64(&self, key: &str) -> Option<f64> {
900 self.get(key)?.as_f64()
901 }
902
903 pub fn get_bool(&self, key: &str) -> Option<bool> {
905 self.get(key)?.as_bool()
906 }
907}
908
909#[cfg(feature = "std")]
910impl std::fmt::Display for MetadataValue {
911 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
912 write!(f, "{}", self.to_string_representation())
913 }
914}
915
916#[cfg(not(feature = "std"))]
917impl fmt::Display for MetadataValue {
918 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
919 match self {
920 MetadataValue::U8(v) => write!(f, "{}", v),
921 MetadataValue::I8(v) => write!(f, "{}", v),
922 MetadataValue::U16(v) => write!(f, "{}", v),
923 MetadataValue::I16(v) => write!(f, "{}", v),
924 MetadataValue::U32(v) => write!(f, "{}", v),
925 MetadataValue::I32(v) => write!(f, "{}", v),
926 MetadataValue::F32(v) => write!(f, "{}", v),
927 MetadataValue::Bool(v) => write!(f, "{}", v),
928 MetadataValue::String(v) => write!(f, "\"{}\"", v),
929 MetadataValue::Array(arr) => {
930 write!(f, "[")?;
931 for (i, value) in arr.iter().enumerate() {
932 if i > 0 {
933 write!(f, ", ")?;
934 }
935 write!(f, "{}", value)?;
936 }
937 write!(f, "]")
938 }
939 MetadataValue::U64(v) => write!(f, "{}", v),
940 MetadataValue::I64(v) => write!(f, "{}", v),
941 MetadataValue::F64(v) => write!(f, "{}", v),
942 }
943 }
944}
945
946#[cfg(all(test, feature = "std"))]
947mod tests {
948 use super::*;
949 use std::io::Cursor;
950
951 #[test]
952 fn test_metadata_value_types() {
953 let val = MetadataValue::U32(42);
954 assert_eq!(val.value_type(), GGUFValueType::U32);
955 assert_eq!(val.as_u64(), Some(42));
956
957 let val = MetadataValue::String("hello".to_string());
958 assert_eq!(val.value_type(), GGUFValueType::String);
959 assert_eq!(val.as_str(), Some("hello"));
960
961 let val = MetadataValue::Bool(true);
962 assert_eq!(val.value_type(), GGUFValueType::Bool);
963 assert_eq!(val.as_bool(), Some(true));
964 }
965
966 #[test]
967 fn test_metadata_value_io() {
968 let original = MetadataValue::F32(std::f32::consts::PI);
969
970 let mut buffer = Vec::new();
971 original.write_to(&mut buffer).unwrap();
972
973 let mut cursor = Cursor::new(buffer);
974 let read_value = MetadataValue::read_from(&mut cursor, GGUFValueType::F32).unwrap();
975
976 assert_eq!(original, read_value);
977 }
978
979 #[test]
980 fn test_metadata_array() {
981 let values = vec![MetadataValue::U32(1), MetadataValue::U32(2), MetadataValue::U32(3)];
982
983 let array = MetadataArray::new(GGUFValueType::U32, values).unwrap();
984 assert_eq!(array.len(), 3);
985 assert_eq!(array.element_type, GGUFValueType::U32);
986 assert_eq!(array.get(0), Some(&MetadataValue::U32(1)));
987 }
988
989 #[test]
990 fn test_metadata_array_type_mismatch() {
991 let values = vec![
992 MetadataValue::U32(1),
993 MetadataValue::String("hello".to_string()), ];
995
996 let result = MetadataArray::new(GGUFValueType::U32, values);
997 assert!(result.is_err());
998 }
999
1000 #[test]
1001 fn test_metadata_collection() {
1002 let mut metadata = Metadata::new();
1003 metadata.insert("name".to_string(), MetadataValue::String("test_model".to_string()));
1004 metadata.insert("version".to_string(), MetadataValue::U32(1));
1005
1006 assert_eq!(metadata.len(), 2);
1007 assert_eq!(metadata.get_string("name"), Some("test_model"));
1008 assert_eq!(metadata.get_u64("version"), Some(1));
1009 assert!(metadata.contains_key("name"));
1010 assert!(!metadata.contains_key("nonexistent"));
1011 }
1012
1013 #[test]
1014 fn test_metadata_io() {
1015 let mut original = Metadata::new();
1016 original.insert("name".to_string(), MetadataValue::String("test".to_string()));
1017 original.insert("count".to_string(), MetadataValue::U64(42));
1018
1019 let mut buffer = Vec::new();
1020 original.write_to(&mut buffer).unwrap();
1021
1022 let mut cursor = Cursor::new(buffer);
1023 let read_metadata = Metadata::read_from(&mut cursor, 2).unwrap();
1024
1025 assert_eq!(read_metadata.len(), 2);
1026 assert_eq!(read_metadata.get_string("name"), Some("test"));
1027 assert_eq!(read_metadata.get_u64("count"), Some(42));
1028 }
1029
1030 #[test]
1031 fn test_large_string_round_trip() {
1032 let long_value = MetadataValue::String("x".repeat(128 * 1024));
1033 let mut buffer = Vec::new();
1034 long_value.write_to(&mut buffer).unwrap();
1035
1036 let decoded =
1037 MetadataValue::read_from(&mut Cursor::new(buffer), GGUFValueType::String).unwrap();
1038 assert_eq!(decoded, long_value);
1039 }
1040
1041 #[test]
1042 fn test_array_io() {
1043 let values = vec![MetadataValue::I32(-1), MetadataValue::I32(0), MetadataValue::I32(42)];
1044
1045 let original = MetadataArray::new(GGUFValueType::I32, values).unwrap();
1046
1047 let mut buffer = Vec::new();
1048 original.write_to(&mut buffer).unwrap();
1049
1050 let mut cursor = Cursor::new(buffer);
1051 let read_array = MetadataArray::read_from(&mut cursor).unwrap();
1052
1053 assert_eq!(read_array.element_type, GGUFValueType::I32);
1054 assert_eq!(read_array.len(), 3);
1055 assert_eq!(read_array.get(2), Some(&MetadataValue::I32(42)));
1056 }
1057
1058 #[test]
1059 fn test_large_tokenizer_array_round_trip() {
1060 let values = (0..128_000).map(MetadataValue::U32).collect();
1061 let original = MetadataArray::new(GGUFValueType::U32, values).unwrap();
1062
1063 let mut buffer = Vec::new();
1064 original.write_to(&mut buffer).unwrap();
1065 let decoded = MetadataArray::read_from(&mut Cursor::new(buffer)).unwrap();
1066
1067 assert_eq!(decoded, original);
1068 }
1069
1070 #[test]
1071 fn test_truncated_huge_string_declaration_is_rejected_without_large_allocation() {
1072 let encoded_length = u64::MAX.to_le_bytes();
1073 let error =
1074 MetadataValue::read_from(&mut Cursor::new(encoded_length), GGUFValueType::String)
1075 .unwrap_err();
1076
1077 assert!(matches!(
1078 error,
1079 GGUFError::Io(_) | GGUFError::Format(_) | GGUFError::InvalidMetadata(_)
1080 ));
1081 }
1082
1083 #[test]
1084 fn test_truncated_huge_array_declaration_is_rejected_without_large_allocation() {
1085 let mut encoded = Vec::new();
1086 encoded.extend_from_slice(&(GGUFValueType::U8 as u32).to_le_bytes());
1087 encoded.extend_from_slice(&u64::MAX.to_le_bytes());
1088
1089 let error = MetadataArray::read_from(&mut Cursor::new(encoded)).unwrap_err();
1090 assert!(matches!(error, GGUFError::Io(_) | GGUFError::InvalidMetadata(_)));
1091 }
1092
1093 #[test]
1094 fn test_compact_array_is_rejected_before_decoded_memory_amplification() {
1095 let mut encoded = Vec::new();
1096 encoded.extend_from_slice(&6u64.to_le_bytes());
1097 encoded.extend_from_slice(b"tokens");
1098 encoded.extend_from_slice(&(GGUFValueType::Array as u32).to_le_bytes());
1099 encoded.extend_from_slice(&(GGUFValueType::U8 as u32).to_le_bytes());
1100 let oversized_elements =
1101 GGUF_MAX_METADATA_DECODED_SIZE / core::mem::size_of::<MetadataValue>() + 1;
1102 encoded.extend_from_slice(&(oversized_elements as u64).to_le_bytes());
1103
1104 let error =
1105 Metadata::read_from_with_limit(&mut Cursor::new(encoded), 1, GGUF_MAX_METADATA_SIZE)
1106 .unwrap_err();
1107 assert!(error.to_string().contains("Decoded metadata allocation exceeds budget"));
1108 }
1109
1110 #[test]
1111 fn test_metadata_key_validation() {
1112 for key in ["general.name", "tokenizer.ggml.model", "block_count", "layer.0.name"] {
1113 let mut metadata = Metadata::new();
1114 metadata.insert(key.to_string(), MetadataValue::U32(1));
1115 assert!(metadata.validate().is_ok(), "valid key rejected: {key}");
1116 }
1117
1118 for key in [
1119 "Uppercase",
1120 "has-dash",
1121 ".leading",
1122 "trailing.",
1123 "double..dot",
1124 "_leading",
1125 "trailing_",
1126 "double__underscore",
1127 ] {
1128 let mut metadata = Metadata::new();
1129 metadata.insert(key.to_string(), MetadataValue::U32(1));
1130 assert!(metadata.validate().is_err(), "invalid key accepted: {key}");
1131 }
1132 }
1133
1134 #[test]
1135 fn test_invalid_bool_is_rejected() {
1136 let mut cursor = Cursor::new([2u8]);
1137 assert!(MetadataValue::read_from(&mut cursor, GGUFValueType::Bool).is_err());
1138 }
1139
1140 #[test]
1141 fn test_array_length_mismatch_is_rejected() {
1142 let array = MetadataArray {
1143 element_type: GGUFValueType::U32,
1144 length: 2,
1145 values: vec![MetadataValue::U32(1)],
1146 };
1147 assert!(array.validate().is_err());
1148 assert!(array.write_to(&mut Vec::new()).is_err());
1149 }
1150}