<commit_before>use {sleep_ms};
use mio::*;
use mio::tcp::*;
use std::io::Write;
use std::time::Duration;

const MS: u64 = 1_000;

#[test]
pub fn test_tcp_listener_level_triggered() {
    let poll = Poll::new().unwrap();
    let mut pevents = Events::new();

    // Create the listener
    let l = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();

    // Register the listener with `Poll`
    poll.register(&l, Token(0), EventSet::readable(), PollOpt::level()).unwrap();

    let s1 = TcpStream::connect(&l.local_addr().unwrap()).unwrap();
    poll.register(&s1, Token(1), EventSet::readable(), PollOpt::edge()).unwrap();

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(0));

    assert_eq!(events.len(), 1);
    assert_eq!(events[0], Event::new(EventSet::readable(), Token(0)));

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(0));
    assert_eq!(events.len(), 1);
    assert_eq!(events[0], Event::new(EventSet::readable(), Token(0)));

    // Accept the connection then test that the events stop
    let _ = l.accept().unwrap();

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(0));
    assert!(events.is_empty(), "actual={:?}", events);

    let s3 = TcpStream::connect(&l.local_addr().unwrap()).unwrap();
    poll.register(&s3, Token(2), EventSet::readable(), PollOpt::edge()).unwrap();

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(0));
    assert_eq!(events.len(), 1);
    assert_eq!(events[0], Event::new(EventSet::readable(), Token(0)));

    drop(l);

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(0));
    assert!(events.is_empty());
}

#[test]
pub fn test_tcp_stream_level_triggered() {
    let poll = Poll::new().unwrap();
    let mut pevents = Events::new();

    // Create the listener
    let l = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();

    // Register the listener with `Poll`
    poll.register(&l, Token(0), EventSet::readable(), PollOpt::edge()).unwrap();

    let mut s1 = TcpStream::connect(&l.local_addr().unwrap()).unwrap();
    poll.register(&s1, Token(1), EventSet::readable() | EventSet::writable(), PollOpt::level()).unwrap();

    // Sleep a bit to ensure it arrives at dest
    sleep_ms(250);

    let _ = poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events: Vec<Event> = (0..pevents.len()).map(|i| pevents.get(i).unwrap()).collect();
    assert!(events.len() == 2, "actual={:?}", events);
    assert_eq!(filter(&pevents, Token(1))[0], Event::new(EventSet::writable(), Token(1)));

    // Server side of socket
    let (mut s1_tx, _) = l.accept().unwrap().unwrap();

    // Sleep a bit to ensure it arrives at dest
    sleep_ms(250);

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(1));
    assert_eq!(events.len(), 1);
    assert_eq!(events[0], Event::new(EventSet::writable(), Token(1)));

    // Register the socket
    poll.register(&s1_tx, Token(123), EventSet::readable(), PollOpt::edge()).unwrap();

    // Write some data
    let res = s1_tx.write(b"hello world!");
    assert!(res.unwrap() > 0);

    // Sleep a bit to ensure it arrives at dest
    sleep_ms(250);

    // Poll rx end
    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(1));
    assert!(events.len() == 1, "actual={:?}", events);
    assert_eq!(events[0], Event::new(EventSet::readable() | EventSet::writable(), Token(1)));

    // Reading the data should clear it
    let mut res = vec![];
    while s1.try_read_buf(&mut res).unwrap().is_some() {
    }

    assert_eq!(res, b"hello world!");

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(1));
    assert!(events.len() == 1);
    assert_eq!(events[0], Event::new(EventSet::writable(), Token(1)));

    // Closing the socket clears all active level events
    drop(s1);

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(1));
    assert!(events.is_empty());
}

fn filter(events: &Events, token: Token) -> Vec<Event> {
    (0..events.len()).map(|i| events.get(i).unwrap())
                     .filter(|e| e.token() == token)
                     .collect()
}
<commit_msg>Fix broken level test<commit_after>use {sleep_ms};
use mio::*;
use mio::tcp::*;
use std::io::Write;
use std::time::Duration;

const MS: u64 = 1_000;

#[test]
pub fn test_tcp_listener_level_triggered() {
    let poll = Poll::new().unwrap();
    let mut pevents = Events::new();

    // Create the listener
    let l = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();

    // Register the listener with `Poll`
    poll.register(&l, Token(0), EventSet::readable(), PollOpt::level()).unwrap();

    let s1 = TcpStream::connect(&l.local_addr().unwrap()).unwrap();
    poll.register(&s1, Token(1), EventSet::readable(), PollOpt::edge()).unwrap();

    while filter(&pevents, Token(0)).len() == 0 {
        poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    }
    let events = filter(&pevents, Token(0));

    assert_eq!(events.len(), 1);
    assert_eq!(events[0], Event::new(EventSet::readable(), Token(0)));

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(0));
    assert_eq!(events.len(), 1);
    assert_eq!(events[0], Event::new(EventSet::readable(), Token(0)));

    // Accept the connection then test that the events stop
    let _ = l.accept().unwrap();

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(0));
    assert!(events.is_empty(), "actual={:?}", events);

    let s3 = TcpStream::connect(&l.local_addr().unwrap()).unwrap();
    poll.register(&s3, Token(2), EventSet::readable(), PollOpt::edge()).unwrap();

    while filter(&pevents, Token(0)).len() == 0 {
        poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    }
    let events = filter(&pevents, Token(0));

    assert_eq!(events.len(), 1);
    assert_eq!(events[0], Event::new(EventSet::readable(), Token(0)));

    drop(l);

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(0));
    assert!(events.is_empty());
}

#[test]
pub fn test_tcp_stream_level_triggered() {
    let poll = Poll::new().unwrap();
    let mut pevents = Events::new();

    // Create the listener
    let l = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();

    // Register the listener with `Poll`
    poll.register(&l, Token(0), EventSet::readable(), PollOpt::edge()).unwrap();

    let mut s1 = TcpStream::connect(&l.local_addr().unwrap()).unwrap();
    poll.register(&s1, Token(1), EventSet::readable() | EventSet::writable(), PollOpt::level()).unwrap();

    // Sleep a bit to ensure it arrives at dest
    sleep_ms(250);

    let _ = poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events: Vec<Event> = (0..pevents.len()).map(|i| pevents.get(i).unwrap()).collect();
    assert!(events.len() == 2, "actual={:?}", events);
    assert_eq!(filter(&pevents, Token(1))[0], Event::new(EventSet::writable(), Token(1)));

    // Server side of socket
    let (mut s1_tx, _) = l.accept().unwrap().unwrap();

    // Sleep a bit to ensure it arrives at dest
    sleep_ms(250);

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(1));
    assert_eq!(events.len(), 1);
    assert_eq!(events[0], Event::new(EventSet::writable(), Token(1)));

    // Register the socket
    poll.register(&s1_tx, Token(123), EventSet::readable(), PollOpt::edge()).unwrap();

    // Write some data
    let res = s1_tx.write(b"hello world!");
    assert!(res.unwrap() > 0);

    // Sleep a bit to ensure it arrives at dest
    sleep_ms(250);

    // Poll rx end
    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(1));
    assert!(events.len() == 1, "actual={:?}", events);
    assert_eq!(events[0], Event::new(EventSet::readable() | EventSet::writable(), Token(1)));

    // Reading the data should clear it
    let mut res = vec![];
    while s1.try_read_buf(&mut res).unwrap().is_some() {
    }

    assert_eq!(res, b"hello world!");

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(1));
    assert!(events.len() == 1);
    assert_eq!(events[0], Event::new(EventSet::writable(), Token(1)));

    // Closing the socket clears all active level events
    drop(s1);

    poll.poll(&mut pevents, Some(Duration::from_millis(MS))).unwrap();
    let events = filter(&pevents, Token(1));
    assert!(events.is_empty());
}

fn filter(events: &Events, token: Token) -> Vec<Event> {
    (0..events.len()).map(|i| events.get(i).unwrap())
                     .filter(|e| e.token() == token)
                     .collect()
}
<|endoftext|>


<commit_before>// TODO: Implement `libtock_runtime` and `libtock_unittest`, which are
// referenced in the comment on `RawSyscalls`.

/// `RawSyscalls` allows a fake Tock kernel to be injected into components for
/// unit testing. It is implemented by `libtock_runtime::TockSyscalls` and
/// `libtock_unittest::FakeSyscalls`. Components should not use `RawSyscalls`
/// directly; instead, use the `Syscalls` trait, which provides higher-level
/// interfaces to the system calls.

// RawSyscalls is designed to minimize the amount of handwritten assembly code
// needed without generating unnecessary instructions. There are a few major
// factors affecting its design:
//     1. Most system calls only clobber r0-r4 (*), while yield has a far longer
//        clobber list. As such, yield must have its own assembly
//        implementation.
//     2. The compiler is unable to optimize away unused arguments. For example,
//        memop's "get process RAM start address" operation only needs r0 set,
//        while memop's "break" operation needs both r0 and r1 set. If our
//        inline assembly calls "get process RAM start address" but sets both r0
//        and r1, the compiler doesn't know that r1 will be
//        ignored so setting that register will not be optimized away. Therefore
//        we want to set the minimum number of argument registers possible.
//     3. The cost of specifying unused return registers is only that of
//        unnecessarily marking a register as clobbered. Explanation: After
//        inlining, an unused register is marked as "changed by the assembly"
//        but can immediately be re-used by the compiler, which is the same as a
//        clobbered register. System calls should generally be
//        inlined -- and even if they aren't, the unused return values will
//        probably be passed in caller-saved registers (this is true for the C
//        ABI, so probably true for the Rust ABI), which are treated as
//        clobbered regardless.
//
// (*) When this file refers to registers, it uses the same naming convention as
// the Tock 2.0 syscalls TRD. Registers r0-r4 correspond to ARM registers r0-r4
// and RISC-V registers a0-a4.
//
// Currently, yield takes exactly one argument, to specify what yield type to
// do. Therefore we only need one raw yield call.
//
// Subscribe, command, read-write allow, and read-only allow all take four
// argument types. Even when calling command IDs that have unused arguments, we
// still need to clear the argument registers so as to avoid passing
// confidential data to capsules (this is in line with Tock's threat model). As
// such, four_arg_syscall() is used for all subscribe, command, read-only allow,
// and read-write allow system calls.
//
// Memop takes 1 or 2 arguments (operation and an optional argument). Because it
// is part of the core kernel, it is okay for us to leave arbitrary data in the
// argument register for operations where the argument register is unused
// (again, in line with Tock's threat model). As such, for efficiency, we need
// two raw memop calls: one for operations without an argument and one for
// operations with an argument.
//
// The success type for Memop calls depends on the operation perform. However,
// all *currently defined* memop operations return either Success or Success
// with u32. Therefore, the memop implementations only need to mark r0 and r1 as
// clobbered, not r2 and r3. This choice of clobbers will need to be revisited
// if and when a memop operation that returns more data is added.
//
// The decision of where to use u32 and usize can be a bit tricky. The Tock
// syscall ABI is currently only specified for 32-bit systems, so on real Tock
// systems both types match the size of a register, but the unit test
// environment can be either 32 bit or 64 bit. This interface uses usize for
// values that can contain pointers, so that pointers are not truncated in the
// unit test environment. To keep types as consistent as possible, it uses u32
// for all values that cannot be pointers.
pub trait RawSyscalls {
    // raw_yield should:
    //     1. Call syscall class 0
    //     2. Use register r0 for input and output as an inlateout register,
    //        passing in r0_in and returning its value.
    //     3. Mark all caller-saved registers as lateout clobbers.
    //     4. NOT provide any of the following options:
    //            pure             (yield has side effects)
    //            nomem            (a callback can read + write globals)
    //            readonly         (a callback can write globals)
    //            preserves_flags  (a callback can change flags)
    //            noreturn         (yield is expected to return)
    //            nostack          (a callback needs the stack)
    //
    // Design note: This is safe because the yield types that currently exist
    // are safe. If an unsafe yield type is added, we will need to make
    // raw_yield unsafe. Although raw_yield shouldn't be called by code outside
    // this crate, it can be, so that is a backwards-incompatible change. We
    // pass YieldType rather than a usize because if we used usize directly then
    // this API becomes unsound if the kernel adds support for an unsafe yield
    // type (or even one that takes one more argument).
    fn raw_yield(r0_in: YieldType) -> u32;

