// Copyright 2018 Google LLC
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

use std::marker::PhantomData;
use std::mem::{self, MaybeUninit};
use std::ptr::NonNull;

/// A trait that can be used to ensure that users of the boringssl module can't
/// implement a trait.
///
/// See the [API Guidelines] for details.
///
/// [API Guidelines]: https://rust-lang-nursery.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
pub trait Sealed {}

macro_rules! sealed {
    ($name:ident) => {
        impl ::boringssl::wrapper::Sealed for ::boringssl::raw::ffi::$name {}
    };
}

macro_rules! impl_traits {
    (@inner $name:ident, CNew => $fn:tt) => {
        c_new!($name, $fn);
    };
    (@inner $name:ident, CUpRef => $fn:tt) => {
        c_up_ref!($name, $fn);
    };
    (@inner $name:ident, CFree => $fn:tt) => {
        c_free!($name, $fn);
    };
    (@inner $name:ident, CInit => $fn:tt) => {
        c_init!($name, $fn);
    };
    (@inner $name:ident, CDestruct => $fn:tt) => {
        c_destruct!($name, $fn);
    };
    (@inner $name:ident, $trait:ident => $fn:tt) => {
        compile_error!(concat!("unrecognized trait ", stringify!($trait)));
    };
    ($name:ident, $($trait:ident => $fn:tt),*) => {
        sealed!($name);
        $(impl_traits!(@inner $name, $trait => $fn);)*
    };
}

/// A C object from the BoringSSL API which can be allocated and constructed.
pub unsafe trait CNew: Sealed {
    /// Returns a new, constructed, heap-allocated object, or NULL on failure.
    ///
    /// This should not be called directly; instead, use `new`.
    #[deprecated(note = "do not call new_raw directly; instead, call new")]
    unsafe fn new_raw() -> *mut Self;

    /// Returns a new, constructed, heap-allocated object, or `None` on failure.
    #[must_use]
    unsafe fn new() -> Option<NonNull<Self>> {
        #[allow(deprecated)]
        NonNull::new(Self::new_raw())
    }
}

macro_rules! c_new {
    ($name:ident, $new:ident) => {
        unsafe impl ::boringssl::wrapper::CNew for ::boringssl::raw::ffi::$name {
            unsafe fn new_raw() -> *mut Self {
                ::boringssl::raw::ffi::$new()
            }
        }
    };
}

/// A C object from the BoringSSL API which has a reference count that can be
/// increased.
pub unsafe trait CUpRef: Sealed {
    /// Increases an object's reference count.
    unsafe fn up_ref(slf: *mut Self);
}

macro_rules! c_up_ref {
    ($name:ident, $up_ref:ident) => {
        unsafe impl ::boringssl::wrapper::CUpRef for ::boringssl::raw::ffi::$name {
            unsafe fn up_ref(slf: *mut Self) {
                use boringssl::abort::UnwrapAbort;
                ::boringssl::raw::one_or_err(
                    stringify!($up_ref),
                    ::boringssl::raw::ffi::$up_ref(slf),
                )
                .unwrap_abort()
            }
        }
    };
}

/// A C object from the BoringSSL API which can be freed.
pub unsafe trait CFree: Sealed {
    /// Frees a heap-allocated object.
    ///
    /// If this is a reference-counted object, `free` decrements the reference
    /// count, and frees the object if it reaches zero. Otherwise, if this is
    /// not a reference-counted object, it frees it.
    unsafe fn free(slf: *mut Self);
}

macro_rules! c_free {
    ($name:ident, $free:ident) => {
        unsafe impl ::boringssl::wrapper::CFree for ::boringssl::raw::ffi::$name {
            unsafe fn free(slf: *mut Self) {
                ::boringssl::raw::ffi::$free(slf)
            }
        }
    };
}

/// A C object from the BoringSSL API which can be initialized.
pub unsafe trait CInit: Sealed {
    /// Initializes an uninitialized object.
    ///
    /// # Safety
    ///
    /// `init` must not be called on an initialized object.
    unsafe fn init(slf: *mut Self);
}

#[allow(unused)] // TODO: Remove once it's used in the 'raw' module
macro_rules! c_init {
    ($name:ident, $init:ident) => {
        unsafe impl ::boringssl::wrapper::CInit for ::boringssl::raw::ffi::$name {
            unsafe fn init(slf: *mut Self) {
                ::boringssl::raw::ffi::$init(slf)
            }
        }
    };
}

