etp 0.0.1-alpha

Embedded Tester Library (ETP). Control embedded devices from host!
Documentation
use std::sync::atomic::AtomicU16;

pub(crate) static PRIVATE_TX_ID: AtomicU16 = AtomicU16::new(0);

pub(crate) fn get_transaction_id() -> u16 {
    PRIVATE_TX_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
}

pub const ETP_MESSAGE_HEADER_SIZE: usize = 8;
pub const ETP_MESSAGE_HEADER_STATUS_INDEX: usize = 3;
#[derive(Clone, Debug)]
pub(crate) struct EtpMessageHeader {
    pub length: u16,
    pub payload_type: EtpPayloadType,
    pub status: EtpStatusCodes,
    pub transaction_id: u16,
    pub operation: EtpOperations,
}

impl EtpMessageHeader {
    pub fn new(
        payload_length: u16,
        status: EtpStatusCodes,
        payload_type: EtpPayloadType,
        operation: EtpOperations,
    ) -> Self {
        EtpMessageHeader {
            length: payload_length + ETP_MESSAGE_HEADER_SIZE as u16,
            payload_type,
            status,
            transaction_id: get_transaction_id(),
            operation,
        }
    }
}

impl Into<Vec<u8>> for EtpMessageHeader {
    fn into(self) -> Vec<u8> {
        let mut header = Vec::new();
        // Everything is little endian
        header.extend_from_slice(&self.length.to_le_bytes());
        header.push(self.payload_type as u8);
        header.push(self.status as u8);
        header.extend_from_slice(&self.transaction_id.to_le_bytes());
        let op = self.operation as u16;
        header.extend_from_slice(&op.to_le_bytes());

        header
    }
}

impl TryFrom<&[u8]> for EtpMessageHeader {
    type Error = &'static str;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        if value.len() != 8 {
            return Err("Invalid byte length for ETP message header");
        }
        let length = u16::from_le_bytes([value[0], value[1]]);
        let payload_type = value[2];
        let status = EtpStatusCodes::try_from(value[ETP_MESSAGE_HEADER_STATUS_INDEX])?;
        let transaction_id = u16::from_le_bytes([value[4], value[5]]);
        let operation = u16::from_le_bytes([value[6], value[7]]);

        Ok(EtpMessageHeader {
            length,
            payload_type: EtpPayloadType::try_from(payload_type)
                .map_err(|_| "Invalid payload type")?,
            status,
            transaction_id,
            operation: EtpOperations::try_from(operation).map_err(|_| "Invalid operation")?,
        })
    }
}

#[repr(u16)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum EtpOperations {
    GetFirmwareInfo = 0x0000,
    Reset = 0x0001,
    GetSupportedOperations = 0x0002,
    ConfigureTransport = 0x0003,
    GetGpioInfo = 0x0100,
    GpioInit = 0x0101,
    GpioRead = 0x0102,
    GpioWrite = 0x0103,
    DebugPrint = 0x00db,
}

impl TryFrom<u16> for EtpOperations {
    type Error = &'static str;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        match value {
            0x0000 => Ok(EtpOperations::GetFirmwareInfo),
            0x0001 => Ok(EtpOperations::Reset),
            0x0002 => Ok(EtpOperations::GetSupportedOperations),
            0x0003 => Ok(EtpOperations::ConfigureTransport),
            0x0100 => Ok(EtpOperations::GetGpioInfo),
            0x0101 => Ok(EtpOperations::GpioInit),
            0x0102 => Ok(EtpOperations::GpioRead),
            0x0103 => Ok(EtpOperations::GpioWrite),
            0x00db => Ok(EtpOperations::DebugPrint),
            _ => Err("Invalid ETP operation"),
        }
    }
}

#[repr(u8)]
pub(crate) enum FirmwareInfoCommands {
    // TODO: Protocol version?
    Version = 0x01,
    FirmwareVersion = 0x02,
    BuildDate = 0x03,
    HardwareType = 0x04,
}

impl TryFrom<u8> for FirmwareInfoCommands {
    type Error = &'static str;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0x01 => Ok(FirmwareInfoCommands::Version),
            0x02 => Ok(FirmwareInfoCommands::FirmwareVersion),
            0x03 => Ok(FirmwareInfoCommands::BuildDate),
            0x04 => Ok(FirmwareInfoCommands::HardwareType),
            _ => Err("Invalid firmware info command"),
        }
    }
}

#[repr(u8)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ResetType {
    ReInit = 0x01,
    SoftReset = 0x02,
    HardReset = 0x03,
    Bootloader = 0x04,
}

impl TryFrom<u8> for ResetType {
    type Error = &'static str;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0x01 => Ok(ResetType::ReInit),
            0x02 => Ok(ResetType::SoftReset),
            0x03 => Ok(ResetType::HardReset),
            0x04 => Ok(ResetType::Bootloader),
            _ => Err("Invalid reset type"),
        }
    }
}

