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}