// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//! Module containing versions of the standard library's [`Read`](std::io::Read) and
//! [`Write`](std::io::Write) traits compatible with volatile memory accesses.

use crate::bitmap::BitmapSlice;
use crate::volatile_memory::copy_slice_impl::{copy_from_volatile_slice, copy_to_volatile_slice};
use crate::{VolatileMemoryError, VolatileSlice};
use std::io::{Cursor, ErrorKind, Stdout};
use std::os::fd::AsRawFd;

/// A version of the standard library's [`Read`](std::io::Read) trait that operates on volatile
/// memory instead of slices
///
/// This trait is needed as rust slices (`&[u8]` and `&mut [u8]`) cannot be used when operating on
/// guest memory [1].
///
/// [1]: https://github.com/rust-vmm/vm-memory/pull/217
pub trait ReadVolatile {
    /// Tries to read some bytes into the given [`VolatileSlice`] buffer, returning how many bytes
    /// were read.
    ///
    /// The behavior of implementations should be identical to [`Read::read`](std::io::Read::read)
    fn read_volatile<B: BitmapSlice>(
        &mut self,
        buf: &mut VolatileSlice<B>,
    ) -> Result<usize, VolatileMemoryError>;

    /// Tries to fill the given [`VolatileSlice`] buffer by reading from `self` returning an error
    /// if insufficient bytes could be read.
    ///
    /// The default implementation is identical to that of [`Read::read_exact`](std::io::Read::read_exact)
    fn read_exact_volatile<B: BitmapSlice>(
        &mut self,
        buf: &mut VolatileSlice<B>,
    ) -> Result<(), VolatileMemoryError> {
        // Implementation based on https://github.com/rust-lang/rust/blob/7e7483d26e3cec7a44ef00cf7ae6c9c8c918bec6/library/std/src/io/mod.rs#L465

        let mut partial_buf = buf.offset(0)?;

        while !partial_buf.is_empty() {
            match self.read_volatile(&mut partial_buf) {
                Err(VolatileMemoryError::IOError(err)) if err.kind() == ErrorKind::Interrupted => {
                    continue
                }
                Ok(0) => {
                    return Err(VolatileMemoryError::IOError(std::io::Error::new(
                        ErrorKind::UnexpectedEof,
                        "failed to fill whole buffer",
                    )))
                }
                Ok(bytes_read) => partial_buf = partial_buf.offset(bytes_read)?,
                Err(err) => return Err(err),
            }
        }

        Ok(())
    }
}

/// A version of the standard library's [`Write`](std::io::Write) trait that operates on volatile
/// memory instead of slices.
///
/// This trait is needed as rust slices (`&[u8]` and `&mut [u8]`) cannot be used when operating on
/// guest memory [1].
///
/// [1]: https://github.com/rust-vmm/vm-memory/pull/217
pub trait WriteVolatile {
    /// Tries to write some bytes from the given [`VolatileSlice`] buffer, returning how many bytes
    /// were written.
    ///
    /// The behavior of implementations should be identical to [`Write::write`](std::io::Write::write)
    fn write_volatile<B: BitmapSlice>(
        &mut self,
        buf: &VolatileSlice<B>,
    ) -> Result<usize, VolatileMemoryError>;

    /// Tries write the entire content of the given [`VolatileSlice`] buffer to `self` returning an
    /// error if not all bytes could be written.
    ///
    /// The default implementation is identical to that of [`Write::write_all`](std::io::Write::write_all)
    fn write_all_volatile<B: BitmapSlice>(
        &mut self,
        buf: &VolatileSlice<B>,
    ) -> Result<(), VolatileMemoryError> {
        // Based on https://github.com/rust-lang/rust/blob/7e7483d26e3cec7a44ef00cf7ae6c9c8c918bec6/library/std/src/io/mod.rs#L1570

        let mut partial_buf = buf.offset(0)?;

        while !partial_buf.is_empty() {
            match self.write_volatile(&partial_buf) {
                Err(VolatileMemoryError::IOError(err)) if err.kind() == ErrorKind::Interrupted => {
                    continue
                }
                Ok(0) => {
                    return Err(VolatileMemoryError::IOError(std::io::Error::new(
                        ErrorKind::WriteZero,
                        "failed to write whole buffer",
                    )))
                }
                Ok(bytes_written) => partial_buf = partial_buf.offset(bytes_written)?,
                Err(err) => return Err(err),
            }
        }

        Ok(())
    }
}

