1use crate::asset_type::ModelType;
4use crate::error::Error;
5use crate::manifest::ManifestSource;
6use crate::{gguf, onnx, safetensors};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Format {
11 Gguf,
13 SafeTensors,
15 Onnx,
17}
18
19impl Format {
20 pub fn detect(data: &[u8]) -> Option<Format> {
28 if gguf::is_gguf(data) {
29 Some(Format::Gguf)
30 } else if safetensors::is_safetensors(data) {
31 Some(Format::SafeTensors)
32 } else if onnx::is_onnx(data) {
33 Some(Format::Onnx)
34 } else {
35 None
36 }
37 }
38
39 pub fn name(self) -> &'static str {
41 match self {
42 Format::Gguf => "GGUF",
43 Format::SafeTensors => "SafeTensors",
44 Format::Onnx => "ONNX",
45 }
46 }
47
48 pub fn model_type(self) -> ModelType {
52 match self {
53 Format::Gguf | Format::SafeTensors => ModelType::Generic,
54 Format::Onnx => ModelType::Onnx,
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct Report {
62 pub format: Format,
64 pub has_embedded_manifest: bool,
66 pub has_remote_uri: bool,
68}
69
70impl Report {
71 pub fn is_compliant(&self) -> bool {
73 self.has_embedded_manifest || self.has_remote_uri
74 }
75}
76
77pub fn embed_manifest(data: &[u8], source: &ManifestSource) -> Result<Vec<u8>, Error> {
82 embed_manifest_as(data, detect(data)?, source)
83}
84
85pub fn embed_manifest_as(
87 data: &[u8],
88 format: Format,
89 source: &ManifestSource,
90) -> Result<Vec<u8>, Error> {
91 match format {
92 Format::Gguf => gguf::embed(data, source),
93 Format::SafeTensors => safetensors::embed(data, source),
94 Format::Onnx => onnx::embed(data, source),
95 }
96}
97
98pub fn read_manifest(data: &[u8]) -> Result<Vec<u8>, Error> {
100 match detect(data)? {
101 Format::Gguf => gguf::read_store(data),
102 Format::SafeTensors => safetensors::read_store(data),
103 Format::Onnx => onnx::read_store(data),
104 }
105}
106
107pub fn read_manifest_uri(data: &[u8]) -> Result<Option<String>, Error> {
110 match detect(data)? {
111 Format::Gguf => gguf::read_uri(data),
112 Format::SafeTensors => safetensors::read_uri(data),
113 Format::Onnx => onnx::read_uri(data),
114 }
115}
116
117pub fn remove_manifest(data: &[u8]) -> Result<Vec<u8>, Error> {
119 match detect(data)? {
120 Format::Gguf => gguf::remove(data),
121 Format::SafeTensors => safetensors::remove(data),
122 Format::Onnx => onnx::remove(data),
123 }
124}
125
126pub fn verify(data: &[u8]) -> Result<Report, Error> {
128 let format = detect(data)?;
129 let has_embedded_manifest = match read_manifest(data) {
130 Ok(_) => true,
131 Err(Error::NotFound) => false,
132 Err(e) => return Err(e),
133 };
134 let has_remote_uri = read_manifest_uri(data)?.is_some();
135 Ok(Report {
136 format,
137 has_embedded_manifest,
138 has_remote_uri,
139 })
140}
141
142fn detect(data: &[u8]) -> Result<Format, Error> {
143 Format::detect(data).ok_or(Error::UnknownFormat)
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::gguf::tests::sample_gguf;
150 use crate::onnx::tests::sample_onnx;
151 use crate::safetensors::tests::sample_safetensors;
152
153 #[test]
154 fn detects_each_format() {
155 assert_eq!(Format::detect(&sample_gguf()), Some(Format::Gguf));
156 assert_eq!(
157 Format::detect(&sample_safetensors(None)),
158 Some(Format::SafeTensors)
159 );
160 assert_eq!(Format::detect(&sample_onnx()), Some(Format::Onnx));
161 assert_eq!(Format::detect(b"random bytes here!!"), None);
162 }
163
164 #[test]
165 fn model_types() {
166 assert_eq!(Format::Onnx.model_type().as_str(), "c2pa.types.model.onnx");
167 assert_eq!(Format::Gguf.model_type().as_str(), "c2pa.types.model");
168 }
169
170 #[test]
171 fn dispatch_round_trip_all_formats() {
172 for data in [sample_gguf(), sample_safetensors(None), sample_onnx()] {
173 let out = embed_manifest(&data, &ManifestSource::embedded(vec![1, 2, 3])).unwrap();
174 assert_eq!(read_manifest(&out).unwrap(), vec![1, 2, 3]);
175 let report = verify(&out).unwrap();
176 assert!(report.is_compliant());
177 assert!(report.has_embedded_manifest);
178 let cleaned = remove_manifest(&out).unwrap();
179 assert!(matches!(read_manifest(&cleaned), Err(Error::NotFound)));
180 }
181 }
182
183 #[test]
184 fn unknown_format_errors() {
185 assert!(matches!(
186 embed_manifest(b"nope", &ManifestSource::embedded(vec![1])),
187 Err(Error::UnknownFormat)
188 ));
189 }
190}