<commit_before>// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::fs::OpenOptions;
use std::io::prelude::*;
use std::mem;
use std::path::Path;
use std::path::PathBuf;
use std::result;
use std::slice;

use data_model::DataInit;
use remain::sorted;
use thiserror::Error;
use vm_memory::GuestAddress;
use vm_memory::GuestMemory;

#[sorted]
#[derive(Error, Debug)]
pub enum Error {
    /// The SMBIOS table has too little address space to be stored.
    #[error("The SMBIOS table has too little address space to be stored")]
    AddressOverflow,
    /// Failure while zeroing out the memory for the SMBIOS table.
    #[error("Failure while zeroing out the memory for the SMBIOS table")]
    Clear,
    /// Invalid table entry point checksum
    #[error("Failure to verify host SMBIOS entry checksum")]
    InvalidChecksum,
    /// Incorrect or not readable host SMBIOS data
    #[error("Failure to read host SMBIOS data")]
    InvalidInput,
    /// Failure while reading SMBIOS data file
    #[error("Failure while reading SMBIOS data file")]
    IoFailed,
    /// There was too little guest memory to store the entire SMBIOS table.
    #[error("There was too little guest memory to store the SMBIOS table")]
    NotEnoughMemory,
    /// Failure while opening SMBIOS data file
    #[error("Failure while opening SMBIOS data file {1}: {0}")]
    OpenFailed(std::io::Error, PathBuf),
    /// Failure to write additional data to memory
    #[error("Failure to write additional data to memory")]
    WriteData,
    /// Failure to write SMBIOS entrypoint structure
    #[error("Failure to write SMBIOS entrypoint structure")]
    WriteSmbiosEp,
}

pub type Result<T> = result::Result<T, Error>;

const SMBIOS_START: u64 = 0xf0000; // First possible location per the spec.

// Constants sourced from SMBIOS Spec 2.3.1.
const SM2_MAGIC_IDENT: &[u8; 4usize] = b"_SM_";

// Constants sourced from SMBIOS Spec 3.2.0.
const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
const BIOS_INFORMATION: u8 = 0;
const SYSTEM_INFORMATION: u8 = 1;
const END_OF_TABLE: u8 = 127;
const PCI_SUPPORTED: u64 = 1 << 7;
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;

fn compute_checksum<T: Copy>(v: &T) -> u8 {
    // Safe because we are only reading the bytes within the size of the `T` reference `v`.
    let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
    let mut checksum: u8 = 0;
    for i in v_slice.iter() {
        checksum = checksum.wrapping_add(*i);
    }
    (!checksum).wrapping_add(1)
}

#[repr(packed)]
#[derive(Default, Copy)]
pub struct Smbios23Intermediate {
    pub signature: [u8; 5usize],
    pub checksum: u8,
    pub length: u16,
    pub address: u32,
    pub count: u16,
    pub revision: u8,
}

unsafe impl data_model::DataInit for Smbios23Intermediate {}

impl Clone for Smbios23Intermediate {
    fn clone(&self) -> Self {
        *self
    }
}

#[repr(packed)]
#[derive(Default, Copy)]
pub struct Smbios23Entrypoint {
    pub signature: [u8; 4usize],
    pub checksum: u8,
    pub length: u8,
    pub majorver: u8,
    pub minorver: u8,
    pub max_size: u16,
    pub revision: u8,
    pub reserved: [u8; 5usize],
    pub dmi: Smbios23Intermediate,
}

unsafe impl data_model::DataInit for Smbios23Entrypoint {}

impl Clone for Smbios23Entrypoint {
    fn clone(&self) -> Self {
        *self
    }
}

#[repr(packed)]
#[derive(Default, Copy)]
pub struct Smbios30Entrypoint {
    pub signature: [u8; 5usize],
    pub checksum: u8,
    pub length: u8,
    pub majorver: u8,
    pub minorver: u8,
    pub docrev: u8,
    pub revision: u8,
    pub reserved: u8,
    pub max_size: u32,
    pub physptr: u64,
}
unsafe impl data_model::DataInit for Smbios30Entrypoint {}

impl Clone for Smbios30Entrypoint {
    fn clone(&self) -> Self {
        *self
    }
}

#[repr(packed)]
#[derive(Default, Copy)]
pub struct SmbiosBiosInfo {
    pub typ: u8,
    pub length: u8,
    pub handle: u16,
    pub vendor: u8,
    pub version: u8,
    pub start_addr: u16,
    pub release_date: u8,
    pub rom_size: u8,
    pub characteristics: u64,
    pub characteristics_ext1: u8,
    pub characteristics_ext2: u8,
}

impl Clone for SmbiosBiosInfo {
    fn clone(&self) -> Self {
        *self
    }
}

unsafe impl data_model::DataInit for SmbiosBiosInfo {}

#[repr(packed)]
#[derive(Default, Copy)]
pub struct SmbiosSysInfo {
    pub typ: u8,
    pub length: u8,
    pub handle: u16,
    pub manufacturer: u8,
    pub product_name: u8,
    pub version: u8,
    pub serial_number: u8,
    pub uuid: [u8; 16usize],
    pub wake_up_type: u8,
    pub sku: u8,
    pub family: u8,
}

impl Clone for SmbiosSysInfo {
    fn clone(&self) -> Self {
        *self
    }
}

unsafe impl data_model::DataInit for SmbiosSysInfo {}

fn write_and_incr<T: DataInit>(
    mem: &GuestMemory,
    val: T,
    mut curptr: GuestAddress,
) -> Result<GuestAddress> {
    mem.write_obj_at_addr(val, curptr)
        .map_err(|_| Error::WriteData)?;
    curptr = curptr
        .checked_add(mem::size_of::<T>() as u64)
        .ok_or(Error::NotEnoughMemory)?;
    Ok(curptr)
}

fn write_string(mem: &GuestMemory, val: &str, mut curptr: GuestAddress) -> Result<GuestAddress> {
    for c in val.as_bytes().iter() {
        curptr = write_and_incr(mem, *c, curptr)?;
    }
    curptr = write_and_incr(mem, 0_u8, curptr)?;
    Ok(curptr)
}

fn setup_smbios_from_file(mem: &GuestMemory, path: &Path) -> Result<()> {
    let mut sme_path = PathBuf::from(path);
    sme_path.push("smbios_entry_point");
    let mut sme = Vec::new();
    OpenOptions::new()
        .read(true)
        .open(&sme_path)
        .map_err(|e| Error::OpenFailed(e, sme_path))?
        .read_to_end(&mut sme)
        .map_err(|_| Error::IoFailed)?;

    let mut dmi_path = PathBuf::from(path);
    dmi_path.push("DMI");
    let mut dmi = Vec::new();
    OpenOptions::new()
        .read(true)
        .open(&dmi_path)
        .map_err(|e| Error::OpenFailed(e, dmi_path))?
        .read_to_end(&mut dmi)
        .map_err(|_| Error::IoFailed)?;

    // Try SMBIOS 3.0 format.
    if sme.len() == mem::size_of::<Smbios30Entrypoint>() && sme.starts_with(SM3_MAGIC_IDENT) {
        let mut smbios_ep = Smbios30Entrypoint::default();
        smbios_ep.as_mut_slice().copy_from_slice(&sme);

        let physptr = GuestAddress(SMBIOS_START)
            .checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
            .ok_or(Error::NotEnoughMemory)?;

        mem.write_at_addr(&dmi, physptr)
            .map_err(|_| Error::NotEnoughMemory)?;

        // Update EP DMI location
        smbios_ep.physptr = physptr.offset();
        smbios_ep.checksum = 0;
        smbios_ep.checksum = compute_checksum(&smbios_ep);

        mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
            .map_err(|_| Error::NotEnoughMemory)?;

        return Ok(());
    }

    // Try SMBIOS 2.3 format.
    if sme.len() == mem::size_of::<Smbios23Entrypoint>() && sme.starts_with(SM2_MAGIC_IDENT) {
        let mut smbios_ep = Smbios23Entrypoint::default();
        smbios_ep.as_mut_slice().copy_from_slice(&sme);

        let physptr = GuestAddress(SMBIOS_START)
            .checked_add(mem::size_of::<Smbios23Entrypoint>() as u64)
            .ok_or(Error::NotEnoughMemory)?;

        mem.write_at_addr(&dmi, physptr)
            .map_err(|_| Error::NotEnoughMemory)?;

        // Update EP DMI location
        smbios_ep.dmi.address = physptr.offset() as u32;
        smbios_ep.dmi.checksum = 0;
        smbios_ep.dmi.checksum = compute_checksum(&smbios_ep.dmi);
        smbios_ep.checksum = 0;
        smbios_ep.checksum = compute_checksum(&smbios_ep);

        mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
            .map_err(|_| Error::WriteSmbiosEp)?;

        return Ok(());
    }

    Err(Error::InvalidInput)
}

pub fn setup_smbios(mem: &GuestMemory, dmi_path: Option<PathBuf>) -> Result<()> {
    if let Some(dmi_path) = dmi_path {
        return setup_smbios_from_file(mem, &dmi_path);
    }

    let physptr = GuestAddress(SMBIOS_START)
        .checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
        .ok_or(Error::NotEnoughMemory)?;
    let mut curptr = physptr;
    let mut handle = 0;

    {
        handle += 1;
        let smbios_biosinfo = SmbiosBiosInfo {
            typ: BIOS_INFORMATION,
            length: mem::size_of::<SmbiosBiosInfo>() as u8,
            handle,
            vendor: 1,  // First string written in this section
            version: 2, // Second string written in this section
            characteristics: PCI_SUPPORTED,
            characteristics_ext2: IS_VIRTUAL_MACHINE,
            ..Default::default()
        };
        curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
        curptr = write_string(mem, "crosvm", curptr)?;
        curptr = write_string(mem, "0", curptr)?;
        curptr = write_and_incr(mem, 0_u8, curptr)?;
    }

    {
        handle += 1;
        let smbios_sysinfo = SmbiosSysInfo {
            typ: SYSTEM_INFORMATION,
            length: mem::size_of::<SmbiosSysInfo>() as u8,
            handle,
            manufacturer: 1, // First string written in this section
            product_name: 2, // Second string written in this section
            ..Default::default()
        };
        curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
        curptr = write_string(mem, "ChromiumOS", curptr)?;
        curptr = write_string(mem, "crosvm", curptr)?;
        curptr = write_and_incr(mem, 0u8, curptr)?;
    }

    {
        handle += 1;
        let smbios_sysinfo = SmbiosSysInfo {
            typ: END_OF_TABLE,
            length: mem::size_of::<SmbiosSysInfo>() as u8,
            handle,
            ..Default::default()
        };
        curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
        curptr = write_and_incr(mem, 0_u8, curptr)?;
    }

    {
        let mut smbios_ep = Smbios30Entrypoint::default();
        smbios_ep.signature = *SM3_MAGIC_IDENT;
        smbios_ep.length = mem::size_of::<Smbios30Entrypoint>() as u8;
        // SMBIOS rev 3.2.0
        smbios_ep.majorver = 0x03;
        smbios_ep.minorver = 0x02;
        smbios_ep.docrev = 0x00;
        smbios_ep.revision = 0x01; // SMBIOS 3.0
        smbios_ep.max_size = curptr.offset_from(physptr) as u32;
        smbios_ep.physptr = physptr.offset();
        smbios_ep.checksum = compute_checksum(&smbios_ep);
        mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
            .map_err(|_| Error::WriteSmbiosEp)?;
    }

    Ok(())
}

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

    #[test]
    fn struct_size() {
        assert_eq!(
            mem::size_of::<Smbios23Entrypoint>(),
            0x1fusize,
            concat!("Size of: ", stringify!(Smbios23Entrypoint))
        );
        assert_eq!(
            mem::size_of::<Smbios30Entrypoint>(),
            0x18usize,
            concat!("Size of: ", stringify!(Smbios30Entrypoint))
        );
        assert_eq!(
            mem::size_of::<SmbiosBiosInfo>(),
            0x14usize,
            concat!("Size of: ", stringify!(SmbiosBiosInfo))
        );
        assert_eq!(
            mem::size_of::<SmbiosSysInfo>(),
            0x1busize,
            concat!("Size of: ", stringify!(SmbiosSysInfo))
        );
    }

    #[test]
    fn entrypoint_checksum() {
        let mem = GuestMemory::new(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();

        // Use default 3.0 SMBIOS format.
        setup_smbios(&mem, None).unwrap();

        let smbios_ep: Smbios30Entrypoint =
            mem.read_obj_from_addr(GuestAddress(SMBIOS_START)).unwrap();

        assert_eq!(compute_checksum(&smbios_ep), 0);
    }
}
<commit_msg>smbios: derive Clone for smbios structs<commit_after>// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::fs::OpenOptions;
use std::io::prelude::*;
use std::mem;
use std::path::Path;
use std::path::PathBuf;
use std::result;
use std::slice;

