Skip to main content

osdp/command/
file_transfer.rs

1//! `osdp_FILETRANSFER` (`0x7C`) — chunked file upload to the PD.
2//!
3//! # Spec: §6.21
4
5use crate::error::Error;
6use crate::payload_util::require_at_least;
7use alloc::vec::Vec;
8
9/// `osdp_FILETRANSFER` body.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct FileTransfer {
12    /// Vendor-specific file type.
13    pub file_type: u8,
14    /// Total file size, in bytes.
15    pub total_size: u32,
16    /// Byte offset of this fragment.
17    pub offset: u32,
18    /// File fragment.
19    pub fragment: Vec<u8>,
20}
21
22impl FileTransfer {
23    /// Encode.
24    pub fn encode(&self) -> Result<Vec<u8>, Error> {
25        if self.fragment.len() > u16::MAX as usize {
26            return Err(Error::MalformedPayload {
27                code: 0x7C,
28                reason: "FILETRANSFER fragment > 65535 bytes",
29            });
30        }
31        let mut out = Vec::with_capacity(11 + self.fragment.len());
32        out.push(self.file_type);
33        out.extend_from_slice(&self.total_size.to_le_bytes());
34        out.extend_from_slice(&self.offset.to_le_bytes());
35        out.extend_from_slice(&(self.fragment.len() as u16).to_le_bytes());
36        out.extend_from_slice(&self.fragment);
37        Ok(out)
38    }
39
40    /// Decode.
41    pub fn decode(data: &[u8]) -> Result<Self, Error> {
42        require_at_least(data, 11, 0x7C)?;
43        let total_size = u32::from_le_bytes([data[1], data[2], data[3], data[4]]);
44        let offset = u32::from_le_bytes([data[5], data[6], data[7], data[8]]);
45        let frag_len = u16::from_le_bytes([data[9], data[10]]) as usize;
46        if data.len() != 11 + frag_len {
47            return Err(Error::MalformedPayload {
48                code: 0x7C,
49                reason: "FILETRANSFER fragment length disagrees with payload",
50            });
51        }
52        Ok(Self {
53            file_type: data[0],
54            total_size,
55            offset,
56            fragment: data[11..11 + frag_len].to_vec(),
57        })
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn roundtrip() {
67        let body = FileTransfer {
68            file_type: 0x07,
69            total_size: 0x0000_0100,
70            offset: 0x0000_0040,
71            fragment: alloc::vec![0xAA, 0xBB, 0xCC],
72        };
73        let bytes = body.encode().unwrap();
74        // file_type | total_size LE | offset LE | frag_len LE | fragment
75        assert_eq!(
76            bytes,
77            [
78                0x07, 0x00, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x03, 0x00, 0xAA, 0xBB, 0xCC
79            ]
80        );
81        assert_eq!(FileTransfer::decode(&bytes).unwrap(), body);
82    }
83
84    #[test]
85    fn decode_rejects_short_header() {
86        assert!(matches!(
87            FileTransfer::decode(&[0; 10]),
88            Err(Error::PayloadTooShort { code: 0x7C, .. })
89        ));
90    }
91
92    #[test]
93    fn decode_rejects_length_mismatch() {
94        // Header advertises a 5-byte fragment but only 2 bytes follow.
95        let bad = [
96            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xAA, 0xBB,
97        ];
98        assert!(matches!(
99            FileTransfer::decode(&bad),
100            Err(Error::MalformedPayload { code: 0x7C, .. })
101        ));
102    }
103}