esp-idf-svc 0.45.0

Implementation of the embedded-svc traits for ESP-IDF (Espressif's IoT Development Framework)
use core::cmp::min;
use core::fmt::Write;
use core::mem;
use core::ptr;

use ::log::*;
use embedded_svc::ota::Slot;

use embedded_svc::io;
use embedded_svc::ota;

use esp_idf_sys::*;

use crate::errors::EspIOError;
use crate::private::{common::*, cstr::*, mutex};

static TAKEN: mutex::Mutex<bool> = mutex::Mutex::wrap(mutex::RawMutex::new(), false);

impl From<Newtype<&esp_app_desc_t>> for ota::FirmwareInfo {
    fn from(app_desc: Newtype<&esp_app_desc_t>) -> Self {
        let app_desc = app_desc.0;

        let mut result = Self {
            version: unsafe { from_cstr_ptr(&app_desc.version as *const _).into() },
            signature: Some(heapless::Vec::from_slice(&app_desc.app_elf_sha256).unwrap()),
            released: "".into(),
            description: Some(unsafe { from_cstr_ptr(&app_desc.project_name as *const _).into() }),
            download_id: None,
        };

        write!(
            &mut result.released,
            "{} {}",
            unsafe { from_cstr_ptr(&app_desc.date as *const _) },
            unsafe { from_cstr_ptr(&app_desc.time as *const _) }
        )
        .unwrap();

        result
    }
}

pub struct EspFirmwareInfoLoader(heapless::Vec<u8, 512>);

impl EspFirmwareInfoLoader {
    pub fn new() -> Self {
        Self(heapless::Vec::new())
    }
}

impl Default for EspFirmwareInfoLoader {
    fn default() -> Self {
        Self::new()
    }
}

impl io::Io for EspFirmwareInfoLoader {
    type Error = EspIOError;
}

impl ota::FirmwareInfoLoader for EspFirmwareInfoLoader {
    fn load(&mut self, buf: &[u8]) -> Result<ota::LoadResult, Self::Error> {
        if !self.is_loaded() {
            let remaining = self.0.capacity() - self.0.len();
            if remaining > 0 {
                self.0
                    .extend_from_slice(&buf[..min(buf.len(), remaining)])
                    .unwrap();
            }
        }

        Ok(if self.is_loaded() {
            ota::LoadResult::Loaded
        } else {
            ota::LoadResult::LoadMore
        })
    }

    fn is_loaded(&self) -> bool {
        self.0.len()
            >= mem::size_of::<esp_image_header_t>()
                + mem::size_of::<esp_image_segment_header_t>()
                + mem::size_of::<esp_app_desc_t>()
    }

    fn get_info(&self) -> Result<ota::FirmwareInfo, Self::Error> {
        if self.is_loaded() {
            let app_desc_slice = &self.0[0..mem::size_of::<esp_image_header_t>()
                + mem::size_of::<esp_image_segment_header_t>()];

            let app_desc = unsafe {
                (app_desc_slice.as_ptr() as *const esp_app_desc_t)
                    .as_ref()
                    .unwrap()
            };

            Ok(Newtype(app_desc).into())
        } else {
            Err(EspError::from(ESP_ERR_INVALID_SIZE as _).unwrap().into())
        }
    }
}

#[derive(Debug)]
pub struct EspOtaUpdate {
    update_partition: *const esp_partition_t,
    update_handle: esp_ota_handle_t,
}

impl EspOtaUpdate {
    pub fn write(&mut self, buf: &[u8]) -> Result<usize, EspError> {
        self.check_write()?;

        esp!(unsafe { esp_ota_write(self.update_handle, buf.as_ptr() as _, buf.len() as _) })?;

        Ok(buf.len())
    }

    pub fn flush(&mut self) -> Result<(), EspError> {
        self.check_write()?;

        Ok(())
    }

    pub fn complete(&mut self) -> Result<(), EspError> {
        self.check_write()?;

        esp!(unsafe { esp_ota_end(self.update_handle) })?;
        esp!(unsafe { esp_ota_set_boot_partition(self.update_partition) })?;

        self.update_partition = core::ptr::null();
        self.update_handle = 0;

        Ok(())
    }

    pub fn abort(&mut self) -> Result<(), EspError> {
        self.check_write()?;

        esp!(unsafe { esp_ota_abort(self.update_handle) })?;

        self.update_partition = core::ptr::null();
        self.update_handle = 0;

        Ok(())
    }

    fn check_write(&self) -> Result<(), EspError> {
        if !self.update_partition.is_null() {
            Ok(())
        } else {
            Err(EspError::from(ESP_FAIL).unwrap())
        }
    }
}

#[derive(Debug)]
pub struct EspOta(EspOtaUpdate);

impl EspOta {
    pub fn new() -> Result<Self, EspError> {
        let mut taken = TAKEN.lock();

        if *taken {
            esp!(ESP_ERR_INVALID_STATE)?;
        }

        *taken = true;
        Ok(Self(EspOtaUpdate {
            update_partition: core::ptr::null(),
            update_handle: 0,
        }))
    }

    pub fn get_boot_slot(&self) -> Result<Slot, EspError> {
        self.check_read()?;

        self.get_slot(unsafe { esp_ota_get_boot_partition().as_ref().unwrap() })
    }

    pub fn get_running_slot(&self) -> Result<Slot, EspError> {
        self.check_read()?;

        self.get_slot(unsafe { esp_ota_get_boot_partition().as_ref().unwrap() })
    }

    pub fn get_update_slot(&self) -> Result<Slot, EspError> {
        self.check_read()?;

        self.get_slot(unsafe {
            esp_ota_get_next_update_partition(ptr::null())
                .as_ref()
                .unwrap()
        })
    }

    pub fn is_factory_reset_supported(&self) -> Result<bool, EspError> {
        self.check_read()?;

        self.get_factory_partition()
            .map(|factory| !factory.is_null())
    }

    pub fn factory_reset(&mut self) -> Result<(), EspError> {
        self.check_read()?;

        let factory = self.get_factory_partition()?;

        esp!(unsafe { esp_ota_set_boot_partition(factory) })?;

        Ok(())
    }

    pub fn initiate_update(&mut self) -> Result<&mut EspOtaUpdate, EspError> {
        self.check_read()?;

        let partition = unsafe { esp_ota_get_next_update_partition(ptr::null()) };

        let mut handle: esp_ota_handle_t = Default::default();

        esp!(unsafe { esp_ota_begin(partition, OTA_SIZE_UNKNOWN as usize, &mut handle) })?;

        self.0.update_partition = partition;
        self.0.update_handle = handle;

        Ok(&mut self.0)
    }

    pub fn mark_running_slot_valid(&mut self) -> Result<(), EspError> {
        self.check_read()?;

        Ok(esp!(unsafe { esp_ota_mark_app_valid_cancel_rollback() })?)
    }

    pub fn mark_running_slot_invalid_and_reboot(&mut self) -> EspError {
        if let Err(err) = self.check_read() {
            err
        } else if let Err(err) = esp!(unsafe { esp_ota_mark_app_invalid_rollback_and_reboot() }) {
            err
        } else {
            unreachable!()
        }
    }

    fn get_factory_partition(&self) -> Result<*const esp_partition_t, EspError> {
        self.check_read()?;

        let partition_iterator = unsafe {
            esp_partition_find(
                esp_partition_type_t_ESP_PARTITION_TYPE_APP,
                esp_partition_subtype_t_ESP_PARTITION_SUBTYPE_APP_FACTORY,
                b"factory\0" as *const _ as *const _,
            )
        };

        if partition_iterator.is_null() {
            esp!(ESP_ERR_NOT_SUPPORTED)?;
        }

        let partition = unsafe { esp_partition_get(partition_iterator) };

        unsafe { esp_partition_iterator_release(partition_iterator) };

        Ok(partition)
    }

    fn get_slot(&self, partition: &esp_partition_t) -> Result<Slot, EspError> {
        self.check_read()?;

        Ok(Slot {
            label: unsafe { from_cstr_ptr(&partition.label as *const _ as *const _).into() },
            state: self.get_state(partition)?,
            firmware: self.get_firmware_info(partition)?,
        })
    }

    fn get_state(&self, partition: &esp_partition_t) -> Result<ota::SlotState, EspError> {
        let mut state: esp_ota_img_states_t = Default::default();

        let err =
            unsafe { esp_ota_get_state_partition(partition as *const _, &mut state as *mut _) };

        Ok(if err == ESP_ERR_NOT_FOUND {
            ota::SlotState::Unknown
        } else if err == ESP_ERR_NOT_SUPPORTED as _ {
            ota::SlotState::Factory
        } else {
            esp!(err)?;

            #[allow(non_upper_case_globals)]
            match state {
                esp_ota_img_states_t_ESP_OTA_IMG_NEW
                | esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY => ota::SlotState::Unverified,
                esp_ota_img_states_t_ESP_OTA_IMG_VALID => ota::SlotState::Valid,
                esp_ota_img_states_t_ESP_OTA_IMG_INVALID
                | esp_ota_img_states_t_ESP_OTA_IMG_ABORTED => ota::SlotState::Invalid,
                esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED => ota::SlotState::Unknown,
                _ => ota::SlotState::Unknown,
            }
        })
    }

    fn get_firmware_info(
        &self,
        partition: &esp_partition_t,
    ) -> Result<Option<ota::FirmwareInfo>, EspError> {
        let mut app_desc: esp_app_desc_t = Default::default();

        let err =
            unsafe { esp_ota_get_partition_description(partition as *const _, &mut app_desc) };

        Ok(if err == ESP_ERR_NOT_FOUND {
            None
        } else {
            esp!(err)?;

            Some(Newtype(&app_desc).into())
        })
    }

    fn check_read(&self) -> Result<(), EspError> {
        if self.0.update_partition.is_null() {
            Ok(())
        } else {
            Err(EspError::from(ESP_FAIL).unwrap())
        }
    }
}

impl Drop for EspOta {
    fn drop(&mut self) {
        *TAKEN.lock() = false;

        info!("Dropped");
    }
}

impl io::Io for EspOta {
    type Error = EspIOError;
}

impl ota::Ota for EspOta {
    type Update = EspOtaUpdate;

    fn get_boot_slot(&self) -> Result<Slot, Self::Error> {
        EspOta::get_boot_slot(self).map_err(EspIOError)
    }

    fn get_running_slot(&self) -> Result<Slot, Self::Error> {
        EspOta::get_running_slot(self).map_err(EspIOError)
    }

    fn get_update_slot(&self) -> Result<Slot, Self::Error> {
        EspOta::get_update_slot(self).map_err(EspIOError)
    }

    fn is_factory_reset_supported(&self) -> Result<bool, Self::Error> {
        EspOta::is_factory_reset_supported(self).map_err(EspIOError)
    }

    fn factory_reset(&mut self) -> Result<(), Self::Error> {
        EspOta::factory_reset(self).map_err(EspIOError)
    }

    fn initiate_update(&mut self) -> Result<&mut Self::Update, Self::Error> {
        EspOta::initiate_update(self).map_err(EspIOError)
    }

    fn mark_running_slot_valid(&mut self) -> Result<(), Self::Error> {
        EspOta::mark_running_slot_valid(self).map_err(EspIOError)
    }

    fn mark_running_slot_invalid_and_reboot(&mut self) -> Self::Error {
        EspIOError(EspOta::mark_running_slot_invalid_and_reboot(self))
    }
}

unsafe impl Send for EspOtaUpdate {}

impl io::Io for EspOtaUpdate {
    type Error = EspIOError;
}

impl ota::OtaUpdate for EspOtaUpdate {
    fn complete(&mut self) -> Result<(), Self::Error> {
        EspOtaUpdate::complete(self)?;

        Ok(())
    }

    fn abort(&mut self) -> Result<(), Self::Error> {
        EspOtaUpdate::abort(self)?;

        Ok(())
    }
}

impl io::Write for EspOtaUpdate {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
        let size = EspOtaUpdate::write(self, buf)?;

        Ok(size)
    }

    fn flush(&mut self) -> Result<(), Self::Error> {
        EspOtaUpdate::flush(self)?;

        Ok(())
    }
}