    // four_arg_syscall is used to invoke the subscribe, command, read-write
    // allow, and read-only allow system calls.
    //
    // four_arg_syscall's inline assembly should have the following properties:
    //     1. Calls the syscall class specified by class
    //     2. Passes r0-r3 in the corresponding registers as inlateout
    //        registers. Returns r0-r3 in order.
    //     3. Does not mark any registers as clobbered.
    //     4. Has all of the following options:
    //            preserves_flags  (these system calls do not touch flags)
    //            nostack          (these system calls do not touch the stack)
    //     5. Does NOT have any of the following options:
    //            pure      (these system calls have side effects)
    //            nomem     (the compiler needs to write to globals before allow)
    //            readonly  (rw allow can modify memory)
    //            noreturn  (all these system calls are expected to return)
    //
    // Note that subscribe's application data argument can potentially contain a
    // pointer, so r3 can contain a pointer (in addition to r1 and r2, which
    // more obviously contain pointers for subscribe and memop).
    //
    // For subscribe(), the callback pointer should be either 0 (for the null
    // callback) or an `unsafe extern fn(u32, u32, u32, usize)`.
    /// # Safety
    /// `four_arg_syscall` must NOT be used to invoke yield. Otherwise, it has
    /// the same safety invariants as the underlying system call, which varies
    /// depending on the system call class.
    unsafe fn four_arg_syscall(
        r0: u32,
        r1: u32,
        r2: usize,
        r3: usize,
        class: u8,
    ) -> (u32, usize, usize, usize);

    // zero_arg_memop is used to invoke memop operations that do not accept an
    // argument register. Because there are no memop commands that set r2 or r3,
    // this only needs to return r0 and r1.
    //
    // Memop commands may panic in the unit test environment, as not all memop
    // calls can be sensibly implemented in that environment.
    //
    // zero_arg_memop's inline assembly should have the following properties:
    //     1. Calls syscall class 5
    //     2. Specifies r0 as an inlateout register, and r1 as a lateout
    //        register.
    //     3. Does not mark any registers as clobbered.
    //     4. Has all of the following options:
    //            preserves_flags
    //            nostack
    //            nomem            (it is okay for the compiler to cache globals
    //                              across memop calls)
    //     5. Does NOT have any of the following options:
    //            pure      (two invocations of the same memop can return
    //                       different values)
    //            readonly  (incompatible with nomem)
    //            noreturn
    //
    // Design note: like raw_yield, this is safe because memops that currently
    // exist are safe. zero_arg_memop takes a ZeroArgMemop rather than a u32 so
    // that if the kernel adds an unsafe memop -- or one that can clobber r2/r3
    // --  this API doesn't become unsound.
    fn zero_arg_memop(r0_in: ZeroArgMemop) -> (u32, usize);

    // one_arg_memop is used to invoke memop operations that take an argument.
    // Because there are no memop operations that set r2 or r3, this only needs
    // to return r0 and r1.
    //
    // one_arg_memop's inline assembly should:
    //     1. Call syscall class 5
    //     2. Specify r0 and r1 as inlateout registers, and return (r0, r1)
    //     3. Not mark any registers as clobbered.
    //     4. Have all of the following options:
    //            preserves_flags
    //            nostack
    //            nomem            (the compiler can cache globals across memop
    //                              calls)
    //     5. Does NOT have any of the following options:
    //            pure      Two invocations of sbrk can return different values
    //            readonly  Incompatible with nomem
    //            noreturn
    //
    // Design note: like raw_yield, this is safe because memops that currently
    // exist are safe. zero_arg_memop takes a ZeroArgMemop rather than a u32 so
    // that if the kernel adds an unsafe memop -- or one that can clobber r2/r3
    // -- this API doesn't become unsound.
    fn one_arg_memop(r0_in: OneArgMemop, r1: usize) -> (u32, usize);
}

#[non_exhaustive]
#[repr(u32)]
pub enum OneArgMemop {
    Brk = 0,
    Sbrk = 1,
    FlashRegionStart = 8,
    FlashRegionEnd = 9,
    SpecifyStackTop = 10,
    SpecifyHeapStart = 11,
    // Note: before adding new memop operations, make sure the assumptions in
    // the design notes on `one_arg_memop` are valid for the new operation type.
}

// TODO: When the numeric values (0 and 1) are assigned to the yield types,
// specify those values here.
#[non_exhaustive]
#[repr(u32)]
pub enum YieldType {
    Wait,
    NoWait,
}

#[non_exhaustive]
#[repr(u32)]
pub enum ZeroArgMemop {
    MemoryStart = 2,
    MemoryEnd = 3,
    FlashStart = 4,
    FlashEnd = 5,
    GrantStart = 6,
    FlashRegions = 7,
    // Note: before adding new memop operations, make sure the assumptions in
    // the design notes on `zero_arg_memop` are valid for the new operation
    // type.
}
<commit_msg>Rewrite my explanation of the design of RawSyscalls based on Phil's feedback.<commit_after>// TODO: Implement `libtock_runtime` and `libtock_unittest`, which are
// referenced in the comment on `RawSyscalls`.

/// `RawSyscalls` allows a fake Tock kernel to be injected into components for
/// unit testing. It is implemented by `libtock_runtime::TockSyscalls` and
/// `libtock_unittest::FakeSyscalls`. Components should not use `RawSyscalls`
/// directly; instead, use the `Syscalls` trait, which provides higher-level
/// interfaces to the system calls.

// RawSyscalls is designed to minimize the amount of handwritten assembly code
// needed without generating unnecessary instructions. This comment describes
// the thought process that led to the choice of methods for RawSyscalls. There
// are a few major considerations affecting its design:
//
//     1. Most system calls only clobber r0-r4 (*), while yield has a far longer
//        clobber list. As such, yield must have its own assembly
//        implementation.
//     2. The compiler is unable to optimize away unused arguments. For example,
//        memop's "get process RAM start address" operation only needs r0 set,
//        while memop's "break" operation needs both r0 and r1 set. If our
//        inline assembly calls "get process RAM start address" but sets both r0
//        and r1, the compiler doesn't know that r1 will be
//        ignored so setting that register will not be optimized away. Therefore
//        we want to set the minimum number of argument registers possible.
//     3. The cost of specifying unused return registers is only that of
//        unnecessarily marking a register as clobbered. Explanation: After
//        inlining, an unused register is marked as "changed by the assembly"
//        but can immediately be re-used by the compiler, which is the same as a
//        clobbered register. System calls should generally be
//        inlined -- and even if they aren't, the unused return values will
//        probably be passed in caller-saved registers (this is true for the C
//        ABI, so probably true for the Rust ABI), which are treated as
//        clobbered regardless.
//
// (*) When this file refers to registers, it uses the same naming convention as
// the Tock 2.0 syscalls TRD. Registers r0-r4 correspond to ARM registers r0-r4
// and RISC-V registers a0-a4.
//
// Currently, yield takes exactly one argument (to specify what yield type to
// do). Therefore we only need one raw yield call.
//
// Based on these considerations, it would make sense to have the following
// methods:
//     yield
//     zero_arg_syscall
//     one_arg_syscall
//     two_arg_syscall
//     three_arg_syscall
//     four_arg_syscall
//
// However, there are no system calls that take 0 or 3 arguments, so we do not
// need the corresponding methods. This leaves yield, 1-arg, 2-arg, and 4-arg
// system calls.
//
// The 1-arg and 2-arg system calls are only used for memop. Memop currently has
// the property that none of its operations can lead to undefined behavior.
// Therefore, we can rename the 1-arg and 2-arg system calls to zero_arg_memop
// and one_arg_memop and make them safe methods (the argument counts change
// because the number of system call arguments is one greater than the number of
// arguments passed to the specific op).
//
// This only leaves four_arg_syscall, which is used to implement subscribe,
// command, read-write allow, and read-only allow.
//
// Therefore the final design has 4 methods:
//     yield
//     zero_arg_memop
//     one_arg_memop
//     four_arg_syscall
//
// If a new system call class that uses fewer than four arguments is added, then
// the above list will need to be revised.
//
// Note that `command` always needs to use four_arg_syscall, even when calling a
// command with fewer arguments, because we don't want to leak the
// (possibly secret) values in the r2 and r3 registers to untrusted capsules.
// Yield and memop do not have this concern and can leave arbitrary data in r2
// and r3, because they are implemented by the core kernel, which is trusted.
//
// The success type for Memop calls depends on the operation performed. However,
// all *currently defined* memop operations return either Success or Success
// with u32. Therefore, the memop implementations only need to mark r0 and r1 as
// clobbered, not r2 and r3. This choice of clobbers will need to be revisited
// if and when a memop operation that returns more data is added.
//
// The decision of where to use u32 and usize can be a bit tricky. The Tock
// syscall ABI is currently only specified for 32-bit systems, so on real Tock
// systems both types match the size of a register, but the unit test
// environment can be either 32 bit or 64 bit. This interface uses usize for
// values that can contain pointers, so that pointers are not truncated in the
// unit test environment. To keep types as consistent as possible, it uses u32
// for all values that cannot be pointers.
pub trait RawSyscalls {
    // raw_yield should:
    //     1. Call syscall class 0
    //     2. Use register r0 for input and output as an inlateout register,
    //        passing in r0_in and returning its value.
    //     3. Mark all caller-saved registers as lateout clobbers.
    //     4. NOT provide any of the following options:
    //            pure             (yield has side effects)
    //            nomem            (a callback can read + write globals)
    //            readonly         (a callback can write globals)
    //            preserves_flags  (a callback can change flags)
    //            noreturn         (yield is expected to return)
    //            nostack          (a callback needs the stack)
    //
    // Design note: This is safe because the yield types that currently exist
    // are safe. If an unsafe yield type is added, we will need to make
    // raw_yield unsafe. Although raw_yield shouldn't be called by code outside
    // this crate, it can be, so that is a backwards-incompatible change. We
    // pass YieldType rather than a usize because if we used usize directly then
    // this API becomes unsound if the kernel adds support for an unsafe yield
    // type (or even one that takes one more argument).
    fn raw_yield(r0_in: YieldType) -> u32;

