use std::convert::TryFrom;
use std::ffi::CString;
use std::path::PathBuf;
use anyhow::{bail, Context, Result};
use bindgen::status::{ot_status_create_record_t, status_create, status_err, status_extract};
use num_enum::TryFromPrimitive;
use object::{Object, ObjectSection};
use zerocopy::FromBytes;
pub use bindgen::status::absl_status_t as RawStatusCode;
pub use bindgen::status::status_t as RawStatus;
#[derive(Debug, serde::Serialize, serde::Deserialize, TryFromPrimitive, PartialEq, Eq)]
#[repr(u32)]
pub enum StatusCode {
Ok = bindgen::status::absl_status_code_kOk,
Cancelled = bindgen::status::absl_status_code_kCancelled,
Unknown = bindgen::status::absl_status_code_kUnknown,
InvalidArgument = bindgen::status::absl_status_code_kInvalidArgument,
DeadlineExceeded = bindgen::status::absl_status_code_kDeadlineExceeded,
NotFound = bindgen::status::absl_status_code_kNotFound,
AlreadyExists = bindgen::status::absl_status_code_kAlreadyExists,
PermissionDenied = bindgen::status::absl_status_code_kPermissionDenied,
ResourceExhausted = bindgen::status::absl_status_code_kResourceExhausted,
FailedPrecondition = bindgen::status::absl_status_code_kFailedPrecondition,
Aborted = bindgen::status::absl_status_code_kAborted,
OutOfRange = bindgen::status::absl_status_code_kOutOfRange,
Unimplemented = bindgen::status::absl_status_code_kUnimplemented,
Internal = bindgen::status::absl_status_code_kInternal,
Unavailable = bindgen::status::absl_status_code_kUnavailable,
DataLoss = bindgen::status::absl_status_code_kDataLoss,
Unauthenticated = bindgen::status::absl_status_code_kUnauthenticated,
}
fn status_create_safe(code: StatusCode, mod_id: u32, file: String, arg: i32) -> RawStatus {
let file = CString::new(file).expect("CString::new failed");
unsafe { status_create(code as u32, mod_id, file.as_ptr(), arg) }
}
fn c_string_to_string(array: &[i8]) -> String {
let array = array
.iter()
.map(|c| *c as u8)
.take_while(|c| *c != 0u8)
.collect::<Vec<_>>();
String::from_utf8_lossy(&array).to_string()
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Status {
pub module_id: String,
pub arg: i32,
pub code: StatusCode,
}
impl Status {
pub fn from_raw_status(status: RawStatus) -> Result<Status> {
let mut code_str: *const std::os::raw::c_char = std::ptr::null();
let mut arg = 0i32;
let mut mod_id: [std::os::raw::c_char; 3] = [0; 3];
let mod_id_ptr = &mut mod_id as *mut i8;
let is_err_status = unsafe { status_extract(status, &mut code_str, &mut arg, mod_id_ptr) };
let code = match is_err_status {
false => StatusCode::Ok,
true => {
let raw_code = unsafe { status_err(status) };
StatusCode::try_from(raw_code)?
}
};
Ok(Status {
module_id: c_string_to_string(&mod_id),
arg,
code,
})
}
pub fn from_u32(status: u32) -> Result<Status> {
Self::from_raw_status(RawStatus {
value: status as i32,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn status_manip_test_good() {
const CODE: StatusCode = StatusCode::PermissionDenied;
const MOD_ID: &str = "abc";
const ARG: i32 = 42;
let mod_id_bytes = MOD_ID.as_bytes();
assert_eq!(mod_id_bytes.len(), 3);
let mod_id_val = ((mod_id_bytes[0].to_ascii_uppercase() as u32) << 16)
| ((mod_id_bytes[1].to_ascii_uppercase() as u32) << 21)
| ((mod_id_bytes[2].to_ascii_uppercase() as u32) << 26);
let raw_status = status_create_safe(CODE, mod_id_val, "".to_string(), ARG);
let decoded_status = Status::from_raw_status(raw_status);
assert!(decoded_status.is_ok());
let decoded_status = decoded_status.unwrap();
assert_eq!(decoded_status.module_id, MOD_ID.to_ascii_uppercase());
assert_eq!(decoded_status.code, CODE);
assert_eq!(decoded_status.arg, ARG);
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct StatusCreateRecord {
pub module_id: Option<u32>,
pub filename: String,
}
impl StatusCreateRecord {
pub fn get_module_id(&self) -> Result<String> {
const DEFAULT_MODULE_ID: u32 = 0;
let mod_id = self.module_id.map_or(DEFAULT_MODULE_ID, |mod_id| mod_id);
let status = status_create_safe(StatusCode::Unknown, mod_id, self.filename.clone(), 0);
let status = Status::from_raw_status(status)?;
Ok(status.module_id)
}
}
const UNKNOWN_MODULE_ID: u32 = bindgen::status::ot_status_create_record_magic_OT_SCR_UNKNOWN_MOD_ID;
impl TryFrom<ot_status_create_record_t> for StatusCreateRecord {
type Error = anyhow::Error;
fn try_from(record: ot_status_create_record_t) -> Result<StatusCreateRecord> {
let filename = c_string_to_string(&record.filename);
Ok(StatusCreateRecord {
module_id: Some(record.module_id).filter(|mod_id| mod_id != &UNKNOWN_MODULE_ID),
filename,
})
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct StatusCreateRecords {
pub records: Vec<StatusCreateRecord>,
}
impl StatusCreateRecords {
pub fn find_module_id(&self, mod_id: &String) -> Vec<String> {
let iter = self
.records
.iter()
.filter(|rec| rec.get_module_id().map_or(false, |id| id == *mod_id))
.map(|rec| rec.filename.clone());
std::collections::HashSet::<String>::from_iter(iter)
.into_iter()
.collect()
}
}
pub fn load_elf(elf_file: &PathBuf) -> Result<StatusCreateRecords> {
let file_data = std::fs::read(elf_file)
.with_context(|| format!("Could not read ELF file {}.", elf_file.display()))?;
let file = object::File::parse(&*file_data)
.with_context(|| format!("Could not parse ELF file {}", elf_file.display()))?;
let section = file
.section_by_name(".ot.status_create_record")
.ok_or_else(|| {
anyhow::anyhow!("ELF file should have a .ot.status_create_record section")
})?;
let status_create_records = section
.data()
.context("cannot read .ot.status_create_record section data")?;
const RECORD_SIZE: usize = std::mem::size_of::<ot_status_create_record_t>();
if status_create_records.len() % RECORD_SIZE != 0 {
bail!(".ot.status_create_record section size ({}) is not a multiple of the ot_status_create_record_t size ({})",
status_create_records.len(), RECORD_SIZE);
}
let records = status_create_records
.chunks(RECORD_SIZE)
.map(|chunk| ot_status_create_record_t::read_from(chunk).unwrap())
.map(StatusCreateRecord::try_from)
.collect::<Result<_>>()?;
Ok(StatusCreateRecords { records })
}