shuru-linux 0.4.1

KVM backend for shuru
pub(super) const VIRTIO_BLK_T_IN: u32 = 0;
pub(super) const VIRTIO_BLK_T_OUT: u32 = 1;
pub(super) const VIRTIO_BLK_T_DISCARD: u32 = 11;
pub(super) const VIRTIO_BLK_T_WRITE_ZEROES: u32 = 13;
pub(super) const VIRTIO_BLK_S_UNSUPP: u8 = 2;
pub(super) const MAX_RANGES: usize = 32;
pub(super) const MAX_RANGE_SECTORS: u32 = 2 * 1024 * 1024;

pub(super) fn decode_ranges(
    payload: &[u8],
    capacity: u64,
    kind: u32,
) -> Result<Vec<(i64, i64, bool)>, u8> {
    if payload.is_empty() || !payload.len().is_multiple_of(16) || payload.len() / 16 > MAX_RANGES {
        return Err(VIRTIO_BLK_S_IOERR);
    }
    let mut ranges = Vec::new();
    for bytes in payload.chunks_exact(16) {
        let sector = u64::from_le_bytes(bytes[..8].try_into().unwrap());
        let count = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
        let flags = u32::from_le_bytes(bytes[12..16].try_into().unwrap());
        if (kind == VIRTIO_BLK_T_DISCARD && flags != 0) || flags & !1 != 0 {
            return Err(VIRTIO_BLK_S_UNSUPP);
        }
        if count > MAX_RANGE_SECTORS || sector > capacity || count as u64 > capacity - sector {
            return Err(VIRTIO_BLK_S_IOERR);
        }
        let end = sector
            .checked_add(count as u64)
            .and_then(|s| s.checked_mul(512))
            .filter(|n| *n <= i64::MAX as u64)
            .ok_or(VIRTIO_BLK_S_IOERR)?;
        let len = count as i64 * 512;
        ranges.push((end as i64 - len, len, flags & 1 != 0));
    }
    Ok(ranges)
}

pub(super) const VIRTIO_BLK_S_OK: u8 = 0;
pub(super) const VIRTIO_BLK_S_IOERR: u8 = 1;

#[cfg(test)]
mod tests {
    use super::*;

    fn range(sector: u64, count: u32, flags: u32) -> Vec<u8> {
        [
            sector.to_le_bytes().as_slice(),
            count.to_le_bytes().as_slice(),
            flags.to_le_bytes().as_slice(),
        ]
        .concat()
    }

    #[test]
    fn validates_all_ranges_and_signed_host_offsets() {
        assert_eq!(
            decode_ranges(&range(8, 8, 1), 16, VIRTIO_BLK_T_WRITE_ZEROES),
            Ok(vec![(4096, 4096, true)])
        );
        assert_eq!(
            decode_ranges(&range(8, 9, 0), 16, VIRTIO_BLK_T_DISCARD),
            Err(VIRTIO_BLK_S_IOERR)
        );
        assert_eq!(
            decode_ranges(&range(u64::MAX, 1, 0), u64::MAX, VIRTIO_BLK_T_DISCARD),
            Err(VIRTIO_BLK_S_IOERR)
        );
        assert_eq!(
            decode_ranges(
                &range(i64::MAX as u64 / 512, 1, 0),
                u64::MAX,
                VIRTIO_BLK_T_DISCARD
            ),
            Err(VIRTIO_BLK_S_IOERR)
        );
        let mut payload = range(0, 8, 0);
        payload.extend(range(15, 2, 0));
        assert_eq!(
            decode_ranges(&payload, 16, VIRTIO_BLK_T_DISCARD),
            Err(VIRTIO_BLK_S_IOERR)
        );
    }

    #[test]
    fn enforces_segment_count_length_flags_and_limits() {
        for payload in [
            vec![],
            vec![0; 15],
            vec![0; (MAX_RANGES + 1) * 16],
            range(0, MAX_RANGE_SECTORS + 1, 0),
        ] {
            assert_eq!(
                decode_ranges(&payload, u64::MAX, VIRTIO_BLK_T_DISCARD),
                Err(VIRTIO_BLK_S_IOERR)
            );
        }
        assert_eq!(
            decode_ranges(&range(0, 8, 1), 16, VIRTIO_BLK_T_DISCARD),
            Err(VIRTIO_BLK_S_UNSUPP)
        );
        assert_eq!(
            decode_ranges(&range(0, 8, 2), 16, VIRTIO_BLK_T_WRITE_ZEROES),
            Err(VIRTIO_BLK_S_UNSUPP)
        );
        assert!(decode_ranges(&vec![0; MAX_RANGES * 16], 16, VIRTIO_BLK_T_DISCARD).is_ok());
        assert!(decode_ranges(&range(16, 0, 0), 16, VIRTIO_BLK_T_DISCARD).is_ok());
    }
}