Skip to main content

spvirit/
packet.rs

1//! Raw-frame `Packet` wrapper exposed via `Channel.read_packet` /
2//! `Channel.read_until`.
3
4use pyo3::prelude::*;
5use pyo3::types::{PyBytes, PyDict};
6
7use spvirit_codec::epics_decode::{PvaPacket, command_name};
8
9/// Owned PVA frame with header accessors and on-demand detail decoding.
10#[pyclass(name = "Packet", module = "spvirit.lowlevel", frozen)]
11pub struct PyPacket {
12    bytes: Vec<u8>,
13    magic: u8,
14    version: u8,
15    command: u8,
16    flags_raw: u8,
17    is_application: bool,
18    is_control: bool,
19    is_segmented: u8,
20    is_client: bool,
21    is_server: bool,
22    is_msb: bool,
23    payload_length: u32,
24}
25
26impl PyPacket {
27    pub(crate) fn raw(&self) -> &[u8] {
28        &self.bytes
29    }
30
31    pub fn from_bytes(bytes: Vec<u8>) -> Self {
32        if bytes.len() >= 8 {
33            let pkt = PvaPacket::new(&bytes);
34            return Self {
35                magic: pkt.header.magic,
36                version: pkt.header.version,
37                command: pkt.header.command,
38                flags_raw: pkt.header.flags.raw,
39                is_application: pkt.header.flags.is_application,
40                is_control: pkt.header.flags.is_control,
41                is_segmented: pkt.header.flags.is_segmented,
42                is_client: pkt.header.flags.is_client,
43                is_server: pkt.header.flags.is_server,
44                is_msb: pkt.header.flags.is_msb,
45                payload_length: pkt.header.payload_length,
46                bytes,
47            };
48        }
49        Self {
50            magic: 0,
51            version: 0,
52            command: 0,
53            flags_raw: 0,
54            is_application: false,
55            is_control: false,
56            is_segmented: 0,
57            is_client: false,
58            is_server: false,
59            is_msb: false,
60            payload_length: 0,
61            bytes,
62        }
63    }
64}
65
66#[pymethods]
67impl PyPacket {
68    #[getter]
69    fn magic(&self) -> u8 {
70        self.magic
71    }
72    #[getter]
73    fn version(&self) -> u8 {
74        self.version
75    }
76    #[getter]
77    fn command(&self) -> u8 {
78        self.command
79    }
80    #[getter]
81    fn command_name(&self) -> &'static str {
82        command_name(self.command)
83    }
84    #[getter]
85    fn flags(&self) -> u8 {
86        self.flags_raw
87    }
88    #[getter]
89    fn is_application(&self) -> bool {
90        self.is_application
91    }
92    #[getter]
93    fn is_control(&self) -> bool {
94        self.is_control
95    }
96    #[getter]
97    fn is_segmented(&self) -> u8 {
98        self.is_segmented
99    }
100    #[getter]
101    fn is_client(&self) -> bool {
102        self.is_client
103    }
104    #[getter]
105    fn is_server(&self) -> bool {
106        self.is_server
107    }
108    #[getter]
109    fn is_msb(&self) -> bool {
110        self.is_msb
111    }
112    #[getter]
113    fn payload_length(&self) -> u32 {
114        self.payload_length
115    }
116
117    #[getter]
118    fn bytes<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
119        PyBytes::new(py, &self.bytes)
120    }
121
122    #[getter]
123    fn payload<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
124        let start = 8.min(self.bytes.len());
125        PyBytes::new(py, &self.bytes[start..])
126    }
127
128    fn __len__(&self) -> usize {
129        self.bytes.len()
130    }
131
132    /// Decode command-specific payload details.  Delegates to the
133    /// standalone codec module.
134    fn details<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
135        let mut pkt = PvaPacket::new(&self.bytes);
136        let d = PyDict::new(py);
137        if let Some(cmd) = pkt.decode_payload() {
138            crate::codec::fill_command_details(py, &cmd, &d)?;
139        }
140        Ok(d)
141    }
142
143    /// Alias for the free `spvirit.codec.decode_packet` function
144    /// applied to this packet's bytes.
145    fn decode<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
146        crate::codec::decode_packet(py, &self.bytes)
147    }
148
149    fn __repr__(&self) -> String {
150        format!(
151            "Packet(command={} ({}), flags=0x{:02x}, payload_length={})",
152            self.command,
153            self.command_name(),
154            self.flags_raw,
155            self.payload_length
156        )
157    }
158}