use data_model::DataInit;
use remain::sorted;
use thiserror::Error;
use vm_memory::GuestAddress;
use vm_memory::GuestMemory;

#[sorted]
#[derive(Error, Debug)]
pub enum Error {
    /// The SMBIOS table has too little address space to be stored.
    #[error("The SMBIOS table has too little address space to be stored")]
    AddressOverflow,
    /// Failure while zeroing out the memory for the SMBIOS table.
    #[error("Failure while zeroing out the memory for the SMBIOS table")]
    Clear,
    /// Invalid table entry point checksum
    #[error("Failure to verify host SMBIOS entry checksum")]
    InvalidChecksum,
    /// Incorrect or not readable host SMBIOS data
    #[error("Failure to read host SMBIOS data")]
    InvalidInput,
    /// Failure while reading SMBIOS data file
    #[error("Failure while reading SMBIOS data file")]
    IoFailed,
    /// There was too little guest memory to store the entire SMBIOS table.
    #[error("There was too little guest memory to store the SMBIOS table")]
    NotEnoughMemory,
    /// Failure while opening SMBIOS data file
    #[error("Failure while opening SMBIOS data file {1}: {0}")]
    OpenFailed(std::io::Error, PathBuf),
    /// Failure to write additional data to memory
    #[error("Failure to write additional data to memory")]
    WriteData,
    /// Failure to write SMBIOS entrypoint structure
    #[error("Failure to write SMBIOS entrypoint structure")]
    WriteSmbiosEp,
}

pub type Result<T> = result::Result<T, Error>;

const SMBIOS_START: u64 = 0xf0000; // First possible location per the spec.

// Constants sourced from SMBIOS Spec 2.3.1.
const SM2_MAGIC_IDENT: &[u8; 4usize] = b"_SM_";

// Constants sourced from SMBIOS Spec 3.2.0.
const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
const BIOS_INFORMATION: u8 = 0;
const SYSTEM_INFORMATION: u8 = 1;
const END_OF_TABLE: u8 = 127;
const PCI_SUPPORTED: u64 = 1 << 7;
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;

fn compute_checksum<T: Copy>(v: &T) -> u8 {
    // Safe because we are only reading the bytes within the size of the `T` reference `v`.
    let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
    let mut checksum: u8 = 0;
    for i in v_slice.iter() {
        checksum = checksum.wrapping_add(*i);
    }
    (!checksum).wrapping_add(1)
}

#[repr(packed)]
#[derive(Default, Clone, Copy)]
pub struct Smbios23Intermediate {
    pub signature: [u8; 5usize],
    pub checksum: u8,
    pub length: u16,
    pub address: u32,
    pub count: u16,
    pub revision: u8,
}

unsafe impl data_model::DataInit for Smbios23Intermediate {}

#[repr(packed)]
#[derive(Default, Clone, Copy)]
pub struct Smbios23Entrypoint {
    pub signature: [u8; 4usize],
    pub checksum: u8,
    pub length: u8,
    pub majorver: u8,
    pub minorver: u8,
    pub max_size: u16,
    pub revision: u8,
    pub reserved: [u8; 5usize],
    pub dmi: Smbios23Intermediate,
}

unsafe impl data_model::DataInit for Smbios23Entrypoint {}

#[repr(packed)]
#[derive(Default, Clone, Copy)]
pub struct Smbios30Entrypoint {
    pub signature: [u8; 5usize],
    pub checksum: u8,
    pub length: u8,
    pub majorver: u8,
    pub minorver: u8,
    pub docrev: u8,
    pub revision: u8,
    pub reserved: u8,
    pub max_size: u32,
    pub physptr: u64,
}

unsafe impl data_model::DataInit for Smbios30Entrypoint {}

#[repr(packed)]
#[derive(Default, Clone, Copy)]
pub struct SmbiosBiosInfo {
    pub typ: u8,
    pub length: u8,
    pub handle: u16,
    pub vendor: u8,
    pub version: u8,
    pub start_addr: u16,
    pub release_date: u8,
    pub rom_size: u8,
    pub characteristics: u64,
    pub characteristics_ext1: u8,
    pub characteristics_ext2: u8,
}

unsafe impl data_model::DataInit for SmbiosBiosInfo {}

#[repr(packed)]
#[derive(Default, Clone, Copy)]
pub struct SmbiosSysInfo {
    pub typ: u8,
    pub length: u8,
    pub handle: u16,
    pub manufacturer: u8,
    pub product_name: u8,
    pub version: u8,
    pub serial_number: u8,
    pub uuid: [u8; 16usize],
    pub wake_up_type: u8,
    pub sku: u8,
    pub family: u8,
}

unsafe impl data_model::DataInit for SmbiosSysInfo {}

fn write_and_incr<T: DataInit>(
    mem: &GuestMemory,
    val: T,
    mut curptr: GuestAddress,
) -> Result<GuestAddress> {
    mem.write_obj_at_addr(val, curptr)
        .map_err(|_| Error::WriteData)?;
    curptr = curptr
        .checked_add(mem::size_of::<T>() as u64)
        .ok_or(Error::NotEnoughMemory)?;
    Ok(curptr)
}

fn write_string(mem: &GuestMemory, val: &str, mut curptr: GuestAddress) -> Result<GuestAddress> {
    for c in val.as_bytes().iter() {
        curptr = write_and_incr(mem, *c, curptr)?;
    }
    curptr = write_and_incr(mem, 0_u8, curptr)?;
    Ok(curptr)
}

fn setup_smbios_from_file(mem: &GuestMemory, path: &Path) -> Result<()> {
    let mut sme_path = PathBuf::from(path);
    sme_path.push("smbios_entry_point");
    let mut sme = Vec::new();
    OpenOptions::new()
        .read(true)
        .open(&sme_path)
        .map_err(|e| Error::OpenFailed(e, sme_path))?
        .read_to_end(&mut sme)
        .map_err(|_| Error::IoFailed)?;

    let mut dmi_path = PathBuf::from(path);
    dmi_path.push("DMI");
    let mut dmi = Vec::new();
    OpenOptions::new()
        .read(true)
        .open(&dmi_path)
        .map_err(|e| Error::OpenFailed(e, dmi_path))?
        .read_to_end(&mut dmi)
        .map_err(|_| Error::IoFailed)?;

    // Try SMBIOS 3.0 format.
    if sme.len() == mem::size_of::<Smbios30Entrypoint>() && sme.starts_with(SM3_MAGIC_IDENT) {
        let mut smbios_ep = Smbios30Entrypoint::default();
        smbios_ep.as_mut_slice().copy_from_slice(&sme);

        let physptr = GuestAddress(SMBIOS_START)
            .checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
            .ok_or(Error::NotEnoughMemory)?;

        mem.write_at_addr(&dmi, physptr)
            .map_err(|_| Error::NotEnoughMemory)?;

        // Update EP DMI location
        smbios_ep.physptr = physptr.offset();
        smbios_ep.checksum = 0;
        smbios_ep.checksum = compute_checksum(&smbios_ep);

        mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
            .map_err(|_| Error::NotEnoughMemory)?;

        return Ok(());
    }

    // Try SMBIOS 2.3 format.
    if sme.len() == mem::size_of::<Smbios23Entrypoint>() && sme.starts_with(SM2_MAGIC_IDENT) {
        let mut smbios_ep = Smbios23Entrypoint::default();
        smbios_ep.as_mut_slice().copy_from_slice(&sme);

        let physptr = GuestAddress(SMBIOS_START)
            .checked_add(mem::size_of::<Smbios23Entrypoint>() as u64)
            .ok_or(Error::NotEnoughMemory)?;

        mem.write_at_addr(&dmi, physptr)
            .map_err(|_| Error::NotEnoughMemory)?;

        // Update EP DMI location
        smbios_ep.dmi.address = physptr.offset() as u32;
        smbios_ep.dmi.checksum = 0;
        smbios_ep.dmi.checksum = compute_checksum(&smbios_ep.dmi);
        smbios_ep.checksum = 0;
        smbios_ep.checksum = compute_checksum(&smbios_ep);

        mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
            .map_err(|_| Error::WriteSmbiosEp)?;

        return Ok(());
    }

    Err(Error::InvalidInput)
}

