#![allow(clippy::unwrap_used, clippy::panic, clippy::expect_used)]
use donglora_protocol::{
Command, DeviceMessage, ErrorCode, FrameDecoder, FrameResult, Info, LoRaBandwidth,
LoRaCodingRate, LoRaConfig, LoRaHeaderMode, MAX_WIRE_FRAME, Modulation, OkPayload, Owner,
RadioChipId, RxOrigin, RxPayload, SetConfigResult, SetConfigResultCode, TxDonePayload, TxFlags,
TxResult, cap, commands, encode_frame,
};
use heapless::Vec as HVec;
fn encode_cmd_frame(cmd: &Command, tag: u16) -> HVec<u8, MAX_WIRE_FRAME> {
let mut payload_buf = [0u8; 320];
let payload_len = cmd.encode_payload(&mut payload_buf).unwrap();
let mut wire = [0u8; MAX_WIRE_FRAME];
let n = encode_frame(cmd.type_id(), tag, &payload_buf[..payload_len], &mut wire).unwrap();
let mut out = HVec::new();
out.extend_from_slice(&wire[..n]).unwrap();
out
}
fn encode_dev_frame(msg: &DeviceMessage, tag: u16) -> HVec<u8, MAX_WIRE_FRAME> {
let mut payload_buf = [0u8; 320];
let payload_len = msg.encode_payload(&mut payload_buf).unwrap();
let mut wire = [0u8; MAX_WIRE_FRAME];
let n = encode_frame(msg.type_id(), tag, &payload_buf[..payload_len], &mut wire).unwrap();
let mut out = HVec::new();
out.extend_from_slice(&wire[..n]).unwrap();
out
}
fn decode_single_frame(wire: &[u8]) -> (u8, u16, HVec<u8, 320>) {
let mut decoder = FrameDecoder::new();
let mut out: Option<(u8, u16, HVec<u8, 320>)> = None;
decoder.feed(wire, |res| match res {
FrameResult::Ok {
type_id,
tag,
payload,
} => {
let mut p = HVec::new();
p.extend_from_slice(payload).unwrap();
out = Some((type_id, tag, p));
}
FrameResult::Err(e) => panic!("decode error: {:?}", e),
});
out.expect("expected one frame")
}
#[test]
fn c21_ping_encodes_to_spec_bytes() {
let wire = encode_cmd_frame(&Command::Ping, 0x0001);
assert_eq!(wire.as_slice(), &[0x03, 0x01, 0x01, 0x03, 0x9D, 0xC8, 0x00]);
}
#[test]
fn c21_ok_encodes_to_spec_bytes() {
let wire = encode_dev_frame(&DeviceMessage::Ok(OkPayload::Empty), 0x0001);
assert_eq!(wire.as_slice(), &[0x03, 0x80, 0x01, 0x03, 0xF7, 0xC4, 0x00]);
}
#[test]
fn c21_decode_roundtrip() {
let (type_id, tag, payload) = decode_single_frame(&[0x03, 0x01, 0x01, 0x03, 0x9D, 0xC8, 0x00]);
assert_eq!(type_id, commands::TYPE_PING);
assert_eq!(tag, 0x0001);
assert_eq!(payload.as_slice(), &[]);
assert_eq!(Command::parse(type_id, &payload).unwrap(), Command::Ping);
}
fn c23_lora() -> LoRaConfig {
LoRaConfig {
freq_hz: 868_100_000,
sf: 7,
bw: LoRaBandwidth::Khz125,
cr: LoRaCodingRate::Cr4_5,
preamble_len: 8,
sync_word: 0x1424,
tx_power_dbm: 14,
header_mode: LoRaHeaderMode::Explicit,
payload_crc: true,
iq_invert: false,
}
}
#[test]
fn c23_set_config_encodes_to_spec_bytes() {
let cmd = Command::SetConfig(Modulation::LoRa(c23_lora()));
let wire = encode_cmd_frame(&cmd, 0x0003);
let expected: &[u8] = &[
0x03, 0x03, 0x03, 0x08, 0x01, 0xA0, 0x27, 0xBE, 0x33, 0x07, 0x07, 0x02, 0x08, 0x04, 0x24,
0x14, 0x0E, 0x02, 0x01, 0x03, 0xD9, 0x1F, 0x00,
];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c23_set_config_ok_response() {
let result = SetConfigResult {
result: SetConfigResultCode::Applied,
owner: Owner::Mine,
current: Modulation::LoRa(c23_lora()),
};
let msg = DeviceMessage::Ok(OkPayload::SetConfig(result));
let wire = encode_dev_frame(&msg, 0x0003);
let expected: &[u8] = &[
0x03, 0x80, 0x03, 0x01, 0x09, 0x01, 0x01, 0xA0, 0x27, 0xBE, 0x33, 0x07, 0x07, 0x02, 0x08,
0x04, 0x24, 0x14, 0x0E, 0x02, 0x01, 0x03, 0xC8, 0x91, 0x00,
];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c24_tx_with_cad() {
let mut data = HVec::new();
data.extend_from_slice(b"Hello").unwrap();
let cmd = Command::Tx {
flags: TxFlags::default(),
data,
};
let wire = encode_cmd_frame(&cmd, 0x0004);
let expected: &[u8] = &[
0x03, 0x04, 0x04, 0x01, 0x08, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x26, 0x40, 0x00,
];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c24_tx_ok() {
let wire = encode_dev_frame(&DeviceMessage::Ok(OkPayload::Empty), 0x0004);
assert_eq!(wire.as_slice(), &[0x03, 0x80, 0x04, 0x03, 0x02, 0x3B, 0x00]);
}
#[test]
fn c24_tx_done() {
let td = TxDonePayload {
result: TxResult::Transmitted,
airtime_us: 30_976,
};
let wire = encode_dev_frame(&DeviceMessage::TxDone(td), 0x0004);
let expected: &[u8] = &[
0x03, 0xC1, 0x04, 0x01, 0x01, 0x02, 0x79, 0x01, 0x03, 0xE3, 0xFA, 0x00,
];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c25_tx_skip_cad() {
let mut data = HVec::new();
data.extend_from_slice(b"URGENT").unwrap();
let cmd = Command::Tx {
flags: TxFlags { skip_cad: true },
data,
};
let wire = encode_cmd_frame(&cmd, 0x0005);
let expected: &[u8] = &[
0x03, 0x04, 0x05, 0x0A, 0x01, 0x55, 0x52, 0x47, 0x45, 0x4E, 0x54, 0xDB, 0x1C, 0x00,
];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c25_tx_done() {
let td = TxDonePayload {
result: TxResult::Transmitted,
airtime_us: 33_792,
};
let wire = encode_dev_frame(&DeviceMessage::TxDone(td), 0x0005);
let expected: &[u8] = &[
0x03, 0xC1, 0x05, 0x01, 0x01, 0x02, 0x84, 0x01, 0x03, 0x81, 0xE3, 0x00,
];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c26_rx_start() {
let wire = encode_cmd_frame(&Command::RxStart, 0x0006);
assert_eq!(wire.as_slice(), &[0x03, 0x05, 0x06, 0x03, 0xCA, 0x8D, 0x00]);
}
#[test]
fn c26_rx_event() {
let mut data = HVec::new();
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]).unwrap();
let rx = RxPayload {
rssi_tenths_dbm: -735,
snr_tenths_db: 95,
freq_err_hz: -125,
timestamp_us: 42_000_000,
crc_valid: true,
packets_dropped: 0,
origin: RxOrigin::Ota,
data,
};
let wire = encode_dev_frame(&DeviceMessage::Rx(rx), 0x0000);
let expected: &[u8] = &[
0x02, 0xC0, 0x01, 0x04, 0x21, 0xFD, 0x5F, 0x09, 0x83, 0xFF, 0xFF, 0xFF, 0x80, 0xDE, 0x80,
0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x07, 0x01, 0x02, 0x03, 0x04, 0xB9, 0x8E,
0x00,
];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c26_rx_stop() {
let wire = encode_cmd_frame(&Command::RxStop, 0x0008);
assert_eq!(wire.as_slice(), &[0x03, 0x06, 0x08, 0x03, 0x95, 0xF7, 0x00]);
}
#[test]
fn c51_err_notconfigured() {
let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::ENotConfigured), 0x0028);
let expected: &[u8] = &[0x03, 0x81, 0x28, 0x02, 0x03, 0x03, 0x53, 0x7E, 0x00];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c53_err_eparam() {
let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::EParam), 0x002A);
let expected: &[u8] = &[0x03, 0x81, 0x2A, 0x02, 0x01, 0x03, 0x59, 0xF5, 0x00];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c54_err_ebusy() {
let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::EBusy), 0x002B);
let expected: &[u8] = &[0x03, 0x81, 0x2B, 0x02, 0x06, 0x03, 0x7A, 0x1A, 0x00];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c56_unknown_command_type_encodes_to_spec() {
let mut wire = [0u8; MAX_WIRE_FRAME];
let n = encode_frame(0x10, 0x003C, &[0xDE, 0xAD], &mut wire).unwrap();
let expected: &[u8] = &[0x03, 0x10, 0x3C, 0x05, 0xDE, 0xAD, 0xE2, 0x24, 0x00];
assert_eq!(&wire[..n], expected);
}
#[test]
fn c56_unknown_command_decoder_still_parses_frame() {
let wire: &[u8] = &[0x03, 0x10, 0x3C, 0x05, 0xDE, 0xAD, 0xE2, 0x24, 0x00];
let (type_id, tag, payload) = decode_single_frame(wire);
assert_eq!(type_id, 0x10);
assert_eq!(tag, 0x003C);
assert_eq!(payload.as_slice(), &[0xDE, 0xAD]);
assert!(matches!(
Command::parse(type_id, &payload),
Err(donglora_protocol::CommandParseError::UnknownType)
));
}
#[test]
fn c56_err_eunknown_cmd_reply() {
let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::EUnknownCmd), 0x003C);
let expected: &[u8] = &[0x03, 0x81, 0x3C, 0x02, 0x05, 0x03, 0xA3, 0x05, 0x00];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c62_rx_bad_crc_delivered_anyway() {
let mut data = HVec::new();
data.extend_from_slice(&[0x41, 0x42, 0xFF, 0x00, 0xCC])
.unwrap();
let rx = RxPayload {
rssi_tenths_dbm: -1050,
snr_tenths_db: -80,
freq_err_hz: 2200,
timestamp_us: 120_000_000,
crc_valid: false,
packets_dropped: 0,
origin: RxOrigin::Ota,
data,
};
let wire = encode_dev_frame(&DeviceMessage::Rx(rx), 0x0000);
let expected: &[u8] = &[
0x02, 0xC0, 0x01, 0x07, 0xE6, 0xFB, 0xB0, 0xFF, 0x98, 0x08, 0x01, 0x01, 0x04, 0x0E, 0x27,
0x07, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x41, 0x42, 0xFF, 0x04, 0xCC, 0x04,
0x3C, 0x00,
];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c64_async_err_eframe() {
let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::EFrame), 0x0000);
let expected: &[u8] = &[0x02, 0x81, 0x01, 0x05, 0x02, 0x01, 0xCE, 0xEF, 0x00];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c65_async_err_eradio() {
let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::ERadio), 0x0000);
let expected: &[u8] = &[0x02, 0x81, 0x01, 0x05, 0x01, 0x01, 0x9D, 0xBA, 0x00];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn c81_ping_no_zeros_in_body() {
let wire = encode_cmd_frame(&Command::Ping, 0x0101);
assert_eq!(wire.as_slice(), &[0x06, 0x01, 0x01, 0x01, 0xBC, 0xD8, 0x00]);
}
#[test]
fn c22_get_info_request() {
let wire = encode_cmd_frame(&Command::GetInfo, 0x0002);
assert_eq!(wire.as_slice(), &[0x03, 0x02, 0x02, 0x03, 0x9E, 0xC4, 0x00]);
}
#[test]
fn c22_get_info_response() {
let mut mcu = [0u8; donglora_protocol::MAX_MCU_UID_LEN];
mcu[..8].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x23, 0x45, 0x67]);
let info = Info {
proto_major: 1,
proto_minor: 0,
fw_major: 0,
fw_minor: 1,
fw_patch: 0,
radio_chip_id: RadioChipId::Sx1262.as_u16(),
capability_bitmap: cap::LORA | cap::FSK | cap::CAD_BEFORE_TX,
supported_sf_bitmap: 0x1FE0,
supported_bw_bitmap: 0x03FF,
max_payload_bytes: 255,
rx_queue_capacity: 64,
tx_queue_capacity: 16,
freq_min_hz: 150_000_000,
freq_max_hz: 960_000_000,
tx_power_min_dbm: -9,
tx_power_max_dbm: 22,
mcu_uid_len: 8,
mcu_uid: mcu,
radio_uid_len: 0,
radio_uid: [0u8; donglora_protocol::MAX_RADIO_UID_LEN],
};
let wire = encode_dev_frame(&DeviceMessage::Ok(OkPayload::Info(info)), 0x0002);
let expected: &[u8] = &[
0x03, 0x80, 0x02, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, 0x02, 0x01, 0x01,
0x01, 0x01, 0x01, 0x06, 0xE0, 0x1F, 0xFF, 0x03, 0xFF, 0x02, 0x40, 0x02, 0x10, 0x05, 0x80,
0xD1, 0xF0, 0x08, 0x0F, 0x70, 0x38, 0x39, 0xF7, 0x16, 0x08, 0xDE, 0xAD, 0xBE, 0xEF, 0x01,
0x23, 0x45, 0x67, 0x03, 0xFA, 0xA4, 0x00,
];
assert_eq!(wire.as_slice(), expected);
}
#[test]
fn decode_tx_roundtrip() {
let mut data = HVec::new();
data.extend_from_slice(b"Hello").unwrap();
let cmd = Command::Tx {
flags: TxFlags::default(),
data,
};
let wire = encode_cmd_frame(&cmd, 0x0004);
let (type_id, tag, payload) = decode_single_frame(&wire);
assert_eq!(type_id, commands::TYPE_TX);
assert_eq!(tag, 0x0004);
assert_eq!(Command::parse(type_id, &payload).unwrap(), cmd);
}
#[test]
fn decode_set_config_roundtrip() {
let cmd = Command::SetConfig(Modulation::LoRa(c23_lora()));
let wire = encode_cmd_frame(&cmd, 0x0003);
let (type_id, tag, payload) = decode_single_frame(&wire);
assert_eq!(type_id, commands::TYPE_SET_CONFIG);
assert_eq!(tag, 0x0003);
assert_eq!(Command::parse(type_id, &payload).unwrap(), cmd);
}
#[test]
fn decode_rx_roundtrip() {
let mut data = HVec::new();
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]).unwrap();
let rx = RxPayload {
rssi_tenths_dbm: -735,
snr_tenths_db: 95,
freq_err_hz: -125,
timestamp_us: 42_000_000,
crc_valid: true,
packets_dropped: 0,
origin: RxOrigin::Ota,
data: data.clone(),
};
let wire = encode_dev_frame(&DeviceMessage::Rx(rx.clone()), 0x0000);
let (type_id, tag, payload) = decode_single_frame(&wire);
assert_eq!(type_id, 0xC0);
assert_eq!(tag, 0x0000);
let decoded = DeviceMessage::parse(type_id, &payload, None).unwrap();
assert_eq!(decoded, DeviceMessage::Rx(rx));
}