#![warn(missing_docs)]
use crate::mboot::{McuBoot, ResultStatus, protocols::ProtocolOpen, tags::property::PropertyTagDiscriminants};
use crate::{
protocols::{i2c::I2CProtocol, protocol_impl::ProtocolImpl, uart::UARTProtocol},
tags::status::StatusCode,
};
use std::{
ffi::{CStr, CString},
ptr, slice,
str::FromStr,
};
type CMcuBoot = libc::c_void;
type CStatus = i32;
type ErrorData = i64;
#[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct CGetPropertyResponse {
pub status: CStatus,
pub response_words: *mut u32,
pub response_words_len: usize,
pub property_type: u8,
}
#[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct CReadMemoryResponse {
pub status: CStatus,
pub response_words: *mut u32,
pub response_words_len: usize,
pub bytes: *mut u8,
pub bytes_len: usize,
}
#[repr(C)]
pub enum CProtocol {
UART,
I2c,
}
pub const ERROR_NULL_POINTER_ARG: CStatus = -1;
pub const ERROR_INVALID_PROPERTY_TAG: CStatus = -2;
pub const ERROR_COMMUNICATION_ERROR: CStatus = -3;
unsafe fn get_mboot<'a>(mboot: *mut CMcuBoot) -> &'a mut McuBoot<ProtocolImpl> {
unsafe { &mut *mboot.cast::<McuBoot<ProtocolImpl>>() }
}
#[expect(
clippy::missing_panics_doc,
reason = "CString panics only if the data contains 0, this will not happen with the status string"
)]
#[must_use]
pub unsafe extern "C" fn mboot_get_status_text(status: CStatus) -> *mut libc::c_char {
let text: CString = match u32::try_from(status) {
Ok(status) => match StatusCode::try_from(status) {
Ok(status) => CString::from_str(&status.to_string()).unwrap(),
Err(_) => CString::from_str("invalid status code").unwrap(),
},
Err(_) => CString::from_str(match status {
ERROR_NULL_POINTER_ARG => "passed NULL pointer in function argument",
ERROR_INVALID_PROPERTY_TAG => "invalid propery tag passed in arguments",
ERROR_COMMUNICATION_ERROR => "error while communicating with the device",
_ => "unknown status code",
})
.unwrap(),
};
text.into_raw()
}
pub unsafe extern "C" fn mboot_free_status_text(status_text: *mut libc::c_char) {
if !status_text.is_null() {
drop(unsafe { CString::from_raw(status_text) });
}
}
fn return_error(status: &ResultStatus) -> CStatus {
match status {
Ok(status) => *status as CStatus,
Err(_) => ERROR_COMMUNICATION_ERROR,
}
}
unsafe fn free_box_data<T>(value: *mut T) {
if !value.is_null() {
drop(unsafe { Box::from_raw(value) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_create(device_path: *const libc::c_char, protocol: CProtocol) -> *mut CMcuBoot {
let c_str = unsafe { CStr::from_ptr(device_path) };
let Ok(device_path_str) = c_str.to_str() else {
return ptr::null_mut();
};
let device: ProtocolImpl = match protocol {
CProtocol::UART => match UARTProtocol::open(device_path_str) {
Ok(p) => p.into(),
Err(_) => return ptr::null_mut(),
},
CProtocol::I2c => match I2CProtocol::open(device_path_str) {
Ok(p) => p.into(),
Err(_) => return ptr::null_mut(),
},
};
let mboot = Box::new(McuBoot::new(device));
Box::into_raw(mboot).cast::<CMcuBoot>()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_destroy(mboot: *mut CMcuBoot) {
unsafe { free_box_data(mboot.cast::<McuBoot<ProtocolImpl>>()) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_get_property(
mboot: *mut CMcuBoot,
tag: u8,
memory_index: u32,
response: *mut CGetPropertyResponse,
) -> CStatus {
if mboot.is_null() || response.is_null() {
return ERROR_NULL_POINTER_ARG;
}
let response = unsafe { &mut *response };
*response = CGetPropertyResponse::default();
let mboot = unsafe { get_mboot(mboot) };
let Ok(tag_enum) = PropertyTagDiscriminants::try_from(tag) else {
return ERROR_INVALID_PROPERTY_TAG;
};
match mboot.get_property(tag_enum, memory_index) {
Ok(res) => {
let words: Box<[u32]> = if res.response_words.is_empty() {
Box::new([0u32])
} else {
res.response_words
};
let words_len = words.len();
let words_ptr = Box::into_raw(words);
let status = res.status as CStatus;
*response = CGetPropertyResponse {
status,
response_words: words_ptr.cast::<u32>(),
response_words_len: words_len,
property_type: tag,
};
status
}
Err(_) => ERROR_COMMUNICATION_ERROR,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_read_memory(
mboot: *mut CMcuBoot,
start_address: u32,
byte_count: u32,
memory_id: u32,
response: *mut CReadMemoryResponse,
) -> libc::c_int {
if mboot.is_null() || response.is_null() {
return ERROR_NULL_POINTER_ARG;
}
let response = unsafe { &mut *response };
*response = CReadMemoryResponse::default();
let mboot = unsafe { get_mboot(mboot) };
match mboot.read_memory(start_address, byte_count, memory_id) {
Ok(res) => {
let words = Box::new(res.response_words);
let words_len = words.len();
let words_ptr = Box::into_raw(words);
let bytes: Box<[u8]> = if res.bytes.is_empty() {
Box::new([0u8])
} else {
res.bytes
};
let bytes_len = bytes.len();
let bytes_ptr = Box::into_raw(bytes).cast::<u8>();
let status = res.status as CStatus;
*response = CReadMemoryResponse {
status,
response_words: words_ptr.cast::<u32>(),
response_words_len: words_len,
bytes: bytes_ptr,
bytes_len,
};
status
}
Err(_) => ERROR_COMMUNICATION_ERROR,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_write_memory(
mboot: *mut CMcuBoot,
start_address: u32,
memory_id: u32,
bytes: *const u8,
byte_count: usize,
) -> CStatus {
if mboot.is_null() || bytes.is_null() {
return ERROR_NULL_POINTER_ARG;
}
let mboot = unsafe { get_mboot(mboot) };
let bytes = unsafe { slice::from_raw_parts(bytes, byte_count) };
return_error(&mboot.write_memory(start_address, memory_id, bytes))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_flash_erase_all(mboot: *mut CMcuBoot, memory_id: u32) -> CStatus {
if mboot.is_null() {
return ERROR_NULL_POINTER_ARG;
}
let mboot = unsafe { get_mboot(mboot) };
return_error(&mboot.flash_erase_all(memory_id))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_receive_sb_file(mboot: *mut CMcuBoot, bytes: *const u8, byte_count: usize) -> CStatus {
if mboot.is_null() || bytes.is_null() {
return ERROR_NULL_POINTER_ARG;
}
let bytes = unsafe { slice::from_raw_parts(bytes, byte_count) };
let mboot = unsafe { get_mboot(mboot) };
return_error(&mboot.receive_sb_file(bytes))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_flash_program_once(
mboot: *mut CMcuBoot,
index: u32,
count: u32,
data: u32,
verify: bool,
) -> CStatus {
if mboot.is_null() {
return ERROR_NULL_POINTER_ARG;
}
let mboot = unsafe { get_mboot(mboot) };
return_error(&mboot.flash_program_once(index, count, data, verify))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_flash_read_once(mboot: *mut CMcuBoot, index: u32, count: u32) -> ErrorData {
if mboot.is_null() {
return ERROR_NULL_POINTER_ARG.into();
}
let mboot = unsafe { get_mboot(mboot) };
match mboot.flash_read_once(index, count) {
Ok(res) => res.into(),
Err(_) => ERROR_COMMUNICATION_ERROR.into(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_free_response_words(words: *mut u32) {
unsafe { free_box_data(words) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_free_bytes(bytes: *mut u8) {
unsafe { free_box_data(bytes) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mboot_free_read_memory_response(response: *mut CReadMemoryResponse) {
let response = unsafe { *response };
unsafe {
free_box_data(response.response_words);
free_box_data(response.bytes);
}
}