pub fn setup_smbios(mem: &GuestMemory, dmi_path: Option<PathBuf>) -> Result<()> {
    if let Some(dmi_path) = dmi_path {
        return setup_smbios_from_file(mem, &dmi_path);
    }

    let physptr = GuestAddress(SMBIOS_START)
        .checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
        .ok_or(Error::NotEnoughMemory)?;
    let mut curptr = physptr;
    let mut handle = 0;

    {
        handle += 1;
        let smbios_biosinfo = SmbiosBiosInfo {
            typ: BIOS_INFORMATION,
            length: mem::size_of::<SmbiosBiosInfo>() as u8,
            handle,
            vendor: 1,  // First string written in this section
            version: 2, // Second string written in this section
            characteristics: PCI_SUPPORTED,
            characteristics_ext2: IS_VIRTUAL_MACHINE,
            ..Default::default()
        };
        curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
        curptr = write_string(mem, "crosvm", curptr)?;
        curptr = write_string(mem, "0", curptr)?;
        curptr = write_and_incr(mem, 0_u8, curptr)?;
    }

    {
        handle += 1;
        let smbios_sysinfo = SmbiosSysInfo {
            typ: SYSTEM_INFORMATION,
            length: mem::size_of::<SmbiosSysInfo>() as u8,
            handle,
            manufacturer: 1, // First string written in this section
            product_name: 2, // Second string written in this section
            ..Default::default()
        };
        curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
        curptr = write_string(mem, "ChromiumOS", curptr)?;
        curptr = write_string(mem, "crosvm", curptr)?;
        curptr = write_and_incr(mem, 0u8, curptr)?;
    }

    {
        handle += 1;
        let smbios_sysinfo = SmbiosSysInfo {
            typ: END_OF_TABLE,
            length: mem::size_of::<SmbiosSysInfo>() as u8,
            handle,
            ..Default::default()
        };
        curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
        curptr = write_and_incr(mem, 0_u8, curptr)?;
    }

    {
        let mut smbios_ep = Smbios30Entrypoint::default();
        smbios_ep.signature = *SM3_MAGIC_IDENT;
        smbios_ep.length = mem::size_of::<Smbios30Entrypoint>() as u8;
        // SMBIOS rev 3.2.0
        smbios_ep.majorver = 0x03;
        smbios_ep.minorver = 0x02;
        smbios_ep.docrev = 0x00;
        smbios_ep.revision = 0x01; // SMBIOS 3.0
        smbios_ep.max_size = curptr.offset_from(physptr) as u32;
        smbios_ep.physptr = physptr.offset();
        smbios_ep.checksum = compute_checksum(&smbios_ep);
        mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
            .map_err(|_| Error::WriteSmbiosEp)?;
    }

    Ok(())
}

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

    #[test]
    fn struct_size() {
        assert_eq!(
            mem::size_of::<Smbios23Entrypoint>(),
            0x1fusize,
            concat!("Size of: ", stringify!(Smbios23Entrypoint))
        );
        assert_eq!(
            mem::size_of::<Smbios30Entrypoint>(),
            0x18usize,
            concat!("Size of: ", stringify!(Smbios30Entrypoint))
        );
        assert_eq!(
            mem::size_of::<SmbiosBiosInfo>(),
            0x14usize,
            concat!("Size of: ", stringify!(SmbiosBiosInfo))
        );
        assert_eq!(
            mem::size_of::<SmbiosSysInfo>(),
            0x1busize,
            concat!("Size of: ", stringify!(SmbiosSysInfo))
        );
    }

    #[test]
    fn entrypoint_checksum() {
        let mem = GuestMemory::new(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();

        // Use default 3.0 SMBIOS format.
        setup_smbios(&mem, None).unwrap();

        let smbios_ep: Smbios30Entrypoint =
            mem.read_obj_from_addr(GuestAddress(SMBIOS_START)).unwrap();

        assert_eq!(compute_checksum(&smbios_ep), 0);
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! The [Response](https://fetch.spec.whatwg.org/#responses) object
//! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch)
use {FetchMetadata, FilteredMetadata, Metadata, NetworkError};
use hyper::header::{AccessControlExposeHeaders, ContentType, Headers};
use hyper::status::StatusCode;
use hyper_serde::Serde;
use std::ascii::AsciiExt;
use std::cell::{Cell, RefCell};
use std::sync::{Arc, Mutex};
use url::Url;

/// [Response type](https://fetch.spec.whatwg.org/#concept-response-type)
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum ResponseType {
    Basic,
    CORS,
    Default,
    Error(NetworkError),
    Opaque,
    OpaqueRedirect
}

/// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason)
#[derive(Clone, Copy, Deserialize, Serialize, HeapSizeOf)]
pub enum TerminationReason {
    EndUserAbort,
    Fatal,
    Timeout
}

/// The response body can still be pushed to after fetch
/// This provides a way to store unfinished response bodies
#[derive(Clone, Debug, PartialEq, HeapSizeOf)]
pub enum ResponseBody {
    Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
    Receiving(Vec<u8>),
    Done(Vec<u8>),
}

impl ResponseBody {
    pub fn is_done(&self) -> bool {
        match *self {
            ResponseBody::Done(..) => true,
            ResponseBody::Empty | ResponseBody::Receiving(..) => false
        }
    }
}

/// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state)
#[derive(Clone, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum CacheState {
    None,
    Local,
    Validated,
    Partial
}

/// [Https state](https://fetch.spec.whatwg.org/#concept-response-https-state)
#[derive(Clone, Copy, HeapSizeOf, Deserialize, Serialize)]
pub enum HttpsState {
    None,
    Deprecated,
    Modern
}

pub enum ResponseMsg {
    Chunk(Vec<u8>),
    Finished,
    Errored
}

/// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
#[derive(Clone, HeapSizeOf)]
pub struct Response {
    pub response_type: ResponseType,
    pub termination_reason: Option<TerminationReason>,
    pub url: Option<Url>,
    pub url_list: RefCell<Vec<Url>>,
    /// `None` can be considered a StatusCode of `0`.
    #[ignore_heap_size_of = "Defined in hyper"]
    pub status: Option<StatusCode>,
    pub raw_status: Option<(u16, Vec<u8>)>,
    #[ignore_heap_size_of = "Defined in hyper"]
    pub headers: Headers,
    #[ignore_heap_size_of = "Mutex heap size undefined"]
    pub body: Arc<Mutex<ResponseBody>>,
    pub cache_state: CacheState,
    pub https_state: HttpsState,
    pub referrer: Option<Url>,
    /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response
    /// is a filtered response
    pub internal_response: Option<Box<Response>>,
    /// whether or not to try to return the internal_response when asked for actual_response
    pub return_internal: Cell<bool>,
}

impl Response {
    pub fn new() -> Response {
        Response {
            response_type: ResponseType::Default,
            termination_reason: None,
            url: None,
            url_list: RefCell::new(Vec::new()),
            status: Some(StatusCode::Ok),
            raw_status: Some((200, b"OK".to_vec())),
            headers: Headers::new(),
            body: Arc::new(Mutex::new(ResponseBody::Empty)),
            cache_state: CacheState::None,
            https_state: HttpsState::None,
            referrer: None,
            internal_response: None,
            return_internal: Cell::new(true)
        }
    }

    pub fn network_error(e: NetworkError) -> Response {
        Response {
            response_type: ResponseType::Error(e),
            termination_reason: None,
            url: None,
            url_list: RefCell::new(vec![]),
            status: None,
            raw_status: None,
            headers: Headers::new(),
            body: Arc::new(Mutex::new(ResponseBody::Empty)),
            cache_state: CacheState::None,
            https_state: HttpsState::None,
            referrer: None,
            internal_response: None,
            return_internal: Cell::new(true)
        }
    }

    pub fn is_network_error(&self) -> bool {
        match self.response_type {
            ResponseType::Error(..) => true,
            _ => false
        }
    }

    pub fn get_network_error(&self) -> Option<&NetworkError> {
        match self.response_type {
            ResponseType::Error(ref e) => Some(e),
            _ => None,
        }
    }

    pub fn actual_response(&self) -> &Response {
        if self.return_internal.get() && self.internal_response.is_some() {
            &**self.internal_response.as_ref().unwrap()
        } else {
            self
        }
    }

    pub fn to_actual(self) -> Response {
        if self.return_internal.get() && self.internal_response.is_some() {
            *self.internal_response.unwrap()
        } else {
            self
        }
    }

    /// Convert to a filtered response, of type `filter_type`.
    /// Do not use with type Error or Default
    pub fn to_filtered(self, filter_type: ResponseType) -> Response {
        match filter_type {
            ResponseType::Default | ResponseType::Error(..) => panic!(),
            _ => (),
        }

        let old_response = self.to_actual();

        if let ResponseType::Error(e) = old_response.response_type {
            return Response::network_error(e);
        }

        let old_headers = old_response.headers.clone();
        let mut response = old_response.clone();
        response.internal_response = Some(Box::new(old_response));
        response.response_type = filter_type;

        match response.response_type {
            ResponseType::Default | ResponseType::Error(..) => unreachable!(),

            ResponseType::Basic => {
                let headers = old_headers.iter().filter(|header| {
                    match &*header.name().to_ascii_lowercase() {
                        "set-cookie" | "set-cookie2" => false,
                        _ => true
                    }
                }).collect();
                response.headers = headers;
            },

            ResponseType::CORS => {
                let access = old_headers.get::<AccessControlExposeHeaders>();
                let allowed_headers = access.as_ref().map(|v| &v[..]).unwrap_or(&[]);

                let headers = old_headers.iter().filter(|header| {
                    match &*header.name().to_ascii_lowercase() {
                        "cache-control" | "content-language" | "content-type" |
                        "expires" | "last-modified" | "pragma" => true,
                        "set-cookie" | "set-cookie2" => false,
                        header => {
                            let result =
                                allowed_headers.iter().find(|h| *header == *h.to_ascii_lowercase());
                            result.is_some()
                        }
                    }
                }).collect();
                response.headers = headers;
            },

            ResponseType::Opaque => {
                response.url_list = RefCell::new(vec![]);
                response.url = None;
                response.headers = Headers::new();
                response.status = None;
                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
                response.cache_state = CacheState::None;
            },

            ResponseType::OpaqueRedirect => {
                response.headers = Headers::new();
                response.status = None;
                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
                response.cache_state = CacheState::None;
            }
        }

        response
    }

    pub fn metadata(&self) -> Result<FetchMetadata, NetworkError> {
        fn init_metadata(response: &Response, url: &Url) -> Metadata {
            let mut metadata = Metadata::default(url.clone());
            metadata.set_content_type(match response.headers.get() {
                Some(&ContentType(ref mime)) => Some(mime),
                None => None
            });
            metadata.headers = Some(Serde(response.headers.clone()));
            metadata.status = response.raw_status.clone();
            metadata.https_state = response.https_state;
            metadata.referrer = response.referrer.clone();
            metadata
        };

        if let Some(error) = self.get_network_error() {
            return Err(error.clone());
        }

        let metadata = self.url.as_ref().map(|url| init_metadata(self, url));

        if let Some(ref response) = self.internal_response {
            match response.url {
                Some(ref url) => {
                    let unsafe_metadata = init_metadata(response, url);

                    Ok(FetchMetadata::Filtered {
                        filtered: match metadata {
                            Some(m) => FilteredMetadata::Transparent(m),
                            None => FilteredMetadata::Opaque
                        },
                        unsafe_: unsafe_metadata
                    })
                }
                None => Err(NetworkError::Internal("No url found in unsafe response".to_owned()))
            }
        } else {
            Ok(FetchMetadata::Unfiltered(metadata.unwrap()))
        }
    }
}
<commit_msg>Implement Debug for Response.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! The [Response](https://fetch.spec.whatwg.org/#responses) object
//! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch)
use {FetchMetadata, FilteredMetadata, Metadata, NetworkError};
use hyper::header::{AccessControlExposeHeaders, ContentType, Headers};
use hyper::status::StatusCode;
use hyper_serde::Serde;
use std::ascii::AsciiExt;
use std::cell::{Cell, RefCell};
use std::sync::{Arc, Mutex};
use url::Url;

/// [Response type](https://fetch.spec.whatwg.org/#concept-response-type)
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum ResponseType {
    Basic,
    CORS,
    Default,
    Error(NetworkError),
    Opaque,
    OpaqueRedirect
}

/// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason)
#[derive(Debug, Clone, Copy, Deserialize, Serialize, HeapSizeOf)]
pub enum TerminationReason {
    EndUserAbort,
    Fatal,
    Timeout
}

/// The response body can still be pushed to after fetch
/// This provides a way to store unfinished response bodies
#[derive(Clone, Debug, PartialEq, HeapSizeOf)]
pub enum ResponseBody {
    Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
    Receiving(Vec<u8>),
    Done(Vec<u8>),
}

impl ResponseBody {
    pub fn is_done(&self) -> bool {
        match *self {
            ResponseBody::Done(..) => true,
            ResponseBody::Empty | ResponseBody::Receiving(..) => false
        }
    }
}

/// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state)
#[derive(Clone, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum CacheState {
    None,
    Local,
    Validated,
    Partial
}

/// [Https state](https://fetch.spec.whatwg.org/#concept-response-https-state)
#[derive(Debug, Clone, Copy, HeapSizeOf, Deserialize, Serialize)]
pub enum HttpsState {
    None,
    Deprecated,
    Modern
}

pub enum ResponseMsg {
    Chunk(Vec<u8>),
    Finished,
    Errored
}

/// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
#[derive(Debug, Clone, HeapSizeOf)]
pub struct Response {
    pub response_type: ResponseType,
    pub termination_reason: Option<TerminationReason>,
    pub url: Option<Url>,
    pub url_list: RefCell<Vec<Url>>,
    /// `None` can be considered a StatusCode of `0`.
    #[ignore_heap_size_of = "Defined in hyper"]
    pub status: Option<StatusCode>,
    pub raw_status: Option<(u16, Vec<u8>)>,
    #[ignore_heap_size_of = "Defined in hyper"]
    pub headers: Headers,
    #[ignore_heap_size_of = "Mutex heap size undefined"]
    pub body: Arc<Mutex<ResponseBody>>,
    pub cache_state: CacheState,
    pub https_state: HttpsState,
    pub referrer: Option<Url>,
    /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response
    /// is a filtered response
    pub internal_response: Option<Box<Response>>,
    /// whether or not to try to return the internal_response when asked for actual_response
    pub return_internal: Cell<bool>,
}

impl Response {
    pub fn new() -> Response {
        Response {
            response_type: ResponseType::Default,
            termination_reason: None,
            url: None,
            url_list: RefCell::new(Vec::new()),
            status: Some(StatusCode::Ok),
            raw_status: Some((200, b"OK".to_vec())),
            headers: Headers::new(),
            body: Arc::new(Mutex::new(ResponseBody::Empty)),
            cache_state: CacheState::None,
            https_state: HttpsState::None,
            referrer: None,
            internal_response: None,
            return_internal: Cell::new(true)
        }
    }

    pub fn network_error(e: NetworkError) -> Response {
        Response {
            response_type: ResponseType::Error(e),
            termination_reason: None,
            url: None,
            url_list: RefCell::new(vec![]),
            status: None,
            raw_status: None,
            headers: Headers::new(),
            body: Arc::new(Mutex::new(ResponseBody::Empty)),
            cache_state: CacheState::None,
            https_state: HttpsState::None,
            referrer: None,
            internal_response: None,
            return_internal: Cell::new(true)
        }
    }

    pub fn is_network_error(&self) -> bool {
        match self.response_type {
            ResponseType::Error(..) => true,
            _ => false
        }
    }

    pub fn get_network_error(&self) -> Option<&NetworkError> {
        match self.response_type {
            ResponseType::Error(ref e) => Some(e),
            _ => None,
        }
    }

    pub fn actual_response(&self) -> &Response {
        if self.return_internal.get() && self.internal_response.is_some() {
            &**self.internal_response.as_ref().unwrap()
        } else {
            self
        }
    }

    pub fn to_actual(self) -> Response {
        if self.return_internal.get() && self.internal_response.is_some() {
            *self.internal_response.unwrap()
        } else {
            self
        }
    }

    /// Convert to a filtered response, of type `filter_type`.
    /// Do not use with type Error or Default
    pub fn to_filtered(self, filter_type: ResponseType) -> Response {
        match filter_type {
            ResponseType::Default | ResponseType::Error(..) => panic!(),
            _ => (),
        }

        let old_response = self.to_actual();

        if let ResponseType::Error(e) = old_response.response_type {
            return Response::network_error(e);
        }

        let old_headers = old_response.headers.clone();
        let mut response = old_response.clone();
        response.internal_response = Some(Box::new(old_response));
        response.response_type = filter_type;

        match response.response_type {
            ResponseType::Default | ResponseType::Error(..) => unreachable!(),

            ResponseType::Basic => {
                let headers = old_headers.iter().filter(|header| {
                    match &*header.name().to_ascii_lowercase() {
                        "set-cookie" | "set-cookie2" => false,
                        _ => true
                    }
                }).collect();
                response.headers = headers;
            },

            ResponseType::CORS => {
                let access = old_headers.get::<AccessControlExposeHeaders>();
                let allowed_headers = access.as_ref().map(|v| &v[..]).unwrap_or(&[]);

                let headers = old_headers.iter().filter(|header| {
                    match &*header.name().to_ascii_lowercase() {
                        "cache-control" | "content-language" | "content-type" |
                        "expires" | "last-modified" | "pragma" => true,
                        "set-cookie" | "set-cookie2" => false,
                        header => {
                            let result =
                                allowed_headers.iter().find(|h| *header == *h.to_ascii_lowercase());
                            result.is_some()
                        }
                    }
                }).collect();
                response.headers = headers;
            },

            ResponseType::Opaque => {
                response.url_list = RefCell::new(vec![]);
                response.url = None;
                response.headers = Headers::new();
                response.status = None;
                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
                response.cache_state = CacheState::None;
            },

            ResponseType::OpaqueRedirect => {
                response.headers = Headers::new();
                response.status = None;
                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
                response.cache_state = CacheState::None;
            }
        }

        response
    }

    pub fn metadata(&self) -> Result<FetchMetadata, NetworkError> {
        fn init_metadata(response: &Response, url: &Url) -> Metadata {
            let mut metadata = Metadata::default(url.clone());
            metadata.set_content_type(match response.headers.get() {
                Some(&ContentType(ref mime)) => Some(mime),
                None => None
            });
            metadata.headers = Some(Serde(response.headers.clone()));
            metadata.status = response.raw_status.clone();
            metadata.https_state = response.https_state;
            metadata.referrer = response.referrer.clone();
            metadata
        };

        if let Some(error) = self.get_network_error() {
            return Err(error.clone());
        }

        let metadata = self.url.as_ref().map(|url| init_metadata(self, url));

        if let Some(ref response) = self.internal_response {
            match response.url {
                Some(ref url) => {
                    let unsafe_metadata = init_metadata(response, url);

                    Ok(FetchMetadata::Filtered {
                        filtered: match metadata {
                            Some(m) => FilteredMetadata::Transparent(m),
                            None => FilteredMetadata::Opaque
                        },
                        unsafe_: unsafe_metadata
                    })
                }
                None => Err(NetworkError::Internal("No url found in unsafe response".to_owned()))
            }
        } else {
            Ok(FetchMetadata::Unfiltered(metadata.unwrap()))
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use kvproto::eraftpb::{Entry, Snapshot};

// unstable.entris[i] has raft log position i+unstable.offset.
// Note that unstable.offset may be less than the highest log
// position in storage; this means that the next write to storage
// might need to truncate the log before persisting unstable.entries.
#[derive(Debug, PartialEq, Default)]
pub struct Unstable {
    // the incoming unstable snapshot, if any.
    pub snapshot: Option<Snapshot>,
    // all entries that have not yet been written to storage.
    pub entries: Vec<Entry>,
    pub offset: u64,

    pub tag: String,
}

impl Unstable {
    pub fn new(offset: u64, tag: String) -> Unstable {
        Unstable {
            offset: offset,
            snapshot: None,
            entries: vec![],
            tag: tag,
        }
    }
    // maybe_first_index returns the index of the first possible entry in entries
    // if it has a snapshot.
    pub fn maybe_first_index(&self) -> Option<u64> {
        self.snapshot
            .as_ref()
            .map(|snap| snap.get_metadata().get_index() + 1)
    }

    // maybe_last_index returns the last index if it has at least one
    // unstable entry or snapshot.
    pub fn maybe_last_index(&self) -> Option<u64> {
        match self.entries.len() {
            0 => {
                self.snapshot
                    .as_ref()
                    .map(|snap| snap.get_metadata().get_index())
            }
            len => Some(self.offset + len as u64 - 1),
        }
    }

    // maybe_term returns the term of the entry at index idx, if there
    // is any.
    pub fn maybe_term(&self, idx: u64) -> Option<u64> {
        if idx < self.offset {
            if self.snapshot.is_none() {
                return None;
            }

            let meta = self.snapshot.as_ref().unwrap().get_metadata();
            if idx == meta.get_index() {
                return Some(meta.get_term());
            }
            return None;
        }
        self.maybe_last_index().and_then(|last| {
            if idx > last {
                return None;
            }
            Some(self.entries[(idx - self.offset) as usize].get_term())
        })
    }

    pub fn stable_to(&mut self, idx: u64, term: u64) {
        let t = self.maybe_term(idx);
        if t.is_none() {
            return;
        }

        if t.unwrap() == term && idx >= self.offset {
            let start = idx + 1 - self.offset;
            self.entries.drain(..start as usize);
            self.offset = idx + 1;
        }
    }

    pub fn stable_snap_to(&mut self, idx: u64) {
        if self.snapshot.is_none() {
            return;
        }
        if idx == self.snapshot.as_ref().unwrap().get_metadata().get_index() {
            self.snapshot = None;
        }
    }

    pub fn restore(&mut self, snap: Snapshot) {
        self.entries.clear();
        self.offset = snap.get_metadata().get_index() + 1;
        self.snapshot = Some(snap);
    }

    // append entries to unstable, truncate local block first if overlapped.
    pub fn truncate_and_append(&mut self, ents: &[Entry]) {
        let after = ents[0].get_index();
        if after == self.offset + self.entries.len() as u64 {
            // after is the next index in the self.entries, append directly
            self.entries.extend_from_slice(ents);
        } else if after <= self.offset {
            // The log is being truncated to before our current offset
            // portion, so set the offset and replace the entries
            self.offset = after;
            self.entries.clear();
            self.entries.extend_from_slice(ents);
        } else {
            // truncate to after and copy to self.entries then append
            let off = self.offset;
            self.must_check_outofbounds(off, after);
            self.entries.truncate((after - off) as usize);
            self.entries.extend_from_slice(ents);
        }
    }

    pub fn slice(&self, lo: u64, hi: u64) -> &[Entry] {
        self.must_check_outofbounds(lo, hi);
        let l = lo as usize;
        let h = hi as usize;
        let off = self.offset as usize;
        &self.entries[l - off..h - off]
    }

    pub fn must_check_outofbounds(&self, lo: u64, hi: u64) {
        if lo > hi {
            panic!("{} invalid unstable.slice {} > {}", self.tag, lo, hi)
        }
        let upper = self.offset + self.entries.len() as u64;
        if lo < self.offset || hi > upper {
            panic!("{} unstable.slice[{}, {}] out of bound[{}, {}]",
                   self.tag,
                   lo,
                   hi,
                   self.offset,
                   upper)
        }
    }
}

#[cfg(test)]
mod test {
    use kvproto::eraftpb::{Entry, Snapshot};
    use raft::log_unstable::Unstable;

    fn new_entry(index: u64, term: u64) -> Entry {
        let mut e = Entry::new();
        e.set_term(term);
        e.set_index(index);
        e
    }

    fn new_snapshot(index: u64, term: u64) -> Snapshot {
        use kvproto::eraftpb::{SnapshotMetadata, Snapshot};
        let mut snap = Snapshot::new();
        let mut meta = SnapshotMetadata::new();
        meta.set_index(index);
        meta.set_term(term);
        snap.set_metadata(meta);
        snap
    }

    #[test]
    fn test_maybe_first_index() {
        // entry, offset, snap, wok, windex,
        let tests = vec![
            // no snapshot
            (Some(new_entry(5,1)), 5, None, false, 0),
            (None, 0, None, false, 0),
            // has snapshot
            (Some(new_entry(5, 1)), 5, Some(new_snapshot(4,1)), true, 5),
            (None, 5, Some(new_snapshot(4,1)), true, 5),
        ];

        for (entries, offset, snapshot, wok, windex) in tests {
            let u = Unstable {
                entries: entries.map_or(vec![], |entry| vec![entry]),
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            let index = u.maybe_first_index();
            match index {
                None => assert!(!wok),
                Some(index) => assert_eq!(index, windex),
            }
        }
    }

    #[test]
    fn test_maybe_last_index() {
        // entry, offset, snap, wok, windex,
        let tests = vec![
            (Some(new_entry(5,1)), 5, None, true, 5),
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), true, 5),
            // last in snapshot
            (None, 5, Some(new_snapshot(4,1)), true, 4),
            // empty unstable
            (None, 0, None, false, 0),
       ];

        for (entries, offset, snapshot, wok, windex) in tests {
            let u = Unstable {
                entries: entries.map_or(vec![], |entry| vec![entry]),
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            let index = u.maybe_last_index();
            match index {
                None => assert!(!wok),
                Some(index) => assert_eq!(index, windex),
            }
        }
    }

    #[test]
    fn test_maybe_term() {
        // entry, offset, snap, index, wok, wterm
        let tests = vec![
            // term from entries
            (Some(new_entry(5,1)), 5, None, 5, true, 1),
            (Some(new_entry(5,1)), 5, None, 6, false, 0),
            (Some(new_entry(5,1)), 5, None, 4, false, 0),
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), 5, true, 1),
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), 6, false, 0),
            // term from snapshot
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), 4, true, 1),
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), 3, false, 0),
            (None, 5, Some(new_snapshot(4,1)), 5, false, 0),
            (None, 5, Some(new_snapshot(4,1)), 4, true, 1),
            (None, 0, None, 5, false, 0),
       ];

        for (entries, offset, snapshot, index, wok, wterm) in tests {
            let u = Unstable {
                entries: entries.map_or(vec![], |entry| vec![entry]),
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            let term = u.maybe_term(index);
            match term {
                None => assert!(!wok),
                Some(term) => assert_eq!(term, wterm),
            }
        }
    }

    #[test]
    fn test_restore() {
        let mut u = Unstable {
            entries: vec![new_entry(5, 1)],
            offset: 5,
            snapshot: Some(new_snapshot(4, 1)),
            ..Default::default()
        };

        let s = new_snapshot(6, 2);
        u.restore(s.clone());

        assert_eq!(u.offset, s.get_metadata().get_index() + 1);
        assert!(u.entries.is_empty());
        assert_eq!(u.snapshot.unwrap(), s);
    }

    #[test]
    fn test_stable_to() {
        // entries, offset, snap, index, term, woffset, wlen
        let tests = vec![
            (vec![], 0, None, 5, 1, 0, 0),
            // stable to the first entry
            (vec![new_entry(5,1)], 5, None, 5, 1, 6, 0),
            (vec![new_entry(5,1), new_entry(6, 0)], 5, None, 5, 1, 6, 1),
            // stable to the first entry and term mismatch
            (vec![new_entry(6,2)], 5, None, 6, 1, 5, 1),
            // stable to old entry
            (vec![new_entry(5,1)], 5, None, 4, 1, 5, 1),
            (vec![new_entry(5,1)], 5, None, 4, 2, 5, 1),
            // with snapshot
            // stable to the first entry
            (vec![new_entry(5,1)], 5, Some(new_snapshot(4,1)), 5, 1, 6, 0),
            // stable to the first entry
            (vec![new_entry(5,1), new_entry(6,1)], 5, Some(new_snapshot(4,1)), 5, 1, 6, 1),
            // stable to the first entry and term mismatch
            (vec![new_entry(6,2)], 5, Some(new_snapshot(5,1)), 6, 1, 5, 1),
            // stable to snapshot
            (vec![new_entry(5,1)], 5, Some(new_snapshot(4,1)), 4, 1, 5, 1),
            // stable to old entry
            (vec![new_entry(5,2)], 5, Some(new_snapshot(4,2)), 4, 1, 5, 1),
       ];

        for (entries, offset, snapshot, index, term, woffset, wlen) in tests {
            let mut u = Unstable {
                entries: entries,
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            u.stable_to(index, term);
            assert_eq!(u.offset, woffset);
            assert_eq!(u.entries.len(), wlen);
        }
    }

    #[test]
    fn test_truncate_and_append() {
        // entries, offset, snap, to_append, woffset, wentries
        let tests = vec![
            // replace to the end
            (vec![new_entry(5, 1)], 5, None,
                vec![new_entry(6, 1), new_entry(7, 1)],
                    5, vec![new_entry(5,1), new_entry(6,1), new_entry(7,1)]),

            // replace to unstable entries
           (vec![new_entry(5, 1)], 5, None,
               vec![new_entry(5, 2), new_entry(6, 2)],
                   5, vec![new_entry(5, 2), new_entry(6, 2)]),

            (vec![new_entry(5, 1)], 5, None,
                vec![new_entry(4, 2), new_entry(5, 2), new_entry(6, 2)],
                    4, vec![new_entry(4,2), new_entry(5, 2), new_entry(6, 2)]),

            // truncate existing entries and append
           (vec![new_entry(5, 1), new_entry(6, 1), new_entry(7, 1)], 5, None,
               vec![new_entry(6, 2)],
                   5, vec![new_entry(5, 1), new_entry(6, 2)]),

            (vec![new_entry(5, 1), new_entry(6, 1), new_entry(7, 1)], 5, None,
                vec![new_entry(7, 2), new_entry(8, 2)],
                    5, vec![new_entry(5, 1), new_entry(6, 1), new_entry(7, 2), new_entry(8, 2)]),
      ];

        for (entries, offset, snapshot, to_append, woffset, wentries) in tests {
            let mut u = Unstable {
                entries: entries,
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            u.truncate_and_append(&to_append);
            assert_eq!(u.offset, woffset);
            assert_eq!(u.entries, wentries);
        }
    }
}
<commit_msg>Fix some typos (#1980)<commit_after>// Copyright 2016 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use kvproto::eraftpb::{Entry, Snapshot};

// unstable.entries[i] has raft log position i+unstable.offset.
// Note that unstable.offset may be less than the highest log
// position in storage; this means that the next write to storage
// might need to truncate the log before persisting unstable.entries.
#[derive(Debug, PartialEq, Default)]
pub struct Unstable {
    // the incoming unstable snapshot, if any.
    pub snapshot: Option<Snapshot>,
    // all entries that have not yet been written to storage.
    pub entries: Vec<Entry>,
    pub offset: u64,

    pub tag: String,
}

impl Unstable {
    pub fn new(offset: u64, tag: String) -> Unstable {
        Unstable {
            offset: offset,
            snapshot: None,
            entries: vec![],
            tag: tag,
        }
    }
    // maybe_first_index returns the index of the first possible entry in entries
    // if it has a snapshot.
    pub fn maybe_first_index(&self) -> Option<u64> {
        self.snapshot
            .as_ref()
            .map(|snap| snap.get_metadata().get_index() + 1)
    }

    // maybe_last_index returns the last index if it has at least one
    // unstable entry or snapshot.
    pub fn maybe_last_index(&self) -> Option<u64> {
        match self.entries.len() {
            0 => {
                self.snapshot
                    .as_ref()
                    .map(|snap| snap.get_metadata().get_index())
            }
            len => Some(self.offset + len as u64 - 1),
        }
    }

    // maybe_term returns the term of the entry at index idx, if there
    // is any.
    pub fn maybe_term(&self, idx: u64) -> Option<u64> {
        if idx < self.offset {
            if self.snapshot.is_none() {
                return None;
            }

            let meta = self.snapshot.as_ref().unwrap().get_metadata();
            if idx == meta.get_index() {
                return Some(meta.get_term());
            }
            return None;
        }
        self.maybe_last_index().and_then(|last| {
            if idx > last {
                return None;
            }
            Some(self.entries[(idx - self.offset) as usize].get_term())
        })
    }

    pub fn stable_to(&mut self, idx: u64, term: u64) {
        let t = self.maybe_term(idx);
        if t.is_none() {
            return;
        }

        if t.unwrap() == term && idx >= self.offset {
            let start = idx + 1 - self.offset;
            self.entries.drain(..start as usize);
            self.offset = idx + 1;
        }
    }

    pub fn stable_snap_to(&mut self, idx: u64) {
        if self.snapshot.is_none() {
            return;
        }
        if idx == self.snapshot.as_ref().unwrap().get_metadata().get_index() {
            self.snapshot = None;
        }
    }

    pub fn restore(&mut self, snap: Snapshot) {
        self.entries.clear();
        self.offset = snap.get_metadata().get_index() + 1;
        self.snapshot = Some(snap);
    }

    // append entries to unstable, truncate local block first if overlapped.
    pub fn truncate_and_append(&mut self, ents: &[Entry]) {
        let after = ents[0].get_index();
        if after == self.offset + self.entries.len() as u64 {
            // after is the next index in the self.entries, append directly
            self.entries.extend_from_slice(ents);
        } else if after <= self.offset {
            // The log is being truncated to before our current offset
            // portion, so set the offset and replace the entries
            self.offset = after;
            self.entries.clear();
            self.entries.extend_from_slice(ents);
        } else {
            // truncate to after and copy to self.entries then append
            let off = self.offset;
            self.must_check_outofbounds(off, after);
            self.entries.truncate((after - off) as usize);
            self.entries.extend_from_slice(ents);
        }
    }

    pub fn slice(&self, lo: u64, hi: u64) -> &[Entry] {
        self.must_check_outofbounds(lo, hi);
        let l = lo as usize;
        let h = hi as usize;
        let off = self.offset as usize;
        &self.entries[l - off..h - off]
    }

    pub fn must_check_outofbounds(&self, lo: u64, hi: u64) {
        if lo > hi {
            panic!("{} invalid unstable.slice {} > {}", self.tag, lo, hi)
        }
        let upper = self.offset + self.entries.len() as u64;
        if lo < self.offset || hi > upper {
            panic!("{} unstable.slice[{}, {}] out of bound[{}, {}]",
                   self.tag,
                   lo,
                   hi,
                   self.offset,
                   upper)
        }
    }
}

#[cfg(test)]
mod test {
    use kvproto::eraftpb::{Entry, Snapshot};
    use raft::log_unstable::Unstable;

    fn new_entry(index: u64, term: u64) -> Entry {
        let mut e = Entry::new();
        e.set_term(term);
        e.set_index(index);
        e
    }

    fn new_snapshot(index: u64, term: u64) -> Snapshot {
        use kvproto::eraftpb::{SnapshotMetadata, Snapshot};
        let mut snap = Snapshot::new();
        let mut meta = SnapshotMetadata::new();
        meta.set_index(index);
        meta.set_term(term);
        snap.set_metadata(meta);
        snap
    }

    #[test]
    fn test_maybe_first_index() {
        // entry, offset, snap, wok, windex,
        let tests = vec![
            // no snapshot
            (Some(new_entry(5,1)), 5, None, false, 0),
            (None, 0, None, false, 0),
            // has snapshot
            (Some(new_entry(5, 1)), 5, Some(new_snapshot(4,1)), true, 5),
            (None, 5, Some(new_snapshot(4,1)), true, 5),
        ];

        for (entries, offset, snapshot, wok, windex) in tests {
            let u = Unstable {
                entries: entries.map_or(vec![], |entry| vec![entry]),
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            let index = u.maybe_first_index();
            match index {
                None => assert!(!wok),
                Some(index) => assert_eq!(index, windex),
            }
        }
    }

    #[test]
    fn test_maybe_last_index() {
        // entry, offset, snap, wok, windex,
        let tests = vec![
            (Some(new_entry(5,1)), 5, None, true, 5),
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), true, 5),
            // last in snapshot
            (None, 5, Some(new_snapshot(4,1)), true, 4),
            // empty unstable
            (None, 0, None, false, 0),
       ];

        for (entries, offset, snapshot, wok, windex) in tests {
            let u = Unstable {
                entries: entries.map_or(vec![], |entry| vec![entry]),
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            let index = u.maybe_last_index();
            match index {
                None => assert!(!wok),
                Some(index) => assert_eq!(index, windex),
            }
        }
    }

    #[test]
    fn test_maybe_term() {
        // entry, offset, snap, index, wok, wterm
        let tests = vec![
            // term from entries
            (Some(new_entry(5,1)), 5, None, 5, true, 1),
            (Some(new_entry(5,1)), 5, None, 6, false, 0),
            (Some(new_entry(5,1)), 5, None, 4, false, 0),
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), 5, true, 1),
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), 6, false, 0),
            // term from snapshot
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), 4, true, 1),
            (Some(new_entry(5,1)), 5, Some(new_snapshot(4,1)), 3, false, 0),
            (None, 5, Some(new_snapshot(4,1)), 5, false, 0),
            (None, 5, Some(new_snapshot(4,1)), 4, true, 1),
            (None, 0, None, 5, false, 0),
       ];

        for (entries, offset, snapshot, index, wok, wterm) in tests {
            let u = Unstable {
                entries: entries.map_or(vec![], |entry| vec![entry]),
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            let term = u.maybe_term(index);
            match term {
                None => assert!(!wok),
                Some(term) => assert_eq!(term, wterm),
            }
        }
    }

    #[test]
    fn test_restore() {
        let mut u = Unstable {
            entries: vec![new_entry(5, 1)],
            offset: 5,
            snapshot: Some(new_snapshot(4, 1)),
            ..Default::default()
        };

        let s = new_snapshot(6, 2);
        u.restore(s.clone());

        assert_eq!(u.offset, s.get_metadata().get_index() + 1);
        assert!(u.entries.is_empty());
        assert_eq!(u.snapshot.unwrap(), s);
    }

    #[test]
    fn test_stable_to() {
        // entries, offset, snap, index, term, woffset, wlen
        let tests = vec![
            (vec![], 0, None, 5, 1, 0, 0),
            // stable to the first entry
            (vec![new_entry(5,1)], 5, None, 5, 1, 6, 0),
            (vec![new_entry(5,1), new_entry(6, 0)], 5, None, 5, 1, 6, 1),
            // stable to the first entry and term mismatch
            (vec![new_entry(6,2)], 5, None, 6, 1, 5, 1),
            // stable to old entry
            (vec![new_entry(5,1)], 5, None, 4, 1, 5, 1),
            (vec![new_entry(5,1)], 5, None, 4, 2, 5, 1),
            // with snapshot
            // stable to the first entry
            (vec![new_entry(5,1)], 5, Some(new_snapshot(4,1)), 5, 1, 6, 0),
            // stable to the first entry
            (vec![new_entry(5,1), new_entry(6,1)], 5, Some(new_snapshot(4,1)), 5, 1, 6, 1),
            // stable to the first entry and term mismatch
            (vec![new_entry(6,2)], 5, Some(new_snapshot(5,1)), 6, 1, 5, 1),
            // stable to snapshot
            (vec![new_entry(5,1)], 5, Some(new_snapshot(4,1)), 4, 1, 5, 1),
            // stable to old entry
            (vec![new_entry(5,2)], 5, Some(new_snapshot(4,2)), 4, 1, 5, 1),
       ];

        for (entries, offset, snapshot, index, term, woffset, wlen) in tests {
            let mut u = Unstable {
                entries: entries,
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            u.stable_to(index, term);
            assert_eq!(u.offset, woffset);
            assert_eq!(u.entries.len(), wlen);
        }
    }

    #[test]
    fn test_truncate_and_append() {
        // entries, offset, snap, to_append, woffset, wentries
        let tests = vec![
            // replace to the end
            (vec![new_entry(5, 1)], 5, None,
                vec![new_entry(6, 1), new_entry(7, 1)],
                    5, vec![new_entry(5,1), new_entry(6,1), new_entry(7,1)]),

            // replace to unstable entries
           (vec![new_entry(5, 1)], 5, None,
               vec![new_entry(5, 2), new_entry(6, 2)],
                   5, vec![new_entry(5, 2), new_entry(6, 2)]),

            (vec![new_entry(5, 1)], 5, None,
                vec![new_entry(4, 2), new_entry(5, 2), new_entry(6, 2)],
                    4, vec![new_entry(4,2), new_entry(5, 2), new_entry(6, 2)]),

            // truncate existing entries and append
           (vec![new_entry(5, 1), new_entry(6, 1), new_entry(7, 1)], 5, None,
               vec![new_entry(6, 2)],
                   5, vec![new_entry(5, 1), new_entry(6, 2)]),

            (vec![new_entry(5, 1), new_entry(6, 1), new_entry(7, 1)], 5, None,
                vec![new_entry(7, 2), new_entry(8, 2)],
                    5, vec![new_entry(5, 1), new_entry(6, 1), new_entry(7, 2), new_entry(8, 2)]),
      ];

        for (entries, offset, snapshot, to_append, woffset, wentries) in tests {
            let mut u = Unstable {
                entries: entries,
                offset: offset,
                snapshot: snapshot,
                ..Default::default()
            };
            u.truncate_and_append(&to_append);
            assert_eq!(u.offset, woffset);
            assert_eq!(u.entries, wentries);
        }
    }
}
<|endoftext|>