    // four_arg_syscall is used to invoke the subscribe, command, read-write
    // allow, and read-only allow system calls.
    //
    // four_arg_syscall's inline assembly should have the following properties:
    //     1. Calls the syscall class specified by class
    //     2. Passes r0-r3 in the corresponding registers as inlateout
    //        registers. Returns r0-r3 in order.
    //     3. Does not mark any registers as clobbered.
    //     4. Has all of the following options:
    //            preserves_flags  (these system calls do not touch flags)
    //            nostack          (these system calls do not touch the stack)
    //     5. Does NOT have any of the following options:
    //            pure      (these system calls have side effects)
    //            nomem     (the compiler needs to write to globals before allow)
    //            readonly  (rw allow can modify memory)
    //            noreturn  (all these system calls are expected to return)
    //
    // Note that subscribe's application data argument can potentially contain a
    // pointer, so r3 can contain a pointer (in addition to r1 and r2, which
    // more obviously contain pointers for subscribe and memop).
    //
    // For subscribe(), the callback pointer should be either 0 (for the null
    // callback) or an `unsafe extern fn(u32, u32, u32, usize)`.
    /// # Safety
    /// `four_arg_syscall` must NOT be used to invoke yield. Otherwise, it has
    /// the same safety invariants as the underlying system call, which varies
    /// depending on the system call class.
    unsafe fn four_arg_syscall(
        r0: u32,
        r1: u32,
        r2: usize,
        r3: usize,
        class: u8,
    ) -> (u32, usize, usize, usize);

    // zero_arg_memop is used to invoke memop operations that do not accept an
    // argument register. Because there are no memop commands that set r2 or r3,
    // this only needs to return r0 and r1.
    //
    // Memop commands may panic in the unit test environment, as not all memop
    // calls can be sensibly implemented in that environment.
    //
    // zero_arg_memop's inline assembly should have the following properties:
    //     1. Calls syscall class 5
    //     2. Specifies r0 as an inlateout register, and r1 as a lateout
    //        register.
    //     3. Does not mark any registers as clobbered.
    //     4. Has all of the following options:
    //            preserves_flags
    //            nostack
    //            nomem            (it is okay for the compiler to cache globals
    //                              across memop calls)
    //     5. Does NOT have any of the following options:
    //            pure      (two invocations of the same memop can return
    //                       different values)
    //            readonly  (incompatible with nomem)
    //            noreturn
    //
    // Design note: like raw_yield, this is safe because memops that currently
    // exist are safe. zero_arg_memop takes a ZeroArgMemop rather than a u32 so
    // that if the kernel adds an unsafe memop -- or one that can clobber r2/r3
    // --  this API doesn't become unsound.
    fn zero_arg_memop(r0_in: ZeroArgMemop) -> (u32, usize);

    // one_arg_memop is used to invoke memop operations that take an argument.
    // Because there are no memop operations that set r2 or r3, this only needs
    // to return r0 and r1.
    //
    // one_arg_memop's inline assembly should:
    //     1. Call syscall class 5
    //     2. Specify r0 and r1 as inlateout registers, and return (r0, r1)
    //     3. Not mark any registers as clobbered.
    //     4. Have all of the following options:
    //            preserves_flags
    //            nostack
    //            nomem            (the compiler can cache globals across memop
    //                              calls)
    //     5. Does NOT have any of the following options:
    //            pure      Two invocations of sbrk can return different values
    //            readonly  Incompatible with nomem
    //            noreturn
    //
    // Design note: like raw_yield, this is safe because memops that currently
    // exist are safe. zero_arg_memop takes a ZeroArgMemop rather than a u32 so
    // that if the kernel adds an unsafe memop -- or one that can clobber r2/r3
    // -- this API doesn't become unsound.
    fn one_arg_memop(r0_in: OneArgMemop, r1: usize) -> (u32, usize);
}

#[non_exhaustive]
#[repr(u32)]
pub enum OneArgMemop {
    Brk = 0,
    Sbrk = 1,
    FlashRegionStart = 8,
    FlashRegionEnd = 9,
    SpecifyStackTop = 10,
    SpecifyHeapStart = 11,
    // Note: before adding new memop operations, make sure the assumptions in
    // the design notes on `one_arg_memop` are valid for the new operation type.
}

// TODO: When the numeric values (0 and 1) are assigned to the yield types,
// specify those values here.
#[non_exhaustive]
#[repr(u32)]
pub enum YieldType {
    Wait,
    NoWait,
}

#[non_exhaustive]
#[repr(u32)]
pub enum ZeroArgMemop {
    MemoryStart = 2,
    MemoryEnd = 3,
    FlashStart = 4,
    FlashEnd = 5,
    GrantStart = 6,
    FlashRegions = 7,
    // Note: before adding new memop operations, make sure the assumptions in
    // the design notes on `zero_arg_memop` are valid for the new operation
    // type.
}
<|endoftext|>


<commit_before>use std::convert::TryFrom;
use std::io::Read;
use std::path;

#[rustfmt::skip]
use ::image;

use crate::context::{Context, DebugId};
use crate::error::GameError;
use crate::error::GameResult;
use crate::filesystem;
use crate::graphics;
use crate::graphics::shader::*;
use crate::graphics::*;

/// Generic in-GPU-memory image data available to be drawn on the screen.
/// You probably just want to look at the `Image` type.
#[derive(Clone, PartialEq)]
pub struct ImageGeneric<B>
where
    B: BackendSpec,
{
    pub(crate) texture: gfx::handle::RawShaderResourceView<B::Resources>,
    pub(crate) texture_handle: gfx::handle::RawTexture<B::Resources>,
    pub(crate) sampler_info: gfx::texture::SamplerInfo,
    pub(crate) blend_mode: Option<BlendMode>,
    pub(crate) width: u16,
    pub(crate) height: u16,

    pub(crate) debug_id: DebugId,
}

impl<B> ImageGeneric<B>
where
    B: BackendSpec,
{
    /// A helper function that just takes a factory directly so we can make an image
    /// without needing the full context object, so we can create an Image while still
    /// creating the GraphicsContext.
    pub(crate) fn make_raw(
        factory: &mut <B as BackendSpec>::Factory,
        sampler_info: &texture::SamplerInfo,
        width: u16,
        height: u16,
        rgba: &[u8],
        color_format: gfx::format::Format,
        debug_id: DebugId,
    ) -> GameResult<Self> {
        if width == 0 || height == 0 {
            let msg = format!(
                "Tried to create a texture of size {}x{}, each dimension must
                be >0",
                width, height
            );
            return Err(GameError::ResourceLoadError(msg));
        }
        // Check for overflow, which might happen on 32-bit systems.
        // Textures can be max u16*u16, pixels, but then have 4 bytes per pixel.
        let uwidth = usize::from(width);
        let uheight = usize::from(height);
        let expected_bytes = uwidth
            .checked_mul(uheight)
            .and_then(|size| size.checked_mul(4))
            .ok_or_else(|| {
                let msg = format!(
                    "Integer overflow in Image::make_raw, image size: {} {}",
                    uwidth, uheight
                );
                GameError::ResourceLoadError(msg)
            })?;
        if expected_bytes != rgba.len() {
            let msg = format!(
                "Tried to create a texture of size {}x{}, but gave {} bytes of data (expected {})",
                width,
                height,
                rgba.len(),
                expected_bytes
            );
            return Err(GameError::ResourceLoadError(msg));
        }

        let kind = gfx::texture::Kind::D2(width, height, gfx::texture::AaMode::Single);
        use gfx::memory::Bind;
        let gfx::format::Format(surface_format, channel_type) = color_format;
        let texinfo = gfx::texture::Info {
            kind,
            levels: 1,
            format: surface_format,
            bind: Bind::SHADER_RESOURCE
                | Bind::RENDER_TARGET
                | Bind::TRANSFER_SRC
                | Bind::TRANSFER_DST,
            usage: gfx::memory::Usage::Dynamic,
        };
        let raw_tex = factory.create_texture_raw(
            texinfo,
            Some(channel_type),
            Some((&[&rgba], gfx::texture::Mipmap::Provided)),
        )?;
        let resource_desc = gfx::texture::ResourceDesc {
            channel: channel_type,
            layer: None,
            min: 0,
            max: raw_tex.get_info().levels - 1,
            swizzle: gfx::format::Swizzle::new(),
        };
        let raw_view = factory.view_texture_as_shader_resource_raw(&raw_tex, resource_desc)?;
        Ok(Self {
            texture: raw_view,
            texture_handle: raw_tex,
            sampler_info: *sampler_info,
            blend_mode: None,
            width,
            height,
            debug_id,
        })
    }

    /// A helper function to get the raw gfx texture handle
    pub fn get_raw_texture_handle(&self) -> gfx::handle::RawTexture<B::Resources> {
        self.texture_handle.clone()
    }

    /// A helper function to get the raw gfx texture view
    pub fn get_raw_texture_view(&self) -> gfx::handle::RawShaderResourceView<B::Resources> {
        self.texture.clone()
    }
}

/// In-GPU-memory image data available to be drawn on the screen,
/// using the OpenGL backend.
///
/// Under the hood this is just an `Arc`'ed texture handle and
/// some metadata, so cloning it is fairly cheap; it doesn't
/// make another copy of the underlying image data.
pub type Image = ImageGeneric<GlBackendSpec>;

/// The supported formats for saving an image.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ImageFormat {
    /// .png image format (defaults to RGBA with 8-bit channels.)
    Png,
}

