318 lines
14 KiB
Rust
318 lines
14 KiB
Rust
//! Generated Kaptan-dialect translation for the two file messages.
|
|
|
|
use kaptan_mavlink::generated::kaptan::{
|
|
FILE_CONTROL_DATA, FILE_DATA_DATA, FileOperation as WireFileOperation,
|
|
FileStatus as WireFileStatus, MavMessage,
|
|
};
|
|
|
|
use crate::{
|
|
Content, ControlRequest, ControlResponse, DataRequest, DataResponse, FileOperation,
|
|
FileRequest, FileResponse, FileStatus, Target,
|
|
};
|
|
|
|
/// Failure while decoding a generated custom message as a service request.
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum DecodeError {
|
|
/// The message is not one of the two file records.
|
|
UnsupportedMessage,
|
|
/// A response was presented to the request decoder.
|
|
WrongDirection,
|
|
/// The request used a broadcast or zero target.
|
|
InvalidTarget,
|
|
/// The control content was not canonically zero-padded.
|
|
InvalidContent,
|
|
}
|
|
|
|
/// Decodes one generated file message into the bounded service request.
|
|
pub fn decode_request(message: &MavMessage) -> Result<FileRequest, DecodeError> {
|
|
match message {
|
|
MavMessage::FILE_CONTROL(data) => {
|
|
if data.status != WireFileStatus::FILE_STATUS_UNSPECIFIED {
|
|
return Err(DecodeError::WrongDirection);
|
|
}
|
|
let target = target(data.target_system, data.target_component)?;
|
|
let content = Content::from_wire(data.content_length, data.content)
|
|
.ok_or(DecodeError::InvalidContent)?;
|
|
Ok(FileRequest::Control(ControlRequest {
|
|
operation: decode_operation(data.operation),
|
|
target,
|
|
position: data.position,
|
|
file_size: data.file_size,
|
|
checksum_crc32c: data.checksum_crc32c,
|
|
transfer_id: data.transfer_id,
|
|
content,
|
|
}))
|
|
}
|
|
MavMessage::FILE_DATA(data) => {
|
|
if data.status != WireFileStatus::FILE_STATUS_UNSPECIFIED {
|
|
return Err(DecodeError::WrongDirection);
|
|
}
|
|
Ok(FileRequest::Data(DataRequest {
|
|
target: target(data.target_system, data.target_component)?,
|
|
position: data.position,
|
|
transfer_id: data.transfer_id,
|
|
data: data.data,
|
|
}))
|
|
}
|
|
_ => Err(DecodeError::UnsupportedMessage),
|
|
}
|
|
}
|
|
|
|
/// Decodes one generated file message as a host-side service response.
|
|
pub fn decode_response(message: &MavMessage) -> Result<FileResponse, DecodeError> {
|
|
match message {
|
|
MavMessage::FILE_CONTROL(data) => {
|
|
if data.status == WireFileStatus::FILE_STATUS_UNSPECIFIED {
|
|
return Err(DecodeError::WrongDirection);
|
|
}
|
|
let target = target(data.target_system, data.target_component)?;
|
|
let content = Content::from_wire(data.content_length, data.content)
|
|
.ok_or(DecodeError::InvalidContent)?;
|
|
Ok(FileResponse::Control(ControlResponse {
|
|
operation: decode_operation(data.operation),
|
|
target,
|
|
position: data.position,
|
|
file_size: data.file_size,
|
|
checksum_crc32c: data.checksum_crc32c,
|
|
transfer_id: data.transfer_id,
|
|
status: decode_status(data.status),
|
|
content,
|
|
}))
|
|
}
|
|
MavMessage::FILE_DATA(data) => {
|
|
if data.status != WireFileStatus::FILE_STATUS_OK {
|
|
return Err(DecodeError::WrongDirection);
|
|
}
|
|
Ok(FileResponse::Data(DataResponse {
|
|
target: target(data.target_system, data.target_component)?,
|
|
position: data.position,
|
|
transfer_id: data.transfer_id,
|
|
data: data.data,
|
|
}))
|
|
}
|
|
_ => Err(DecodeError::UnsupportedMessage),
|
|
}
|
|
}
|
|
|
|
/// Encodes one service response as a generated Kaptan-dialect message.
|
|
pub fn encode_response(response: FileResponse) -> MavMessage {
|
|
match response {
|
|
FileResponse::Control(response) => {
|
|
MavMessage::FILE_CONTROL(encode_control_response(response))
|
|
}
|
|
FileResponse::Data(response) => MavMessage::FILE_DATA(encode_data_response(response)),
|
|
}
|
|
}
|
|
|
|
/// Encodes one host request as a generated Kaptan-dialect message.
|
|
pub fn encode_request(request: FileRequest) -> MavMessage {
|
|
match request {
|
|
FileRequest::Control(request) => MavMessage::FILE_CONTROL(FILE_CONTROL_DATA {
|
|
position: request.position,
|
|
file_size: request.file_size,
|
|
checksum_crc32c: request.checksum_crc32c,
|
|
transfer_id: request.transfer_id,
|
|
target_system: request.target.system(),
|
|
target_component: request.target.component(),
|
|
operation: encode_operation(request.operation),
|
|
status: WireFileStatus::FILE_STATUS_UNSPECIFIED,
|
|
content_length: request.content.len(),
|
|
content: request.content.wire_bytes(),
|
|
}),
|
|
FileRequest::Data(request) => MavMessage::FILE_DATA(FILE_DATA_DATA {
|
|
position: request.position,
|
|
transfer_id: request.transfer_id,
|
|
target_system: request.target.system(),
|
|
target_component: request.target.component(),
|
|
status: WireFileStatus::FILE_STATUS_UNSPECIFIED,
|
|
data: request.data,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn encode_control_response(response: ControlResponse) -> FILE_CONTROL_DATA {
|
|
FILE_CONTROL_DATA {
|
|
position: response.position,
|
|
file_size: response.file_size,
|
|
checksum_crc32c: response.checksum_crc32c,
|
|
transfer_id: response.transfer_id,
|
|
target_system: response.target.system(),
|
|
target_component: response.target.component(),
|
|
operation: encode_operation(response.operation),
|
|
status: encode_status(response.status),
|
|
content_length: response.content.len(),
|
|
content: response.content.wire_bytes(),
|
|
}
|
|
}
|
|
|
|
fn encode_data_response(response: DataResponse) -> FILE_DATA_DATA {
|
|
FILE_DATA_DATA {
|
|
position: response.position,
|
|
transfer_id: response.transfer_id,
|
|
target_system: response.target.system(),
|
|
target_component: response.target.component(),
|
|
status: WireFileStatus::FILE_STATUS_OK,
|
|
data: response.data,
|
|
}
|
|
}
|
|
|
|
fn target(system: u8, component: u8) -> Result<Target, DecodeError> {
|
|
Target::try_new(system, component).ok_or(DecodeError::InvalidTarget)
|
|
}
|
|
|
|
const fn decode_operation(operation: WireFileOperation) -> FileOperation {
|
|
match operation {
|
|
WireFileOperation::FILE_OPERATION_OPEN_READ => FileOperation::OpenRead,
|
|
WireFileOperation::FILE_OPERATION_OPEN_WRITE => FileOperation::OpenWrite,
|
|
WireFileOperation::FILE_OPERATION_READ => FileOperation::Read,
|
|
WireFileOperation::FILE_OPERATION_WRITE_ACK => FileOperation::WriteAck,
|
|
WireFileOperation::FILE_OPERATION_CLOSE => FileOperation::Close,
|
|
WireFileOperation::FILE_OPERATION_ABORT => FileOperation::Abort,
|
|
}
|
|
}
|
|
|
|
const fn encode_operation(operation: FileOperation) -> WireFileOperation {
|
|
match operation {
|
|
FileOperation::OpenRead => WireFileOperation::FILE_OPERATION_OPEN_READ,
|
|
FileOperation::OpenWrite => WireFileOperation::FILE_OPERATION_OPEN_WRITE,
|
|
FileOperation::Read => WireFileOperation::FILE_OPERATION_READ,
|
|
FileOperation::WriteAck => WireFileOperation::FILE_OPERATION_WRITE_ACK,
|
|
FileOperation::Close => WireFileOperation::FILE_OPERATION_CLOSE,
|
|
FileOperation::Abort => WireFileOperation::FILE_OPERATION_ABORT,
|
|
}
|
|
}
|
|
|
|
const fn encode_status(status: FileStatus) -> WireFileStatus {
|
|
match status {
|
|
FileStatus::Ok => WireFileStatus::FILE_STATUS_OK,
|
|
FileStatus::Busy => WireFileStatus::FILE_STATUS_BUSY,
|
|
FileStatus::InvalidTransfer => WireFileStatus::FILE_STATUS_INVALID_TRANSFER,
|
|
FileStatus::InvalidState => WireFileStatus::FILE_STATUS_INVALID_STATE,
|
|
FileStatus::InvalidPath => WireFileStatus::FILE_STATUS_INVALID_PATH,
|
|
FileStatus::NotFound => WireFileStatus::FILE_STATUS_NOT_FOUND,
|
|
FileStatus::TooLarge => WireFileStatus::FILE_STATUS_TOO_LARGE,
|
|
FileStatus::InvalidPosition => WireFileStatus::FILE_STATUS_INVALID_POSITION,
|
|
FileStatus::ChecksumMismatch => WireFileStatus::FILE_STATUS_CHECKSUM_MISMATCH,
|
|
FileStatus::ContentRejected => WireFileStatus::FILE_STATUS_CONTENT_REJECTED,
|
|
FileStatus::StorageFailure => WireFileStatus::FILE_STATUS_STORAGE_FAILURE,
|
|
FileStatus::RequestConflict => WireFileStatus::FILE_STATUS_REQUEST_CONFLICT,
|
|
FileStatus::Internal => WireFileStatus::FILE_STATUS_INTERNAL,
|
|
}
|
|
}
|
|
|
|
fn decode_status(status: WireFileStatus) -> FileStatus {
|
|
match status {
|
|
WireFileStatus::FILE_STATUS_UNSPECIFIED => {
|
|
unreachable!("response direction was checked before status decoding")
|
|
}
|
|
WireFileStatus::FILE_STATUS_OK => FileStatus::Ok,
|
|
WireFileStatus::FILE_STATUS_BUSY => FileStatus::Busy,
|
|
WireFileStatus::FILE_STATUS_INVALID_TRANSFER => FileStatus::InvalidTransfer,
|
|
WireFileStatus::FILE_STATUS_INVALID_STATE => FileStatus::InvalidState,
|
|
WireFileStatus::FILE_STATUS_INVALID_PATH => FileStatus::InvalidPath,
|
|
WireFileStatus::FILE_STATUS_NOT_FOUND => FileStatus::NotFound,
|
|
WireFileStatus::FILE_STATUS_TOO_LARGE => FileStatus::TooLarge,
|
|
WireFileStatus::FILE_STATUS_INVALID_POSITION => FileStatus::InvalidPosition,
|
|
WireFileStatus::FILE_STATUS_CHECKSUM_MISMATCH => FileStatus::ChecksumMismatch,
|
|
WireFileStatus::FILE_STATUS_CONTENT_REJECTED => FileStatus::ContentRejected,
|
|
WireFileStatus::FILE_STATUS_STORAGE_FAILURE => FileStatus::StorageFailure,
|
|
WireFileStatus::FILE_STATUS_REQUEST_CONFLICT => FileStatus::RequestConflict,
|
|
WireFileStatus::FILE_STATUS_INTERNAL => FileStatus::Internal,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn file_enum_wire_values_are_stable() {
|
|
assert_eq!(WireFileOperation::FILE_OPERATION_OPEN_READ as u8, 1);
|
|
assert_eq!(WireFileOperation::FILE_OPERATION_OPEN_WRITE as u8, 2);
|
|
assert_eq!(WireFileOperation::FILE_OPERATION_READ as u8, 3);
|
|
assert_eq!(WireFileOperation::FILE_OPERATION_WRITE_ACK as u8, 4);
|
|
assert_eq!(WireFileOperation::FILE_OPERATION_CLOSE as u8, 5);
|
|
assert_eq!(WireFileOperation::FILE_OPERATION_ABORT as u8, 6);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_UNSPECIFIED as u8, 0);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_OK as u8, 1);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_BUSY as u8, 2);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_INVALID_TRANSFER as u8, 3);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_INVALID_STATE as u8, 4);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_INVALID_PATH as u8, 5);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_NOT_FOUND as u8, 6);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_TOO_LARGE as u8, 7);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_INVALID_POSITION as u8, 8);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_CHECKSUM_MISMATCH as u8, 9);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_CONTENT_REJECTED as u8, 10);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_STORAGE_FAILURE as u8, 11);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_REQUEST_CONFLICT as u8, 12);
|
|
assert_eq!(WireFileStatus::FILE_STATUS_INTERNAL as u8, 13);
|
|
assert_eq!(FileStatus::Ok as u8, 1);
|
|
assert_eq!(FileStatus::Busy as u8, 2);
|
|
assert_eq!(FileStatus::InvalidTransfer as u8, 3);
|
|
assert_eq!(FileStatus::InvalidState as u8, 4);
|
|
assert_eq!(FileStatus::InvalidPath as u8, 5);
|
|
assert_eq!(FileStatus::NotFound as u8, 6);
|
|
assert_eq!(FileStatus::TooLarge as u8, 7);
|
|
assert_eq!(FileStatus::InvalidPosition as u8, 8);
|
|
assert_eq!(FileStatus::ChecksumMismatch as u8, 9);
|
|
assert_eq!(FileStatus::ContentRejected as u8, 10);
|
|
assert_eq!(FileStatus::StorageFailure as u8, 11);
|
|
assert_eq!(FileStatus::RequestConflict as u8, 12);
|
|
assert_eq!(FileStatus::Internal as u8, 13);
|
|
}
|
|
|
|
#[test]
|
|
fn control_request_and_response_round_trip_without_untyped_fields() {
|
|
let request = FileRequest::Control(ControlRequest {
|
|
operation: FileOperation::OpenRead,
|
|
target: Target::new(1, 2),
|
|
position: 0,
|
|
file_size: 0,
|
|
checksum_crc32c: 0,
|
|
transfer_id: 7,
|
|
content: Content::try_new(b"/test/data.bin").unwrap(),
|
|
});
|
|
let encoded = encode_request(request);
|
|
assert_eq!(decode_request(&encoded), Ok(request));
|
|
assert_eq!(decode_response(&encoded), Err(DecodeError::WrongDirection));
|
|
|
|
let response = FileResponse::Control(ControlResponse {
|
|
operation: FileOperation::OpenRead,
|
|
target: Target::new(42, 7),
|
|
position: 0,
|
|
file_size: 901,
|
|
checksum_crc32c: 0x1234_5678,
|
|
transfer_id: 7,
|
|
status: FileStatus::Ok,
|
|
content: Content::EMPTY,
|
|
});
|
|
let encoded = encode_response(response);
|
|
assert_eq!(decode_response(&encoded), Ok(response));
|
|
assert_eq!(decode_request(&encoded), Err(DecodeError::WrongDirection));
|
|
}
|
|
|
|
#[test]
|
|
fn data_direction_is_explicit_and_round_trips() {
|
|
let request = FileRequest::Data(DataRequest {
|
|
target: Target::new(1, 2),
|
|
position: 246,
|
|
transfer_id: 7,
|
|
data: [0x5a; crate::DATA_BYTES],
|
|
});
|
|
let encoded = encode_request(request);
|
|
assert_eq!(decode_request(&encoded), Ok(request));
|
|
assert_eq!(decode_response(&encoded), Err(DecodeError::WrongDirection));
|
|
|
|
let response = FileResponse::Data(DataResponse {
|
|
target: Target::new(42, 7),
|
|
position: 246,
|
|
transfer_id: 7,
|
|
data: [0xa5; crate::DATA_BYTES],
|
|
});
|
|
let encoded = encode_response(response);
|
|
assert_eq!(decode_response(&encoded), Ok(response));
|
|
assert_eq!(decode_request(&encoded), Err(DecodeError::WrongDirection));
|
|
}
|
|
}
|