use crate::error::{Qcow2Error, Result};
pub const MAGIC: u32 = 0x5146_49fb;
pub const MIN_HEADER_SIZE: usize = 72;
const INCOMPAT_EXTERNAL_DATA: u64 = 1 << 2;
const INCOMPAT_COMPRESSION_TYPE: u64 = 1 << 3;
const INCOMPAT_EXTENDED_L2: u64 = 1 << 4;
const INCOMPAT_UNSUPPORTED: u64 = INCOMPAT_EXTERNAL_DATA | INCOMPAT_COMPRESSION_TYPE | INCOMPAT_EXTENDED_L2;
fn be_u32(data: &[u8], off: usize) -> u32 {
let mut b = [0u8; 4];
if let Some(s) = data.get(off..off + 4) {
b.copy_from_slice(s);
}
u32::from_be_bytes(b)
}
fn be_u64(data: &[u8], off: usize) -> u64 {
let mut b = [0u8; 8];
if let Some(s) = data.get(off..off + 8) {
b.copy_from_slice(s);
}
u64::from_be_bytes(b)
}
pub struct Qcow2Header {
pub cluster_bits: u32, pub disk_size: u64, pub l1_size: u32, pub l1_table_offset: u64,
}
impl Qcow2Header {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < MIN_HEADER_SIZE {
return Err(Qcow2Error::FileTooSmall);
}
let magic = be_u32(data, 0);
if magic != MAGIC {
return Err(Qcow2Error::BadMagic);
}
let version = be_u32(data, 4);
if version < 2 || version > 3 {
return Err(Qcow2Error::UnsupportedVersion(version));
}
let backing_file_offset = be_u64(data, 8);
if backing_file_offset != 0 {
return Err(Qcow2Error::BackingFileNotSupported);
}
let cluster_bits = be_u32(data, 20);
if !(9..=20).contains(&cluster_bits) {
return Err(Qcow2Error::ClusterBitsOutOfRange(cluster_bits));
}
let disk_size = be_u64(data, 24);
let encryption_method = be_u32(data, 32);
if encryption_method != 0 {
return Err(Qcow2Error::EncryptedNotSupported);
}
let l1_size = be_u32(data, 36);
let l1_table_offset = be_u64(data, 40);
if version == 3 && data.len() >= 80 {
let incompat = be_u64(data, 72);
let unsupported = incompat & INCOMPAT_UNSUPPORTED;
if unsupported != 0 {
return Err(Qcow2Error::UnsupportedIncompatibleFeatures(unsupported));
}
}
Ok(Qcow2Header { cluster_bits, disk_size, l1_size, l1_table_offset })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Qcow2Info {
pub version: u32,
pub cluster_bits: u32,
pub virtual_disk_size: u64,
pub l1_size: u32,
pub has_backing_file: bool,
pub encryption_method: u32,
pub snapshot_count: u32,
pub incompatible_features: u64,
}
impl Qcow2Info {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < MIN_HEADER_SIZE {
return Err(Qcow2Error::FileTooSmall);
}
let magic = be_u32(data, 0);
if magic != MAGIC {
return Err(Qcow2Error::BadMagic);
}
let version = be_u32(data, 4);
if version < 2 || version > 3 {
return Err(Qcow2Error::UnsupportedVersion(version));
}
let has_backing_file = be_u64(data, 8) != 0;
let cluster_bits = be_u32(data, 20);
let virtual_disk_size = be_u64(data, 24);
let encryption_method = be_u32(data, 32);
let l1_size = be_u32(data, 36);
let snapshot_count = be_u32(data, 60);
let incompatible_features = if version == 3 && data.len() >= 80 {
be_u64(data, 72)
} else {
0
};
Ok(Qcow2Info {
version,
cluster_bits,
virtual_disk_size,
l1_size,
has_backing_file,
encryption_method,
snapshot_count,
incompatible_features,
})
}
}