asset_io.rs (9933B)
1 // Copyright 2022 Adobe. All rights reserved. 2 // This file is licensed to you under the Apache License, 3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) 4 // or the MIT license (http://opensource.org/licenses/MIT), 5 // at your option. 6 7 // Unless required by applicable law or agreed to in writing, 8 // this software is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or 10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the 11 // specific language governing permissions and limitations under 12 // each license. 13 14 use std::{ 15 fmt, 16 io::{Cursor, Read, Seek, Write}, 17 path::Path, 18 }; 19 20 use tempfile::NamedTempFile; 21 22 use crate::{assertions::BoxMap, error::Result}; 23 24 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 25 pub enum HashBlockObjectType { 26 Cai, 27 Xmp, 28 Other, 29 } 30 31 impl fmt::Display for HashBlockObjectType { 32 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 33 write!(f, "{self:?}") 34 } 35 } 36 #[derive(Debug)] 37 pub struct HashObjectPositions { 38 pub offset: usize, // offset from beginning of file to the beginning of object 39 pub length: usize, // length of object 40 pub htype: HashBlockObjectType, // type of hash block object 41 } 42 43 pub trait CAIRead: Read + Seek + Send {} 44 45 impl<T> CAIRead for T where T: Read + Seek + Send {} 46 47 impl From<String> for Box<dyn CAIRead> { 48 fn from(val: String) -> Self { 49 Box::new(Cursor::new(val)) 50 } 51 } 52 53 // Helper struct to create a concrete type for CAIRead when 54 // that is required. For example a function defined like this 55 // pub fn read<T>(&self, reader: &mut T) cannot currently accept 56 // a CAIRead trait because it is not Sized (bound to a object). 57 // This will likely change in a future version of Rust. 58 pub(crate) struct CAIReadWrapper<'a> { 59 pub reader: &'a mut dyn CAIRead, 60 } 61 62 impl Read for CAIReadWrapper<'_> { 63 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { 64 self.reader.read(buf) 65 } 66 } 67 68 impl Seek for CAIReadWrapper<'_> { 69 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> { 70 self.reader.seek(pos) 71 } 72 } 73 74 pub trait CAIReadWrite: CAIRead + Write {} 75 76 impl<T> CAIReadWrite for T where T: CAIRead + Write {} 77 78 // Helper struct to create a concrete type for CAIReadWrite when 79 // that is required. For example a function defined like this 80 // pub fn write<T>(&self, writer: &mut T) cannot currently accept 81 // a CAIReadWrite trait because it is not Sized (bound to a object). 82 // This will likely change in a future version of Rust. 83 // go away in future revisions of Rust. 84 pub(crate) struct CAIReadWriteWrapper<'a> { 85 pub reader_writer: &'a mut dyn CAIReadWrite, 86 } 87 88 impl Read for CAIReadWriteWrapper<'_> { 89 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { 90 self.reader_writer.read(buf) 91 } 92 } 93 94 impl Write for CAIReadWriteWrapper<'_> { 95 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { 96 self.reader_writer.write(buf) 97 } 98 99 fn flush(&mut self) -> std::io::Result<()> { 100 self.reader_writer.flush() 101 } 102 } 103 104 impl Seek for CAIReadWriteWrapper<'_> { 105 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> { 106 self.reader_writer.seek(pos) 107 } 108 } 109 110 /// CAIReader trait to insure CAILoader method support both Read & Seek 111 // Interface for in memory CAI reading 112 pub trait CAIReader: Sync + Send { 113 // Return entire CAI block as Vec<u8> 114 fn read_cai(&self, asset_reader: &mut dyn CAIRead) -> Result<Vec<u8>>; 115 116 // Get XMP block 117 fn read_xmp(&self, asset_reader: &mut dyn CAIRead) -> Option<String>; 118 } 119 120 pub trait CAIWriter: Sync + Send { 121 // Writes store_bytes into output_steam using input_stream as the source asset 122 fn write_cai( 123 &self, 124 input_stream: &mut dyn CAIRead, 125 output_stream: &mut dyn CAIReadWrite, 126 store_bytes: &[u8], 127 ) -> Result<()>; 128 129 // Finds location where the C2PA manifests will be placed in the asset specified by input_stream 130 fn get_object_locations_from_stream( 131 &self, 132 input_stream: &mut dyn CAIRead, 133 ) -> Result<Vec<HashObjectPositions>>; 134 135 // Remove entire C2PA manifest store from asset 136 fn remove_cai_store_from_stream( 137 &self, 138 input_stream: &mut dyn CAIRead, 139 output_stream: &mut dyn CAIReadWrite, 140 ) -> Result<()>; 141 } 142 143 pub trait AssetIO: Sync + Send { 144 // Create instance of AssetIO handler. The extension type is passed in so 145 // that format specific customizations can be used during manifest embedding 146 fn new(asset_type: &str) -> Self 147 where 148 Self: Sized; 149 150 // Return AssetIO handler for this asset type 151 fn get_handler(&self, asset_type: &str) -> Box<dyn AssetIO>; 152 153 // Return streaming reader for this asset type 154 fn get_reader(&self) -> &dyn CAIReader; 155 156 // Return streaming writer if available 157 fn get_writer(&self, _asset_type: &str) -> Option<Box<dyn CAIWriter>> { 158 None 159 } 160 161 // Return entire CAI block as Vec<u8> 162 #[allow(dead_code)] 163 fn read_cai_store(&self, asset_path: &Path) -> Result<Vec<u8>>; 164 165 // Write the CAI block to an asset 166 fn save_cai_store(&self, asset_path: &Path, store_bytes: &[u8]) -> Result<()>; 167 168 /// List of standard object offsets 169 /// If the offsets exist return the start of those locations other it should 170 /// return the calculated location of when it should start. There may still be a 171 /// length if the format contains extra header information for example. 172 #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) 173 fn get_object_locations(&self, asset_path: &Path) -> Result<Vec<HashObjectPositions>>; 174 175 // Remove entire C2PA manifest store from asset 176 #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) 177 fn remove_cai_store(&self, asset_path: &Path) -> Result<()>; 178 179 // List of supported extensions and mime types 180 fn supported_types(&self) -> &[&str]; 181 182 /// OPTIONAL INTERFACES 183 184 // Returns [`AssetPatch`] trait if this I/O handler supports patching. 185 #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) 186 fn asset_patch_ref(&self) -> Option<&dyn AssetPatch> { 187 None 188 } 189 190 // Returns [`RemoteRefEmbed`] trait if this I/O handler supports remote reference embedding. 191 fn remote_ref_writer_ref(&self) -> Option<&dyn RemoteRefEmbed> { 192 None 193 } 194 195 // Returns [`AssetBoxHash`] trait if this I/O handler supports box hashing. 196 fn asset_box_hash_ref(&self) -> Option<&dyn AssetBoxHash> { 197 None 198 } 199 200 // Returns [`ComposedManifestRefEmbed`] trait if this I/O handler supports composed data. 201 fn composed_data_ref(&self) -> Option<&dyn ComposedManifestRef> { 202 None 203 } 204 } 205 206 // `AssetPatch` optimizes output generation for asset_io handlers that 207 // are able to patch blocks of data without changing any other data. The 208 // resultant file must still be a valid asset. This saves having to rewrite 209 // assets since only the patched bytes are modified. 210 pub trait AssetPatch { 211 // Patches an existing manifest store with new manifest store. 212 // Only existing manifest stores of the same size may be patched 213 // since any other changes will invalidate asset hashes. 214 #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) 215 fn patch_cai_store(&self, asset_path: &Path, store_bytes: &[u8]) -> Result<()>; 216 } 217 218 // `AssetBoxHash` provides interfaces needed to support C2PA BoxHash functionality. 219 // This trait is only implemented for supported types 220 pub trait AssetBoxHash { 221 // Returns Vec containing all BoxMap level objects in the asset in the order 222 // they occur in the asset. The hashes do not need to be calculated, only the 223 // name and the positional information. The list should be flat with each BoxMap 224 // representing a single entry. 225 fn get_box_map(&self, input_stream: &mut dyn CAIRead) -> Result<Vec<BoxMap>>; 226 } 227 228 // Type of remote reference to embed. Some of the listed 229 // emums are for future uses and experiments. 230 #[allow(dead_code)] 231 pub enum RemoteRefEmbedType { 232 Xmp(String), 233 StegoS(String), 234 StegoB(Vec<u8>), 235 Watermark(String), 236 } 237 238 // `RemoteRefEmbed` is used to embed remote references to external manifests. The 239 // technique used to embed a reference varies bases on the type of embedding. Not 240 // all embedding choices need be supported. 241 pub trait RemoteRefEmbed { 242 // Embed RemoteRefEmbedType into the asset 243 #[allow(dead_code)] // this here for wasm builds to pass clippy (todo: remove) 244 fn embed_reference(&self, asset_path: &Path, embed_ref: RemoteRefEmbedType) -> Result<()>; 245 // Embed RemoteRefEmbedType into the asset stream 246 fn embed_reference_to_stream( 247 &self, 248 source_stream: &mut dyn CAIRead, 249 output_stream: &mut dyn CAIReadWrite, 250 embed_ref: RemoteRefEmbedType, 251 ) -> Result<()>; 252 } 253 254 /// `ComposedManifestRefEmbed` is used to generate a C2PA manifest. The 255 /// returned `Vec<u8>` contains data preformatted to be directly compatible 256 /// with the type specified in `format`. 257 pub trait ComposedManifestRef { 258 // Return entire CAI block as Vec<u8> 259 fn compose_manifest(&self, manifest_data: &[u8], format: &str) -> Result<Vec<u8>>; 260 } 261 262 /// Utility function to rename or copy a temp file to a permanent location. 263 /// 264 /// If the rename is not possible, due to cross volume references & etc, it will copy instead. 265 pub fn rename_or_copy<P>(temp_file: NamedTempFile, asset_path: P) -> Result<()> 266 where 267 P: AsRef<Path>, 268 { 269 // clear temp flag for Windows 270 let (_, path) = temp_file 271 .keep() 272 .map_err(|e| crate::Error::OtherError(Box::new(e)))?; 273 274 std::fs::rename(&path, asset_path.as_ref()) 275 // if rename fails, try to copy in case we are on different volumes 276 .or_else(|_| std::fs::copy(&path, asset_path).and(Ok(()))) 277 .map_err(crate::Error::IoError) 278 }