// We explicitly implement our traits for [`std::fs::File`] and [`std::os::unix::net::UnixStream`]
// instead of providing blanket implementation for [`AsRawFd`] due to trait coherence limitations: A
// blanket implementation would prevent us from providing implementations for `&mut [u8]` below, as
// "an upstream crate could implement AsRawFd for &mut [u8]`.

macro_rules! impl_read_write_volatile_for_raw_fd {
    ($raw_fd_ty:ty) => {
        impl ReadVolatile for $raw_fd_ty {
            fn read_volatile<B: BitmapSlice>(
                &mut self,
                buf: &mut VolatileSlice<B>,
            ) -> Result<usize, VolatileMemoryError> {
                read_volatile_raw_fd(self, buf)
            }
        }

        impl WriteVolatile for $raw_fd_ty {
            fn write_volatile<B: BitmapSlice>(
                &mut self,
                buf: &VolatileSlice<B>,
            ) -> Result<usize, VolatileMemoryError> {
                write_volatile_raw_fd(self, buf)
            }
        }
    };
}

impl WriteVolatile for Stdout {
    fn write_volatile<B: BitmapSlice>(
        &mut self,
        buf: &VolatileSlice<B>,
    ) -> Result<usize, VolatileMemoryError> {
        write_volatile_raw_fd(self, buf)
    }
}

