// Copyright (C) 2017-2018 Baidu, Inc. All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in
//    the documentation and/or other materials provided with the
//    distribution.
//  * Neither the name of Baidu, Inc., nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use sgx_types::*;
use std::vec::Vec;
use std::string::String;

use sgx_trts::trts::*;
use sgx_trts::veh::*;
use sgx_trts::ascii::AsciiExt;
use sgx_trts::c_str::*;
use sgx_trts::error;
use sgx_trts::memchr;
use sgx_trts::libc;
use sgx_trts::enclave::*;

use core::mem;

global_ctors_object! {
    VARNAME, func_name = {()}
}

// veh
extern "C" fn sample_exception_handler(_ : *mut sgx_exception_info_t) -> uint32_t {
    0
}

pub fn test_rsgx_get_thread_policy() {
    assert_eq!(rsgx_get_thread_policy(), SgxThreadPolicy::Bound);
}

pub fn test_trts_sizes() {
    //Only during dev
    //assert_eq!(mem::size_of::<global_data_t>(), 1488);
    //assert_eq!(mem::size_of::<thread_data_t>(), 160);
}

pub fn test_register_first_exception_handler() {
    let handle = rsgx_register_exception_handler(1, sample_exception_handler);
    assert!(handle.is_some());
    assert_eq!(rsgx_unregister_exception_handler(handle.unwrap()), true);
}

pub fn test_register_last_exception_handler() {
    let handle = rsgx_register_exception_handler(0, sample_exception_handler);
    assert!(handle.is_some());
    assert_eq!(rsgx_unregister_exception_handler(handle.unwrap()), true);
}

pub fn test_register_multiple_exception_handler() {
    let mut handler_vec : Vec<exception_handle> = Vec::new();
    let ntest:usize = 100;

    for i in 0..ntest {
        let handle = rsgx_register_exception_handler(i as uint32_t % 2,
                                                     sample_exception_handler);
        assert!(handle.is_some());
        handler_vec.push(handle.unwrap());
    }

    for i in 0..ntest {
        let h = handler_vec[i];
        assert_eq!(rsgx_unregister_exception_handler(h), true);
    }

    for i in 0..ntest {
        let h = handler_vec[i];
        assert_eq!(rsgx_unregister_exception_handler(h), false);
    }
}

// trts
pub fn test_read_rand(){
    let mut rand_arr = [0; 100];
    assert_eq!(rsgx_read_rand(&mut rand_arr[..]), Ok(()));
    let x = rand_arr.iter().fold(0, |sum, &x| sum ^ x);
    // Cannot all be zero
    assert_ne!(x, 0);
}

pub fn test_data_is_within_enclave() {
    #[derive(Clone, Copy)]
    struct SampleDs{
        x : i32,
        y : i32,
        z : [i32;100],
    };
    unsafe impl marker::ContiguousMemory for SampleDs{};
    let mut sample_object : SampleDs = SampleDs{ x : 0, y : 0, z : [0;100]};
    sample_object.x = 100;
    sample_object.y = 100;
    sample_object.z[0] = 100;
    assert_eq!(rsgx_data_is_within_enclave(&sample_object), true);

    let ooo;
    unsafe {
        let ppp = 0xdeadbeafdeadbeaf as * const u8;
        ooo = &*ppp;
    }
    assert_eq!(rsgx_data_is_within_enclave(ooo), false);
}

pub fn test_slice_is_within_enlave() {
    let one_array = [0; 100];
    assert_eq!(rsgx_slice_is_within_enclave(&one_array[..]), true);

    // TODO: Not compiling
    //let mut ooo : &[u8];
    //unsafe {
    //    let ppp = 0xdeadbeafdeadbeaf as * const [u8];
    //    ooo = &*ppp;
    //}
    //assert_eq!(rsgx_slice_is_within_enclave(ooo), false);
}

pub fn test_raw_is_within_enclave(){
    assert_eq!(rsgx_raw_is_within_enclave(test_raw_is_within_enclave as * const u8,
                                          10),
               true);
    assert_eq!(rsgx_raw_is_within_enclave(0xdeadbeafdeadbeaf as * const u8,
                                          10),
               false);
}