<commit_before>use std::{cell::RefCell, rc::Rc, sync::mpsc};

use derive_more::Constructor;

use super::{KeyState, MouseState, WindowState, CONSOLE};
use crate::{
    event::{ButtonState, KeyEvent, MouseButton, MouseEvent},
    render::RenderContext2D,
    window_adapter::WindowAdapter,
    WindowRequest,
};

/// Represents a wrapper for a minifb window. It handles events, propagate them to
/// the window adapter and handles the update and render pipeline.
#[derive(Constructor)]
pub struct Window
where
    A: WindowAdapter,
{
    window: minifb::Window,
    adapter: A,
    render_context: RenderContext2D,
    request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    window_state: WindowState,
    mouse: MouseState,
    update: bool,
    redraw: bool,
    close: bool,
    key_states: Vec<KeyState>,
    key_events: Rc<RefCell<Vec<KeyEvent>>>,
}

impl Window
where
    A: WindowAdapter,
{
    fn push_mouse_event(&mut self, pressed: bool, button: MouseButton) {
        let state = if pressed {
            ButtonState::Down
        } else {
            ButtonState::Up
        };

        self.adapter.mouse_event(MouseEvent {
            x: self.mouse.mouse_pos.0 as f64,
            y: self.mouse.mouse_pos.1 as f64,
            button,
            state,
        });
    }

    fn push_key_down_event(&mut self, index: usize) {
        let key_repeat = match self.key_states.get(index).unwrap().minifb_key {
            minifb::Key::Left
            | minifb::Key::Right
            | minifb::Key::Up
            | minifb::Key::Down
            | minifb::Key::Backspace
            | minifb::Key::Delete => minifb::KeyRepeat::Yes,
            _ => minifb::KeyRepeat::No,
        };

        if self
            .window
            .is_key_pressed(self.key_states.get(index).unwrap().minifb_key, key_repeat)
        {
            self.adapter.key_event(KeyEvent {
                key: self.key_states.get(index).unwrap().key,
                state: ButtonState::Down,
                text: String::default(),
            });
        }
    }

    fn push_key_up_event(&mut self, index: usize) {
        if self
            .window
            .is_key_released(self.key_states.get(index).unwrap().minifb_key)
        {
            self.adapter.key_event(KeyEvent {
                key: self.key_states.get(index).unwrap().key,
                state: ButtonState::Up,
                text: String::default(),
            });
        }
    }

    /// Check if the window is open.
    pub fn is_open(&self) -> bool {
        self.window.is_open() && !self.close
    }

    /// Drain events and propagate the events to the adapter.
    pub fn drain_events(&mut self) {
        self.window.update();

        // mouse move
        if let Some(pos) = self.window.get_mouse_pos(minifb::MouseMode::Discard) {
            if (pos.0.floor(), pos.1.floor()) != self.mouse.mouse_pos {
                self.adapter.mouse(pos.0 as f64, pos.1 as f64);
                self.mouse.mouse_pos = (pos.0.floor(), pos.1.floor());
                self.update = true;
            }
        }

        // mouse buttons
        let left_button_down = self.window.get_mouse_down(minifb::MouseButton::Left);
        let middle_button_down = self.window.get_mouse_down(minifb::MouseButton::Middle);
        let right_button_down = self.window.get_mouse_down(minifb::MouseButton::Right);

        if left_button_down != self.mouse.button_left {
            if left_button_down {
                self.push_mouse_event(true, MouseButton::Left);
            } else {
                self.push_mouse_event(false, MouseButton::Left);
            }
            self.mouse.button_left = left_button_down;
            self.update = true;
        }

        if middle_button_down != self.mouse.button_middle {
            if middle_button_down {
                self.push_mouse_event(true, MouseButton::Middle);
            } else {
                self.push_mouse_event(false, MouseButton::Middle);
            }
            self.mouse.button_middle = middle_button_down;
            self.update = true;
        }

        if right_button_down != self.mouse.button_right {
            if right_button_down {
                self.push_mouse_event(true, MouseButton::Right);
            } else {
                self.push_mouse_event(false, MouseButton::Right);
            }
            self.mouse.button_right = right_button_down;
            self.update = true;
        }

        // scroll
        if let Some(delta) = self.window.get_scroll_wheel() {
            self.adapter.scroll(delta.0 as f64, delta.1 as f64);
            self.update = true;
        }

        // resize
        if self.window_state.size != self.window.get_size() {
            self.window_state.size = self.window.get_size();
            self.render_context.resize(
                self.window_state.size.0 as f64,
                self.window_state.size.1 as f64,
            );
            self.adapter.resize(
                self.window_state.size.0 as f64,
                self.window_state.size.1 as f64,
            );
            self.update = true;
        }

        if self.window_state.active != self.window.is_active() {
            self.adapter.active(self.window.is_active());
            self.window_state.active = self.window.is_active();
        }

        // keys
        while let Some(event) = self.key_events.borrow_mut().pop() {
            self.adapter.key_event(event);
        }

        for i in 0..self.key_states.len() {
            self.push_key_down_event(i);
            self.push_key_up_event(i);
        }
    }

    /// Receives window request from the application and handles them.
    pub fn receive_requests(&mut self) {
        if let Some(request_receiver) = &self.request_receiver {
            for request in request_receiver.try_iter() {
                match request {
                    WindowRequest::Redraw => {
                        self.update = true;
                        self.redraw = true;
                    }
                    WindowRequest::ChangeTitle(title) => {
                        self.window.set_title(&title);
                        self.update = true;
                        self.redraw = true;
                    }
                    WindowRequest::Close => {
                        self.close = true;
                    }
                }
            }
        }
    }

    /// Runs update on the adapter.
    pub fn update(&mut self) {
        if !self.update {
            return;
        }
        self.adapter.run(&mut self.render_context);
        self.update = false;
        self.redraw = true;
    }

    /// Swaps the current frame buffer.
    pub fn render(&mut self) {
        if self.redraw {
            if let Some(data) = self.render_context.data() {
                let _ = self.window.update_with_buffer(
                    data,
                    self.window_state.size.0 as usize,
                    self.window_state.size.1 as usize,
                );
                CONSOLE.time_end("render");
                self.redraw = false;
            }
        }
    }
}
<commit_msg>Fix minifb text input.<commit_after>use std::{cell::RefCell, rc::Rc, sync::mpsc};

