use num::FromPrimitive;
use num_derive::FromPrimitive;
use crate::error::{Error, ErrorCode};
use crate::sc::{self, GeneralCode, StatusReport};
use crate::transport::exchange::{Exchange, MessageMeta};
use crate::transport::{MAX_RX_PAYLOAD_SIZE, MAX_TX_PAYLOAD_SIZE};
use crate::utils::storage::{ReadBuf, WriteBuf};
mod handler;
mod nego;
mod read;
mod write;
pub use handler::*;
pub use read::*;
pub use write::*;
pub type BdxBuffer = crate::transport::exchange::Buffer;
pub const PROTO_ID_BDX: u16 = 0x0002;
pub const BDX_VERSION: u8 = 0;
#[derive(FromPrimitive, Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum OpCode {
SendInit = 0x01,
SendAccept = 0x02,
ReceiveInit = 0x04,
ReceiveAccept = 0x05,
BlockQuery = 0x10,
Block = 0x11,
BlockEof = 0x12,
BlockAck = 0x13,
BlockAckEof = 0x14,
BlockQueryWithSkip = 0x15,
}
impl OpCode {
pub fn meta(self) -> MessageMeta {
MessageMeta {
proto_id: PROTO_ID_BDX,
proto_opcode: self as u8,
reliable: true,
}
}
}
impl From<OpCode> for MessageMeta {
fn from(op: OpCode) -> Self {
op.meta()
}
}
#[derive(FromPrimitive, Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u16)]
pub enum BdxStatus {
LengthTooLarge = 0x0012,
LengthTooShort = 0x0013,
LengthMismatch = 0x0014,
LengthRequired = 0x0015,
BadMessageContents = 0x0016,
BadBlockCounter = 0x0017,
UnexpectedMessage = 0x0018,
ResponderBusy = 0x0019,
TransferFailedUnknownError = 0x001F,
TransferMethodNotSupported = 0x0050,
FileDesignatorUnknown = 0x0051,
StartOffsetNotSupported = 0x0052,
VersionNotSupported = 0x0053,
Unknown = 0x005F,
}
impl BdxStatus {
pub fn as_report(self) -> StatusReport<'static> {
StatusReport {
general_code: GeneralCode::Failure,
proto_id: PROTO_ID_BDX as u32,
proto_code: self as u16,
proto_data: &[],
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TransferControl {
pub version: u8,
pub sender_drive: bool,
pub receiver_drive: bool,
pub async_mode: bool,
}
impl TransferControl {
const SENDER_DRIVE: u8 = 1 << 4;
const RECEIVER_DRIVE: u8 = 1 << 5;
const ASYNC: u8 = 1 << 6;
const VERSION_MASK: u8 = 0x0f;
pub(crate) const fn select(sender_drive: bool) -> Self {
Self {
version: BDX_VERSION,
sender_drive,
receiver_drive: !sender_drive,
async_mode: false,
}
}
fn from_byte(b: u8) -> Self {
Self {
version: b & Self::VERSION_MASK,
sender_drive: b & Self::SENDER_DRIVE != 0,
receiver_drive: b & Self::RECEIVER_DRIVE != 0,
async_mode: b & Self::ASYNC != 0,
}
}
fn to_byte(self) -> u8 {
let mut b = self.version & Self::VERSION_MASK;
if self.sender_drive {
b |= Self::SENDER_DRIVE;
}
if self.receiver_drive {
b |= Self::RECEIVER_DRIVE;
}
if self.async_mode {
b |= Self::ASYNC;
}
b
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RangeControl {
pub def_len: bool,
pub start_offset: bool,
pub wide_range: bool,
}
impl RangeControl {
const DEF_LEN: u8 = 1 << 0;
const START_OFFSET: u8 = 1 << 1;
const WIDE_RANGE: u8 = 1 << 4;
fn from_byte(b: u8) -> Self {
Self {
def_len: b & Self::DEF_LEN != 0,
start_offset: b & Self::START_OFFSET != 0,
wide_range: b & Self::WIDE_RANGE != 0,
}
}
fn to_byte(self) -> u8 {
let mut b = 0;
if self.def_len {
b |= Self::DEF_LEN;
}
if self.start_offset {
b |= Self::START_OFFSET;
}
if self.wide_range {
b |= Self::WIDE_RANGE;
}
b
}
}
#[derive(Debug, Clone)]
pub struct TransferInit<'a> {
pub transfer_control: TransferControl,
pub range_control: RangeControl,
pub max_block_size: u16,
pub start_offset: u64,
pub length: u64,
pub file_designator: &'a [u8],
pub metadata: &'a [u8],
}
impl<'a> TransferInit<'a> {
pub fn parse(payload: &'a [u8]) -> Result<Self, Error> {
let mut rb = ReadBuf::new(payload);
let transfer_control = TransferControl::from_byte(rb.le_u8()?);
let range_control = RangeControl::from_byte(rb.le_u8()?);
let max_block_size = rb.le_u16()?;
let start_offset = if range_control.start_offset {
if range_control.wide_range {
rb.le_u64()?
} else {
rb.le_u32()? as u64
}
} else {
0
};
let length = if range_control.def_len {
if range_control.wide_range {
rb.le_u64()?
} else {
rb.le_u32()? as u64
}
} else {
0
};
let fdl = rb.le_u16()? as usize;
let off = rb.read_off();
let end = off.checked_add(fdl).ok_or(ErrorCode::TruncatedPacket)?;
let file_designator = payload.get(off..end).ok_or(ErrorCode::TruncatedPacket)?;
let metadata = &payload[end..];
Ok(Self {
transfer_control,
range_control,
max_block_size,
start_offset,
length,
file_designator,
metadata,
})
}
pub fn write(&self, wb: &mut WriteBuf) -> Result<(), Error> {
wb.le_u8(self.transfer_control.to_byte())?;
wb.le_u8(self.range_control.to_byte())?;
wb.le_u16(self.max_block_size)?;
if self.range_control.start_offset {
if self.range_control.wide_range {
wb.le_u64(self.start_offset)?;
} else {
wb.le_u32(self.start_offset as u32)?;
}
}
if self.range_control.def_len {
if self.range_control.wide_range {
wb.le_u64(self.length)?;
} else {
wb.le_u32(self.length as u32)?;
}
}
wb.le_u16(self.file_designator.len() as u16)?;
wb.append(self.file_designator)?;
wb.append(self.metadata)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TransferAccept<'a> {
pub receive: bool,
pub transfer_control: TransferControl,
pub range_control: RangeControl,
pub max_block_size: u16,
pub length: u64,
pub metadata: &'a [u8],
}
impl<'a> TransferAccept<'a> {
pub fn parse(receive: bool, payload: &'a [u8]) -> Result<Self, Error> {
let mut rb = ReadBuf::new(payload);
let transfer_control = TransferControl::from_byte(rb.le_u8()?);
let (range_control, max_block_size, length) = if receive {
let range_control = RangeControl::from_byte(rb.le_u8()?);
let max_block_size = rb.le_u16()?;
let length = if range_control.def_len {
if range_control.wide_range {
rb.le_u64()?
} else {
rb.le_u32()? as u64
}
} else {
0
};
(range_control, max_block_size, length)
} else {
(RangeControl::default(), rb.le_u16()?, 0)
};
let metadata = &payload[rb.read_off()..];
Ok(Self {
receive,
transfer_control,
range_control,
max_block_size,
length,
metadata,
})
}
pub fn write(&self, wb: &mut WriteBuf) -> Result<(), Error> {
wb.le_u8(self.transfer_control.to_byte())?;
if self.receive {
wb.le_u8(self.range_control.to_byte())?;
wb.le_u16(self.max_block_size)?;
if self.range_control.def_len {
if self.range_control.wide_range {
wb.le_u64(self.length)?;
} else {
wb.le_u32(self.length as u32)?;
}
}
} else {
wb.le_u16(self.max_block_size)?;
}
wb.append(self.metadata)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Block<'a> {
pub block_counter: u32,
pub data: &'a [u8],
}
impl<'a> Block<'a> {
pub fn parse(payload: &'a [u8]) -> Result<Self, Error> {
let mut rb = ReadBuf::new(payload);
let block_counter = rb.le_u32()?;
let data = &payload[rb.read_off()..];
Ok(Self {
block_counter,
data,
})
}
pub fn write(&self, wb: &mut WriteBuf) -> Result<(), Error> {
wb.le_u32(self.block_counter)?;
wb.append(self.data)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct BlockQuery {
pub block_counter: u32,
}
impl BlockQuery {
pub fn parse(payload: &[u8]) -> Result<Self, Error> {
let mut rb = ReadBuf::new(payload);
Ok(Self {
block_counter: rb.le_u32()?,
})
}
pub fn write(&self, wb: &mut WriteBuf) -> Result<(), Error> {
wb.le_u32(self.block_counter)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct BlockQueryWithSkip {
pub block_counter: u32,
pub bytes_to_skip: u64,
}
impl BlockQueryWithSkip {
pub fn parse(payload: &[u8]) -> Result<Self, Error> {
let mut rb = ReadBuf::new(payload);
Ok(Self {
block_counter: rb.le_u32()?,
bytes_to_skip: rb.le_u64()?,
})
}
pub fn write(&self, wb: &mut WriteBuf) -> Result<(), Error> {
wb.le_u32(self.block_counter)?;
wb.le_u64(self.bytes_to_skip)?;
Ok(())
}
}
pub(crate) fn opcode(meta: &MessageMeta) -> Option<OpCode> {
(meta.proto_id == PROTO_ID_BDX).then(|| OpCode::from_u8(meta.proto_opcode))?
}
const BLOCK_HEADER_LEN: usize = 4;
const fn max_block_size(payload: usize) -> u16 {
let data = payload - BLOCK_HEADER_LEN;
if data > u16::MAX as usize {
u16::MAX
} else {
data as u16
}
}
const MAX_RX_BLOCK_SIZE: u16 = max_block_size(MAX_RX_PAYLOAD_SIZE);
const MAX_TX_BLOCK_SIZE: u16 = max_block_size(MAX_TX_PAYLOAD_SIZE);
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum Drive {
Driver,
Follower,
}
#[cfg(test)]
mod tests {
use crate::utils::storage::WriteBuf;
use super::*;
fn roundtrip_init(msg: &TransferInit) {
let mut buf = [0u8; 256];
let mut wb = WriteBuf::new(&mut buf);
msg.write(&mut wb).unwrap();
let bytes = wb.as_slice().to_vec();
let parsed = TransferInit::parse(&bytes).unwrap();
assert_eq!(parsed.transfer_control, msg.transfer_control);
assert_eq!(parsed.range_control, msg.range_control);
assert_eq!(parsed.max_block_size, msg.max_block_size);
assert_eq!(parsed.start_offset, msg.start_offset);
assert_eq!(parsed.length, msg.length);
assert_eq!(parsed.file_designator, msg.file_designator);
assert_eq!(parsed.metadata, msg.metadata);
}
#[test]
fn transfer_control_flags_roundtrip() {
for tc in [
TransferControl {
version: 0,
sender_drive: true,
receiver_drive: false,
async_mode: false,
},
TransferControl {
version: 0,
sender_drive: false,
receiver_drive: true,
async_mode: false,
},
TransferControl {
version: 0,
sender_drive: true,
receiver_drive: true,
async_mode: true,
},
] {
assert_eq!(TransferControl::from_byte(tc.to_byte()), tc);
}
assert_eq!(
TransferControl {
version: 0,
sender_drive: true,
..Default::default()
}
.to_byte(),
0x10
);
assert_eq!(
TransferControl {
version: 0,
receiver_drive: true,
..Default::default()
}
.to_byte(),
0x20
);
}
#[test]
fn transfer_control_select_picks_one_drive() {
let sender = TransferControl::select(true);
assert!(sender.sender_drive && !sender.receiver_drive && !sender.async_mode);
assert_eq!(sender.version, BDX_VERSION);
let receiver = TransferControl::select(false);
assert!(receiver.receiver_drive && !receiver.sender_drive && !receiver.async_mode);
}
#[test]
fn range_control_flags_roundtrip() {
let rc = RangeControl {
def_len: true,
start_offset: true,
wide_range: true,
};
assert_eq!(RangeControl::from_byte(rc.to_byte()), rc);
assert_eq!(rc.to_byte(), 0x13); }
#[test]
fn receive_init_minimal_roundtrip() {
roundtrip_init(&TransferInit {
transfer_control: TransferControl {
version: 0,
sender_drive: true,
receiver_drive: true,
async_mode: false,
},
range_control: RangeControl::default(),
max_block_size: 1024,
start_offset: 0,
length: 0,
file_designator: b"firmware.bin",
metadata: &[],
});
}
#[test]
fn receive_init_with_offset_and_length_roundtrip() {
roundtrip_init(&TransferInit {
transfer_control: TransferControl {
version: 0,
receiver_drive: true,
..Default::default()
},
range_control: RangeControl {
def_len: true,
start_offset: true,
wide_range: false,
},
max_block_size: 1024,
start_offset: 0x1234,
length: 0x5_6789,
file_designator: b"img",
metadata: &[0xde, 0xad],
});
}
#[test]
fn wide_range_uses_8_octets() {
let msg = TransferInit {
transfer_control: TransferControl {
version: 0,
sender_drive: true,
..Default::default()
},
range_control: RangeControl {
def_len: true,
start_offset: false,
wide_range: true,
},
max_block_size: 512,
start_offset: 0,
length: 0x1_0000_0000, file_designator: b"x",
metadata: &[],
};
roundtrip_init(&msg);
let mut buf = [0u8; 64];
let mut wb = WriteBuf::new(&mut buf);
msg.write(&mut wb).unwrap();
assert_eq!(wb.as_slice().len(), 15);
}
#[test]
fn send_accept_roundtrip() {
let msg = TransferAccept {
receive: false,
transfer_control: TransferControl {
version: 0,
sender_drive: true,
..Default::default()
},
range_control: RangeControl::default(),
max_block_size: 1024,
length: 0,
metadata: &[],
};
let mut buf = [0u8; 64];
let mut wb = WriteBuf::new(&mut buf);
msg.write(&mut wb).unwrap();
assert_eq!(wb.as_slice().len(), 3);
let bytes = wb.as_slice().to_vec();
let parsed = TransferAccept::parse(false, &bytes).unwrap();
assert!(parsed.transfer_control.sender_drive);
assert_eq!(parsed.max_block_size, 1024);
}
#[test]
fn receive_accept_roundtrip() {
let msg = TransferAccept {
receive: true,
transfer_control: TransferControl {
version: 0,
sender_drive: true,
..Default::default()
},
range_control: RangeControl {
def_len: true,
start_offset: false,
wide_range: false,
},
max_block_size: 1024,
length: 123_456,
metadata: &[],
};
let mut buf = [0u8; 64];
let mut wb = WriteBuf::new(&mut buf);
msg.write(&mut wb).unwrap();
let bytes = wb.as_slice().to_vec();
let parsed = TransferAccept::parse(true, &bytes).unwrap();
assert!(parsed.transfer_control.sender_drive);
assert_eq!(parsed.max_block_size, 1024);
assert!(parsed.range_control.def_len);
assert_eq!(parsed.length, 123_456);
}
#[test]
fn block_roundtrip() {
let msg = Block {
block_counter: 7,
data: b"hello world",
};
let mut buf = [0u8; 64];
let mut wb = WriteBuf::new(&mut buf);
msg.write(&mut wb).unwrap();
let bytes = wb.as_slice().to_vec();
let parsed = Block::parse(&bytes).unwrap();
assert_eq!(parsed.block_counter, 7);
assert_eq!(parsed.data, b"hello world");
}
#[test]
fn block_eof_empty_roundtrip() {
let msg = Block {
block_counter: 0,
data: &[],
};
let mut buf = [0u8; 8];
let mut wb = WriteBuf::new(&mut buf);
msg.write(&mut wb).unwrap();
assert_eq!(wb.as_slice().len(), 4); let bytes = wb.as_slice().to_vec();
let parsed = Block::parse(&bytes).unwrap();
assert_eq!(parsed.block_counter, 0);
assert!(parsed.data.is_empty());
}
#[test]
fn block_query_and_skip_roundtrip() {
let mut buf = [0u8; 32];
let mut wb = WriteBuf::new(&mut buf);
BlockQuery { block_counter: 5 }.write(&mut wb).unwrap();
assert_eq!(wb.as_slice().len(), 4);
let bytes = wb.as_slice().to_vec();
assert_eq!(BlockQuery::parse(&bytes).unwrap().block_counter, 5);
let mut wb = WriteBuf::new(&mut buf);
BlockQueryWithSkip {
block_counter: 9,
bytes_to_skip: 0x1_0000,
}
.write(&mut wb)
.unwrap();
assert_eq!(wb.as_slice().len(), 12); let bytes = wb.as_slice().to_vec();
let parsed = BlockQueryWithSkip::parse(&bytes).unwrap();
assert_eq!(parsed.block_counter, 9);
assert_eq!(parsed.bytes_to_skip, 0x1_0000);
}
#[test]
fn truncated_is_rejected() {
assert!(BlockQuery::parse(&[1, 2, 3]).is_err()); assert!(TransferInit::parse(&[0x00]).is_err()); }
#[test]
fn opcode_meta_is_bdx_and_reliable() {
let meta = OpCode::ReceiveInit.meta();
assert_eq!(meta.proto_id, PROTO_ID_BDX);
assert_eq!(meta.proto_opcode, 0x04);
assert!(meta.reliable);
assert_eq!(opcode(&meta), Some(OpCode::ReceiveInit));
}
}