#![allow(incomplete_features)]
#![feature(const_fn)]
#![feature(const_generics)]
#[doc(hidden)]
mod error;
use serde::{de::DeserializeOwned, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::fs;
use std::hash::Hasher;
use std::io::{Seek, SeekFrom, Write};
use std::marker::PhantomData;
pub use crate::error::{Error, Result};
pub use binary_enclave_macro::enclave;
#[doc(hidden)]
pub trait EnclaveLocator {
const SECTION: &'static str;
}
#[repr(C)]
pub struct Enclave<T, const SIZE: usize> {
len: usize,
checksum: u64,
pack: [u8; SIZE],
_phantom: PhantomData<T>,
}
impl<T, const SIZE: usize> Enclave<T, SIZE>
where
T: Default + Serialize + DeserializeOwned + EnclaveLocator,
{
pub const fn new() -> Self {
Self {
len: 0,
checksum: 0,
pack: [0; SIZE],
_phantom: PhantomData
}
}
pub fn decode(&self) -> Result<T> {
let payload: Result<T> = bincode::deserialize(&self.pack).map_err(From::from);
match payload {
Err(e) => Err(e),
Ok(payload) => {
let mut hasher = DefaultHasher::new();
hasher.write(&self.pack[0..self.len as usize]);
if hasher.finish() == self.checksum {
Ok(payload)
} else {
Err(Error::PayloadChecksum)
}
}
}
}
pub fn decode_or_default(&self) -> T {
self.decode().unwrap_or_default()
}
pub fn write(&self, payload: &T) -> Result<usize> {
self._write(payload)
}
#[cfg(target_os = "macos")]
#[doc(hidden)]
pub fn _write(&self, payload: &T) -> Result<usize> {
use goblin::mach;
let mut data = read_binary()?;
let mach = mach::MachO::parse(&data, 0)?;
let segment = mach
.segments
.iter()
.find(|s| s.name().unwrap() == "__DATA")
.ok_or_else(|| Error::SectionNotFound("__DATA Segment not found".into()))?;
let (offset, size) = segment
.sections()?
.iter()
.find(|sec| sec.0.name().unwrap() == T::SECTION)
.map(|x| (x.0.offset, x.0.size))
.ok_or_else(|| Error::SectionNotFound("Binary Section not found".into()))?;
write_binary(&mut data, &payload, offset as usize, size as usize)
}
#[cfg(target_os = "linux")]
#[doc(hidden)]
pub fn _write(&self, payload: &T) -> Result<usize> {
use goblin::elf::Elf;
let mut data = read_binary()?;
let elf = Elf::parse(&data)?;
let section = elf
.section_headers
.iter()
.find(|sec| &elf.shdr_strtab[sec.sh_name] == T::SECTION)
.ok_or_else(|| Error::SectionNotFound("Binary Section not found".into()))?;
write_binary(&mut data, &payload, section.sh_offset as usize, section.sh_size as usize)
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub fn _write(&self, payload: &T) -> Result<usize> {
panic!("Not Supported")
}
}
fn read_binary() -> Result<Vec<u8>> {
let bin_path = std::env::current_exe()?;
let bytes = fs::read(bin_path)?;
Ok(bytes)
}
fn write_binary<T: Serialize>(
data: &mut Vec<u8>,
payload: &T,
offset: usize,
size: usize,
) -> Result<usize> {
let payload = bincode::serialize(payload)?;
if payload.len() > size {
return Err(Error::SectionSizeExceeded {
payload: payload.len(),
section: size,
});
}
let mut hasher = DefaultHasher::new();
hasher.write(&payload);
let mut data = std::io::Cursor::new(data);
data.seek(SeekFrom::Start(offset as u64))?;
data.write_all(&payload.len().to_ne_bytes())?;
data.write_all(&hasher.finish().to_ne_bytes())?;
data.write_all(&payload)?;
let data = data.into_inner();
let file = std::env::current_exe()?;
let perms = fs::metadata(&file)?.permissions();
let file_name = file.file_name().ok_or_else(|| Error::BinaryNotLocated)?;
let mut tmpfile = Clone::clone(&file);
tmpfile.set_file_name(format!("{}.new", &file_name.to_string_lossy()));
fs::write(&tmpfile, &data)?;
fs::rename(&tmpfile, &file)?;
fs::set_permissions(&file, perms)?;
Ok(payload.len())
}