//! 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 { 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 { 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::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)); } }