Skip to main content

pk_command/
types.rs

1//!
2//! This module defines the core data structures and types used in the PK Command protocol implementation.
3
4#[cfg(not(feature = "std"))]
5extern crate alloc;
6#[cfg(not(feature = "std"))]
7extern crate core as std;
8#[cfg(not(feature = "std"))]
9use alloc::{
10    format,
11    string::{String, ToString},
12    vec::Vec,
13};
14
15use crate::util::msg_id;
16
17/// Defines the set of operations supported by the PK Command protocol.
18#[derive(PartialEq, Eq, Clone, Copy, Debug)]
19pub enum Operation {
20    /// To set a variable's value on the device.
21    ///
22    /// 5-character name: `SENDV`
23    SendVariable,
24
25    /// To get a variable's value from the device.
26    ///
27    /// 5-character name: `REQUV`
28    RequireVariable,
29
30    /// To invoke a method on the device.
31    ///
32    /// 5-character name: `INVOK`
33    Invoke,
34
35    /// To get the version of the PK Command processor on the device.
36    ///
37    /// 5-character name: `PKVER`
38    GetVersion,
39
40    /// To indicate the start of a transaction chain.
41    ///
42    /// This is used internally by the [`poll`](crate::PkCommand::poll) method to manage transaction stages
43    /// and usually should not be used directly.
44    ///
45    /// 5-character name: `START`
46    Start,
47
48    /// To indicate the end of a transaction chain.
49    ///
50    /// This is used internally by the [`poll`](crate::PkCommand::poll) method to manage transaction stages
51    /// and usually should not be used directly.
52    ///
53    /// 5-character name: `ENDTR`
54    EndTransaction,
55
56    /// To acknowledge the receipt of a command.
57    ///
58    /// This is used internally by the state machine to manage acknowledgment status and usually should not be used directly.
59    ///
60    /// 5-character name: `ACKNO`
61    Acknowledge,
62
63    /// To request the outbound data from the device.
64    ///
65    /// This is used internally by the state machine to manage transaction stages and usually should not be used directly.
66    ///
67    /// 5-character name: `QUERY`
68    Query,
69
70    /// To return the response data from the device to the host.
71    ///
72    /// This is used internally by the state machine to manage transaction stages and usually should not be used directly.
73    ///
74    /// 5-character name: `RTURN`
75    Return,
76
77    /// To indicate that the current transaction phase has no data.
78    ///
79    /// This is used internally by the state machine to manage transaction stages and usually should not be used directly.
80    ///
81    /// 5-character name: `EMPTY`
82    Empty,
83
84    /// To send a chunk of data.
85    ///
86    /// This is used internally by the state machine to manage transaction stages and usually should not be used directly.
87    ///
88    /// 5-character name: `SDATA`
89    Data,
90
91    /// To indicate that the device is still processing and the transaction should be kept alive.
92    ///
93    /// This is used internally by the state machine to manage transaction stages and usually should not be used directly.
94    ///
95    /// 5-character name: `AWAIT`
96    Await,
97
98    /// To indicate an error occurred during transaction processing.
99    ///
100    /// This is used internally by the state machine to manage error handling and usually should not be used directly.
101    ///
102    /// 5-character name: `ERROR`
103    Error,
104}
105
106impl Operation {
107    /// Returns the 5-character string representation of the operation.
108    pub fn to_name(&self) -> &'static str {
109        use Operation::*;
110        match self {
111            SendVariable => "SENDV",
112            RequireVariable => "REQUV",
113            Invoke => "INVOK",
114            GetVersion => "PKVER",
115            Start => "START",
116            EndTransaction => "ENDTR",
117            Acknowledge => "ACKNO",
118            Query => "QUERY",
119            Return => "RTURN",
120            Empty => "EMPTY",
121            Data => "SDATA",
122            Await => "AWAIT",
123            Error => "ERROR",
124        }
125    }
126
127    /// Creates an `Operation` from its 5-character string representation.
128    pub fn from_name(name: &str) -> Option<Operation> {
129        use Operation::*;
130        match name {
131            "SENDV" => Some(SendVariable),
132            "REQUV" => Some(RequireVariable),
133            "INVOK" => Some(Invoke),
134            "PKVER" => Some(GetVersion),
135            "START" => Some(Start),
136            "ENDTR" => Some(EndTransaction),
137            "ACKNO" => Some(Acknowledge),
138            "QUERY" => Some(Query),
139            "RTURN" => Some(Return),
140            "EMPTY" => Some(Empty),
141            "SDATA" => Some(Data),
142            "AWAIT" => Some(Await),
143            "ERROR" => Some(Error),
144            _ => None,
145        }
146    }
147
148    /// Checks if the operation is a "root operation" that can initiate a transaction chain.
149    pub fn is_root(&self) -> bool {
150        matches!(
151            self,
152            Operation::SendVariable
153                | Operation::RequireVariable
154                | Operation::Invoke
155                | Operation::GetVersion
156        )
157    }
158}
159
160/// Defines the role of a participant in a PK Command transaction.
161///
162/// This is used internally by the state machine to manage transaction flow and usually should not be used directly.
163#[derive(PartialEq, Eq, Clone, Copy)]
164pub enum Role {
165    /// The initiator of the transaction.
166    Host, // 调用方(不一定是主机)
167    /// The receiver and executor of the transaction.
168    Device, // 接收方(不一定是设备)
169    /// Indicates that no transaction is active, and thus no specific role is assigned.
170    Idle, // (空闲期没有角色)
171}
172
173/// Represents a parsed or to-be-sent PK Command.
174///
175/// A command consists of a 2-character base-94 `msg_id`, a 5-character `operation`,
176/// an optional 5-character `object`, and optional variable-length `data`.
177#[derive(PartialEq, Clone, Debug)]
178pub struct Command {
179    /// The numeric message ID (0-8835).
180    pub msg_id: u16,
181    /// The operation to be performed.
182    pub operation: Operation,
183    /// The target object of the operation (e.g., variable or method name).
184    pub object: Option<String>,
185    /// Optional payload data.
186    pub data: Option<Vec<u8>>,
187}
188
189impl Command {
190    /// Parses a byte slice into a [`Command`] struct.
191    ///
192    /// The input must follow the protocol format: `[ID][OP] [OBJ] [DATA]`.
193    ///
194    /// # Arguments
195    /// * `msg_bytes`: The raw bytes received from the transport layer.
196    ///
197    /// # Returns
198    /// A [`Result`] containing the parsed [`Command`] or an error message.
199    ///
200    /// # Errors
201    /// Returns an error if the byte slice is not a valid PK Command. (For example, the length is too short, the MSG ID is invalid,
202    /// or the operation name is unrecognized.)
203    pub fn parse(msg_bytes: &[u8]) -> Result<Command, &'static str> {
204        // 1. 检查最小长度
205        if msg_bytes.len() < 7 {
206            return Err("Invalid length: message is too short.");
207        }
208
209        // 2. 解析 MSG ID
210        let msg_id_slice = msg_bytes.get(0..2).ok_or("Failed to slice MSG ID")?;
211
212        // 3. 特殊处理 ERROR 指令
213        if msg_id_slice == b"  " {
214            // 检查 `ERROR ERROR` 或 `ACKNO ERROR` 结构
215            let op_name_slice = msg_bytes.get(2..7);
216            let space1_slice = msg_bytes.get(7..8);
217            let object_slice = msg_bytes.get(8..13);
218
219            let is_ackno_error = op_name_slice == Some(b"ACKNO")
220                && space1_slice == Some(b" ")
221                && object_slice == Some(b"ERROR");
222            let is_error_error = op_name_slice == Some(b"ERROR")
223                && space1_slice == Some(b" ")
224                && object_slice == Some(b"ERROR");
225
226            if !(is_ackno_error || is_error_error) {
227                return Err("Invalid ERROR command format.");
228            }
229
230            let data = if msg_bytes.len() > 14 {
231                // 检查数据前的空格
232                if msg_bytes.get(13..14) != Some(b" ") {
233                    return Err("Missing space before data in ERROR command.");
234                }
235                // unwrap is safe due to length check msg_bytes.len() > 14
236                Some(msg_bytes.get(14..).unwrap().to_vec())
237            } else if msg_bytes.len() == 13 {
238                // Exactly "  OP_NAME OBJECT"
239                None
240            } else {
241                return Err("Invalid length for ERROR command.");
242            };
243
244            return Ok(Command {
245                msg_id: 0,
246                operation: if op_name_slice == Some(b"ACKNO") {
247                    Operation::Acknowledge
248                } else {
249                    Operation::Error
250                },
251                object: Some("ERROR".to_string()),
252                data,
253            });
254        }
255
256        // 4. 处理常规指令
257        let msg_id_str =
258            std::str::from_utf8(msg_id_slice).map_err(|_| "MSG ID is not valid UTF-8")?;
259        let msg_id = msg_id::to_u16(msg_id_str).map_err(|_| "Invalid MSG ID format.")?;
260
261        let op_name_slice = msg_bytes
262            .get(2..7)
263            .ok_or("Failed to slice operation name.")?;
264        let op_name_str =
265            std::str::from_utf8(op_name_slice).map_err(|_| "Operation name is not valid UTF-8")?;
266        let operation = Operation::from_name(op_name_str).ok_or("Unrecognized operation name.")?;
267
268        // 5. 根据长度和分隔符判断 object 和 data
269        let (object, data) = match msg_bytes.len() {
270            // 只有 MSG ID 和 OP NAME
271            7 => (None, None),
272
273            // 包含 OBJECT
274            13 => {
275                if msg_bytes.get(7..8) != Some(b" ") {
276                    return Err("Missing space after operation name.");
277                }
278                let obj_slice = msg_bytes.get(8..13).ok_or("Failed to slice object.")?;
279                let obj_str =
280                    std::str::from_utf8(obj_slice).map_err(|_| "Object is not valid UTF-8")?;
281                (Some(obj_str.to_string()), None)
282            }
283
284            // 包含 OBJECT 和 DATA
285            len if len > 14 => {
286                if msg_bytes.get(7..8) != Some(b" ") || msg_bytes.get(13..14) != Some(b" ") {
287                    return Err("Missing space separator for object or data.");
288                }
289                let obj_slice = msg_bytes.get(8..13).ok_or("Failed to slice object.")?;
290                let obj_str =
291                    std::str::from_utf8(obj_slice).map_err(|_| "Object is not valid UTF-8")?;
292
293                // unwrap is safe due to length check (len > 14)
294                let data_slice = msg_bytes.get(14..).unwrap();
295                (Some(obj_str.to_string()), Some(data_slice.to_vec()))
296            }
297            // 其他所有长度都是无效的
298            _ => return Err("Invalid message length."),
299        };
300
301        Ok(Command {
302            msg_id,
303            operation,
304            object,
305            data,
306        })
307    }
308
309    /// Serializes the [`Command`] into a [`Vec<u8>`] for transmission.
310    ///
311    /// This method ensures the output matches the fixed-length field requirements
312    /// of the PK Command protocol.
313    ///
314    /// # Panics
315    /// Panics if the `msg_id` is out of the valid 0-8835 range.
316    /// This usually indicates a tragic programming error.
317    ///
318    /// # Examples
319    /// ```
320    /// use pk_command::types::{Command, Operation};
321    /// let cmd = Command {
322    ///     msg_id: 2,
323    ///     operation: Operation::SendVariable,
324    ///     object: Some("VARIA".to_string()),
325    ///     data: Some(b"payload".to_vec()),
326    /// };
327    /// assert_eq!(cmd.to_bytes(), b"!#SENDV VARIA payload".to_vec());
328    /// ```
329    ///
330    /// ```should_panic
331    /// use pk_command::types::{Command, Operation};
332    /// let cmd = Command {
333    ///     msg_id: 9000, // Invalid MSG ID (greater than 8835)
334    ///     operation: Operation::SendVariable,
335    ///     object: Some("VARIA".to_string()),
336    ///     data: Some(b"payload".to_vec()),
337    /// };
338    /// cmd.to_bytes(); // This should panic due to invalid MSG ID
339    /// ```
340    pub fn to_bytes(&self) -> Vec<u8> {
341        let id = match self.operation {
342            Operation::Error => String::from("  "),
343            // ERROR 指令的 ACKNO 的 id 也固定是两个空格
344            Operation::Acknowledge => {
345                if self.object == Some(String::from("ERROR")) {
346                    String::from("  ")
347                } else {
348                    msg_id::from_u16(self.msg_id)
349                        .map_err(|_| panic!("Invalid MSG ID"))
350                        .unwrap()
351                }
352            }
353            _ => msg_id::from_u16(self.msg_id)
354                .map_err(|_| panic!("Invalid MSG ID"))
355                .unwrap(),
356        };
357        if self.data.is_none() {
358            if self.object.is_none() {
359                format!("{}{}", id, self.operation.to_name())
360                    .as_bytes()
361                    .to_vec()
362            } else {
363                format!(
364                    "{}{} {}",
365                    id,
366                    self.operation.to_name(),
367                    self.object.clone().unwrap()
368                )
369                .as_bytes()
370                .to_vec()
371            }
372        } else {
373            let mut vec = format!(
374                "{}{} {}",
375                id,
376                self.operation.to_name(),
377                self.object.clone().unwrap()
378            )
379            .as_bytes()
380            .to_vec();
381            vec.push(b' ');
382            vec.append(&mut self.data.clone().unwrap());
383            vec
384        }
385    }
386}
387
388impl std::fmt::Display for Command {
389    /// Formats the command for debugging or logging purposes.
390    ///
391    /// The output mimics the protocol format, but ensures non-printable data is handled gracefully.
392    ///
393    /// # Example
394    /// ```
395    /// use pk_command::types::{Command, Operation};
396    /// let cmd = Command {
397    ///     msg_id: 0,
398    ///     operation: Operation::Error,
399    ///     object: Some("ERROR".to_string()),
400    ///     data: Some(b"Some error description".to_vec()),
401    /// };
402    /// assert_eq!(format!("{}", cmd), "  ERROR ERROR Some error description");
403    ///
404    /// let cmd_non_printable = Command {
405    ///     msg_id: 1145,
406    ///     operation: Operation::Data,
407    ///     object: Some("QUERY".to_string()),
408    ///     data: Some(vec![0xFF, 0x00, 0xAB]),
409    /// };
410    ///
411    /// // "-2" is the base-94 encoding of 1145, and the data is non-printable,
412    /// // so it should show as "<BINARY DATA>".
413    /// assert_eq!(
414    ///     format!("{}", cmd_non_printable),
415    ///     "-2SDATA QUERY <BINARY DATA: 3 bytes>"
416    /// );
417    ///
418    /// let cmd_utf8 = Command {
419    ///     msg_id: 1145,
420    ///     operation: Operation::Data,
421    ///     object: Some("QUERY".to_string()),
422    ///     data: Some("汉字🐼".as_bytes().to_vec()),
423    /// };
424    /// // The data is valid UTF-8, so it should be displayed as is.
425    /// assert_eq!(format!("{}", cmd_utf8), "-2SDATA QUERY 汉字🐼");
426    /// ```
427    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428        let id = match self.operation {
429            Operation::Error => String::from("  "),
430            // ERROR 指令的 ACKNO 的 id 也固定是两个空格
431            Operation::Acknowledge => {
432                if self.object == Some(String::from("ERROR")) {
433                    String::from("  ")
434                } else {
435                    msg_id::from_u16(self.msg_id).map_err(|_| std::fmt::Error)?
436                }
437            }
438            _ => msg_id::from_u16(self.msg_id).map_err(|_| std::fmt::Error)?, // 将自定义错误转换为 fmt::Error
439        };
440
441        let op = self.operation.to_name();
442
443        write!(f, "{}{}", id, op)?;
444
445        if let Some(obj) = &self.object {
446            write!(f, " {}", obj)?;
447            if let Some(data_vec) = &self.data {
448                #[cfg(not(feature = "std"))]
449                use alloc::str::from_utf8;
450                #[cfg(feature = "std")]
451                use std::str::from_utf8;
452
453                if let Ok(data) = from_utf8(data_vec) {
454                    write!(f, " {}", data)?;
455                } else {
456                    write!(f, " <BINARY DATA: {} bytes>", data_vec.len())?;
457                };
458            }
459        }
460        Ok(())
461    }
462}
463
464/// Indicates the current acknowledgment status of the participant.
465#[derive(PartialEq, Eq, Clone, Copy)]
466pub enum Status {
467    /// Normal state, not awaiting any acknowledgment.
468    Other,
469    /// Currently waiting for a standard `ACKNO` to be received.
470    AwaitingAck,
471    /// Currently waiting for an acknowledgment to an `ERROR` packet.
472    AwaitingErrAck,
473}
474
475/// Defines the high-level stages of a PK Command transaction chain.
476#[derive(PartialEq, Eq, Clone, Copy)]
477pub enum Stage {
478    /// No transaction is currently active.
479    Idle,
480    /// A `START` command has been sent or received.
481    Started,
482    /// The root operation (e.g., `SENDV`) has been established.
483    RootOperationAssigned,
484    /// Parameter data is currently being transferred via `SDATA` or `EMPTY`.
485    SendingParameter,
486    /// All parameters have been sent, and the transaction is awaiting a `QUERY` or processing.
487    ParameterSent,
488    /// The response/return data is currently being transferred.
489    SendingResponse,
490}
491
492#[cfg(test)]
493mod tests {
494    use super::*;
495    use crate::util::msg_id;
496
497    #[test]
498    fn test_command_parse_valid_simple() {
499        let bytes = b"!!START";
500        let cmd = Command::parse(bytes).unwrap();
501        assert_eq!(cmd.msg_id, 0);
502        assert_eq!(cmd.operation, Operation::Start);
503        assert!(cmd.object.is_none());
504        assert!(cmd.data.is_none());
505    }
506
507    #[test]
508    fn test_command_parse_valid_with_object() {
509        let bytes = b"!\"SENDV VARIA";
510        let cmd = Command::parse(bytes).unwrap();
511        assert_eq!(cmd.msg_id, msg_id::to_u16("!\"").unwrap());
512        assert_eq!(cmd.operation, Operation::SendVariable);
513        assert_eq!(cmd.object, Some("VARIA".to_string()));
514        assert!(cmd.data.is_none());
515    }
516
517    #[test]
518    fn test_command_parse_valid_with_object_and_data() {
519        let bytes = b"!#SENDV VARIA data_payload";
520        let cmd = Command::parse(bytes).unwrap();
521        assert_eq!(cmd.msg_id, msg_id::to_u16("!#").unwrap());
522        assert_eq!(cmd.operation, Operation::SendVariable);
523        assert_eq!(cmd.object, Some("VARIA".to_string()));
524        assert_eq!(cmd.data, Some(b"data_payload".to_vec()));
525    }
526
527    #[test]
528    fn test_command_parse_error_command() {
529        let bytes = b"  ERROR ERROR Some error description";
530        let cmd = Command::parse(bytes).unwrap();
531        assert_eq!(cmd.msg_id, 0);
532        assert_eq!(cmd.operation, Operation::Error);
533        assert_eq!(cmd.object, Some("ERROR".to_string()));
534        assert_eq!(cmd.data, Some(b"Some error description".to_vec()));
535    }
536
537    #[test]
538    fn test_command_parse_ackno_error_command() {
539        let bytes = b"  ACKNO ERROR";
540        let cmd = Command::parse(bytes).unwrap();
541        assert_eq!(cmd.msg_id, 0);
542        assert_eq!(cmd.operation, Operation::Acknowledge);
543        assert_eq!(cmd.object, Some("ERROR".to_string()));
544        assert!(cmd.data.is_none());
545    }
546
547    #[test]
548    fn test_command_parse_invalid_error_msg_id() {
549        // space msg_id's are only allowed in ERROR and ACKNO ERROR commands
550        let bytes = b"  START";
551        let result = Command::parse(bytes);
552        assert!(result.is_err());
553    }
554
555    #[test]
556    fn test_command_parse_invalid_too_short() {
557        // assert_eq!(
558        //     Command::parse(b"!!STA"),
559        //     Err("Invalid length: message is too short.")
560        // );
561        let result = Command::parse(b"!!STA");
562        assert!(result.is_err());
563        assert_eq!(result.unwrap_err(), "Invalid length: message is too short.");
564    }
565
566    #[test]
567    fn test_command_parse_invalid_msg_id() {
568        // LF(0x0A) and CR(0x0D) is not in the charset
569        let result = Command::parse(b"\n\rSTART");
570        assert!(result.is_err());
571        assert_eq!(result.unwrap_err(), "Invalid MSG ID format.");
572    }
573
574    #[test]
575    fn test_command_to_bytes_simple() {
576        let cmd = Command {
577            msg_id: 0,
578            operation: Operation::Start,
579            object: None,
580            data: None,
581        };
582        assert_eq!(cmd.to_bytes(), b"!!START".to_vec());
583    }
584
585    #[test]
586    fn test_command_to_bytes_with_object_and_data() {
587        let cmd = Command {
588            msg_id: msg_id::to_u16("!#").unwrap(),
589            operation: Operation::SendVariable,
590            object: Some("VARIA".to_string()),
591            data: Some(b"payload".to_vec()),
592        };
593
594        let mut expected = b"!#SENDV VARIA".to_vec();
595        expected.push(b' ');
596        expected.extend_from_slice(b"payload");
597        assert_eq!(cmd.to_bytes(), expected);
598    }
599
600    #[test]
601    fn test_command_to_bytes_error() {
602        let cmd = Command {
603            msg_id: 0, // msg_id is ignored for ERROR
604            operation: Operation::Error,
605            object: Some("ERROR".to_string()),
606            data: Some(b"Test error".to_vec()),
607        };
608        let mut expected = b"  ERROR ERROR".to_vec();
609        expected.push(b' ');
610        expected.extend_from_slice(b"Test error");
611        assert_eq!(cmd.to_bytes(), expected);
612    }
613}