#[repr(u8)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum EtpPayloadType {
    Command = 0x01,
    Data = 0x02,
    Response = 0x03,
    Event = 0x04,
}

impl TryFrom<u8> for EtpPayloadType {
    type Error = &'static str;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0x01 => Ok(EtpPayloadType::Command),
            0x02 => Ok(EtpPayloadType::Data),
            0x03 => Ok(EtpPayloadType::Response),
            0x04 => Ok(EtpPayloadType::Event),
            _ => Err("Invalid ETP payload type"),
        }
    }
}

#[repr(u8)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum TranportTypes {
    Uart = 0x01,
    Usb = 0x02,
    Wifi = 0x03,
    Ble = 0x04,
    Bluetooth = 0x05,
    TcpIp = 0x06,
}

impl TryFrom<u8> for TranportTypes {
    type Error = &'static str;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0x01 => Ok(TranportTypes::Uart),
            0x02 => Ok(TranportTypes::Usb),
            0x03 => Ok(TranportTypes::Wifi),
            0x04 => Ok(TranportTypes::Ble),
            0x05 => Ok(TranportTypes::Bluetooth),
            0x06 => Ok(TranportTypes::TcpIp),
            _ => Err("Invalid transport type"),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum EtpStatusCodes {
    Success = 0x00,
    InvalidCommand = 0x01,
    InvalidParameter = 0x02,
    InvalidPort = 0x40,
    Failure = 0xFF,
}

impl TryFrom<u8> for EtpStatusCodes {
    type Error = &'static str;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0x00 => Ok(EtpStatusCodes::Success),
            0x01 => Ok(EtpStatusCodes::InvalidCommand),
            0x02 => Ok(EtpStatusCodes::InvalidParameter),
            0x40 => Ok(EtpStatusCodes::InvalidPort),
            0xFF => Ok(EtpStatusCodes::Failure),
            _ => Err("Invalid ETP status code"),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum EtpFirmwareInfoCmd {
    ProtocolVersion = 0x01,
    FirmwareVersion = 0x02,
    BuildDate = 0x03,
    HardwareType = 0x04,
}

impl TryFrom<u8> for EtpFirmwareInfoCmd {
    type Error = &'static str;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0x01 => Ok(EtpFirmwareInfoCmd::ProtocolVersion),
            0x02 => Ok(EtpFirmwareInfoCmd::FirmwareVersion),
            0x03 => Ok(EtpFirmwareInfoCmd::BuildDate),
            0x04 => Ok(EtpFirmwareInfoCmd::HardwareType),
            _ => Err("Invalid ETP firmware info command"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_transaction_id() {
        let tx_id = get_transaction_id();
        assert_eq!(tx_id, 0);
        let tx_id = get_transaction_id();
        assert_eq!(tx_id, 1);

        PRIVATE_TX_ID.store(0xFFFF, std::sync::atomic::Ordering::SeqCst);
        let tx_id = get_transaction_id();
        assert_eq!(tx_id, 0xFFFF);

        let tx_id = get_transaction_id();
        assert_eq!(tx_id, 0);
    }

    #[test]
    fn test_message_header() {
        let header = EtpMessageHeader {
            length: 0x1234,
            payload_type: EtpPayloadType::Command,
            status: EtpStatusCodes::Success,
            transaction_id: 0x5678,
            operation: EtpOperations::GetFirmwareInfo,
        };

        let header_bytes: Vec<u8> = header.clone().into();
        assert_eq!(header_bytes.len(), ETP_MESSAGE_HEADER_SIZE);

        let parsed_header = EtpMessageHeader::try_from(&header_bytes[..]).unwrap();
        assert_eq!(parsed_header.length, header.length);
        assert_eq!(parsed_header.payload_type, header.payload_type);
        assert_eq!(parsed_header.transaction_id, header.transaction_id);
        assert_eq!(parsed_header.operation, header.operation);

        assert_eq!(header_bytes[0], 0x34);
        assert_eq!(header_bytes[1], 0x12);
        assert_eq!(header_bytes[2], 0x01);
        assert_eq!(header_bytes[3], 0x00);
        assert_eq!(header_bytes[4], 0x78);
        assert_eq!(header_bytes[5], 0x56);
        assert_eq!(header_bytes[6], 0x00);
        assert_eq!(header_bytes[7], 0x00);

        let header_replica: EtpMessageHeader =
            EtpMessageHeader::try_from(&header_bytes[..]).unwrap();

        assert_eq!(header_replica.length, header.length);
        assert_eq!(header_replica.payload_type, header.payload_type);
        assert_eq!(header_replica.status, header.status);
        assert_eq!(header_replica.transaction_id, header.transaction_id);
        assert_eq!(header_replica.operation, header.operation);
    }
}