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

//! Provides interface for logging events to binary log buffers.

use log_event_list_bindgen as log_bindgen;
use std::os::raw::c_char;

pub use log_bindgen::log_id_LOG_ID_SECURITY as LogIdSecurity;

/// Whether security logging is enabled.
fn security_log_enabled() -> bool {
    // SAFETY: The call doesn't require any preconditions and only returns an int, so must be safe.
    unsafe { log_bindgen::__android_log_security() != 0 }
}

/// A struct representing that liblog call returned an error. No details are provided.
#[derive(Clone, Copy)]
pub struct LogContextError;

/// Convert liblog status code into a Result.
fn check_liblog_result(status: i32) -> Result<(), LogContextError> {
    if status < 0 {
        Err(LogContextError)
    } else {
        Ok(())
    }
}

/// Event log context.
pub struct LogContext {
    ctx: log_bindgen::android_log_context,
    log_type: log_bindgen::log_id,
}

/// SAFETY: Log context is essentially a buffer with some associated state. All data that is
/// appended to the context is copied into the buffers, no references are ever stored.
unsafe impl Send for LogContext {}

impl LogContext {
    /// Creates a context for a given event tag.
    pub fn new(log_type: log_bindgen::log_id, tag: u32) -> Option<LogContext> {
        if log_type == log_bindgen::log_id_LOG_ID_SECURITY && !security_log_enabled() {
            return None;
        }

        // SAFETY: The method returns a pointer that is stored and always freed exactly once via
        // Drop below.
        let ctx = unsafe { log_bindgen::create_android_logger(tag) };
        if !ctx.is_null() {
            Some(LogContext { ctx, log_type })
        } else {
            None
        }
    }

    /// Appends an i32 to the context.
    pub fn append_i32(self, data: i32) -> Result<Self, LogContextError> {
        // SAFETY: This will only be called on a non-null pointer returned from
        // create_android_logger previously, so should be safe.
        check_liblog_result(unsafe { log_bindgen::android_log_write_int32(self.ctx, data) })?;
        Ok(self)
    }

    /// Appends an i64 to the context.
    pub fn append_i64(self, data: i64) -> Result<Self, LogContextError> {
        // SAFETY: This will only be called on a non-null pointer returned from
        // create_android_logger previously, so should be safe.
        check_liblog_result(unsafe { log_bindgen::android_log_write_int64(self.ctx, data) })?;
        Ok(self)
    }

    /// Appends an f32 to the context.
    pub fn append_f32(self, data: f32) -> Result<Self, LogContextError> {
        // SAFETY: This will only be called on a non-null pointer returned from
        // create_android_logger previously, so should be safe.
        check_liblog_result(unsafe { log_bindgen::android_log_write_float32(self.ctx, data) })?;
        Ok(self)
    }

    /// Append a string to the context.
    pub fn append_str(self, data: &str) -> Result<Self, LogContextError> {
        // SAFETY: This will only be called on a non-null pointer returned from
        // create_android_logger previously, and the function will only read data.len() characters
        // from the str, the pointer itself won't be stored, so should be safe.
        check_liblog_result(unsafe {
            log_bindgen::android_log_write_string8_len(
                self.ctx,
                data.as_ptr() as *const c_char,
                data.len(),
            )
        })?;
        Ok(self)
    }

    /// Begin a sublist of values.
    pub fn begin_list(self) -> Result<Self, LogContextError> {
        // SAFETY: This will only be called on a non-null pointer returned from
        // create_android_logger previously, so should be safe.
        check_liblog_result(unsafe { log_bindgen::android_log_write_list_begin(self.ctx) })?;
        Ok(self)
    }

    /// End a sublist of values.
    pub fn end_list(self) -> Result<Self, LogContextError> {
        // SAFETY: This will only be called on a non-null pointer returned from
        // create_android_logger previously, so should be safe.
        check_liblog_result(unsafe { log_bindgen::android_log_write_list_end(self.ctx) })?;
        Ok(self)
    }

    /// Writes the context to a given buffer type and consumes the context.
    pub fn write(self) -> Result<(), LogContextError> {
        // SAFETY: This will only be called on a non-null pointer returned from
        // create_android_logger previously, so should be safe.
        check_liblog_result(unsafe { log_bindgen::android_log_write_list(self.ctx, self.log_type) })
    }
}

impl Drop for LogContext {
    fn drop(&mut self) {
        // SAFETY: This will only be called on a non-null pointer returned from
        // create_android_logger previously, so should be safe.
        unsafe { log_bindgen::android_log_destroy(&mut self.ctx) };
    }
}
