//! Transport-sized request, response, and provider boundary types. use crate::{CONTENT_BYTES, DATA_BYTES}; /// Exact MAVLink endpoint that owns the virtual-file service. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Target { system: u8, component: u8, } impl Target { /// Creates one exact, non-broadcast endpoint. pub const fn new(system: u8, component: u8) -> Self { assert!(system != 0 && component != 0, "file target must be exact"); Self { system, component } } /// Parses an exact endpoint from an untrusted request. pub const fn try_new(system: u8, component: u8) -> Option { if system == 0 || component == 0 { None } else { Some(Self { system, component }) } } /// Target system identity. pub const fn system(self) -> u8 { self.system } /// Target component identity. pub const fn component(self) -> u8 { self.component } } /// Exact MAVLink source that owns an open transfer. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Principal { system: u8, component: u8, } impl Principal { /// Creates one exact source identity. pub const fn new(system: u8, component: u8) -> Self { assert!( system != 0 && component != 0, "file principal must be exact" ); Self { system, component } } /// Parses an exact source identity from an untrusted frame. pub const fn try_new(system: u8, component: u8) -> Option { if system == 0 || component == 0 { None } else { Some(Self { system, component }) } } pub(crate) const fn target(self) -> Target { Target { system: self.system, component: self.component, } } /// Source system identity. pub const fn system(self) -> u8 { self.system } /// Source component identity. pub const fn component(self) -> u8 { self.component } } /// File operation carried by `FILE_CONTROL`. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum FileOperation { /// Opens a stable read snapshot selected by the supplied path. OpenRead = 1, /// Opens a bounded write staging area selected by the supplied path. OpenWrite = 2, /// Requests the chunk beginning at `position`. Read = 3, /// Acknowledges one accepted upload chunk. WriteAck = 4, /// Completes an open transfer. Close = 5, /// Discards an open transfer. Abort = 6, } /// Authoritative result carried by a control response. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum FileStatus { /// The operation succeeded. Ok = 1, /// Another principal owns the single transfer slot. Busy = 2, /// The transfer identity is absent, stale, or owned by another principal. InvalidTransfer = 3, /// The operation is not valid for the transfer's current mode or state. InvalidState = 4, /// The open request did not carry one canonical bounded path. InvalidPath = 5, /// No virtual file exists at the supplied path. NotFound = 6, /// The declared complete file exceeds the provider's bound. TooLarge = 7, /// A chunk or read did not start at an admissible position. InvalidPosition = 8, /// The complete sequential upload disagrees with its declared CRC-32C. ChecksumMismatch = 9, /// The provider rejected the complete file contents. ContentRejected = 10, /// Durable storage failed; the resulting durable state may be unknown. StorageFailure = 11, /// A retry reused an operation identity with different content. RequestConflict = 12, /// A provider or service invariant failed. Internal = 13, } /// Bounded opaque response detail or open-request path storage. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Content { length: u8, bytes: [u8; CONTENT_BYTES], } impl Content { /// Empty content. pub const EMPTY: Self = Self { length: 0, bytes: [0; CONTENT_BYTES], }; /// Copies a bounded byte sequence. pub fn try_new(bytes: &[u8]) -> Option { if bytes.len() > CONTENT_BYTES { return None; } let mut content = Self::EMPTY; content.bytes[..bytes.len()].copy_from_slice(bytes); content.length = bytes.len() as u8; Some(content) } /// Parses canonical length-delimited wire content. pub(crate) fn from_wire(length: u8, bytes: [u8; CONTENT_BYTES]) -> Option { let length = usize::from(length); if length > CONTENT_BYTES || bytes[length..].iter().any(|byte| *byte != 0) { return None; } Some(Self { length: length as u8, bytes, }) } /// Logical initialized bytes. pub fn as_bytes(&self) -> &[u8] { &self.bytes[..usize::from(self.length)] } /// Logical byte count. pub(crate) const fn len(self) -> u8 { self.length } /// Whether the logical content is empty. pub(crate) const fn is_empty(self) -> bool { self.length == 0 } /// Zero-padded wire storage. pub(crate) const fn wire_bytes(self) -> [u8; CONTENT_BYTES] { self.bytes } } /// Canonical absolute ASCII virtual-file path. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct FilePath(Content); impl FilePath { /// Validates a bounded absolute path without normalization or allocation. pub(crate) fn try_from_content(content: Content) -> Option { let bytes = content.as_bytes(); if bytes.len() < 2 || bytes[0] != b'/' || bytes[bytes.len() - 1] == b'/' { return None; } let mut segment_start = 1; for (index, byte) in bytes.iter().copied().enumerate().skip(1) { if byte == b'/' { if index == segment_start || reserved_segment(&bytes[segment_start..index]) { return None; } segment_start = index + 1; } else if !matches!(byte, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'.' | b'_' | b'-') { return None; } } if reserved_segment(&bytes[segment_start..]) { return None; } Some(Self(content)) } /// Canonical path bytes. pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } } fn reserved_segment(segment: &[u8]) -> bool { segment == b"." || segment == b".." } /// One decoded control request. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ControlRequest { /// Request-selected operation. pub operation: FileOperation, /// Exact addressed service endpoint. pub target: Target, /// Transfer-relative byte position. pub position: u32, /// Complete file size declared by an open-write request. pub file_size: u32, /// Complete CRC-32C declared by an open-write request. pub checksum_crc32c: u32, /// Nonzero client-selected transfer identity. pub transfer_id: u16, /// Canonical path bytes for opens; empty for all other requests. pub content: Content, } /// One decoded upload data request. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct DataRequest { /// Exact addressed service endpoint. pub target: Target, /// Transfer-relative byte position. pub position: u32, /// Nonzero client-selected transfer identity. pub transfer_id: u16, /// Fixed wire chunk. Its logical length comes from the open file size. pub data: [u8; DATA_BYTES], } /// Request accepted by [`crate::FileService`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum FileRequest { /// Control operation. Control(ControlRequest), /// Write data. Data(DataRequest), } /// One control response. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ControlResponse { /// Operation whose outcome is reported. pub operation: FileOperation, /// Requester addressed by the response. pub target: Target, /// Next accepted position or request position on failure. pub position: u32, /// Complete opened file size. pub file_size: u32, /// Complete opened file CRC-32C. pub checksum_crc32c: u32, /// Client-selected transfer identity. pub transfer_id: u16, /// Authoritative outcome. pub status: FileStatus, /// Bounded provider detail. pub content: Content, } /// One read-data response. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct DataResponse { /// Requester addressed by the response. pub target: Target, /// Position of the first logical byte. pub position: u32, /// Transfer identity. pub transfer_id: u16, /// Zero-padded wire data; logical length derives from the opened file size. pub data: [u8; DATA_BYTES], } /// Response produced by [`crate::FileService`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum FileResponse { /// Control outcome. Control(ControlResponse), /// Read data. Data(DataResponse), } /// Stable metadata returned when a provider pins a readable file. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct FileMetadata { /// Exact complete byte length. pub size: u32, /// CRC-32C of the complete pinned bytes. pub checksum_crc32c: u32, } /// Format-agnostic provider rejection. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct FileFailure { /// Stable generic failure classification. status: FileStatus, /// Bounded format-specific detail. content: Content, } impl FileFailure { /// Constructs a provider failure. Success statuses are rejected. pub const fn new(status: FileStatus, content: Content) -> Option { match status { FileStatus::Ok => None, _ => Some(Self { status, content }), } } /// Stable generic failure classification. pub(crate) const fn status(self) -> FileStatus { self.status } /// Bounded format-specific detail. pub(crate) const fn content(self) -> Content { self.content } }