use derive_more::Constructor;

use super::{KeyState, MouseState, WindowState, CONSOLE};
use crate::{
    event::{ButtonState, KeyEvent, MouseButton, MouseEvent},
    render::RenderContext2D,
    window_adapter::WindowAdapter,
    WindowRequest,
};

/// Represents a wrapper for a minifb window. It handles events, propagate them to
/// the window adapter and handles the update and render pipeline.
#[derive(Constructor)]
pub struct Window
where
    A: WindowAdapter,
{
    window: minifb::Window,
    adapter: A,
    render_context: RenderContext2D,
    request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    window_state: WindowState,
    mouse: MouseState,
    update: bool,
    redraw: bool,
    close: bool,
    key_states: Vec<KeyState>,
    key_events: Rc<RefCell<Vec<KeyEvent>>>,
}

impl Window
where
    A: WindowAdapter,
{
    fn push_mouse_event(&mut self, pressed: bool, button: MouseButton) {
        let state = if pressed {
            ButtonState::Down
        } else {
            ButtonState::Up
        };

        self.adapter.mouse_event(MouseEvent {
            x: self.mouse.mouse_pos.0 as f64,
            y: self.mouse.mouse_pos.1 as f64,
            button,
            state,
        });
    }

    fn push_key_down_event(&mut self, index: usize) {
        let key_repeat = match self.key_states.get(index).unwrap().minifb_key {
            minifb::Key::Left
            | minifb::Key::Right
            | minifb::Key::Up
            | minifb::Key::Down
            | minifb::Key::Backspace
            | minifb::Key::Delete => minifb::KeyRepeat::Yes,
            _ => minifb::KeyRepeat::No,
        };

        if self
            .window
            .is_key_pressed(self.key_states.get(index).unwrap().minifb_key, key_repeat)
        {
            self.adapter.key_event(KeyEvent {
                key: self.key_states.get(index).unwrap().key,
                state: ButtonState::Down,
                text: String::default(),
            });

            self.update = true;
        }
    }

    fn push_key_up_event(&mut self, index: usize) {
        if self
            .window
            .is_key_released(self.key_states.get(index).unwrap().minifb_key)
        {
            self.adapter.key_event(KeyEvent {
                key: self.key_states.get(index).unwrap().key,
                state: ButtonState::Up,
                text: String::default(),
            });

            self.update = true;
        }
    }

    /// Check if the window is open.
    pub fn is_open(&self) -> bool {
        self.window.is_open() && !self.close
    }

    /// Drain events and propagate the events to the adapter.
    pub fn drain_events(&mut self) {
        self.window.update();

        // mouse move
        if let Some(pos) = self.window.get_mouse_pos(minifb::MouseMode::Discard) {
            if (pos.0.floor(), pos.1.floor()) != self.mouse.mouse_pos {
                self.adapter.mouse(pos.0 as f64, pos.1 as f64);
                self.mouse.mouse_pos = (pos.0.floor(), pos.1.floor());
                self.update = true;
            }
        }

        // mouse buttons
        let left_button_down = self.window.get_mouse_down(minifb::MouseButton::Left);
        let middle_button_down = self.window.get_mouse_down(minifb::MouseButton::Middle);
        let right_button_down = self.window.get_mouse_down(minifb::MouseButton::Right);

        if left_button_down != self.mouse.button_left {
            if left_button_down {
                self.push_mouse_event(true, MouseButton::Left);
            } else {
                self.push_mouse_event(false, MouseButton::Left);
            }
            self.mouse.button_left = left_button_down;
            self.update = true;
        }

        if middle_button_down != self.mouse.button_middle {
            if middle_button_down {
                self.push_mouse_event(true, MouseButton::Middle);
            } else {
                self.push_mouse_event(false, MouseButton::Middle);
            }
            self.mouse.button_middle = middle_button_down;
            self.update = true;
        }

        if right_button_down != self.mouse.button_right {
            if right_button_down {
                self.push_mouse_event(true, MouseButton::Right);
            } else {
                self.push_mouse_event(false, MouseButton::Right);
            }
            self.mouse.button_right = right_button_down;
            self.update = true;
        }

        // scroll
        if let Some(delta) = self.window.get_scroll_wheel() {
            self.adapter.scroll(delta.0 as f64, delta.1 as f64);
            self.update = true;
        }

        // resize
        if self.window_state.size != self.window.get_size() {
            self.window_state.size = self.window.get_size();
            self.render_context.resize(
                self.window_state.size.0 as f64,
                self.window_state.size.1 as f64,
            );
            self.adapter.resize(
                self.window_state.size.0 as f64,
                self.window_state.size.1 as f64,
            );
            self.update = true;
        }

        if self.window_state.active != self.window.is_active() {
            self.adapter.active(self.window.is_active());
            self.window_state.active = self.window.is_active();
        }

        // keys
        while let Some(event) = self.key_events.borrow_mut().pop() {
            self.adapter.key_event(event);
            self.update = true;
        }

        for i in 0..self.key_states.len() {
            self.push_key_down_event(i);
            self.push_key_up_event(i);
        }
    }

    /// Receives window request from the application and handles them.
    pub fn receive_requests(&mut self) {
        if let Some(request_receiver) = &self.request_receiver {
            for request in request_receiver.try_iter() {
                match request {
                    WindowRequest::Redraw => {
                        self.update = true;
                        self.redraw = true;
                    }
                    WindowRequest::ChangeTitle(title) => {
                        self.window.set_title(&title);
                        self.update = true;
                        self.redraw = true;
                    }
                    WindowRequest::Close => {
                        self.close = true;
                    }
                }
            }
        }
    }

    /// Runs update on the adapter.
    pub fn update(&mut self) {
        if !self.update {
            return;
        }
        self.adapter.run(&mut self.render_context);
        self.update = false;
        self.redraw = true;
    }

    /// Swaps the current frame buffer.
    pub fn render(&mut self) {
        if self.redraw {
            if let Some(data) = self.render_context.data() {
                let _ = self.window.update_with_buffer(
                    data,
                    self.window_state.size.0 as usize,
                    self.window_state.size.1 as usize,
                );
                CONSOLE.time_end("render");
                self.redraw = false;
            }
        }
    }
}
<|endoftext|>