impl Image {
    /// Load a new image from the file at the given path. The documentation for the
    /// [`filesystem`](../filesystem/index.html) module explains how the path must be specified.
    pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
        let mut buf = Vec::new();
        let mut reader = context.filesystem.open(path)?;
        let _ = reader.read_to_end(&mut buf)?;
        Self::from_bytes(context, buf)
    }

    /// Creates a new `Image` from the given buffer, which should contain an image encoded
    /// in a supported image file format.
    pub fn from_bytes(context: &mut Context, bytes: Vec<u8>) -> GameResult<Self> {
        let img = image::load_from_memory(&bytes)?.to_rgba8();
        let (width, height) = img.dimensions();
        let better_width = u16::try_from(width)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image width > u16::MAX")))?;
        let better_height = u16::try_from(height)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image height > u16::MAX")))?;
        Self::from_rgba8(context, better_width, better_height, &img.into_raw())
    }

    /// Creates a new `Image` from the given buffer of `u8` RGBA values.
    ///
    /// The pixel layout is row-major.  That is,
    /// the first 4 `u8` values make the top-left pixel in the `Image`, the
    /// next 4 make the next pixel in the same row, and so on to the end of
    /// the row.  The next `width * 4` values make up the second row, and so
    /// on.
    pub fn from_rgba8(
        context: &mut Context,
        width: u16,
        height: u16,
        rgba: &[u8],
    ) -> GameResult<Self> {
        let debug_id = DebugId::get(context);
        let color_format = context.gfx_context.color_format();
        Self::make_raw(
            &mut *context.gfx_context.factory,
            &context.gfx_context.default_sampler_info,
            width,
            height,
            rgba,
            color_format,
            debug_id,
        )
    }

    /// Dumps the `Image`'s data to a `Vec` of `u8` RGBA values.
    pub fn to_rgba8(&self, ctx: &mut Context) -> GameResult<Vec<u8>> {
        use gfx::memory::Typed;
        use gfx::traits::FactoryExt;

        let gfx = &mut ctx.gfx_context;
        let w = self.width;
        let h = self.height;

        // Note: In the GFX example, the download buffer is created ahead of time
        // and updated on screen resize events. This may be preferable, but then
        // the buffer also needs to be updated when we switch to/from a canvas.
        // Unsure of the performance impact of creating this as it is needed.
        // Probably okay for now though, since this probably won't be a super
        // common operation.
        let dl_buffer = gfx
            .factory
            .create_download_buffer::<u8>(usize::from(w) * usize::from(h) * 4)?;

        let factory = &mut *gfx.factory;
        let mut local_encoder = GlBackendSpec::encoder(factory);

        local_encoder.copy_texture_to_buffer_raw(
            &self.texture_handle,
            None,
            gfx::texture::RawImageInfo {
                xoffset: 0,
                yoffset: 0,
                zoffset: 0,
                width: w,
                height: h,
                depth: 0,
                format: gfx.color_format(),
                mipmap: 0,
            },
            dl_buffer.raw(),
            0,
        )?;
        local_encoder.flush(&mut *gfx.device);

        let reader = gfx.factory.read_mapping(&dl_buffer)?.to_vec();
        Ok(reader.to_vec())
    }

    /// Encode the `Image` to the given file format and
    /// write it out to the given path.
    ///
    /// See the [`filesystem`](../filesystem/index.html) module docs for where exactly
    /// the file will end up.
    pub fn encode<P: AsRef<path::Path>>(
        &self,
        ctx: &mut Context,
        format: ImageFormat,
        path: P,
    ) -> GameResult {
        use std::io;
        let data = self.to_rgba8(ctx)?;
        let f = filesystem::create(ctx, path)?;
        let writer = &mut io::BufWriter::new(f);
        let color_format = image::ColorType::Rgba8;
        match format {
            ImageFormat::Png => image::png::PngEncoder::new(writer)
                .encode(
                    &data,
                    u32::from(self.width),
                    u32::from(self.height),
                    color_format,
                )
                .map_err(Into::into),
        }
    }

    /// A little helper function that creates a new `Image` that is just
    /// a solid square of the given size and color.  Mainly useful for
    /// debugging.
    pub fn solid(context: &mut Context, size: u16, color: Color) -> GameResult<Self> {
        let (r, g, b, a) = color.into();
        let pixel_array: [u8; 4] = [r, g, b, a];
        let size_squared = usize::from(size) * usize::from(size);
        let mut buffer = Vec::with_capacity(size_squared);
        for _i in 0..size_squared {
            buffer.extend(&pixel_array[..]);
        }
        Image::from_rgba8(context, size, size, &buffer)
    }

    /// Return the width of the image.
    pub fn width(&self) -> u16 {
        self.width
    }

    /// Return the height of the image.
    pub fn height(&self) -> u16 {
        self.height
    }

    /// Get the filter mode for the image.
    pub fn filter(&self) -> FilterMode {
        self.sampler_info.filter.into()
    }

    /// Set the filter mode for the image.
    pub fn set_filter(&mut self, mode: FilterMode) {
        self.sampler_info.filter = mode.into();
    }

    /// Returns the dimensions of the image.
    pub fn dimensions(&self) -> Rect {
        Rect::new(0.0, 0.0, f32::from(self.width()), f32::from(self.height()))
    }

    /// Gets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn wrap(&self) -> (WrapMode, WrapMode) {
        (self.sampler_info.wrap_mode.0, self.sampler_info.wrap_mode.1)
    }

    /// Sets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn set_wrap(&mut self, wrap_x: WrapMode, wrap_y: WrapMode) {
        self.sampler_info.wrap_mode.0 = wrap_x;
        self.sampler_info.wrap_mode.1 = wrap_y;
    }
}

impl fmt::Debug for Image {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "<Image: {}x{}, {:p}, texture address {:p}, sampler: {:?}>",
            self.width(),
            self.height(),
            self,
            &self.texture,
            &self.sampler_info
        )
    }
}

impl Drawable for Image {
    fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
        self.debug_id.assert(ctx);

        let src_width = param.src.w;
        let src_height = param.src.h;
        // We have to mess with the scale to make everything
        // be its-unit-size-in-pixels.
        let scale_x = src_width * f32::from(self.width);
        let scale_y = src_height * f32::from(self.height);
        let new_param = match param.trans {
            Transform::Values { scale, .. } => param.scale(mint::Vector2 {
                x: scale.x * scale_x,
                y: scale.y * scale_y,
            }),
            Transform::Matrix(m) => param.transform(
                Matrix4::from(m) * Matrix4::from_scale(glam::vec3(scale_x, scale_y, 1.0)),
            ),
        };

        draw_image_raw(self, ctx, new_param)
    }

    fn dimensions(&self, _: &mut Context) -> Option<graphics::Rect> {
        Some(self.dimensions())
    }

    fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
        self.blend_mode = mode;
    }

    fn blend_mode(&self) -> Option<BlendMode> {
        self.blend_mode
    }
}

pub(crate) fn draw_image_raw(image: &Image, ctx: &mut Context, param: DrawParam) -> GameResult {
    let gfx = &mut ctx.gfx_context;

    gfx.update_instance_properties(param)?;
    let sampler = gfx
        .samplers
        .get_or_insert(image.sampler_info, gfx.factory.as_mut());
    gfx.data.vbuf = gfx.quad_vertex_buffer.clone();
    let typed_thingy = gfx
        .backend_spec
        .raw_to_typed_shader_resource(image.texture.clone());
    gfx.data.tex = (typed_thingy, sampler);
    let previous_mode: Option<BlendMode> = if let Some(mode) = image.blend_mode {
        let current_mode = gfx.blend_mode();
        if current_mode != mode {
            gfx.set_blend_mode(mode)?;
            Some(current_mode)
        } else {
            None
        }
    } else {
        None
    };

    gfx.draw(None)?;
    if let Some(mode) = previous_mode {
        gfx.set_blend_mode(mode)?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ContextBuilder;
    #[test]
    fn test_invalid_image_size() {
        let (ctx, _) = &mut ContextBuilder::new("unittest", "unittest").build().unwrap();
        let _i = assert!(Image::from_rgba8(ctx, 0, 0, &[]).is_err());
        let _i = assert!(Image::from_rgba8(ctx, 3432, 432, &[]).is_err());
        let _i = Image::from_rgba8(ctx, 2, 2, &[99; 16]).unwrap();
    }
}
<commit_msg>from_bytes should take a slice reference<commit_after>use std::convert::TryFrom;
use std::io::Read;
use std::path;

#[rustfmt::skip]
use ::image;

use crate::context::{Context, DebugId};
use crate::error::GameError;
use crate::error::GameResult;
use crate::filesystem;
use crate::graphics;
use crate::graphics::shader::*;
use crate::graphics::*;

/// Generic in-GPU-memory image data available to be drawn on the screen.
/// You probably just want to look at the `Image` type.
#[derive(Clone, PartialEq)]
pub struct ImageGeneric<B>
where
    B: BackendSpec,
{
    pub(crate) texture: gfx::handle::RawShaderResourceView<B::Resources>,
    pub(crate) texture_handle: gfx::handle::RawTexture<B::Resources>,
    pub(crate) sampler_info: gfx::texture::SamplerInfo,
    pub(crate) blend_mode: Option<BlendMode>,
    pub(crate) width: u16,
    pub(crate) height: u16,

    pub(crate) debug_id: DebugId,
}

impl<B> ImageGeneric<B>
where
    B: BackendSpec,
{
    /// A helper function that just takes a factory directly so we can make an image
    /// without needing the full context object, so we can create an Image while still
    /// creating the GraphicsContext.
    pub(crate) fn make_raw(
        factory: &mut <B as BackendSpec>::Factory,
        sampler_info: &texture::SamplerInfo,
        width: u16,
        height: u16,
        rgba: &[u8],
        color_format: gfx::format::Format,
        debug_id: DebugId,
    ) -> GameResult<Self> {
        if width == 0 || height == 0 {
            let msg = format!(
                "Tried to create a texture of size {}x{}, each dimension must
                be >0",
                width, height
            );
            return Err(GameError::ResourceLoadError(msg));
        }
        // Check for overflow, which might happen on 32-bit systems.
        // Textures can be max u16*u16, pixels, but then have 4 bytes per pixel.
        let uwidth = usize::from(width);
        let uheight = usize::from(height);
        let expected_bytes = uwidth
            .checked_mul(uheight)
            .and_then(|size| size.checked_mul(4))
            .ok_or_else(|| {
                let msg = format!(
                    "Integer overflow in Image::make_raw, image size: {} {}",
                    uwidth, uheight
                );
                GameError::ResourceLoadError(msg)
            })?;
        if expected_bytes != rgba.len() {
            let msg = format!(
                "Tried to create a texture of size {}x{}, but gave {} bytes of data (expected {})",
                width,
                height,
                rgba.len(),
                expected_bytes
            );
            return Err(GameError::ResourceLoadError(msg));
        }

        let kind = gfx::texture::Kind::D2(width, height, gfx::texture::AaMode::Single);
        use gfx::memory::Bind;
        let gfx::format::Format(surface_format, channel_type) = color_format;
        let texinfo = gfx::texture::Info {
            kind,
            levels: 1,
            format: surface_format,
            bind: Bind::SHADER_RESOURCE
                | Bind::RENDER_TARGET
                | Bind::TRANSFER_SRC
                | Bind::TRANSFER_DST,
            usage: gfx::memory::Usage::Dynamic,
        };
        let raw_tex = factory.create_texture_raw(
            texinfo,
            Some(channel_type),
            Some((&[&rgba], gfx::texture::Mipmap::Provided)),
        )?;
        let resource_desc = gfx::texture::ResourceDesc {
            channel: channel_type,
            layer: None,
            min: 0,
            max: raw_tex.get_info().levels - 1,
            swizzle: gfx::format::Swizzle::new(),
        };
        let raw_view = factory.view_texture_as_shader_resource_raw(&raw_tex, resource_desc)?;
        Ok(Self {
            texture: raw_view,
            texture_handle: raw_tex,
            sampler_info: *sampler_info,
            blend_mode: None,
            width,
            height,
            debug_id,
        })
    }

    /// A helper function to get the raw gfx texture handle
    pub fn get_raw_texture_handle(&self) -> gfx::handle::RawTexture<B::Resources> {
        self.texture_handle.clone()
    }

    /// A helper function to get the raw gfx texture view
    pub fn get_raw_texture_view(&self) -> gfx::handle::RawShaderResourceView<B::Resources> {
        self.texture.clone()
    }
}

/// In-GPU-memory image data available to be drawn on the screen,
/// using the OpenGL backend.
///
/// Under the hood this is just an `Arc`'ed texture handle and
/// some metadata, so cloning it is fairly cheap; it doesn't
/// make another copy of the underlying image data.
pub type Image = ImageGeneric<GlBackendSpec>;

/// The supported formats for saving an image.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ImageFormat {
    /// .png image format (defaults to RGBA with 8-bit channels.)
    Png,
}

