use core::convert::Infallible;
use crate::{
peripheral::{Peripheral, PeripheralRef},
peripherals::SHA,
};
const ALIGN_SIZE: usize = core::mem::size_of::<u32>();
#[cfg(esp32)]
const U32_FROM_BYTES: fn([u8; 4]) -> u32 = u32::from_be_bytes;
#[cfg(not(esp32))]
const U32_FROM_BYTES: fn([u8; 4]) -> u32 = u32::from_ne_bytes;
#[derive(Debug)]
struct AlignmentHelper {
buf: [u8; ALIGN_SIZE],
buf_fill: usize,
}
impl AlignmentHelper {
pub fn default() -> AlignmentHelper {
AlignmentHelper {
buf: [0u8; ALIGN_SIZE],
buf_fill: 0,
}
}
pub unsafe fn flush_to(&mut self, dst: *mut u32) -> usize {
if self.buf_fill != 0 {
for i in self.buf_fill..ALIGN_SIZE {
self.buf[i] = 0;
}
dst.write_volatile(U32_FROM_BYTES(self.buf));
}
let flushed = self.buf_fill;
self.buf_fill = 0;
return flushed;
}
#[allow(unused)]
pub unsafe fn volatile_write_bytes(&mut self, dst: *mut u32, val: u8, count: usize) {
let mut cursor = 0;
if self.buf_fill != 0 {
for i in self.buf_fill..ALIGN_SIZE {
self.buf[i] = val;
}
dst.write_volatile(U32_FROM_BYTES(self.buf));
cursor = 1;
self.buf_fill = 0;
}
core::ptr::write_bytes(dst.add(cursor), val, count);
}
pub unsafe fn aligned_volatile_copy<'a>(
&mut self,
dst: *mut u32,
src: &'a [u8],
dst_bound: usize,
) -> (&'a [u8], bool) {
assert!(dst_bound > 0);
let mut nsrc = src;
let mut cursor = 0;
if self.buf_fill != 0 {
let max_fill = ALIGN_SIZE - self.buf_fill;
let (nbuf, src) = src.split_at(core::cmp::min(src.len(), max_fill));
nsrc = src;
for i in 0..max_fill {
match nbuf.get(i) {
Some(v) => {
self.buf[self.buf_fill + i] = *v;
self.buf_fill += 1;
}
None => return (&[], false), }
}
dst.write_volatile(U32_FROM_BYTES(self.buf));
cursor += 1;
self.buf_fill = 0;
}
if dst_bound <= cursor * ALIGN_SIZE {
return (nsrc, true);
}
let (to_write, remaining) = nsrc.split_at(core::cmp::min(
dst_bound - cursor * ALIGN_SIZE,
(nsrc.len() / ALIGN_SIZE) * ALIGN_SIZE, ));
if to_write.len() > 0 {
for (i, v) in to_write.chunks_exact(ALIGN_SIZE).enumerate() {
dst.add(i)
.write_volatile(U32_FROM_BYTES(v.try_into().unwrap()).to_be());
}
}
let was_bounded = dst_bound - to_write.len() == 0;
if remaining.len() > 0 && remaining.len() < 4 {
for i in 0..remaining.len() {
self.buf[i] = remaining[i];
}
self.buf_fill = remaining.len();
return (&[], was_bounded);
}
return (remaining, was_bounded);
}
}
pub struct Sha<'d> {
sha: PeripheralRef<'d, SHA>,
mode: ShaMode,
alignment_helper: AlignmentHelper,
cursor: usize,
first_run: bool,
finished: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum ShaMode {
SHA1,
#[cfg(not(esp32))]
SHA224,
SHA256,
#[cfg(any(esp32s2, esp32s3, esp32))]
SHA384,
#[cfg(any(esp32s2, esp32s3, esp32))]
SHA512,
#[cfg(any(esp32s2, esp32s3))]
SHA512_224,
#[cfg(any(esp32s2, esp32s3))]
SHA512_256,
}
#[cfg(not(esp32))]
fn mode_as_bits(mode: ShaMode) -> u8 {
match mode {
ShaMode::SHA1 => 0,
ShaMode::SHA224 => 1,
ShaMode::SHA256 => 2,
#[cfg(any(esp32s2, esp32s3))]
ShaMode::SHA384 => 3,
#[cfg(any(esp32s2, esp32s3))]
ShaMode::SHA512 => 4,
#[cfg(any(esp32s2, esp32s3))]
ShaMode::SHA512_224 => 5,
#[cfg(any(esp32s2, esp32s3))]
ShaMode::SHA512_256 => 6,
}
}
impl<'d> Sha<'d> {
pub fn new(sha: impl Peripheral<P = SHA> + 'd, mode: ShaMode) -> Self {
crate::into_ref!(sha);
#[cfg(not(esp32))]
sha.mode
.write(|w| unsafe { w.mode().bits(mode_as_bits(mode)) });
Self {
sha,
mode,
cursor: 0,
first_run: true,
finished: false,
alignment_helper: AlignmentHelper::default(),
}
}
pub fn first_run(&self) -> bool {
self.first_run
}
pub fn finished(&self) -> bool {
self.finished
}
#[cfg(not(esp32))]
fn process_buffer(&mut self) {
if self.first_run {
unsafe {
self.sha.start.as_ptr().write_volatile(1u32);
}
self.first_run = false;
} else {
unsafe {
self.sha.continue_.as_ptr().write_volatile(1u32);
}
}
}
#[cfg(esp32)]
fn process_buffer(&mut self) {
if self.first_run {
match self.mode {
ShaMode::SHA1 => self.sha.sha1_start.write(|w| unsafe { w.bits(1) }),
ShaMode::SHA256 => self.sha.sha256_start.write(|w| unsafe { w.bits(1) }),
ShaMode::SHA384 => self.sha.sha384_start.write(|w| unsafe { w.bits(1) }),
ShaMode::SHA512 => self.sha.sha512_start.write(|w| unsafe { w.bits(1) }),
}
self.first_run = false;
} else {
match self.mode {
ShaMode::SHA1 => self.sha.sha1_continue.write(|w| unsafe { w.bits(1) }),
ShaMode::SHA256 => self.sha.sha256_continue.write(|w| unsafe { w.bits(1) }),
ShaMode::SHA384 => self.sha.sha384_continue.write(|w| unsafe { w.bits(1) }),
ShaMode::SHA512 => self.sha.sha512_continue.write(|w| unsafe { w.bits(1) }),
}
}
}
fn chunk_length(&self) -> usize {
return match self.mode {
ShaMode::SHA1 | ShaMode::SHA256 => 64,
#[cfg(not(esp32))]
ShaMode::SHA224 => 64,
#[cfg(not(any(esp32c2, esp32c3, esp32c6)))]
_ => 128,
};
}
#[cfg(esp32)]
fn is_busy(&self) -> bool {
match self.mode {
ShaMode::SHA1 => self.sha.sha1_busy.read().sha1_busy().bit_is_set(),
ShaMode::SHA256 => self.sha.sha256_busy.read().sha256_busy().bit_is_set(),
ShaMode::SHA384 => self.sha.sha384_busy.read().sha384_busy().bit_is_set(),
ShaMode::SHA512 => self.sha.sha512_busy.read().sha512_busy().bit_is_set(),
}
}
#[cfg(not(esp32))]
fn is_busy(&self) -> bool {
self.sha.busy.read().bits() != 0
}
pub fn digest_length(&self) -> usize {
match self.mode {
ShaMode::SHA1 => 20,
#[cfg(not(esp32))]
ShaMode::SHA224 => 28,
ShaMode::SHA256 => 32,
#[cfg(any(esp32, esp32s2, esp32s3))]
ShaMode::SHA384 => 48,
#[cfg(any(esp32, esp32s2, esp32s3))]
ShaMode::SHA512 => 64,
#[cfg(any(esp32s2, esp32s3))]
ShaMode::SHA512_224 => 28,
#[cfg(any(esp32s2, esp32s3))]
ShaMode::SHA512_256 => 32,
}
}
#[cfg(not(esp32))]
fn input_ptr(&self) -> *mut u32 {
return self.sha.m_mem[0].as_ptr() as *mut u32;
}
#[cfg(esp32)]
fn input_ptr(&self) -> *mut u32 {
return self.sha.text[0].as_ptr() as *mut u32;
}
#[cfg(not(esp32))]
fn output_ptr(&self) -> *const u32 {
return self.sha.h_mem[0].as_ptr() as *const u32;
}
#[cfg(esp32)]
fn output_ptr(&self) -> *const u32 {
return self.sha.text[0].as_ptr() as *const u32;
}
fn flush_data(&mut self) -> nb::Result<(), Infallible> {
if self.is_busy() {
return Err(nb::Error::WouldBlock);
}
unsafe {
let dst_ptr = self
.input_ptr()
.add((self.cursor % self.chunk_length()) / ALIGN_SIZE);
let flushed = self.alignment_helper.flush_to(dst_ptr);
if flushed != 0 {
self.cursor = self.cursor.wrapping_add(ALIGN_SIZE - flushed);
if self.cursor % self.chunk_length() == 0 {
self.process_buffer();
}
}
}
Ok(())
}
fn write_data<'a>(&mut self, incoming: &'a [u8]) -> nb::Result<&'a [u8], Infallible> {
let mod_cursor = self.cursor % self.chunk_length();
unsafe {
let ptr = self.input_ptr().add(mod_cursor / ALIGN_SIZE);
let (remaining, bound_reached) = self.alignment_helper.aligned_volatile_copy(
ptr,
incoming,
self.chunk_length() - mod_cursor,
);
self.cursor = self.cursor.wrapping_add(incoming.len() - remaining.len());
if bound_reached {
self.process_buffer();
}
Ok(remaining)
}
}
pub fn update<'a>(&mut self, buffer: &'a [u8]) -> nb::Result<&'a [u8], Infallible> {
if self.is_busy() {
return Err(nb::Error::WouldBlock);
}
self.finished = false;
let remaining = self.write_data(buffer)?;
Ok(remaining)
}
pub fn finish(&mut self, output: &mut [u8]) -> nb::Result<(), Infallible> {
if self.is_busy() {
return Err(nb::Error::WouldBlock);
}
let chunk_len = self.chunk_length();
if !self.finished {
let length = self.cursor * 8;
nb::block!(self.update(&[0x80]))?; nb::block!(self.flush_data())?; debug_assert!(self.cursor % 4 == 0);
let mod_cursor = self.cursor % chunk_len;
if chunk_len - mod_cursor < chunk_len / 8 {
let pad_len = chunk_len - mod_cursor;
unsafe {
let m_cursor_ptr = self.input_ptr().add(mod_cursor / ALIGN_SIZE);
self.alignment_helper.volatile_write_bytes(
m_cursor_ptr,
0,
pad_len / ALIGN_SIZE,
);
}
self.process_buffer();
self.cursor = self.cursor.wrapping_add(pad_len);
while self.is_busy() {}
}
let mod_cursor = self.cursor % chunk_len; unsafe {
let m_cursor_ptr = self.input_ptr();
let pad_ptr = m_cursor_ptr.add(mod_cursor / ALIGN_SIZE);
let pad_len = (chunk_len - mod_cursor) - ALIGN_SIZE;
self.alignment_helper
.volatile_write_bytes(pad_ptr, 0, pad_len / ALIGN_SIZE);
let end_ptr = m_cursor_ptr.add((chunk_len / ALIGN_SIZE) - 1);
#[cfg(not(esp32))]
end_ptr.write_volatile(length.to_be() as u32);
#[cfg(esp32)]
end_ptr.write_volatile(length.to_le() as u32);
}
self.process_buffer();
while self.is_busy() {}
#[cfg(esp32)]
{
match self.mode {
ShaMode::SHA1 => unsafe { self.sha.sha1_load.write(|w| w.bits(1)) },
ShaMode::SHA256 => unsafe { self.sha.sha256_load.write(|w| w.bits(1)) },
ShaMode::SHA384 => unsafe { self.sha.sha384_load.write(|w| w.bits(1)) },
ShaMode::SHA512 => unsafe { self.sha.sha512_load.write(|w| w.bits(1)) },
}
while self.is_busy() {}
}
self.finished = true;
}
unsafe {
let digest_ptr = self.output_ptr();
let out_ptr = output.as_mut_ptr() as *mut u32;
let digest_out = core::cmp::min(self.digest_length(), output.len()) / ALIGN_SIZE;
for i in 0..digest_out {
#[cfg(not(esp32))]
out_ptr.add(i).write(*digest_ptr.add(i));
#[cfg(esp32)]
out_ptr.add(i).write((*digest_ptr.add(i)).to_be());
}
}
Ok(())
}
}