use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use crate::seph::PacketTypes;
mod event;
pub use event::{DecodedEvent, DecodedEventType};
mod bolos;
pub(crate) mod callbacks;
use bolos::handle_bolos_apdu;
pub use crate::io_legacy::{ApduHeader, Event, Reply, StatusWords};
use crate::io_callbacks::nbgl_register_callbacks;
use ledger_secure_sdk_sys::seph as sys_seph;
pub const DEFAULT_BUF_SIZE: usize = 273;
static mut COMM_INITIALIZED: bool = false;
pub struct CommStorage<const N: usize = DEFAULT_BUF_SIZE> {
inner: UnsafeCell<MaybeUninit<Comm<N>>>,
}
unsafe impl<const N: usize> Sync for CommStorage<N> {}
impl<const N: usize> CommStorage<N> {
pub const fn new() -> Self {
Self {
inner: UnsafeCell::new(MaybeUninit::uninit()),
}
}
pub fn init(&'static self, comm: Comm<N>) -> &'static mut Comm<N> {
if unsafe { COMM_INITIALIZED } {
panic!("CommStorage already initialized. Only one Comm instance can exist.");
}
unsafe { COMM_INITIALIZED = true };
unsafe {
let ptr = self.inner.get();
(*ptr).write(comm);
(*ptr).assume_init_mut()
}
}
}
#[macro_export]
macro_rules! define_comm {
($name:ident) => {
static $name: $crate::io::CommStorage<{ $crate::io::DEFAULT_BUF_SIZE }> =
$crate::io::CommStorage::new();
};
($name:ident, $size:expr) => {
static $name: $crate::io::CommStorage<$size> = $crate::io::CommStorage::new();
};
}
#[cfg(any(target_os = "nanosplus", target_os = "nanox"))]
use crate::buttons::ButtonEvent;
#[cfg(any(target_os = "nanosplus", target_os = "nanox"))]
use ledger_secure_sdk_sys::buttons::{ButtonsState, get_button_event};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommError {
Overflow,
IoError,
}
pub struct Comm<const N: usize = DEFAULT_BUF_SIZE> {
buf: [u8; N],
expected_cla: Option<u8>,
apdu_type: u8,
#[cfg(any(target_os = "nanosplus", target_os = "nanox"))]
buttons: ButtonsState,
pending_apdu: bool,
pending_header: ApduHeader,
pending_offset: usize,
pending_length: usize,
}
impl<const N: usize> Comm<N> {
pub fn new() -> Self {
Self {
buf: [0; N],
expected_cla: None,
apdu_type: PacketTypes::PacketTypeNone as u8,
#[cfg(any(target_os = "nanosplus", target_os = "nanox"))]
buttons: ButtonsState::default(),
pending_apdu: false,
pending_header: ApduHeader {
cla: 0,
ins: 0,
p1: 0,
p2: 0,
},
pending_offset: 0,
pending_length: 0,
}
}
pub(crate) fn nbgl_register_comm(&mut self) {
callbacks::set_comm::<N>(self);
nbgl_register_callbacks(
callbacks::next_event_ahead_impl::<N>,
callbacks::fetch_apdu_header_impl::<N>,
callbacks::reply_status_impl::<N>,
);
}
fn recv(&mut self, check_se_event: bool) -> Result<Rx<'_, N>, CommError> {
let result = sys_seph::io_rx(&mut self.buf, check_se_event);
if result < 0 {
return Err(CommError::IoError);
}
Ok(Rx {
comm: self,
len: result as usize,
})
}
pub fn begin_response(&mut self) -> CommandResponse<'_, N> {
CommandResponse { comm: self, len: 0 }
}
pub fn send<T: Into<Reply>>(&mut self, data: &[u8], reply: T) -> Result<(), CommError> {
self.begin_response().extend(data)?.send(reply).unwrap();
Ok(())
}
pub fn try_next_event(&mut self) -> DecodedEvent<N> {
if self.pending_apdu {
self.pending_apdu = false;
return DecodedEvent::from_type(DecodedEventType::Apdu {
header: self.pending_header,
offset: self.pending_offset,
length: self.pending_length,
});
}
self.recv(true).unwrap().decode_event()
}
pub fn next_event(&mut self) -> DecodedEvent<N> {
loop {
let ety = self.try_next_event().into_type();
if !matches!(ety, DecodedEventType::Ignored) {
return DecodedEvent::from_type(ety);
}
}
}
pub fn next_command(&mut self) -> Command<'_, N> {
loop {
let ety = self.next_event().into_type();
match ety {
DecodedEventType::Apdu {
header,
offset,
length,
} => {
if header.cla == 0xB0 {
handle_bolos_apdu::<N>(self, header.ins, header.p1, header.p2);
continue;
}
if let Some(cla) = self.expected_cla {
if header.cla != cla {
let _ = self.begin_response().send(StatusWords::BadCla);
continue;
}
}
return Command::new(self, header, offset, length);
}
DecodedEventType::ApduError(e) => self.send(&[], StatusWords::from(e)).unwrap(),
_ => {}
}
}
}
pub fn set_expected_cla(&mut self, cla: u8) {
self.expected_cla = Some(cla);
}
}
pub enum ApduError {
BadLen,
}
impl From<ApduError> for StatusWords {
fn from(e: ApduError) -> Self {
match e {
ApduError::BadLen => StatusWords::BadLen,
}
}
}
pub struct Command<'a, const N: usize = DEFAULT_BUF_SIZE> {
comm: &'a mut Comm<N>,
header: ApduHeader,
offset: usize,
length: usize,
}
impl<'a, const N: usize> Command<'a, N> {
pub fn new(comm: &'a mut Comm<N>, header: ApduHeader, offset: usize, length: usize) -> Self {
Self {
comm,
header,
offset,
length,
}
}
pub fn decode<T>(&self) -> Result<T, Reply>
where
T: TryFrom<ApduHeader>,
Reply: From<<T as TryFrom<ApduHeader>>::Error>,
{
T::try_from(self.header).map_err(Reply::from)
}
pub fn get_data(&self) -> &[u8] {
&self.comm.buf[self.offset..self.offset + self.length]
}
pub fn into_response(self) -> CommandResponse<'a, N> {
CommandResponse {
comm: self.comm,
len: 0,
}
}
pub fn into_comm(self) -> &'a mut Comm<N> {
self.comm
}
pub fn reply<T: Into<Reply>>(self, data: &[u8], reply: T) -> Result<(), CommError> {
self.into_response().extend(data)?.send(reply)?;
Ok(())
}
}
pub(crate) struct Rx<'a, const N: usize = DEFAULT_BUF_SIZE> {
comm: &'a mut Comm<N>,
len: usize,
}
impl<'a, const N: usize> core::ops::Deref for Rx<'a, N> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<'a, const N: usize> Rx<'a, N> {
pub fn as_slice(&self) -> &[u8] {
&self.comm.buf[..self.len]
}
pub fn decode_event(self) -> DecodedEvent<N> {
DecodedEvent::new(self.comm, self.len)
}
}
pub struct CommandResponse<'a, const N: usize = DEFAULT_BUF_SIZE> {
comm: &'a mut Comm<N>,
len: usize,
}
impl<'a, const N: usize> CommandResponse<'a, N> {
pub fn new(comm: &'a mut Comm<N>) -> Self {
Self { comm, len: 0 }
}
pub fn len(&self) -> usize {
self.len
}
#[inline]
fn try_append(&mut self, src: &[u8]) -> Result<(), CommError> {
let start = self.len;
let end = start.checked_add(src.len()).ok_or(CommError::Overflow)?;
if end > N - 2 {
return Err(CommError::Overflow);
}
self.comm.buf[start..end].copy_from_slice(src);
self.len = end;
Ok(())
}
pub fn extend(self, src: &[u8]) -> Result<Self, CommError> {
let mut this = self;
this.try_append(src)?;
Ok(this)
}
pub fn append(&mut self, src: &[u8]) -> Result<&mut Self, CommError> {
self.try_append(src)?;
Ok(self)
}
pub fn send<T: Into<Reply>>(mut self, reply: T) -> Result<&'a mut Comm<N>, CommError> {
let sw: u16 = reply.into().0;
self.append(sw.to_be_bytes().as_ref())?;
let n = self.len;
if 0 > sys_seph::io_tx(self.comm.apdu_type, self.comm.buf[..n].as_ref(), n) {
return Err(CommError::IoError);
}
self.comm.pending_apdu = false;
Ok(self.comm)
}
pub fn clear(&mut self) {
self.len = 0;
}
}
impl<const N: usize> Drop for Comm<N> {
fn drop(&mut self) {
callbacks::clear_comm();
callbacks::clear_panic_handler();
unsafe { COMM_INITIALIZED = false };
}
}
pub fn init_comm<const N: usize>(storage: &'static CommStorage<N>) -> &'static mut Comm<N> {
let comm_ref = storage.init(Comm::new());
comm_ref.nbgl_register_comm();
callbacks::register_panic_handler::<N>();
comm_ref
}