impl Image {
    /// Load a new image from the file at the given path. The documentation for the
    /// [`filesystem`](../filesystem/index.html) module explains how the path must be specified.
    pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
        let mut buf = Vec::new();
        let mut reader = context.filesystem.open(path)?;
        let _ = reader.read_to_end(&mut buf)?;
        Self::from_bytes(context, &buf)
    }

    /// Creates a new `Image` from the given buffer, which should contain an image encoded
    /// in a supported image file format.
    pub fn from_bytes(context: &mut Context, bytes: &[u8]) -> GameResult<Self> {
        let img = image::load_from_memory(bytes)?.to_rgba8();
        let (width, height) = img.dimensions();
        let better_width = u16::try_from(width)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image width > u16::MAX")))?;
        let better_height = u16::try_from(height)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image height > u16::MAX")))?;
        Self::from_rgba8(context, better_width, better_height, &img.into_raw())
    }

    /// Creates a new `Image` from the given buffer of `u8` RGBA values.
    ///
    /// The pixel layout is row-major.  That is,
    /// the first 4 `u8` values make the top-left pixel in the `Image`, the
    /// next 4 make the next pixel in the same row, and so on to the end of
    /// the row.  The next `width * 4` values make up the second row, and so
    /// on.
    pub fn from_rgba8(
        context: &mut Context,
        width: u16,
        height: u16,
        rgba: &[u8],
    ) -> GameResult<Self> {
        let debug_id = DebugId::get(context);
        let color_format = context.gfx_context.color_format();
        Self::make_raw(
            &mut *context.gfx_context.factory,
            &context.gfx_context.default_sampler_info,
            width,
            height,
            rgba,
            color_format,
            debug_id,
        )
    }

    /// Dumps the `Image`'s data to a `Vec` of `u8` RGBA values.
    pub fn to_rgba8(&self, ctx: &mut Context) -> GameResult<Vec<u8>> {
        use gfx::memory::Typed;
        use gfx::traits::FactoryExt;

        let gfx = &mut ctx.gfx_context;
        let w = self.width;
        let h = self.height;

        // Note: In the GFX example, the download buffer is created ahead of time
        // and updated on screen resize events. This may be preferable, but then
        // the buffer also needs to be updated when we switch to/from a canvas.
        // Unsure of the performance impact of creating this as it is needed.
        // Probably okay for now though, since this probably won't be a super
        // common operation.
        let dl_buffer = gfx
            .factory
            .create_download_buffer::<u8>(usize::from(w) * usize::from(h) * 4)?;

        let factory = &mut *gfx.factory;
        let mut local_encoder = GlBackendSpec::encoder(factory);

        local_encoder.copy_texture_to_buffer_raw(
            &self.texture_handle,
            None,
            gfx::texture::RawImageInfo {
                xoffset: 0,
                yoffset: 0,
                zoffset: 0,
                width: w,
                height: h,
                depth: 0,
                format: gfx.color_format(),
                mipmap: 0,
            },
            dl_buffer.raw(),
            0,
        )?;
        local_encoder.flush(&mut *gfx.device);

        let reader = gfx.factory.read_mapping(&dl_buffer)?.to_vec();
        Ok(reader.to_vec())
    }

    /// Encode the `Image` to the given file format and
    /// write it out to the given path.
    ///
    /// See the [`filesystem`](../filesystem/index.html) module docs for where exactly
    /// the file will end up.
    pub fn encode<P: AsRef<path::Path>>(
        &self,
        ctx: &mut Context,
        format: ImageFormat,
        path: P,
    ) -> GameResult {
        use std::io;
        let data = self.to_rgba8(ctx)?;
        let f = filesystem::create(ctx, path)?;
        let writer = &mut io::BufWriter::new(f);
        let color_format = image::ColorType::Rgba8;
        match format {
            ImageFormat::Png => image::png::PngEncoder::new(writer)
                .encode(
                    &data,
                    u32::from(self.width),
                    u32::from(self.height),
                    color_format,
                )
                .map_err(Into::into),
        }
    }

    /// A little helper function that creates a new `Image` that is just
    /// a solid square of the given size and color.  Mainly useful for
    /// debugging.
    pub fn solid(context: &mut Context, size: u16, color: Color) -> GameResult<Self> {
        let (r, g, b, a) = color.into();
        let pixel_array: [u8; 4] = [r, g, b, a];
        let size_squared = usize::from(size) * usize::from(size);
        let mut buffer = Vec::with_capacity(size_squared);
        for _i in 0..size_squared {
            buffer.extend(&pixel_array[..]);
        }
        Image::from_rgba8(context, size, size, &buffer)
    }

    /// Return the width of the image.
    pub fn width(&self) -> u16 {
        self.width
    }

    /// Return the height of the image.
    pub fn height(&self) -> u16 {
        self.height
    }

    /// Get the filter mode for the image.
    pub fn filter(&self) -> FilterMode {
        self.sampler_info.filter.into()
    }

    /// Set the filter mode for the image.
    pub fn set_filter(&mut self, mode: FilterMode) {
        self.sampler_info.filter = mode.into();
    }

    /// Returns the dimensions of the image.
    pub fn dimensions(&self) -> Rect {
        Rect::new(0.0, 0.0, f32::from(self.width()), f32::from(self.height()))
    }

    /// Gets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn wrap(&self) -> (WrapMode, WrapMode) {
        (self.sampler_info.wrap_mode.0, self.sampler_info.wrap_mode.1)
    }

    /// Sets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn set_wrap(&mut self, wrap_x: WrapMode, wrap_y: WrapMode) {
        self.sampler_info.wrap_mode.0 = wrap_x;
        self.sampler_info.wrap_mode.1 = wrap_y;
    }
}

impl fmt::Debug for Image {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "<Image: {}x{}, {:p}, texture address {:p}, sampler: {:?}>",
            self.width(),
            self.height(),
            self,
            &self.texture,
            &self.sampler_info
        )
    }
}

impl Drawable for Image {
    fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
        self.debug_id.assert(ctx);

        let src_width = param.src.w;
        let src_height = param.src.h;
        // We have to mess with the scale to make everything
        // be its-unit-size-in-pixels.
        let scale_x = src_width * f32::from(self.width);
        let scale_y = src_height * f32::from(self.height);
        let new_param = match param.trans {
            Transform::Values { scale, .. } => param.scale(mint::Vector2 {
                x: scale.x * scale_x,
                y: scale.y * scale_y,
            }),
            Transform::Matrix(m) => param.transform(
                Matrix4::from(m) * Matrix4::from_scale(glam::vec3(scale_x, scale_y, 1.0)),
            ),
        };

        draw_image_raw(self, ctx, new_param)
    }

    fn dimensions(&self, _: &mut Context) -> Option<graphics::Rect> {
        Some(self.dimensions())
    }

    fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
        self.blend_mode = mode;
    }

    fn blend_mode(&self) -> Option<BlendMode> {
        self.blend_mode
    }
}