/// A C object from the BoringSSL API which can be destructed.
pub unsafe trait CDestruct: Sealed {
    /// Destructs an initialized object.
    ///
    /// # Safety
    ///
    /// `slf` must be an initialized object. After a call to `destruct`, `slf`
    /// is uninitialized.
    unsafe fn destruct(slf: *mut Self);
}

macro_rules! c_destruct {
    ($name:ident, _) => {
        unsafe impl ::boringssl::wrapper::CDestruct for ::boringssl::raw::ffi::$name {
            unsafe fn destruct(_slf: *mut Self) {}
        }
    };
    ($name:ident, $destruct:tt) => {
        unsafe impl ::boringssl::wrapper::CDestruct for ::boringssl::raw::ffi::$name {
            unsafe fn destruct(slf: *mut Self) {
                ::boringssl::raw::ffi::$destruct(slf)
            }
        }
    };
}

/// A wrapper around a pointer to a heap-allocated, constructed C object from
/// the BoringSSL API.
///
/// `CHeapWrapper` maintains the invariant that the object it references is
/// always allocated and constructed. This means that:
/// - If the object can be reference counted, `CHeapWrapper` implements `Clone`
///   by incrementing the reference count, and decrementing on `Drop`.
/// - If the object cannot be reference counted, `CHeapWrapper` does not
///   implement `Clone`, but will still free the object on `Drop`.
///
/// `CHeapWrapper`s are not thread-safe; they do not implement `Send` or `Sync`.
pub struct CHeapWrapper<C: CFree> {
    // NOTE: NonNull ensures that CHeapWrapper is !Send + !Sync. If this struct
    // is changed, make sure it's still !Send + !Sync.
    obj: NonNull<C>,
}

impl<C: CFree> CHeapWrapper<C> {
    /// Takes ownership of a constructed object.
    ///
    /// # Safety
    ///
    /// `obj` must point to an allocated, constructed object. The caller must
    /// ensure that, when the returned `CHeapWrapper` is dropped, it is safe to
    /// call `C::free` on `obj`. In most cases, this means that the caller
    /// should not free `obj`, and instead consider ownership of `obj` to have
    /// transferred to the new `CHeapWrapper`.
    ///
    /// The caller must also ensure that no pointers to the object will ever be
    /// used by other threads so long as this `CHeapWrapper` exists.
    #[must_use]
    pub unsafe fn new_from(obj: NonNull<C>) -> CHeapWrapper<C> {
        CHeapWrapper { obj }
    }

    #[must_use]
    pub fn as_mut(&mut self) -> *mut C {
        self.obj.as_ptr()
    }

    #[must_use]
    pub fn as_const(&self) -> *const C {
        self.obj.as_ptr()
    }

    /// Consumes this `CHeapWrapper` and return the underlying pointer.
    ///
    /// The object will not be freed. Instead, the caller takes logical
    /// ownership of the object.
    #[must_use]
    pub fn into_mut(self) -> *mut C {
        // NOTE: This method safe for the same reason that mem::forget is safe:
        // it's equivalent to sending it to a thread that goes to sleep forever
        // or creating a Rc cycle or some other silly-but-safe behavior.
        let ptr = self.obj.as_ptr();
        mem::forget(self);
        ptr
    }
}

impl<C: CNew + CFree> Default for CHeapWrapper<C> {
    fn default() -> CHeapWrapper<C> {
        // TODO(joshlf): In order for this to be safe, CNew must provide the
        // safety guarantee that it's always safe to call CNew::new and then
        // later to call CFree::free on that object (e.g., see the safety
        // comment on CStackWrapper::new).
        unsafe {
            use boringssl::abort::UnwrapAbort;
            let obj = C::new().expect_abort("could not allocate object");
            CHeapWrapper { obj }
        }
    }
}

impl<C: CUpRef + CFree> Clone for CHeapWrapper<C> {
    fn clone(&self) -> CHeapWrapper<C> {
        unsafe { C::up_ref(self.obj.as_ptr()) };
        CHeapWrapper { obj: self.obj }
    }
}

