mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-12-08 21:26:17 +00:00
538 lines
22 KiB
Rust
538 lines
22 KiB
Rust
use thiserror::Error;
|
|
use wgt::{
|
|
error::{ErrorType, WebGpuError},
|
|
AdapterInfo,
|
|
};
|
|
|
|
pub const HEADER_LENGTH: usize = size_of::<PipelineCacheHeader>();
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Error)]
|
|
#[non_exhaustive]
|
|
pub enum PipelineCacheValidationError {
|
|
#[error("The pipeline cache data was truncated")]
|
|
Truncated,
|
|
#[error("The pipeline cache data was longer than recorded")]
|
|
// TODO: Is it plausible that this would happen
|
|
Extended,
|
|
#[error("The pipeline cache data was corrupted (e.g. the hash didn't match)")]
|
|
Corrupted,
|
|
#[error("The pipeline cacha data was out of date and so cannot be safely used")]
|
|
Outdated,
|
|
#[error("The cache data was created for a different device")]
|
|
DeviceMismatch,
|
|
#[error("Pipeline cacha data was created for a future version of wgpu")]
|
|
Unsupported,
|
|
}
|
|
|
|
impl PipelineCacheValidationError {
|
|
/// Could the error have been avoided?
|
|
/// That is, is there a mistake in user code interacting with the cache
|
|
pub fn was_avoidable(&self) -> bool {
|
|
match self {
|
|
PipelineCacheValidationError::DeviceMismatch => true,
|
|
PipelineCacheValidationError::Truncated
|
|
| PipelineCacheValidationError::Unsupported
|
|
| PipelineCacheValidationError::Extended
|
|
// It's unusual, but not implausible, to be downgrading wgpu
|
|
| PipelineCacheValidationError::Outdated
|
|
| PipelineCacheValidationError::Corrupted => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WebGpuError for PipelineCacheValidationError {
|
|
fn webgpu_error_type(&self) -> ErrorType {
|
|
ErrorType::Validation
|
|
}
|
|
}
|
|
|
|
/// Validate the data in a pipeline cache
|
|
pub fn validate_pipeline_cache<'d>(
|
|
cache_data: &'d [u8],
|
|
adapter: &AdapterInfo,
|
|
validation_key: [u8; 16],
|
|
) -> Result<&'d [u8], PipelineCacheValidationError> {
|
|
let adapter_key = adapter_key(adapter)?;
|
|
let Some((header, remaining_data)) = PipelineCacheHeader::read(cache_data) else {
|
|
return Err(PipelineCacheValidationError::Truncated);
|
|
};
|
|
if header.magic != MAGIC {
|
|
return Err(PipelineCacheValidationError::Corrupted);
|
|
}
|
|
if header.header_version != HEADER_VERSION {
|
|
return Err(PipelineCacheValidationError::Outdated);
|
|
}
|
|
if header.cache_abi != ABI {
|
|
return Err(PipelineCacheValidationError::Outdated);
|
|
}
|
|
if header.backend != adapter.backend as u8 {
|
|
return Err(PipelineCacheValidationError::DeviceMismatch);
|
|
}
|
|
if header.adapter_key != adapter_key {
|
|
return Err(PipelineCacheValidationError::DeviceMismatch);
|
|
}
|
|
if header.validation_key != validation_key {
|
|
// If the validation key is wrong, that means that this device has changed
|
|
// in a way where the cache won't be compatible since the cache was made,
|
|
// so it is outdated
|
|
return Err(PipelineCacheValidationError::Outdated);
|
|
}
|
|
let data_size: usize = header
|
|
.data_size
|
|
.try_into()
|
|
// If the data was previously more than 4GiB, and we're still on a 32 bit system (ABI check, above)
|
|
// Then the data must be corrupted
|
|
.map_err(|_| PipelineCacheValidationError::Corrupted)?;
|
|
if remaining_data.len() < data_size {
|
|
return Err(PipelineCacheValidationError::Truncated);
|
|
}
|
|
if remaining_data.len() > data_size {
|
|
return Err(PipelineCacheValidationError::Extended);
|
|
}
|
|
if header.hash_space != HASH_SPACE_VALUE {
|
|
return Err(PipelineCacheValidationError::Corrupted);
|
|
}
|
|
Ok(remaining_data)
|
|
}
|
|
|
|
pub fn add_cache_header(
|
|
in_region: &mut [u8],
|
|
data: &[u8],
|
|
adapter: &AdapterInfo,
|
|
validation_key: [u8; 16],
|
|
) {
|
|
assert_eq!(in_region.len(), HEADER_LENGTH);
|
|
let header = PipelineCacheHeader {
|
|
adapter_key: adapter_key(adapter)
|
|
.expect("Called add_cache_header for an adapter which doesn't support cache data. This is a wgpu internal bug"),
|
|
backend: adapter.backend as u8,
|
|
cache_abi: ABI,
|
|
magic: MAGIC,
|
|
header_version: HEADER_VERSION,
|
|
validation_key,
|
|
hash_space: HASH_SPACE_VALUE,
|
|
data_size: data
|
|
.len()
|
|
.try_into()
|
|
.expect("Cache larger than u64::MAX bytes"),
|
|
};
|
|
header.write(in_region);
|
|
}
|
|
|
|
const MAGIC: [u8; 8] = *b"WGPUPLCH";
|
|
const HEADER_VERSION: u32 = 1;
|
|
const ABI: u32 = size_of::<*const ()>() as u32;
|
|
|
|
/// The value used to fill [`PipelineCacheHeader::hash_space`]
|
|
///
|
|
/// If we receive reports of pipeline cache data corruption which is not otherwise caught
|
|
/// on a real device, it would be worth modifying this
|
|
///
|
|
/// Note that wgpu does not protect against malicious writes to e.g. a file used
|
|
/// to store a pipeline cache.
|
|
/// That is the resonsibility of the end application, such as by using a
|
|
/// private space.
|
|
const HASH_SPACE_VALUE: u64 = 0xFEDCBA9_876543210;
|
|
|
|
#[repr(C)]
|
|
#[derive(PartialEq, Eq)]
|
|
struct PipelineCacheHeader {
|
|
/// The magic header to ensure that we have the right file format
|
|
/// Has a value of MAGIC, as above
|
|
magic: [u8; 8],
|
|
// /// The total size of this header, in bytes
|
|
// header_size: u32,
|
|
/// The version of this wgpu header
|
|
/// Should be equal to HEADER_VERSION above
|
|
///
|
|
/// This must always be the second item, after the value above
|
|
header_version: u32,
|
|
/// The number of bytes in the pointers of this ABI, because some drivers
|
|
/// have previously not distinguished between their 32 bit and 64 bit drivers
|
|
/// leading to Vulkan data corruption
|
|
cache_abi: u32,
|
|
/// The id for the backend in use, from [wgt::Backend]
|
|
backend: u8,
|
|
/// The key which identifiers the device/adapter.
|
|
/// This is used to validate that this pipeline cache (probably) was produced for
|
|
/// the expected device.
|
|
/// On Vulkan: it is a combination of vendor ID and device ID
|
|
adapter_key: [u8; 15],
|
|
/// A key used to validate that this device is still compatible with the cache
|
|
///
|
|
/// This should e.g. contain driver version and/or intermediate compiler versions
|
|
validation_key: [u8; 16],
|
|
/// The length of the data which is sent to/recieved from the backend
|
|
data_size: u64,
|
|
/// Space reserved for a hash of the data in future
|
|
///
|
|
/// We assume that your cache storage system will be relatively robust, and so
|
|
/// do not validate this hash
|
|
///
|
|
/// Therefore, this will always have a value of [`HASH_SPACE_VALUE`]
|
|
hash_space: u64,
|
|
}
|
|
|
|
impl PipelineCacheHeader {
|
|
fn read(data: &[u8]) -> Option<(PipelineCacheHeader, &[u8])> {
|
|
let mut reader = Reader {
|
|
data,
|
|
total_read: 0,
|
|
};
|
|
let magic = reader.read_array()?;
|
|
let header_version = reader.read_u32()?;
|
|
let cache_abi = reader.read_u32()?;
|
|
let backend = reader.read_byte()?;
|
|
let adapter_key = reader.read_array()?;
|
|
let validation_key = reader.read_array()?;
|
|
let data_size = reader.read_u64()?;
|
|
let data_hash = reader.read_u64()?;
|
|
|
|
assert_eq!(reader.total_read, size_of::<PipelineCacheHeader>());
|
|
|
|
Some((
|
|
PipelineCacheHeader {
|
|
magic,
|
|
header_version,
|
|
cache_abi,
|
|
backend,
|
|
adapter_key,
|
|
validation_key,
|
|
data_size,
|
|
hash_space: data_hash,
|
|
},
|
|
reader.data,
|
|
))
|
|
}
|
|
|
|
fn write(&self, into: &mut [u8]) -> Option<()> {
|
|
let mut writer = Writer { data: into };
|
|
writer.write_array(&self.magic)?;
|
|
writer.write_u32(self.header_version)?;
|
|
writer.write_u32(self.cache_abi)?;
|
|
writer.write_byte(self.backend)?;
|
|
writer.write_array(&self.adapter_key)?;
|
|
writer.write_array(&self.validation_key)?;
|
|
writer.write_u64(self.data_size)?;
|
|
writer.write_u64(self.hash_space)?;
|
|
|
|
assert_eq!(writer.data.len(), 0);
|
|
Some(())
|
|
}
|
|
}
|
|
|
|
fn adapter_key(adapter: &AdapterInfo) -> Result<[u8; 15], PipelineCacheValidationError> {
|
|
match adapter.backend {
|
|
wgt::Backend::Vulkan => {
|
|
// If these change size, the header format needs to change
|
|
// We set the type explicitly so this won't compile in that case
|
|
let v: [u8; 4] = adapter.vendor.to_be_bytes();
|
|
let d: [u8; 4] = adapter.device.to_be_bytes();
|
|
let adapter = [
|
|
255, 255, 255, v[0], v[1], v[2], v[3], d[0], d[1], d[2], d[3], 255, 255, 255, 255,
|
|
];
|
|
Ok(adapter)
|
|
}
|
|
_ => Err(PipelineCacheValidationError::Unsupported),
|
|
}
|
|
}
|
|
|
|
struct Reader<'a> {
|
|
data: &'a [u8],
|
|
total_read: usize,
|
|
}
|
|
|
|
impl<'a> Reader<'a> {
|
|
fn read_byte(&mut self) -> Option<u8> {
|
|
let res = *self.data.first()?;
|
|
self.total_read += 1;
|
|
self.data = &self.data[1..];
|
|
Some(res)
|
|
}
|
|
fn read_array<const N: usize>(&mut self) -> Option<[u8; N]> {
|
|
// Only greater than because we're indexing fenceposts, not items
|
|
if N > self.data.len() {
|
|
return None;
|
|
}
|
|
let (start, data) = self.data.split_at(N);
|
|
self.total_read += N;
|
|
self.data = data;
|
|
Some(start.try_into().expect("off-by-one-error in array size"))
|
|
}
|
|
|
|
// fn read_u16(&mut self) -> Option<u16> {
|
|
// self.read_array().map(u16::from_be_bytes)
|
|
// }
|
|
fn read_u32(&mut self) -> Option<u32> {
|
|
self.read_array().map(u32::from_be_bytes)
|
|
}
|
|
fn read_u64(&mut self) -> Option<u64> {
|
|
self.read_array().map(u64::from_be_bytes)
|
|
}
|
|
}
|
|
|
|
struct Writer<'a> {
|
|
data: &'a mut [u8],
|
|
}
|
|
|
|
impl<'a> Writer<'a> {
|
|
fn write_byte(&mut self, byte: u8) -> Option<()> {
|
|
self.write_array(&[byte])
|
|
}
|
|
fn write_array<const N: usize>(&mut self, array: &[u8; N]) -> Option<()> {
|
|
// Only greater than because we're indexing fenceposts, not items
|
|
if N > self.data.len() {
|
|
return None;
|
|
}
|
|
let data = core::mem::take(&mut self.data);
|
|
let (start, data) = data.split_at_mut(N);
|
|
self.data = data;
|
|
start.copy_from_slice(array);
|
|
Some(())
|
|
}
|
|
|
|
// fn write_u16(&mut self, value: u16) -> Option<()> {
|
|
// self.write_array(&value.to_be_bytes())
|
|
// }
|
|
fn write_u32(&mut self, value: u32) -> Option<()> {
|
|
self.write_array(&value.to_be_bytes())
|
|
}
|
|
fn write_u64(&mut self, value: u64) -> Option<()> {
|
|
self.write_array(&value.to_be_bytes())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use alloc::{string::String, vec::Vec};
|
|
use wgt::AdapterInfo;
|
|
|
|
use crate::pipeline_cache::{PipelineCacheValidationError as E, HEADER_LENGTH};
|
|
|
|
use super::ABI;
|
|
|
|
// Assert the correct size
|
|
const _: [(); HEADER_LENGTH] = [(); 64];
|
|
|
|
const ADAPTER: AdapterInfo = AdapterInfo {
|
|
name: String::new(),
|
|
vendor: 0x0002_FEED,
|
|
device: 0xFEFE_FEFE,
|
|
device_type: wgt::DeviceType::Other,
|
|
driver: String::new(),
|
|
driver_info: String::new(),
|
|
backend: wgt::Backend::Vulkan,
|
|
};
|
|
|
|
// IMPORTANT: If these tests fail, then you MUST increment HEADER_VERSION
|
|
const VALIDATION_KEY: [u8; 16] = u128::to_be_bytes(0xFFFFFFFF_FFFFFFFF_88888888_88888888);
|
|
#[test]
|
|
fn written_header() {
|
|
let mut result = [0; HEADER_LENGTH];
|
|
super::add_cache_header(&mut result, &[], &ADAPTER, VALIDATION_KEY);
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x0u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let expected = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
|
|
assert_eq!(result.as_slice(), expected.as_slice());
|
|
}
|
|
|
|
#[test]
|
|
fn valid_data() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x0u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
let expected: &[u8] = &[];
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Ok(expected));
|
|
}
|
|
#[test]
|
|
fn invalid_magic() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"NOT_WGPU", // (Wrong) MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x0u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Err(E::Corrupted));
|
|
}
|
|
|
|
#[test]
|
|
fn wrong_version() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 2, 0, 0, 0, ABI as u8], // (wrong) Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x0u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Err(E::Outdated));
|
|
}
|
|
#[test]
|
|
fn wrong_abi() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
// a 14 bit ABI is improbable
|
|
[0, 0, 0, 1, 0, 0, 0, 14], // Version and (wrong) ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x0u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Header
|
|
];
|
|
let cache = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Err(E::Outdated));
|
|
}
|
|
|
|
#[test]
|
|
fn wrong_backend() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[2, 255, 255, 255, 0, 2, 0xFE, 0xED], // (wrong) Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x0u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Err(E::DeviceMismatch));
|
|
}
|
|
#[test]
|
|
fn wrong_adapter() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0x00], // Backend and (wrong) Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x0u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Err(E::DeviceMismatch));
|
|
}
|
|
#[test]
|
|
fn wrong_validation() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_00000000u64.to_be_bytes(), // (wrong) Validation key
|
|
0x0u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Err(E::Outdated));
|
|
}
|
|
#[test]
|
|
fn too_little_data() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x064u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Err(E::Truncated));
|
|
}
|
|
#[test]
|
|
fn not_no_data() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
100u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache
|
|
.into_iter()
|
|
.flatten()
|
|
.chain(core::iter::repeat_n(0u8, 100))
|
|
.collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
let expected: &[u8] = &[0; 100];
|
|
assert_eq!(validation_result, Ok(expected));
|
|
}
|
|
#[test]
|
|
fn too_much_data() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x064u64.to_be_bytes(), // Data size
|
|
0xFEDCBA9_876543210u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache
|
|
.into_iter()
|
|
.flatten()
|
|
.chain(core::iter::repeat_n(0u8, 200))
|
|
.collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Err(E::Extended));
|
|
}
|
|
#[test]
|
|
fn wrong_hash() {
|
|
let cache: [[u8; 8]; HEADER_LENGTH / 8] = [
|
|
*b"WGPUPLCH", // MAGIC
|
|
[0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI
|
|
[1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key
|
|
0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key
|
|
0x88888888_88888888u64.to_be_bytes(), // Validation key
|
|
0x0u64.to_be_bytes(), // Data size
|
|
0x00000000_00000000u64.to_be_bytes(), // Hash
|
|
];
|
|
let cache = cache.into_iter().flatten().collect::<Vec<u8>>();
|
|
let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY);
|
|
assert_eq!(validation_result, Err(E::Corrupted));
|
|
}
|
|
}
|