pub fn test_data_is_outside_enclave() {
    #[derive(Clone, Copy)]
    struct SampleDs{
        x : i32,
        y : i32,
        z : [i32;100],
    };
    unsafe impl marker::ContiguousMemory for SampleDs{};
    let mut sample_object : SampleDs = SampleDs{ x : 0, y : 0, z : [0;100]};
    sample_object.x = 100;
    sample_object.y = 100;
    sample_object.z[0] = 100;
    assert_eq!(rsgx_data_is_outside_enclave(&sample_object), false);

    let ooo;
    unsafe {
        let ppp = 0xdeadbeafdeadbeaf as * const u8;
        ooo = &*ppp;
    }
    assert_eq!(rsgx_data_is_outside_enclave(ooo), true);
}

pub fn test_slice_is_outside_enclave() {
    let one_array = [0; 100];
    assert_eq!(rsgx_slice_is_outside_enclave(&one_array[..]), false);

    // TODO: Not compiling
    //let mut ooo : &[u8];
    //unsafe {
    //    let ppp = 0xdeadbeafdeadbeaf as * const [u8];
    //    ooo = &*ppp;
    //}
    //assert_eq!(rsgx_slice_is_within_enclave(ooo), true);
}

pub fn test_raw_is_outside_enclave(){
    assert_eq!(rsgx_raw_is_outside_enclave(test_raw_is_outside_enclave as * const u8,
                                          10),
               false);
    assert_eq!(rsgx_raw_is_outside_enclave(0xdeadbeafdeadbeaf as * const u8,
                                          10),
               true);
}

// macros

pub fn test_global_ctors_object (){
    assert_eq!(VARNAME(), ());
}

// oom
// I don't think we can test oom

// error
pub fn test_error() {
    // XXX: Top 11 should be the same in all unix?
    let errorinfo_vec:Vec<(i32,&'static str)> = vec![
        (1 , "Operation not permitted"),
        (2 , "No such file or directory"),
        (3 , "No such process"),
        (4 , "Interrupted system call"),
        (5 , "Input/output error"),
        (6 , "Device not configured"),
        (7 , "Argument list too long"),
        (8 , "Exec format error"),
        (9 , "Bad file descriptor"),
        (10, "No child processes"),
        (11, "Resource deadlock avoided"),
        ];

    for case in errorinfo_vec {
        let mut buf:[i8;64] = [0;64];
        error::set_errno(case.0);
        unsafe { error::error_string(error::errno(),&mut buf[..]);}
        let answer:Vec<u8> = buf.iter().map(|&x| x as u8).collect();
        let ans_str = String::from_utf8(answer).unwrap();
        assert_eq!(ans_str.trim_matches('\0'),
                   case.1);
    }

}

// libc
pub fn test_rts_libc_memchr(){
    let test_str = "abcdedfg";
    assert_eq!(
            unsafe {
                libc::memchr(
                        test_str.as_ptr() as * const u8,
                        'd' as u8,
                        test_str.len())},
            test_str[3..].as_ptr());
    assert_eq!(
            unsafe {
                libc::memchr(
                        "abcdefg".as_ptr() as * const u8,
                        'z' as u8,
                        test_str.len())},
            0 as * const u8);
}

pub fn test_rts_libc_memrchr(){
    let test_str = "abcdedfg";
    assert_eq!(
            unsafe {
                libc::memrchr(
                        test_str.as_ptr() as * const u8,
                        'd' as u8,
                        test_str.len())},
            test_str[5..].as_ptr());
    assert_eq!(
            unsafe {
                libc::memrchr(
                        "abcdefg".as_ptr() as * const u8,
                        'z' as u8,
                        test_str.len())},
            0 as * const u8);
}

// memchr

pub fn test_rts_memchr_memchr(){
    let test_str = "abcdedfg".as_bytes();
    let needle = 'd' as u8;
    assert_eq!(memchr::memchr(needle, test_str), Some(3));
    let needle = 'z' as u8;
    assert_eq!(memchr::memchr(needle, test_str), None);
}

pub fn test_rts_memchr_memrchr(){
    let test_str = "abcdedfg".as_bytes();
    let needle = 'd' as u8;
    assert_eq!(memchr::memrchr(needle, test_str), Some(5));
    let needle = 'z' as u8;
    assert_eq!(memchr::memrchr(needle, test_str), None);
}