impl_read_write_volatile_for_raw_fd!(std::fs::File);
impl_read_write_volatile_for_raw_fd!(std::os::unix::net::UnixStream);
impl_read_write_volatile_for_raw_fd!(std::os::fd::OwnedFd);
impl_read_write_volatile_for_raw_fd!(std::os::fd::BorrowedFd<'_>);

/// Tries to do a single `read` syscall on the provided file descriptor, storing the data raed in
/// the given [`VolatileSlice`].
///
/// Returns the numbers of bytes read.
fn read_volatile_raw_fd<Fd: AsRawFd>(
    raw_fd: &mut Fd,
    buf: &mut VolatileSlice<impl BitmapSlice>,
) -> Result<usize, VolatileMemoryError> {
    let fd = raw_fd.as_raw_fd();
    let guard = buf.ptr_guard_mut();

    let dst = guard.as_ptr().cast::<libc::c_void>();

    // SAFETY: We got a valid file descriptor from `AsRawFd`. The memory pointed to by `dst` is
    // valid for writes of length `buf.len() by the invariants upheld by the constructor
    // of `VolatileSlice`.
    let bytes_read = unsafe { libc::read(fd, dst, buf.len()) };

    if bytes_read < 0 {
        // We don't know if a partial read might have happened, so mark everything as dirty
        buf.bitmap().mark_dirty(0, buf.len());

        Err(VolatileMemoryError::IOError(std::io::Error::last_os_error()))
    } else {
        let bytes_read = bytes_read.try_into().unwrap();
        buf.bitmap().mark_dirty(0, bytes_read);
        Ok(bytes_read)
    }
}

/// Tries to do a single `write` syscall on the provided file descriptor, attempting to write the
/// data stored in the given [`VolatileSlice`].
///
/// Returns the numbers of bytes written.
fn write_volatile_raw_fd<Fd: AsRawFd>(
    raw_fd: &mut Fd,
    buf: &VolatileSlice<impl BitmapSlice>,
) -> Result<usize, VolatileMemoryError> {
    let fd = raw_fd.as_raw_fd();
    let guard = buf.ptr_guard();

    let src = guard.as_ptr().cast::<libc::c_void>();

    // SAFETY: We got a valid file descriptor from `AsRawFd`. The memory pointed to by `src` is
    // valid for reads of length `buf.len() by the invariants upheld by the constructor
    // of `VolatileSlice`.
    let bytes_written = unsafe { libc::write(fd, src, buf.len()) };

    if bytes_written < 0 {
        Err(VolatileMemoryError::IOError(std::io::Error::last_os_error()))
    } else {
        Ok(bytes_written.try_into().unwrap())
    }
}

impl WriteVolatile for &mut [u8] {
    fn write_volatile<B: BitmapSlice>(
        &mut self,
        buf: &VolatileSlice<B>,
    ) -> Result<usize, VolatileMemoryError> {
        let total = buf.len().min(self.len());
        let src = buf.subslice(0, total)?;

        // SAFETY:
        // We check above that `src` is contiguously allocated memory of length `total <= self.len())`.
        // Furthermore, both src and dst of the call to
        // copy_from_volatile_slice are valid for reads and writes respectively of length `total`
        // since total is the minimum of lengths of the memory areas pointed to. The areas do not
        // overlap, since `dst` is inside guest memory, and buf is a slice (no slices to guest
        // memory are possible without violating rust's aliasing rules).
        let written = unsafe { copy_from_volatile_slice(self.as_mut_ptr(), &src, total) };

        // Advance the slice, just like the stdlib: https://doc.rust-lang.org/src/std/io/impls.rs.html#335
        *self = std::mem::take(self).split_at_mut(written).1;

        Ok(written)
    }

    fn write_all_volatile<B: BitmapSlice>(
        &mut self,
        buf: &VolatileSlice<B>,
    ) -> Result<(), VolatileMemoryError> {
        // Based on https://github.com/rust-lang/rust/blob/f7b831ac8a897273f78b9f47165cf8e54066ce4b/library/std/src/io/impls.rs#L376-L382
        if self.write_volatile(buf)? == buf.len() {
            Ok(())
        } else {
            Err(VolatileMemoryError::IOError(std::io::Error::new(
                ErrorKind::WriteZero,
                "failed to write whole buffer",
            )))
        }
    }
}

impl ReadVolatile for &[u8] {
    fn read_volatile<B: BitmapSlice>(
        &mut self,
        buf: &mut VolatileSlice<B>,
    ) -> Result<usize, VolatileMemoryError> {
        let total = buf.len().min(self.len());
        let dst = buf.subslice(0, total)?;

        // SAFETY:
        // We check above that `dst` is contiguously allocated memory of length `total <= self.len())`.
        // Furthermore, both src and dst of the call to copy_to_volatile_slice are valid for reads
        // and writes respectively of length `total` since total is the minimum of lengths of the
        // memory areas pointed to. The areas do not overlap, since `dst` is inside guest memory,
        // and buf is a slice (no slices to guest memory are possible without violating rust's aliasing rules).
        let read = unsafe { copy_to_volatile_slice(&dst, self.as_ptr(), total) };

        // Advance the slice, just like the stdlib: https://doc.rust-lang.org/src/std/io/impls.rs.html#232-310
        *self = self.split_at(read).1;

        Ok(read)
    }

    fn read_exact_volatile<B: BitmapSlice>(
        &mut self,
        buf: &mut VolatileSlice<B>,
    ) -> Result<(), VolatileMemoryError> {
        // Based on https://github.com/rust-lang/rust/blob/f7b831ac8a897273f78b9f47165cf8e54066ce4b/library/std/src/io/impls.rs#L282-L302
        if buf.len() > self.len() {
            return Err(VolatileMemoryError::IOError(std::io::Error::new(
                ErrorKind::UnexpectedEof,
                "failed to fill whole buffer",
            )));
        }

        self.read_volatile(buf).map(|_| ())
    }
}

// WriteVolatile implementation for Vec<u8> is based upon the Write impl for Vec, which
// defers to Vec::append_elements, after which the below functionality is modelled.
impl WriteVolatile for Vec<u8> {
    fn write_volatile<B: BitmapSlice>(
        &mut self,
        buf: &VolatileSlice<B>,
    ) -> Result<usize, VolatileMemoryError> {
        let count = buf.len();
        self.reserve(count);
        let len = self.len();

        // SAFETY: Calling Vec::reserve() above guarantees the the backing storage of the Vec has
        // length at least `len + count`. This means that self.as_mut_ptr().add(len) remains within
        // the same allocated object, the offset does not exceed isize (as otherwise reserve would
        // have panicked), and does not rely on address space wrapping around.
        // In particular, the entire `count` bytes after `self.as_mut_ptr().add(count)` is
        // contiguously allocated and valid for writes.
        // Lastly, `copy_to_volatile_slice` correctly initialized `copied_len` additional bytes
        // in the Vec's backing storage, and we assert this to be equal to `count`. Additionally,
        // `len + count` is at most the reserved capacity of the vector. Thus the call to `set_len`
        // is safe.
        unsafe {
            let copied_len = copy_from_volatile_slice(self.as_mut_ptr().add(len), buf, count);

            assert_eq!(copied_len, count);
            self.set_len(len + count);
        }
        Ok(count)
    }
}

// ReadVolatile and WriteVolatile implementations for Cursor<T> is modelled after the standard
// library's implementation (modulo having to inline `Cursor::remaining_slice`, as that's nightly only)
impl<T> ReadVolatile for Cursor<T>
where
    T: AsRef<[u8]>,
{
    fn read_volatile<B: BitmapSlice>(
        &mut self,
        buf: &mut VolatileSlice<B>,
    ) -> Result<usize, VolatileMemoryError> {
        let inner = self.get_ref().as_ref();
        let len = self.position().min(inner.len() as u64);
        let n = ReadVolatile::read_volatile(&mut &inner[(len as usize)..], buf)?;
        self.set_position(self.position() + n as u64);
        Ok(n)
    }

    fn read_exact_volatile<B: BitmapSlice>(
        &mut self,
        buf: &mut VolatileSlice<B>,
    ) -> Result<(), VolatileMemoryError> {
        let inner = self.get_ref().as_ref();
        let n = buf.len();
        let len = self.position().min(inner.len() as u64);
        ReadVolatile::read_exact_volatile(&mut &inner[(len as usize)..], buf)?;
        self.set_position(self.position() + n as u64);
        Ok(())
    }
}

impl WriteVolatile for Cursor<&mut [u8]> {
    fn write_volatile<B: BitmapSlice>(
        &mut self,
        buf: &VolatileSlice<B>,
    ) -> Result<usize, VolatileMemoryError> {
        let pos = self.position().min(self.get_ref().len() as u64);
        let n = WriteVolatile::write_volatile(&mut &mut self.get_mut()[(pos as usize)..], buf)?;
        self.set_position(self.position() + n as u64);
        Ok(n)
    }

    // no write_all provided in standard library, since our default for write_all is based on the
    // standard library's write_all, omitting it here as well will correctly mimic stdlib behavior.
}

#[cfg(test)]
mod tests {
    use crate::io::{ReadVolatile, WriteVolatile};
    use crate::{VolatileMemoryError, VolatileSlice};
    use std::io::{Cursor, ErrorKind, Read, Seek, Write};
    use vmm_sys_util::tempfile::TempFile;

    // ---- Test ReadVolatile for &[u8] ----
    fn read_4_bytes_to_5_byte_memory(source: Vec<u8>, expected_output: [u8; 5]) {
        // Test read_volatile for &[u8] works
        let mut memory = vec![0u8; 5];

        assert_eq!(
            (&source[..])
                .read_volatile(&mut VolatileSlice::from(&mut memory[..4]))
                .unwrap(),
            source.len().min(4)
        );
        assert_eq!(&memory, &expected_output);

        // Test read_exact_volatile for &[u8] works
        let mut memory = vec![0u8; 5];
        let result = (&source[..]).read_exact_volatile(&mut VolatileSlice::from(&mut memory[..4]));

        // read_exact fails if there are not enough bytes in input to completely fill
        // memory[..4]
        if source.len() < 4 {
            match result.unwrap_err() {
                VolatileMemoryError::IOError(ioe) => {
                    assert_eq!(ioe.kind(), ErrorKind::UnexpectedEof)
                }
                err => panic!("{:?}", err),
            }
            assert_eq!(memory, vec![0u8; 5]);
        } else {
            result.unwrap();
            assert_eq!(&memory, &expected_output);
        }
    }

    // ---- Test ReadVolatile for File ----
    fn read_4_bytes_from_file(source: Vec<u8>, expected_output: [u8; 5]) {
        let mut temp_file = TempFile::new().unwrap().into_file();
        temp_file.write_all(source.as_ref()).unwrap();
        temp_file.rewind().unwrap();

        // Test read_volatile for File works
        let mut memory = vec![0u8; 5];

        assert_eq!(
            temp_file
                .read_volatile(&mut VolatileSlice::from(&mut memory[..4]))
                .unwrap(),
            source.len().min(4)
        );
        assert_eq!(&memory, &expected_output);

        temp_file.rewind().unwrap();

        // Test read_exact_volatile for File works
        let mut memory = vec![0u8; 5];

        let read_exact_result =
            temp_file.read_exact_volatile(&mut VolatileSlice::from(&mut memory[..4]));

        if source.len() < 4 {
            read_exact_result.unwrap_err();
        } else {
            read_exact_result.unwrap();
        }
        assert_eq!(&memory, &expected_output);
    }

    #[test]
    fn test_read_volatile() {
        let test_cases = [
            (vec![1u8, 2], [1u8, 2, 0, 0, 0]),
            (vec![1, 2, 3, 4], [1, 2, 3, 4, 0]),
            // ensure we don't have a buffer overrun
            (vec![5, 6, 7, 8, 9], [5, 6, 7, 8, 0]),
        ];

        for (input, output) in test_cases {
            read_4_bytes_to_5_byte_memory(input.clone(), output);
            read_4_bytes_from_file(input, output);
        }
    }

    // ---- Test WriteVolatile for &mut [u8] ----
    fn write_4_bytes_to_5_byte_vec(mut source: Vec<u8>, expected_result: [u8; 5]) {
        let mut memory = vec![0u8; 5];

        // Test write_volatile for &mut [u8] works
        assert_eq!(
            (&mut memory[..4])
                .write_volatile(&VolatileSlice::from(source.as_mut_slice()))
                .unwrap(),
            source.len().min(4)
        );
        assert_eq!(&memory, &expected_result);

        // Test write_all_volatile for &mut [u8] works
        let mut memory = vec![0u8; 5];

        let result =
            (&mut memory[..4]).write_all_volatile(&VolatileSlice::from(source.as_mut_slice()));

        if source.len() > 4 {
            match result.unwrap_err() {
                VolatileMemoryError::IOError(ioe) => {
                    assert_eq!(ioe.kind(), ErrorKind::WriteZero)
                }
                err => panic!("{:?}", err),
            }
            // This quirky behavior of writing to the slice even in the case of failure is also
            // exhibited by the stdlib
            assert_eq!(&memory, &expected_result);
        } else {
            result.unwrap();
            assert_eq!(&memory, &expected_result);
        }
    }

    // ---- Test ẂriteVolatile for File works ----
    fn write_5_bytes_to_file(mut source: Vec<u8>) {
        // Test write_volatile for File works
        let mut temp_file = TempFile::new().unwrap().into_file();

        temp_file
            .write_volatile(&VolatileSlice::from(source.as_mut_slice()))
            .unwrap();
        temp_file.rewind().unwrap();

        let mut written = vec![0u8; source.len()];
        temp_file.read_exact(written.as_mut_slice()).unwrap();

        assert_eq!(source, written);
        // check no excess bytes were written to the file
        assert_eq!(temp_file.read(&mut [0u8]).unwrap(), 0);

        // Test write_all_volatile for File works
        let mut temp_file = TempFile::new().unwrap().into_file();

        temp_file
            .write_all_volatile(&VolatileSlice::from(source.as_mut_slice()))
            .unwrap();
        temp_file.rewind().unwrap();

        let mut written = vec![0u8; source.len()];
        temp_file.read_exact(written.as_mut_slice()).unwrap();

        assert_eq!(source, written);
        // check no excess bytes were written to the file
        assert_eq!(temp_file.read(&mut [0u8]).unwrap(), 0);
    }

    #[test]
    fn test_write_volatile() {
        let test_cases = [
            (vec![1u8, 2], [1u8, 2, 0, 0, 0]),
            (vec![1, 2, 3, 4], [1, 2, 3, 4, 0]),
            // ensure we don't have a buffer overrun
            (vec![5, 6, 7, 8, 9], [5, 6, 7, 8, 0]),
        ];

        for (input, output) in test_cases {
            write_4_bytes_to_5_byte_vec(input.clone(), output);
            write_5_bytes_to_file(input);
        }
    }

    #[test]
    fn test_read_volatile_for_cursor() {
        let read_buffer = [1, 2, 3, 4, 5, 6, 7];
        let mut output = vec![0u8; 5];

        let mut cursor = Cursor::new(read_buffer);

        // Read 4 bytes from cursor to volatile slice (amount read limited by volatile slice length)
        assert_eq!(
            cursor
                .read_volatile(&mut VolatileSlice::from(&mut output[..4]))
                .unwrap(),
            4
        );
        assert_eq!(output, vec![1, 2, 3, 4, 0]);

        // Read next 3 bytes from cursor to volatile slice (amount read limited by length of remaining data in cursor)
        assert_eq!(
            cursor
                .read_volatile(&mut VolatileSlice::from(&mut output[..4]))
                .unwrap(),
            3
        );
        assert_eq!(output, vec![5, 6, 7, 4, 0]);

        cursor.set_position(0);
        // Same as first test above, but with read_exact
        cursor
            .read_exact_volatile(&mut VolatileSlice::from(&mut output[..4]))
            .unwrap();
        assert_eq!(output, vec![1, 2, 3, 4, 0]);

        // Same as above, but with read_exact. Should fail now, because we cannot fill a 4 byte buffer
        // with whats remaining in the cursor (3 bytes). Output should remain unchanged.
        assert!(cursor
            .read_exact_volatile(&mut VolatileSlice::from(&mut output[..4]))
            .is_err());
        assert_eq!(output, vec![1, 2, 3, 4, 0]);
    }

    #[test]
    fn test_write_volatile_for_cursor() {
        let mut write_buffer = vec![0u8; 7];
        let mut input = [1, 2, 3, 4];

        let mut cursor = Cursor::new(write_buffer.as_mut_slice());

        // Write 4 bytes from volatile slice to cursor (amount written limited by volatile slice length)
        assert_eq!(
            cursor
                .write_volatile(&VolatileSlice::from(input.as_mut_slice()))
                .unwrap(),
            4
        );
        assert_eq!(cursor.get_ref(), &[1, 2, 3, 4, 0, 0, 0]);

        // Write 3 bytes from volatile slice to cursor (amount written limited by remaining space in cursor)
        assert_eq!(
            cursor
                .write_volatile(&VolatileSlice::from(input.as_mut_slice()))
                .unwrap(),
            3
        );
        assert_eq!(cursor.get_ref(), &[1, 2, 3, 4, 1, 2, 3]);
    }

    #[test]
    fn test_write_volatile_for_vec() {
        let mut write_buffer = Vec::new();
        let mut input = [1, 2, 3, 4];

        assert_eq!(
            write_buffer
                .write_volatile(&VolatileSlice::from(input.as_mut_slice()))
                .unwrap(),
            4
        );

        assert_eq!(&write_buffer, &input);
    }
}