<commit_before>//! Applies changes to the IDE state transactionally.

use std::{fmt, sync::Arc};

use base_db::{
    salsa::{Database, Durability, SweepStrategy},
    Change, FileId, SourceRootId,
};
use profile::{memory_usage, Bytes};
use rustc_hash::FxHashSet;

use crate::{symbol_index::SymbolsDatabase, RootDatabase};

#[derive(Debug)]
struct AddFile {
    file_id: FileId,
    path: String,
    text: Arc<String>,
}

#[derive(Debug)]
struct RemoveFile {
    file_id: FileId,
    path: String,
}

#[derive(Default)]
struct RootChange {
    added: Vec<AddFile>,
    removed: Vec<RemoveFile>,
}

impl fmt::Debug for RootChange {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("RootChange")
            .field("added", &self.added.len())
            .field("removed", &self.removed.len())
            .finish()
    }
}

impl RootDatabase {
    pub fn request_cancellation(&mut self) {
        let _p = profile::span("RootDatabase::request_cancellation");
        self.salsa_runtime_mut().synthetic_write(Durability::LOW);
    }

    pub fn apply_change(&mut self, change: Change) {
        let _p = profile::span("RootDatabase::apply_change");
        self.request_cancellation();
        log::info!("apply_change {:?}", change);
        if let Some(roots) = &change.roots {
            let mut local_roots = FxHashSet::default();
            let mut library_roots = FxHashSet::default();
            for (idx, root) in roots.iter().enumerate() {
                let root_id = SourceRootId(idx as u32);
                if root.is_library {
                    library_roots.insert(root_id);
                } else {
                    local_roots.insert(root_id);
                }
            }
            self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
            self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH);
        }
        change.apply(self);
    }

    pub fn collect_garbage(&mut self) {
        if cfg!(target_arch = "wasm32") {
            return;
        }

        let _p = profile::span("RootDatabase::collect_garbage");

        let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();

        base_db::ParseQuery.in_db(self).sweep(sweep);
        hir::db::ParseMacroExpansionQuery.in_db(self).sweep(sweep);

        // Macros do take significant space, but less then the syntax trees
        // self.query(hir::db::MacroDefQuery).sweep(sweep);
        // self.query(hir::db::MacroArgTextQuery).sweep(sweep);
        // self.query(hir::db::MacroExpandQuery).sweep(sweep);

        hir::db::AstIdMapQuery.in_db(self).sweep(sweep);

        hir::db::BodyWithSourceMapQuery.in_db(self).sweep(sweep);

        hir::db::ExprScopesQuery.in_db(self).sweep(sweep);
        hir::db::InferQueryQuery.in_db(self).sweep(sweep);
        hir::db::BodyQuery.in_db(self).sweep(sweep);
    }

    // Feature: Memory Usage
    //
    // Clears rust-analyzer's internal database and prints memory usage statistics.
    //
    // |===
    // | Editor  | Action Name
    //
    // | VS Code | **Rust Analyzer: Memory Usage (Clears Database)**
    // |===
    // image::https://user-images.githubusercontent.com/48062697/113065592-08559f00-91b1-11eb-8c96-64b88068ec02.gif[]
    pub fn per_query_memory_usage(&mut self) -> Vec<(String, Bytes)> {
        let mut acc: Vec<(String, Bytes)> = vec![];
        let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();
        macro_rules! sweep_each_query {
            ($($q:path)*) => {$(
                let before = memory_usage().allocated;
                $q.in_db(self).sweep(sweep);
                let after = memory_usage().allocated;
                let q: $q = Default::default();
                let name = format!("{:?}", q);
                acc.push((name, before - after));

                let before = memory_usage().allocated;
                $q.in_db(self).sweep(sweep.discard_everything());
                let after = memory_usage().allocated;
                let q: $q = Default::default();
                let name = format!("{:?} (deps)", q);
                acc.push((name, before - after));

                let before = memory_usage().allocated;
                $q.in_db(self).purge();
                let after = memory_usage().allocated;
                let q: $q = Default::default();
                let name = format!("{:?} (purge)", q);
                acc.push((name, before - after));
            )*}
        }
        sweep_each_query![
            // SourceDatabase
            base_db::ParseQuery
            base_db::CrateGraphQuery

            // SourceDatabaseExt
            base_db::FileTextQuery
            base_db::FileSourceRootQuery
            base_db::SourceRootQuery
            base_db::SourceRootCratesQuery

            // AstDatabase
            hir::db::AstIdMapQuery
            hir::db::MacroArgTextQuery
            hir::db::MacroDefQuery
            hir::db::ParseMacroExpansionQuery
            hir::db::MacroExpandQuery
            hir::db::HygieneFrameQuery

            // DefDatabase
            hir::db::FileItemTreeQuery
            hir::db::BlockDefMapQuery
            hir::db::CrateDefMapQueryQuery
            hir::db::FieldsAttrsQuery
            hir::db::VariantsAttrsQuery
            hir::db::FieldsAttrsSourceMapQuery
            hir::db::VariantsAttrsSourceMapQuery
            hir::db::StructDataQuery
            hir::db::UnionDataQuery
            hir::db::EnumDataQuery
            hir::db::ImplDataQuery
            hir::db::TraitDataQuery
            hir::db::TypeAliasDataQuery
            hir::db::FunctionDataQuery
            hir::db::ConstDataQuery
            hir::db::StaticDataQuery
            hir::db::BodyWithSourceMapQuery
            hir::db::BodyQuery
            hir::db::ExprScopesQuery
            hir::db::GenericParamsQuery
            hir::db::AttrsQuery
            hir::db::CrateLangItemsQuery
            hir::db::LangItemQuery
            hir::db::ImportMapQuery

            // HirDatabase
            hir::db::InferQueryQuery
            hir::db::TyQuery
            hir::db::ValueTyQuery
            hir::db::ImplSelfTyQuery
            hir::db::ImplTraitQuery
            hir::db::FieldTypesQuery
            hir::db::CallableItemSignatureQuery
            hir::db::GenericPredicatesForParamQuery
            hir::db::GenericPredicatesQuery
            hir::db::GenericDefaultsQuery
            hir::db::InherentImplsInCrateQuery
            hir::db::TraitEnvironmentQuery
            hir::db::TraitImplsInCrateQuery
            hir::db::TraitImplsInDepsQuery
            hir::db::AssociatedTyDataQuery
            hir::db::AssociatedTyDataQuery
            hir::db::TraitDatumQuery
            hir::db::StructDatumQuery
            hir::db::ImplDatumQuery
            hir::db::FnDefDatumQuery
            hir::db::ReturnTypeImplTraitsQuery
            hir::db::InternCallableDefQuery
            hir::db::InternTypeParamIdQuery
            hir::db::InternImplTraitIdQuery
            hir::db::InternClosureQuery
            hir::db::AssociatedTyValueQuery
            hir::db::TraitSolveQueryQuery

            // SymbolsDatabase
            crate::symbol_index::FileSymbolsQuery
            crate::symbol_index::LibrarySymbolsQuery
            crate::symbol_index::LocalRootsQuery
            crate::symbol_index::LibraryRootsQuery

            // LineIndexDatabase
            crate::LineIndexQuery
        ];

        // To collect interned data, we need to bump the revision counter by performing a synthetic
        // write.
        // We do this after collecting the non-interned queries to correctly attribute memory used
        // by interned data.
        self.salsa_runtime_mut().synthetic_write(Durability::HIGH);

        sweep_each_query![
            // AstDatabase
            hir::db::InternMacroQuery

            // InternDatabase
            hir::db::InternFunctionQuery
            hir::db::InternStructQuery
            hir::db::InternUnionQuery
            hir::db::InternEnumQuery
            hir::db::InternConstQuery
            hir::db::InternStaticQuery
            hir::db::InternTraitQuery
            hir::db::InternTypeAliasQuery
            hir::db::InternImplQuery

            // HirDatabase
            hir::db::InternTypeParamIdQuery
        ];

        acc.sort_by_key(|it| std::cmp::Reverse(it.1));
        acc
    }
}
<commit_msg>Remove unused structs in ide_db<commit_after>//! Applies changes to the IDE state transactionally.

