use core::mem::MaybeUninit;
use alloc::ffi::CString;
use alloc::string::String;
use thiserror::Error;
use tinyvec::TinyVec;
use uefi::boot::ScopedProtocol;
use uefi::proto::ProtocolPointer;
use uefi::{CStr8, Event};
use uefi::{
CStr16, CString16, boot,
data_types::PoolString,
proto::device_path::{
DevicePath, PoolDevicePath, build,
text::{AllowShortcuts, DevicePathToText, DisplayOnly},
},
};
use crate::{BootResult, config::types::Architecture};
const MACHINE_ID_LEN: usize = 32;
const MAX_PATH: usize = 256;
#[derive(Error, Debug)]
pub enum StrError {
#[error("Could not convert String to CString16: {0}")]
CstrFromStr(#[from] uefi::data_types::FromStrError),
#[error("Could not convert a byte slice to a CString: {0}")]
FromSliceWithNul(#[from] uefi::data_types::FromSliceWithNulError),
#[error("Could not convert String to CString: {0}")]
CstringFromStr(#[from] alloc::ffi::NulError),
}
#[derive(Error, Debug)]
pub enum DevicePathError {
#[error("Could not build DevicePath: {0}")]
Build(#[from] uefi::proto::device_path::build::BuildError),
#[error("Could not append DevicePath to another DevicePath: {0}")]
DevPathUtil(#[from] uefi::proto::device_path::DevicePathUtilitiesError),
}
#[must_use = "Has no effect if the result is unused"]
pub(crate) fn check_sort_key_valid(sort_key: &str) -> bool {
sort_key
.chars()
.all(|x| x.is_ascii_alphanumeric() || x == '.' || x == '_' || x == '-')
}
#[must_use = "Has no effect if the result is unused"]
pub(crate) fn check_machine_id_valid(machine_id: &str) -> bool {
machine_id.chars().count() == MACHINE_ID_LEN
&& machine_id.chars().all(|x| x.is_ascii_hexdigit())
}
pub(crate) fn device_path_to_text(device_path: &DevicePath) -> BootResult<PoolString> {
let device_path_to_text = locate_protocol::<DevicePathToText>()?;
Ok(device_path_to_text.convert_device_path_to_text(
device_path,
DisplayOnly(true),
AllowShortcuts(false),
)?)
}
pub(crate) fn str_to_cstr(str: &str) -> Result<CString16, StrError> {
Ok(CString16::try_from(str)?)
}
pub(crate) fn get_path_cstr(prefix: &CStr16, filename: &CStr16) -> Result<CString16, StrError> {
let mut path_buf: TinyVec<[_; MAX_PATH]> = TinyVec::with_capacity(prefix.as_slice().len() + 1 + filename.as_slice().len());
path_buf.extend_from_slice(prefix.to_u16_slice());
path_buf.push(u16::from(b'\\'));
path_buf.extend_from_slice(filename.to_u16_slice_with_nul());
Ok(CStr16::from_u16_with_nul(&path_buf)?.into())
}
pub(crate) fn str_to_cstring(str: &str) -> Result<CString, StrError> {
Ok(CString::new(str)?)
}
pub(crate) fn bytes_to_cstr8(bytes: &[u8]) -> Result<&CStr8, StrError> {
Ok(CStr8::from_bytes_with_nul(bytes)?)
}
#[must_use = "Has no effect if the result is unused"]
pub fn get_arch() -> Option<Architecture> {
if cfg!(target_arch = "x86") {
Architecture::new("x86").ok()
} else if cfg!(target_arch = "x86_64") {
Architecture::new("x64").ok()
} else if cfg!(target_arch = "arm") {
Architecture::new("arm").ok()
} else if cfg!(target_arch = "aarch64") {
Architecture::new("aa64").ok()
} else {
None }
}
pub(crate) fn join_to_device_path(
dev_path: &DevicePath,
path: &CStr16,
buf: &mut [u8],
) -> Result<PoolDevicePath, DevicePathError> {
let buf = slice_to_maybe_uninit(buf);
let path: &DevicePath = build::DevicePathBuilder::with_buf(buf)
.push(&build::media::FilePath { path_name: path })?
.finalize()?;
Ok(dev_path.append_path(path)?)
}
#[must_use = "Has no effect if the result is unused"]
pub(crate) fn normalize_path(path: &str) -> String {
path.replace('/', "\\")
}
pub(crate) const fn slice_to_maybe_uninit<T>(slice: &mut [T]) -> &mut [MaybeUninit<T>] {
unsafe {
core::slice::from_raw_parts_mut(slice.as_mut_ptr().cast::<MaybeUninit<T>>(), slice.len())
}
}
pub(crate) fn cstr_ends_with(str: &CStr16, pat: &str) -> bool {
let u16_slice = str.to_u16_slice();
let pat_u16_iter = pat.encode_utf16();
let pat_len = pat_u16_iter.clone().count();
if u16_slice.len() < pat_len {
return false;
}
if pat.chars().any(|c| u32::from(c) >= 0x10000) {
return false; }
let end_slice = &u16_slice[u16_slice.len() - pat_len..];
end_slice.iter().zip(pat_u16_iter).all(|(&x, y)| {
if let Some(x) = char::from_u32(u32::from(x))
&& let Some(y) = char::from_u32(u32::from(y))
{
x.eq_ignore_ascii_case(&y)
} else {
false
}
})
}
pub fn locate_protocol<P: ProtocolPointer>() -> BootResult<ScopedProtocol<P>> {
let handle = boot::get_handle_for_protocol::<P>()?;
Ok(boot::open_protocol_exclusive(handle)?)
}
pub fn create_timer(trigger: boot::TimerTrigger) -> BootResult<Event> {
let timer =
unsafe { boot::create_event(boot::EventType::TIMER, boot::Tpl::APPLICATION, None, None)? };
boot::set_timer(&timer, trigger)?;
Ok(timer)
}
#[cfg(test)]
mod tests {
use super::*;
use uefi::cstr16;
#[test]
fn test_check_sort_key_valid() {
let sort_key = "sort-key";
assert!(check_sort_key_valid(sort_key));
let sort_key = "super Invalid ;; sort key sssz.";
assert!(!check_sort_key_valid(sort_key));
}
#[test]
fn test_check_machine_id_valid() {
let machine_id = "1234567890abcdef1234567890abcdef";
assert!(check_machine_id_valid(machine_id));
let machine_id = "1234567890abcdef1234567890abcdeg";
assert!(!check_machine_id_valid(machine_id));
let machine_id = "obviously invalid";
assert!(!check_machine_id_valid(machine_id));
}
#[test]
fn test_str_to_cstr() -> Result<(), StrError> {
let cstr = str_to_cstr("foo bar")?;
let str = String::from(&cstr);
assert_eq!(str, "foo bar".to_owned());
Ok(())
}
#[test]
fn test_get_path_cstr() -> Result<(), StrError> {
const PREFIX: &CStr16 = cstr16!("\\root");
const FILE: &CStr16 = cstr16!("somefilename");
let path = get_path_cstr(PREFIX, FILE)?;
let str = String::from(&path);
assert_eq!(str, "\\root\\somefilename".to_owned());
Ok(())
}
#[test]
fn test_get_arch() {
if cfg!(target_arch = "x86") {
assert_eq!(get_arch().as_deref().map(String::as_str), Some("x86"));
} else if cfg!(target_arch = "x86_64") {
assert_eq!(get_arch().as_deref().map(String::as_str), Some("x64"));
} else if cfg!(target_arch = "arm") {
assert_eq!(get_arch().as_deref().map(String::as_str), Some("arm"));
} else if cfg!(target_arch = "aarch64") {
assert_eq!(get_arch().as_deref().map(String::as_str), Some("aa64"));
} else {
assert_eq!(get_arch(), None);
}
}
#[test]
fn test_normalize_path() {
let path = "/some/path/from/linux/fs";
assert_eq!(normalize_path(path), "\\some\\path\\from\\linux\\fs");
let path = "\\a\\completely\\normal\\path";
assert_eq!(normalize_path(path), path);
}
}