impl<C: CFree> Drop for CHeapWrapper<C> {
    fn drop(&mut self) {
        unsafe { C::free(self.obj.as_ptr()) };
    }
}

/// A wrapper around a pointer to a C object from the BoringSSL API.
///
/// Unlike `CHeapWrapper` or `CStackWrapper`, `CRef` does not own the pointed-to
/// object, but merely borrows it like a normal Rust reference. The only reason
/// to use `CRef<C>` instead of a `&C` is to make it so that access to the `C`
/// is unsafe, as `CRef` only exposes a raw pointer accessor for its object.
///
/// `CRef` maintains the invariant that the object it references is always
/// allocated and constructed, and that mutable access to the object is disabled
/// for the lifetime of the `CRef`.
pub struct CRef<'a, C> {
    // NOTE: NonNull ensures that CHeapWrapper is !Send + !Sync. If this struct
    // is changed, make sure it's still !Send + !Sync.
    obj: NonNull<C>,
    // Make sure CRef has the lifetime 'a.
    _lifetime: PhantomData<&'a ()>,
}

impl<'a, C> CRef<'a, C> {
    /// Creates a new `CRef` from a raw pointer.
    ///
    /// # Safety
    ///
    /// `obj` must point to an allocated, constructed object. The caller must
    /// ensure that, for the lifetime, `'a`, `obj` will continue to point to the
    /// same allocated, constructed object, and that mutable access to the
    /// object will be disallowed.
    ///
    /// The caller must also ensure that no other pointers to the object will
    /// ever be sent to other threads so long as this `CRef` exists.
    #[must_use]
    pub unsafe fn new(obj: NonNull<C>) -> CRef<'a, C> {
        CRef { obj, _lifetime: PhantomData }
    }

    #[must_use]
    pub fn as_const(&self) -> *const C {
        self.obj.as_ptr()
    }
}

/// A wrapper around a constructed C object from the BoringSSL API.
///
/// `CStackWrapper` maintains the invariant that the object it contains is
/// always constructed. The object is destructed on `Drop`.
///
/// `CStackWrapper`s are not thread-safe; they do not implement `Send` or
/// `Sync`.
pub struct CStackWrapper<C: CDestruct> {
    obj: C,
    // Make sure CStackWrapper doesn't implement Send or Sync regardless of C.
    _no_sync: PhantomData<*mut ()>,
}

impl<C: CDestruct> CStackWrapper<C> {
    /// Constructs a new `CStackWrapper`.
    ///
    /// # Safety
    ///
    /// `obj` must be constructed, and it must be safe for `C::destruct` to be
    /// called on `obj` when this `CStackWrapper` is dropped.
    #[must_use]
    pub unsafe fn new(obj: C) -> CStackWrapper<C> {
        CStackWrapper { obj, _no_sync: PhantomData }
    }

    #[must_use]
    pub fn as_c_ref(&mut self) -> CRef<C> {
        unsafe { CRef::new(NonNull::new_unchecked(&mut self.obj as *mut C)) }
    }

    #[must_use]
    pub fn as_mut(&mut self) -> *mut C {
        &mut self.obj
    }

    #[must_use]
    pub fn as_const(&self) -> *const C {
        &self.obj
    }
}

impl<C: CInit + CDestruct> Default for CStackWrapper<C> {
    // TODO(joshlf): In order for this to be safe, CInit must provide the safety
    // guarantee that it's always safe to call CInit::init and then later to
    // call CDestruct::destruct on that object (e.g., see the safety comment on
    // CStackWrapper::new).
    fn default() -> CStackWrapper<C> {
        unsafe {
            let mut obj = MaybeUninit::<C>::uninit();
            C::init(obj.as_mut_ptr());
            CStackWrapper { obj: obj.assume_init(), _no_sync: PhantomData }
        }
    }
}

impl<C: CDestruct> Drop for CStackWrapper<C> {
    fn drop(&mut self) {
        unsafe { C::destruct(&mut self.obj) }
    }
}

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

    #[test]
    fn test_heap_wrapper_into_mut() {
        // Test that CHeapWrapper::into_mut doesn't free the pointer. If it
        // does, then EC_KEY::free is likely (though not guaranteed) to abort
        // when it finds the refcount at 0.
        let key = CHeapWrapper::<EC_KEY>::default();
        unsafe { EC_KEY::free(key.into_mut()) };
    }
}