// ascii
pub fn test_ascii(){
    assert_eq!("café".to_ascii_uppercase(), "CAFÉ");
    assert_eq!("café".to_ascii_uppercase(), "CAFé");

    let ascii = 'a';
    let non_ascii = '❤';
    let int_ascii = 97;

    assert!(ascii.is_ascii());
    assert!(!non_ascii.is_ascii());
    assert!(int_ascii.is_ascii());
    assert_eq!('A', ascii.to_ascii_uppercase());
    assert_eq!('❤', non_ascii.to_ascii_uppercase());
    assert_eq!(65, int_ascii.to_ascii_uppercase());


    let ascii = 'A';
    let non_ascii = '❤';
    let int_ascii = 65;

    assert_eq!('a', ascii.to_ascii_lowercase());
    assert_eq!('❤', non_ascii.to_ascii_lowercase());
    assert_eq!(97, int_ascii.to_ascii_lowercase());

    let ascii1 = 'A';
    let ascii2 = 'a';
    let ascii3 = 'A';
    let ascii4 = 'z';

    assert!(ascii1.eq_ignore_ascii_case(&ascii2));
    assert!(ascii1.eq_ignore_ascii_case(&ascii3));
    assert!(!ascii1.eq_ignore_ascii_case(&ascii4));

    let mut ascii = 'a';
    ascii.make_ascii_uppercase();
    assert_eq!('A', ascii);

    let mut ascii = 'A';
    ascii.make_ascii_lowercase();
    assert_eq!('a', ascii);
}

// c_str
pub fn test_cstr(){
    let c_string = CString::new("foo").unwrap();
    let ptr = c_string.into_raw();

    unsafe {
        assert_eq!(b'f', *ptr as u8);
        assert_eq!(b'o', *ptr.offset(1) as u8);
        assert_eq!(b'o', *ptr.offset(2) as u8);
        assert_eq!(b'\0', *ptr.offset(3) as u8);
        // retake pointer to free memory
        let _ = CString::from_raw(ptr);
    }

    let c_string = CString::new("foo").unwrap();
    let bytes = c_string.into_bytes();
    assert_eq!(bytes, vec![b'f', b'o', b'o']);

    let c_string = CString::new("foo").unwrap();
    let bytes = c_string.into_bytes_with_nul();
    assert_eq!(bytes, vec![b'f', b'o', b'o', b'\0']);

    let c_string = CString::new("foo").unwrap();
    let bytes = c_string.as_bytes();
    assert_eq!(bytes, &[b'f', b'o', b'o']);

    let c_string = CString::new("foo").unwrap();
    let bytes = c_string.as_bytes_with_nul();
    assert_eq!(bytes, &[b'f', b'o', b'o', b'\0']);

    let c_string = CString::new(b"foo".to_vec()).unwrap();
    let c_str = c_string.as_c_str();
    assert_eq!(c_str, CStr::from_bytes_with_nul(b"foo\0").unwrap());

    let c_string = CString::new(b"foo".to_vec()).unwrap();
    let boxed = c_string.into_boxed_c_str();
    assert_eq!(&*boxed, CStr::from_bytes_with_nul(b"foo\0").unwrap());

    let c_str = CStr::from_bytes_with_nul(b"foo\0").unwrap();
    assert_eq!(c_str.to_bytes(), b"foo");

    let c_str = CStr::from_bytes_with_nul(b"foo\0").unwrap();
    assert_eq!(c_str.to_bytes_with_nul(), b"foo\0");

    let c_str = CStr::from_bytes_with_nul(b"foo\0").unwrap();
    assert_eq!(c_str.to_str(), Ok("foo"));

    let c_str = CStr::from_bytes_with_nul(b"Hello World\0").unwrap();
    assert_eq!(c_str.to_string_lossy(), Cow::Borrowed("Hello World"));

    use std::borrow::Cow;
    use std::ffi::CStr;

    let c_str = CStr::from_bytes_with_nul(b"Hello \xF0\x90\x80World\0").unwrap();
    assert_eq!(
                c_str.to_string_lossy(),
                    Cow::Owned(String::from("Hello �World")) as Cow<str>
            );
}