pub(crate) fn draw_image_raw(image: &Image, ctx: &mut Context, param: DrawParam) -> GameResult {
    let gfx = &mut ctx.gfx_context;

    gfx.update_instance_properties(param)?;
    let sampler = gfx
        .samplers
        .get_or_insert(image.sampler_info, gfx.factory.as_mut());
    gfx.data.vbuf = gfx.quad_vertex_buffer.clone();
    let typed_thingy = gfx
        .backend_spec
        .raw_to_typed_shader_resource(image.texture.clone());
    gfx.data.tex = (typed_thingy, sampler);
    let previous_mode: Option<BlendMode> = if let Some(mode) = image.blend_mode {
        let current_mode = gfx.blend_mode();
        if current_mode != mode {
            gfx.set_blend_mode(mode)?;
            Some(current_mode)
        } else {
            None
        }
    } else {
        None
    };

    gfx.draw(None)?;
    if let Some(mode) = previous_mode {
        gfx.set_blend_mode(mode)?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ContextBuilder;
    #[test]
    fn test_invalid_image_size() {
        let (ctx, _) = &mut ContextBuilder::new("unittest", "unittest").build().unwrap();
        let _i = assert!(Image::from_rgba8(ctx, 0, 0, &[]).is_err());
        let _i = assert!(Image::from_rgba8(ctx, 3432, 432, &[]).is_err());
        let _i = Image::from_rgba8(ctx, 2, 2, &[99; 16]).unwrap();
    }
}
<|endoftext|>


<commit_before>use anyhow::*;
use ape;
use id3;
use lewton::inside_ogg::OggStreamReader;
use log::error;
use metaflac;
use mp3_duration;
use mp4ameta;
use regex::Regex;
use std::fs;
use std::path::Path;
use opus_headers;

use crate::utils;
use crate::utils::AudioFormat;

#[derive(Debug, Clone, PartialEq)]
pub struct SongTags {
	pub disc_number: Option<u32>,
	pub track_number: Option<u32>,
	pub title: Option<String>,
	pub duration: Option<u32>,
	pub artist: Option<String>,
	pub album_artist: Option<String>,
	pub album: Option<String>,
	pub year: Option<i32>,
}

#[cfg_attr(feature = "profile-index", flame)]
pub fn read(path: &Path) -> Option<SongTags> {
	let data = match utils::get_audio_format(path) {
		Some(AudioFormat::FLAC) => Some(read_flac(path)),
		Some(AudioFormat::MP3) => Some(read_id3(path)),
		Some(AudioFormat::MP4) => Some(read_mp4(path)),
		Some(AudioFormat::MPC) => Some(read_ape(path)),
		Some(AudioFormat::OGG) => Some(read_vorbis(path)),
		Some(AudioFormat::OPUS) => Some(read_opus(path)),
		_ => None,
	};
	match data {
		Some(Ok(d)) => Some(d),
		Some(Err(e)) => {
			error!("Error while reading file metadata for '{:?}': {}", path, e);
			None
		}
		None => None,
	}
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_id3(path: &Path) -> Result<SongTags> {
	let tag = {
		#[cfg(feature = "profile-index")]
		let _guard = flame::start_guard("id3_tag_read");
		match id3::Tag::read_from_path(&path) {
			Ok(t) => Ok(t),
			Err(e) => {
				if let Some(t) = e.partial_tag {
					Ok(t)
				} else {
					Err(e)
				}
			}
		}?
	};
	let duration = {
		#[cfg(feature = "profile-index")]
		let _guard = flame::start_guard("mp3_duration");
		mp3_duration::from_path(&path)
			.map(|d| d.as_secs() as u32)
			.ok()
	};

	let artist = tag.artist().map(|s| s.to_string());
	let album_artist = tag.album_artist().map(|s| s.to_string());
	let album = tag.album().map(|s| s.to_string());
	let title = tag.title().map(|s| s.to_string());
	let disc_number = tag.disc();
	let track_number = tag.track();
	let year = tag
		.year()
		.map(|y| y as i32)
		.or_else(|| tag.date_released().and_then(|d| Some(d.year)))
		.or_else(|| tag.date_recorded().and_then(|d| Some(d.year)));

	Ok(SongTags {
		artist,
		album_artist,
		album,
		title,
		duration,
		disc_number,
		track_number,
		year,
	})
}

fn read_ape_string(item: &ape::Item) -> Option<String> {
	match item.value {
		ape::ItemValue::Text(ref s) => Some(s.clone()),
		_ => None,
	}
}

fn read_ape_i32(item: &ape::Item) -> Option<i32> {
	match item.value {
		ape::ItemValue::Text(ref s) => s.parse::<i32>().ok(),
		_ => None,
	}
}

fn read_ape_x_of_y(item: &ape::Item) -> Option<u32> {
	match item.value {
		ape::ItemValue::Text(ref s) => {
			let format = Regex::new(r#"^\d+"#).unwrap();
			if let Some(m) = format.find(s) {
				s[m.start()..m.end()].parse().ok()
			} else {
				None
			}
		}
		_ => None,
	}
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_ape(path: &Path) -> Result<SongTags> {
	let tag = ape::read(path)?;
	let artist = tag.item("Artist").and_then(read_ape_string);
	let album = tag.item("Album").and_then(read_ape_string);
	let album_artist = tag.item("Album artist").and_then(read_ape_string);
	let title = tag.item("Title").and_then(read_ape_string);
	let year = tag.item("Year").and_then(read_ape_i32);
	let disc_number = tag.item("Disc").and_then(read_ape_x_of_y);
	let track_number = tag.item("Track").and_then(read_ape_x_of_y);
	Ok(SongTags {
		artist,
		album_artist,
		album,
		title,
		duration: None,
		disc_number,
		track_number,
		year,
	})
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_vorbis(path: &Path) -> Result<SongTags> {
	let file = fs::File::open(path)?;
	let source = OggStreamReader::new(file)?;

	let mut tags = SongTags {
		artist: None,
		album_artist: None,
		album: None,
		title: None,
		duration: None,
		disc_number: None,
		track_number: None,
		year: None,
	};

	for (key, value) in source.comment_hdr.comment_list {
		match key.as_str() {
			"TITLE" => tags.title = Some(value),
			"ALBUM" => tags.album = Some(value),
			"ARTIST" => tags.artist = Some(value),
			"ALBUMARTIST" => tags.album_artist = Some(value),
			"TRACKNUMBER" => tags.track_number = value.parse::<u32>().ok(),
			"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
			"DATE" => tags.year = value.parse::<i32>().ok(),
			_ => (),
		}
	}

	Ok(tags)
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_opus(path: &Path) -> Result<SongTags> {
	let headers = opus_headers::parse_from_path(path)?;

	let mut tags = SongTags {
		artist: None,
		album_artist: None,
		album: None,
		title: None,
		duration: None,
		disc_number: None,
		track_number: None,
		year: None,
	};

	for (key, value) in headers.comments.user_comments {
		match key.as_str().to_uppercase().as_str() {
			"TITLE" => tags.title = Some(value),
			"ALBUM" => tags.album = Some(value),
			"ARTIST" => tags.artist = Some(value),
			"ALBUMARTIST" => tags.album_artist = Some(value),
			"TRACKNUMBER" => tags.track_number = value.parse::<u32>().ok(),
			"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
			"DATE" => tags.year = value.parse::<i32>().ok(),
			_ => (),
		}
	}

	Ok(tags)
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_flac(path: &Path) -> Result<SongTags> {
	let tag = metaflac::Tag::read_from_path(path)?;
	let vorbis = tag
		.vorbis_comments()
		.ok_or(anyhow!("Missing Vorbis comments"))?;
	let disc_number = vorbis
		.get("DISCNUMBER")
		.and_then(|d| d[0].parse::<u32>().ok());
	let year = vorbis.get("DATE").and_then(|d| d[0].parse::<i32>().ok());
	let mut streaminfo = tag.get_blocks(metaflac::BlockType::StreamInfo);
	let duration = match streaminfo.next() {
		Some(&metaflac::Block::StreamInfo(ref s)) => {
			Some((s.total_samples as u32 / s.sample_rate) as u32)
		}
		_ => None,
	};

	Ok(SongTags {
		artist: vorbis.artist().map(|v| v[0].clone()),
		album_artist: vorbis.album_artist().map(|v| v[0].clone()),
		album: vorbis.album().map(|v| v[0].clone()),
		title: vorbis.title().map(|v| v[0].clone()),
		duration,
		disc_number,
		track_number: vorbis.track(),
		year,
	})
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_mp4(path: &Path) -> Result<SongTags> {
	let tag = mp4ameta::Tag::read_from_path(path)?;
	let mut tags = SongTags {
		artist: None,
		album_artist: None,
		album: None,
		title: None,
		duration: None,
		disc_number: None,
		track_number: None,
		year: None,
	};
	tags.artist = tag.artist().map(|v| v.to_string());
	tags.album_artist = tag.album_artist().map(|v| v.to_string());
	tags.album = tag.album().map(|v| v.to_string());
	tags.title = tag.title().map(|v| v.to_string());
	tags.duration = tag.duration().map(|v| v as u32);
	tags.disc_number = tag.disk_number().0.and_then(|d| Some(d as u32));
	tags.track_number = tag.track_number().0.and_then(|d| Some(d as u32));
	tags.year = tag.year().and_then(|v| v.parse::<i32>().ok());

	Ok(tags)
}

#[test]
fn test_read_metadata() {
	let sample_tags = SongTags {
		disc_number: Some(3),
		track_number: Some(1),
		title: Some("TEST TITLE".into()),
		artist: Some("TEST ARTIST".into()),
		album_artist: Some("TEST ALBUM ARTIST".into()),
		album: Some("TEST ALBUM".into()),
		duration: None,
		year: Some(2016),
	};
	let flac_sample_tag = SongTags {
		duration: Some(0),
		..sample_tags.clone()
	};
	let mp3_sample_tag = SongTags {
		duration: Some(0),
		..sample_tags.clone()
	};
	let m4a_sample_tag = SongTags {
		duration: Some(0),
		..sample_tags.clone()
	};
	assert_eq!(read(Path::new("test/sample.mp3")).unwrap(), mp3_sample_tag);
	assert_eq!(read(Path::new("test/sample.ogg")).unwrap(), sample_tags);
	assert_eq!(read(Path::new("test/sample.flac")).unwrap(), flac_sample_tag);
	assert_eq!(read(Path::new("test/sample.m4a")).unwrap(), m4a_sample_tag);
}
<commit_msg>remove redundant to_str<commit_after>use anyhow::*;
use ape;
use id3;
use lewton::inside_ogg::OggStreamReader;
use log::error;
use metaflac;
use mp3_duration;
use mp4ameta;
use regex::Regex;
use std::fs;
use std::path::Path;
use opus_headers;

use crate::utils;
use crate::utils::AudioFormat;

#[derive(Debug, Clone, PartialEq)]
pub struct SongTags {
	pub disc_number: Option<u32>,
	pub track_number: Option<u32>,
	pub title: Option<String>,
	pub duration: Option<u32>,
	pub artist: Option<String>,
	pub album_artist: Option<String>,
	pub album: Option<String>,
	pub year: Option<i32>,
}

#[cfg_attr(feature = "profile-index", flame)]
pub fn read(path: &Path) -> Option<SongTags> {
	let data = match utils::get_audio_format(path) {
		Some(AudioFormat::FLAC) => Some(read_flac(path)),
		Some(AudioFormat::MP3) => Some(read_id3(path)),
		Some(AudioFormat::MP4) => Some(read_mp4(path)),
		Some(AudioFormat::MPC) => Some(read_ape(path)),
		Some(AudioFormat::OGG) => Some(read_vorbis(path)),
		Some(AudioFormat::OPUS) => Some(read_opus(path)),
		_ => None,
	};
	match data {
		Some(Ok(d)) => Some(d),
		Some(Err(e)) => {
			error!("Error while reading file metadata for '{:?}': {}", path, e);
			None
		}
		None => None,
	}
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_id3(path: &Path) -> Result<SongTags> {
	let tag = {
		#[cfg(feature = "profile-index")]
		let _guard = flame::start_guard("id3_tag_read");
		match id3::Tag::read_from_path(&path) {
			Ok(t) => Ok(t),
			Err(e) => {
				if let Some(t) = e.partial_tag {
					Ok(t)
				} else {
					Err(e)
				}
			}
		}?
	};
	let duration = {
		#[cfg(feature = "profile-index")]
		let _guard = flame::start_guard("mp3_duration");
		mp3_duration::from_path(&path)
			.map(|d| d.as_secs() as u32)
			.ok()
	};

	let artist = tag.artist().map(|s| s.to_string());
	let album_artist = tag.album_artist().map(|s| s.to_string());
	let album = tag.album().map(|s| s.to_string());
	let title = tag.title().map(|s| s.to_string());
	let disc_number = tag.disc();
	let track_number = tag.track();
	let year = tag
		.year()
		.map(|y| y as i32)
		.or_else(|| tag.date_released().and_then(|d| Some(d.year)))
		.or_else(|| tag.date_recorded().and_then(|d| Some(d.year)));

	Ok(SongTags {
		artist,
		album_artist,
		album,
		title,
		duration,
		disc_number,
		track_number,
		year,
	})
}

fn read_ape_string(item: &ape::Item) -> Option<String> {
	match item.value {
		ape::ItemValue::Text(ref s) => Some(s.clone()),
		_ => None,
	}
}

fn read_ape_i32(item: &ape::Item) -> Option<i32> {
	match item.value {
		ape::ItemValue::Text(ref s) => s.parse::<i32>().ok(),
		_ => None,
	}
}

fn read_ape_x_of_y(item: &ape::Item) -> Option<u32> {
	match item.value {
		ape::ItemValue::Text(ref s) => {
			let format = Regex::new(r#"^\d+"#).unwrap();
			if let Some(m) = format.find(s) {
				s[m.start()..m.end()].parse().ok()
			} else {
				None
			}
		}
		_ => None,
	}
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_ape(path: &Path) -> Result<SongTags> {
	let tag = ape::read(path)?;
	let artist = tag.item("Artist").and_then(read_ape_string);
	let album = tag.item("Album").and_then(read_ape_string);
	let album_artist = tag.item("Album artist").and_then(read_ape_string);
	let title = tag.item("Title").and_then(read_ape_string);
	let year = tag.item("Year").and_then(read_ape_i32);
	let disc_number = tag.item("Disc").and_then(read_ape_x_of_y);
	let track_number = tag.item("Track").and_then(read_ape_x_of_y);
	Ok(SongTags {
		artist,
		album_artist,
		album,
		title,
		duration: None,
		disc_number,
		track_number,
		year,
	})
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_vorbis(path: &Path) -> Result<SongTags> {
	let file = fs::File::open(path)?;
	let source = OggStreamReader::new(file)?;

	let mut tags = SongTags {
		artist: None,
		album_artist: None,
		album: None,
		title: None,
		duration: None,
		disc_number: None,
		track_number: None,
		year: None,
	};

	for (key, value) in source.comment_hdr.comment_list {
		match key.as_str() {
			"TITLE" => tags.title = Some(value),
			"ALBUM" => tags.album = Some(value),
			"ARTIST" => tags.artist = Some(value),
			"ALBUMARTIST" => tags.album_artist = Some(value),
			"TRACKNUMBER" => tags.track_number = value.parse::<u32>().ok(),
			"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
			"DATE" => tags.year = value.parse::<i32>().ok(),
			_ => (),
		}
	}

	Ok(tags)
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_opus(path: &Path) -> Result<SongTags> {
	let headers = opus_headers::parse_from_path(path)?;

	let mut tags = SongTags {
		artist: None,
		album_artist: None,
		album: None,
		title: None,
		duration: None,
		disc_number: None,
		track_number: None,
		year: None,
	};

	for (key, value) in headers.comments.user_comments {
		match key.to_uppercase().as_str() {
			"TITLE" => tags.title = Some(value),
			"ALBUM" => tags.album = Some(value),
			"ARTIST" => tags.artist = Some(value),
			"ALBUMARTIST" => tags.album_artist = Some(value),
			"TRACKNUMBER" => tags.track_number = value.parse::<u32>().ok(),
			"DISCNUMBER" => tags.disc_number = value.parse::<u32>().ok(),
			"DATE" => tags.year = value.parse::<i32>().ok(),
			_ => (),
		}
	}

	Ok(tags)
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_flac(path: &Path) -> Result<SongTags> {
	let tag = metaflac::Tag::read_from_path(path)?;
	let vorbis = tag
		.vorbis_comments()
		.ok_or(anyhow!("Missing Vorbis comments"))?;
	let disc_number = vorbis
		.get("DISCNUMBER")
		.and_then(|d| d[0].parse::<u32>().ok());
	let year = vorbis.get("DATE").and_then(|d| d[0].parse::<i32>().ok());
	let mut streaminfo = tag.get_blocks(metaflac::BlockType::StreamInfo);
	let duration = match streaminfo.next() {
		Some(&metaflac::Block::StreamInfo(ref s)) => {
			Some((s.total_samples as u32 / s.sample_rate) as u32)
		}
		_ => None,
	};

	Ok(SongTags {
		artist: vorbis.artist().map(|v| v[0].clone()),
		album_artist: vorbis.album_artist().map(|v| v[0].clone()),
		album: vorbis.album().map(|v| v[0].clone()),
		title: vorbis.title().map(|v| v[0].clone()),
		duration,
		disc_number,
		track_number: vorbis.track(),
		year,
	})
}

#[cfg_attr(feature = "profile-index", flame)]
fn read_mp4(path: &Path) -> Result<SongTags> {
	let tag = mp4ameta::Tag::read_from_path(path)?;
	let mut tags = SongTags {
		artist: None,
		album_artist: None,
		album: None,
		title: None,
		duration: None,
		disc_number: None,
		track_number: None,
		year: None,
	};
	tags.artist = tag.artist().map(|v| v.to_string());
	tags.album_artist = tag.album_artist().map(|v| v.to_string());
	tags.album = tag.album().map(|v| v.to_string());
	tags.title = tag.title().map(|v| v.to_string());
	tags.duration = tag.duration().map(|v| v as u32);
	tags.disc_number = tag.disk_number().0.and_then(|d| Some(d as u32));
	tags.track_number = tag.track_number().0.and_then(|d| Some(d as u32));
	tags.year = tag.year().and_then(|v| v.parse::<i32>().ok());

	Ok(tags)
}

#[test]
fn test_read_metadata() {
	let sample_tags = SongTags {
		disc_number: Some(3),
		track_number: Some(1),
		title: Some("TEST TITLE".into()),
		artist: Some("TEST ARTIST".into()),
		album_artist: Some("TEST ALBUM ARTIST".into()),
		album: Some("TEST ALBUM".into()),
		duration: None,
		year: Some(2016),
	};
	let flac_sample_tag = SongTags {
		duration: Some(0),
		..sample_tags.clone()
	};
	let mp3_sample_tag = SongTags {
		duration: Some(0),
		..sample_tags.clone()
	};
	let m4a_sample_tag = SongTags {
		duration: Some(0),
		..sample_tags.clone()
	};
	assert_eq!(read(Path::new("test/sample.mp3")).unwrap(), mp3_sample_tag);
	assert_eq!(read(Path::new("test/sample.ogg")).unwrap(), sample_tags);
	assert_eq!(read(Path::new("test/sample.flac")).unwrap(), flac_sample_tag);
	assert_eq!(read(Path::new("test/sample.m4a")).unwrap(), m4a_sample_tag);
}
<|endoftext|>


<commit_before>extern crate getopts;
extern crate rusqlite;
extern crate pibq;

use std::env;
use std::time::{Duration, Instant};
use getopts::Options;

use pibq::bluetherm;
use pibq::sql;
use pibq::models::{ConnectionStatus, Reading};

// interval between sending query packtets, in ms
const QUERY_INTERVAL: u64 = 3000;

// if no packets are returned, amount of time to wait before creating a timeout error, in ms
const TIMEOUT_INTERVAL: u64 = 15000;

const HEARTBEAT_INTERVAL: u64 = 1000;

struct Harvester {
    sql_conn: rusqlite::Connection,
    bt_conn: Option<bluetherm::Connection>,
    serial: String,
    disconnected: bool,
    disconnect_reason: Option<bluetherm::ConnectionEvent>,
    send_interval: Duration,
    timeout_interval: Duration,
    last_send: Option<Instant>,
    last_receive: Option<Instant>
}

impl Harvester {
    fn new(sql_conn: rusqlite::Connection, serial: &str) -> Harvester {
        Harvester {
            sql_conn: sql_conn,
            bt_conn: Some(Harvester::connect_bluetherm(serial)),
            serial: serial.to_string(),
            disconnected: true,
            disconnect_reason: None,
            send_interval: Duration::from_millis(QUERY_INTERVAL),
            timeout_interval: Duration::from_millis(TIMEOUT_INTERVAL),
            last_send: None,
            last_receive: None
        }
    }

    fn connect_bluetherm(serial: &str) -> bluetherm::Connection {
        bluetherm::Connection::new(serial, Some(HEARTBEAT_INTERVAL)).unwrap()
    }

    fn start(&mut self) {
        loop {
            match (self.last_send, self.last_receive) {
                (Some(sent), _) if sent.elapsed() < self.send_interval => {},
                (Some(sent), Some(received)) if sent > received => {},
                _ => {
                    self.send_packet();
                    self.last_send = Some(Instant::now());
                }
            }

            let event = self.get_bt_conn().wait().unwrap();

            println!("{}", event);
            match event {
                bluetherm::ConnectionEvent::Packet(p) => {
                    self.record_packet(p);
                    self.last_send = Some(Instant::now());
                    self.last_receive = Some(Instant::now());
                },
                e @ bluetherm::ConnectionEvent::InvalidPacket(_) => { self.bt_error(e); },
                e @ bluetherm::ConnectionEvent::ReadError(_) => { self.bt_error(e); },
                e @ bluetherm::ConnectionEvent::BadData(_) => { self.bt_error(e); },
                e @ bluetherm::ConnectionEvent::Heartbeat => {
                    match self.last_receive {
                        None => {
                            match self.last_send {
                                None => {},
                                Some(sent) => {
                                    if sent.elapsed() > self.timeout_interval {
                                        self.bt_error(e);
                                    }
                                }
                            }
                        },
                        Some(rec) => {
                            if rec.elapsed() > self.timeout_interval {
                                self.bt_error(e);
                            }
                        }
                    }
                }
            }
        }
    }

    fn get_bt_conn(&mut self) -> &mut bluetherm::Connection {
        self.bt_conn.as_mut().unwrap()
    }

    fn record_packet(&mut self, packet: bluetherm::Packet) {
        let mut reading = Reading::new();
        reading.value1 = packet.get_sensor1_reading();
        reading.value2 = packet.get_sensor2_reading();
        sql::insert_reading(&self.sql_conn, &mut reading).unwrap();
    }

    fn send_packet(&mut self) {
        println!("sending packet...");
        let p = bluetherm::Packet::temp_packet();
        match self.get_bt_conn().send(&p) {
            Err(e) => {
                self.bt_error(bluetherm::ConnectionEvent::ReadError(e));
            },
            Ok(_) => {}
        }
        println!("done sending packet.");
    }

    fn bt_error(&mut self, evt: bluetherm::ConnectionEvent) {
        let mut report_error = true;

        if self.disconnected {
            match &self.disconnect_reason {
                &None => {},
                &Some(ref reason) => {
                    match (reason, &evt) {
                        (&bluetherm::ConnectionEvent::InvalidPacket(_), &bluetherm::ConnectionEvent::InvalidPacket(_)) |
                        (&bluetherm::ConnectionEvent::ReadError(_), &bluetherm::ConnectionEvent::ReadError(_)) |
                        (&bluetherm::ConnectionEvent::BadData(_), &bluetherm::ConnectionEvent::BadData(_)) |
                        (&bluetherm::ConnectionEvent::Heartbeat, &bluetherm::ConnectionEvent::Heartbeat) => {
                            report_error = false;
                        },
                        _ => {},
                    }
                }
            }
        }

        if report_error {
            let msg = match evt {
                bluetherm::ConnectionEvent::InvalidPacket(ref p) => { format!("Invalid Packet: [{}]", p) },
                bluetherm::ConnectionEvent::ReadError(ref err) => { format!("Read Error: {}", err) },
                bluetherm::ConnectionEvent::BadData(_) => { format!("Bad Data Read") },
                bluetherm::ConnectionEvent::Heartbeat => { "Timeout".to_string() },
                _ => "Unknown Error".to_string()
            };

            let mut s = ConnectionStatus::new();
            s.is_disconnect = true;
            s.info = Some(msg);
            sql::insert_connection_status(&self.sql_conn, &mut s).unwrap();
        }

        self.disconnected = true;
        self.last_send = None;
        self.last_receive = None;

        println!("error, killing old connection. {}", evt);

        let old = self.bt_conn.take();
        drop(old.unwrap());

        println!("old conneciton dropped; making new");

        self.bt_conn = Some(Harvester::connect_bluetherm(&self.serial));

        println!("new connection made");
    }
}

// fn harvest(sql_conn: rusqlite::Connection, serial: &str) {
//
//     loop {
//         // if disconnected {
//         //     bt_conn = bluetherm::Connection::new(serial, Some(1000)).unwrap();
//         //     let mut s = ConnectionStatus::new();
//         //     s.is_disconnect = true;
//         //     sql::insert_connection_status(&sql_conn, &mut s).unwrap();
//         // }
//
//
//     }
//
// }

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.reqopt("s", "serial", "tty serial device", "DEV");
    opts.optopt("d", "dbfile", "sqlite DB file", "FILE");
    opts.optopt("m", "migrations", "migration folder", "DIR");
    opts.optflag("h", "help", "print this help menu");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => {
            println!("{}", f.to_string());
            print_usage(&program, opts);
            return;
        }
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }
    let serial = matches.opt_str("s").unwrap();
    let dbfile = matches.opt_str("d").unwrap_or("pibq.sqlite".to_string());
    let migrations = matches.opt_str("m").unwrap_or("migrations".to_string());

    let db = sql::get_connection(&dbfile, Some(migrations)).unwrap();

    let mut h = Harvester::new(db, &serial);
    h.start();
}
<commit_msg>changed packet send rate<commit_after>extern crate getopts;
extern crate rusqlite;
extern crate pibq;

use std::env;
use std::time::{Duration, Instant};
use getopts::Options;

use pibq::bluetherm;
use pibq::sql;
use pibq::models::{ConnectionStatus, Reading};

// interval between sending query packtets, in ms
const QUERY_INTERVAL: u64 = 3000;

// if no packets are returned, amount of time to wait before creating a timeout error, in ms
const TIMEOUT_INTERVAL: u64 = 15000;

const HEARTBEAT_INTERVAL: u64 = 1000;

struct Harvester {
    sql_conn: rusqlite::Connection,
    bt_conn: Option<bluetherm::Connection>,
    serial: String,
    disconnected: bool,
    disconnect_reason: Option<bluetherm::ConnectionEvent>,
    send_interval: Duration,
    timeout_interval: Duration,
    last_send: Option<Instant>,
    last_receive: Option<Instant>
}

impl Harvester {
    fn new(sql_conn: rusqlite::Connection, serial: &str) -> Harvester {
        Harvester {
            sql_conn: sql_conn,
            bt_conn: Some(Harvester::connect_bluetherm(serial)),
            serial: serial.to_string(),
            disconnected: true,
            disconnect_reason: None,
            send_interval: Duration::from_millis(QUERY_INTERVAL),
            timeout_interval: Duration::from_millis(TIMEOUT_INTERVAL),
            last_send: None,
            last_receive: None
        }
    }

    fn connect_bluetherm(serial: &str) -> bluetherm::Connection {
        bluetherm::Connection::new(serial, Some(HEARTBEAT_INTERVAL)).unwrap()
    }

    fn start(&mut self) {
        loop {
            match (self.last_send, self.last_receive) {
                (Some(sent), _) if sent.elapsed() < self.send_interval => {},
                //(Some(sent), Some(received)) if sent > received => {},
                _ => {
                    self.send_packet();
                    self.last_send = Some(Instant::now());
                }
            }

            let event = self.get_bt_conn().wait().unwrap();

            println!("{}", event);
            match event {
                bluetherm::ConnectionEvent::Packet(p) => {
                    self.record_packet(p);
                    self.last_send = Some(Instant::now());
                    self.last_receive = Some(Instant::now());
                },
                e @ bluetherm::ConnectionEvent::InvalidPacket(_) => { self.bt_error(e); },
                e @ bluetherm::ConnectionEvent::ReadError(_) => { self.bt_error(e); },
                e @ bluetherm::ConnectionEvent::BadData(_) => { self.bt_error(e); },
                e @ bluetherm::ConnectionEvent::Heartbeat => {
                    match self.last_receive {
                        None => {
                            match self.last_send {
                                None => {},
                                Some(sent) => {
                                    if sent.elapsed() > self.timeout_interval {
                                        self.bt_error(e);
                                    }
                                }
                            }
                        },
                        Some(rec) => {
                            if rec.elapsed() > self.timeout_interval {
                                self.bt_error(e);
                            }
                        }
                    }
                }
            }
        }
    }

    fn get_bt_conn(&mut self) -> &mut bluetherm::Connection {
        self.bt_conn.as_mut().unwrap()
    }

    fn record_packet(&mut self, packet: bluetherm::Packet) {
        let mut reading = Reading::new();
        reading.value1 = packet.get_sensor1_reading();
        reading.value2 = packet.get_sensor2_reading();
        sql::insert_reading(&self.sql_conn, &mut reading).unwrap();
    }

    fn send_packet(&mut self) {
        println!("sending packet...");
        let p = bluetherm::Packet::temp_packet();
        match self.get_bt_conn().send(&p) {
            Err(e) => {
                self.bt_error(bluetherm::ConnectionEvent::ReadError(e));
            },
            Ok(_) => {}
        }
        println!("done sending packet.");
    }

    fn bt_error(&mut self, evt: bluetherm::ConnectionEvent) {
        let mut report_error = true;

        if self.disconnected {
            match &self.disconnect_reason {
                &None => {},
                &Some(ref reason) => {
                    match (reason, &evt) {
                        (&bluetherm::ConnectionEvent::InvalidPacket(_), &bluetherm::ConnectionEvent::InvalidPacket(_)) |
                        (&bluetherm::ConnectionEvent::ReadError(_), &bluetherm::ConnectionEvent::ReadError(_)) |
                        (&bluetherm::ConnectionEvent::BadData(_), &bluetherm::ConnectionEvent::BadData(_)) |
                        (&bluetherm::ConnectionEvent::Heartbeat, &bluetherm::ConnectionEvent::Heartbeat) => {
                            report_error = false;
                        },
                        _ => {},
                    }
                }
            }
        }

        if report_error {
            let msg = match evt {
                bluetherm::ConnectionEvent::InvalidPacket(ref p) => { format!("Invalid Packet: [{}]", p) },
                bluetherm::ConnectionEvent::ReadError(ref err) => { format!("Read Error: {}", err) },
                bluetherm::ConnectionEvent::BadData(_) => { format!("Bad Data Read") },
                bluetherm::ConnectionEvent::Heartbeat => { "Timeout".to_string() },
                _ => "Unknown Error".to_string()
            };

            let mut s = ConnectionStatus::new();
            s.is_disconnect = true;
            s.info = Some(msg);
            sql::insert_connection_status(&self.sql_conn, &mut s).unwrap();
        }

        self.disconnected = true;
        self.last_send = None;
        self.last_receive = None;

        println!("error, killing old connection. {}", evt);

        let old = self.bt_conn.take();
        drop(old.unwrap());

        println!("old conneciton dropped; making new");

        self.bt_conn = Some(Harvester::connect_bluetherm(&self.serial));

        println!("new connection made");
    }
}

// fn harvest(sql_conn: rusqlite::Connection, serial: &str) {
//
//     loop {
//         // if disconnected {
//         //     bt_conn = bluetherm::Connection::new(serial, Some(1000)).unwrap();
//         //     let mut s = ConnectionStatus::new();
//         //     s.is_disconnect = true;
//         //     sql::insert_connection_status(&sql_conn, &mut s).unwrap();
//         // }
//
//
//     }
//
// }

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.reqopt("s", "serial", "tty serial device", "DEV");
    opts.optopt("d", "dbfile", "sqlite DB file", "FILE");
    opts.optopt("m", "migrations", "migration folder", "DIR");
    opts.optflag("h", "help", "print this help menu");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => {
            println!("{}", f.to_string());
            print_usage(&program, opts);
            return;
        }
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }
    let serial = matches.opt_str("s").unwrap();
    let dbfile = matches.opt_str("d").unwrap_or("pibq.sqlite".to_string());
    let migrations = matches.opt_str("m").unwrap_or("migrations".to_string());

    let db = sql::get_connection(&dbfile, Some(migrations)).unwrap();

    let mut h = Harvester::new(db, &serial);
    h.start();
}
<|endoftext|>