use std::sync::Arc;

use base_db::{
    salsa::{Database, Durability, SweepStrategy},
    Change, SourceRootId,
};
use profile::{memory_usage, Bytes};
use rustc_hash::FxHashSet;

use crate::{symbol_index::SymbolsDatabase, RootDatabase};

impl RootDatabase {
    pub fn request_cancellation(&mut self) {
        let _p = profile::span("RootDatabase::request_cancellation");
        self.salsa_runtime_mut().synthetic_write(Durability::LOW);
    }

    pub fn apply_change(&mut self, change: Change) {
        let _p = profile::span("RootDatabase::apply_change");
        self.request_cancellation();
        log::info!("apply_change {:?}", change);
        if let Some(roots) = &change.roots {
            let mut local_roots = FxHashSet::default();
            let mut library_roots = FxHashSet::default();
            for (idx, root) in roots.iter().enumerate() {
                let root_id = SourceRootId(idx as u32);
                if root.is_library {
                    library_roots.insert(root_id);
                } else {
                    local_roots.insert(root_id);
                }
            }
            self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
            self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH);
        }
        change.apply(self);
    }

    pub fn collect_garbage(&mut self) {
        if cfg!(target_arch = "wasm32") {
            return;
        }

        let _p = profile::span("RootDatabase::collect_garbage");

        let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();

        base_db::ParseQuery.in_db(self).sweep(sweep);
        hir::db::ParseMacroExpansionQuery.in_db(self).sweep(sweep);

        // Macros do take significant space, but less then the syntax trees
        // self.query(hir::db::MacroDefQuery).sweep(sweep);
        // self.query(hir::db::MacroArgTextQuery).sweep(sweep);
        // self.query(hir::db::MacroExpandQuery).sweep(sweep);

        hir::db::AstIdMapQuery.in_db(self).sweep(sweep);

        hir::db::BodyWithSourceMapQuery.in_db(self).sweep(sweep);

        hir::db::ExprScopesQuery.in_db(self).sweep(sweep);
        hir::db::InferQueryQuery.in_db(self).sweep(sweep);
        hir::db::BodyQuery.in_db(self).sweep(sweep);
    }

    // Feature: Memory Usage
    //
    // Clears rust-analyzer's internal database and prints memory usage statistics.
    //
    // |===
    // | Editor  | Action Name
    //
    // | VS Code | **Rust Analyzer: Memory Usage (Clears Database)**
    // |===
    // image::https://user-images.githubusercontent.com/48062697/113065592-08559f00-91b1-11eb-8c96-64b88068ec02.gif[]
    pub fn per_query_memory_usage(&mut self) -> Vec<(String, Bytes)> {
        let mut acc: Vec<(String, Bytes)> = vec![];
        let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();
        macro_rules! sweep_each_query {
            ($($q:path)*) => {$(
                let before = memory_usage().allocated;
                $q.in_db(self).sweep(sweep);
                let after = memory_usage().allocated;
                let q: $q = Default::default();
                let name = format!("{:?}", q);
                acc.push((name, before - after));

                let before = memory_usage().allocated;
                $q.in_db(self).sweep(sweep.discard_everything());
                let after = memory_usage().allocated;
                let q: $q = Default::default();
                let name = format!("{:?} (deps)", q);
                acc.push((name, before - after));

                let before = memory_usage().allocated;
                $q.in_db(self).purge();
                let after = memory_usage().allocated;
                let q: $q = Default::default();
                let name = format!("{:?} (purge)", q);
                acc.push((name, before - after));
            )*}
        }
        sweep_each_query![
            // SourceDatabase
            base_db::ParseQuery
            base_db::CrateGraphQuery

            // SourceDatabaseExt
            base_db::FileTextQuery
            base_db::FileSourceRootQuery
            base_db::SourceRootQuery
            base_db::SourceRootCratesQuery

            // AstDatabase
            hir::db::AstIdMapQuery
            hir::db::MacroArgTextQuery
            hir::db::MacroDefQuery
            hir::db::ParseMacroExpansionQuery
            hir::db::MacroExpandQuery
            hir::db::HygieneFrameQuery

            // DefDatabase
            hir::db::FileItemTreeQuery
            hir::db::BlockDefMapQuery
            hir::db::CrateDefMapQueryQuery
            hir::db::FieldsAttrsQuery
            hir::db::VariantsAttrsQuery
            hir::db::FieldsAttrsSourceMapQuery
            hir::db::VariantsAttrsSourceMapQuery
            hir::db::StructDataQuery
            hir::db::UnionDataQuery
            hir::db::EnumDataQuery
            hir::db::ImplDataQuery
            hir::db::TraitDataQuery
            hir::db::TypeAliasDataQuery
            hir::db::FunctionDataQuery
            hir::db::ConstDataQuery
            hir::db::StaticDataQuery
            hir::db::BodyWithSourceMapQuery
            hir::db::BodyQuery
            hir::db::ExprScopesQuery
            hir::db::GenericParamsQuery
            hir::db::AttrsQuery
            hir::db::CrateLangItemsQuery
            hir::db::LangItemQuery
            hir::db::ImportMapQuery

            // HirDatabase
            hir::db::InferQueryQuery
            hir::db::TyQuery
            hir::db::ValueTyQuery
            hir::db::ImplSelfTyQuery
            hir::db::ImplTraitQuery
            hir::db::FieldTypesQuery
            hir::db::CallableItemSignatureQuery
            hir::db::GenericPredicatesForParamQuery
            hir::db::GenericPredicatesQuery
            hir::db::GenericDefaultsQuery
            hir::db::InherentImplsInCrateQuery
            hir::db::TraitEnvironmentQuery
            hir::db::TraitImplsInCrateQuery
            hir::db::TraitImplsInDepsQuery
            hir::db::AssociatedTyDataQuery
            hir::db::AssociatedTyDataQuery
            hir::db::TraitDatumQuery
            hir::db::StructDatumQuery
            hir::db::ImplDatumQuery
            hir::db::FnDefDatumQuery
            hir::db::ReturnTypeImplTraitsQuery
            hir::db::InternCallableDefQuery
            hir::db::InternTypeParamIdQuery
            hir::db::InternImplTraitIdQuery
            hir::db::InternClosureQuery
            hir::db::AssociatedTyValueQuery
            hir::db::TraitSolveQueryQuery

            // SymbolsDatabase
            crate::symbol_index::FileSymbolsQuery
            crate::symbol_index::LibrarySymbolsQuery
            crate::symbol_index::LocalRootsQuery
            crate::symbol_index::LibraryRootsQuery

            // LineIndexDatabase
            crate::LineIndexQuery
        ];

        // To collect interned data, we need to bump the revision counter by performing a synthetic
        // write.
        // We do this after collecting the non-interned queries to correctly attribute memory used
        // by interned data.
        self.salsa_runtime_mut().synthetic_write(Durability::HIGH);

        sweep_each_query![
            // AstDatabase
            hir::db::InternMacroQuery

            // InternDatabase
            hir::db::InternFunctionQuery
            hir::db::InternStructQuery
            hir::db::InternUnionQuery
            hir::db::InternEnumQuery
            hir::db::InternConstQuery
            hir::db::InternStaticQuery
            hir::db::InternTraitQuery
            hir::db::InternTypeAliasQuery
            hir::db::InternImplQuery

            // HirDatabase
            hir::db::InternTypeParamIdQuery
        ];

        acc.sort_by_key(|it| std::cmp::Reverse(it.1));
        acc
    }
}
<|endoftext|>