use crate::gguf::{GGUFModel, GGUF_MAGIC, GGUF_VERSION_V3};
fn build_ancestral_pygmy_v1() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&1u32.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data
}
fn build_ancestral_pygmy_v2() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&2u32.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data
}
fn build_future_pygmy_v4() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&4u32.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data
}
fn build_ancestral_pygmy_v0() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data
}
fn build_ancestral_pygmy_max_version() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&u32::MAX.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data
}
fn build_unaligned_pygmy_odd_offset() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
let name = "test_tensor";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&32u64.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&17u64.to_le_bytes());
while data.len() % 32 != 17 {
data.push(0);
}
for i in 0..32 {
data.extend_from_slice(&(i as f32).to_le_bytes());
}
data
}
fn build_unaligned_pygmy_overflow_offset() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
let name = "overflow_tensor";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&8u64.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&0xFFFF_FFFFu64.to_le_bytes());
data
}
fn build_malformed_pygmy_empty_name() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&4u64.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
data
}
fn build_malformed_pygmy_long_name() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&0x1000_0000u64.to_le_bytes());
data
}
fn build_malformed_pygmy_invalid_type() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
let name = "invalid_type_tensor";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&4u64.to_le_bytes());
data.extend_from_slice(&255u32.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data
}
fn build_malformed_pygmy_zero_dims() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
let name = "scalar_tensor";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
while data.len() % 32 != 0 {
data.push(0);
}
data.extend_from_slice(&3.14f32.to_le_bytes());
data
}
fn build_malformed_pygmy_too_many_dims() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
let name = "hyperdimensional_tensor";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&100u32.to_le_bytes());
for _ in 0..100 {
data.extend_from_slice(&1u64.to_le_bytes());
}
data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
data
}
fn build_malformed_pygmy_overlapping_tensors() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&2u64.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
let name1 = "tensor_a";
data.extend_from_slice(&(name1.len() as u64).to_le_bytes());
data.extend_from_slice(name1.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&32u64.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
let name2 = "tensor_b";
data.extend_from_slice(&(name2.len() as u64).to_le_bytes());
data.extend_from_slice(name2.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&32u64.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&64u64.to_le_bytes());
while data.len() % 32 != 0 {
data.push(0);
}
for i in 0..32 {
data.extend_from_slice(&(i as f32).to_le_bytes());
}
data
}
fn build_shard_pygmy_split_metadata() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes()); data.extend_from_slice(&3u64.to_le_bytes());
let key1 = "split.no";
data.extend_from_slice(&(key1.len() as u64).to_le_bytes());
data.extend_from_slice(key1.as_bytes());
data.extend_from_slice(&4u32.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes());
let key2 = "split.count";
data.extend_from_slice(&(key2.len() as u64).to_le_bytes());
data.extend_from_slice(key2.as_bytes());
data.extend_from_slice(&4u32.to_le_bytes()); data.extend_from_slice(&4u32.to_le_bytes());
let key3 = "split.tensors.count";
data.extend_from_slice(&(key3.len() as u64).to_le_bytes());
data.extend_from_slice(key3.as_bytes());
data.extend_from_slice(&4u32.to_le_bytes()); data.extend_from_slice(&100u32.to_le_bytes());
let name = "shard_tensor";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes());
data.extend_from_slice(&4u64.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
while data.len() % 32 != 0 {
data.push(0);
}
for i in 0..4 {
data.extend_from_slice(&(i as f32).to_le_bytes());
}
data
}
#[test]
fn test_ancestral_pygmy_v1_rejected() {
let data = build_ancestral_pygmy_v1();
let result = GGUFModel::from_bytes(&data);
assert!(result.is_err(), "GGUF v1 should be rejected");
let err = result.unwrap_err();
let msg = err.to_string().to_lowercase();
assert!(
msg.contains("version") || msg.contains("unsupported"),
"Error should mention version: {}",
msg
);
}
#[test]
fn test_ancestral_pygmy_v2_accepted() {
let data = build_ancestral_pygmy_v2();
let result = GGUFModel::from_bytes(&data);
assert!(result.is_ok(), "v2 should be accepted (GH-310)");
}
#[test]
fn test_future_pygmy_v4_rejected() {
let data = build_future_pygmy_v4();
let result = GGUFModel::from_bytes(&data);
assert!(result.is_err(), "Future GGUF v4 should be rejected");
}
#[test]
fn test_ancestral_pygmy_v0_rejected() {
let data = build_ancestral_pygmy_v0();
let result = GGUFModel::from_bytes(&data);
assert!(result.is_err(), "GGUF v0 should be rejected");
}
#[test]
fn test_ancestral_pygmy_max_version_rejected() {
let data = build_ancestral_pygmy_max_version();
let result = GGUFModel::from_bytes(&data);
assert!(result.is_err(), "Max version should be rejected");
}
include!("unaligned_pygmy.rs");