根據架構設計,實現編解碼層的代碼設計
Cargo.toml 加入二進制序列化支持
# 序列化支持
...
bincode = "1.3" # 添加二進制序列化支持
bytes-utils = "0.1" # 添加字節處理工具
開始編碼
錯誤處理(error.rs):
定義了編解碼過程中可能遇到的錯誤類型,使用枚舉定義
use thiserror::Error;#[derive(Error, Debug)]
pub enum CodecError {#[error("數據長度不足")]InsufficientData,#[error("校驗和錯誤")]ChecksumMismatch,#[error("無效的起始符")]InvalidStartByte,#[error("無效的命令標識: {0}")] InvalidCommand(u8),#[error("IO錯誤: {0}")] Io(#[from] std::io::Error),
}
數據幀結構(frame.rs):
- 定義了符合 GBT32960 協議的數據幀結構
- 提供了創建和校驗數據幀的方法
frame.rs
use bytes::{ Bytes, BytesMut, BufMut };
use chrono::NaiveDateTime;
use serde::{ Serialize, Deserialize };#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Frame {pub start_byte: u8, // 起始符 0x23pub command_flag: u8, // 命令標識pub response_flag: u8, // 應答標志pub vin: String, // 車輛識別碼pub encrypt_method: u8, // 加密方式pub payload_length: u16, // 數據單元長度pub payload: Bytes, // 數據單元pub checksum: u8, // BCC校驗碼
}impl Frame {pub fn new(command: u8, vin: String, payload: Bytes) -> Self {let payload_length = payload.len() as u16;Self {start_byte: 0x23,command_flag: command,response_flag: 0xfe,vin,encrypt_method: 0x01,payload_length,payload,checksum: 0x00, // 將在編碼時計算}}pub fn calculate_checksum(&self) -> u8 {let mut bcc: u8 = 0;// 命令標識bcc ^= self.command_flag;// 應答標志bcc ^= self.response_flag;// VIN碼(17位)for byte in self.vin.as_bytes() {bcc ^= byte;}// 加密方式bcc ^= self.encrypt_method;// 數據單元長度(2字節)bcc ^= ((self.payload_length >> 8) & 0xff) as u8;bcc ^= (self.payload_length & 0xff) as u8;// 數據單元for byte in self.payload.iter() {bcc ^= byte;}bcc}
}#[cfg(test)]
mod tests {use super::*;#[test]fn test_calculate_checksum() {let payload = Bytes::from_static(&[0x01, 0x02, 0x03]);let frame = Frame::new(0x01, // command"LSVNV2182E0200001".to_string(), // vinpayload);let checksum = frame.calculate_checksum();assert!(checksum != 0, "校驗和不應該為0");// 創建相同內容的幀,校驗和應該相同let frame2 = Frame::new(0x01,"LSVNV2182E0200001".to_string(),Bytes::from_static(&[0x01, 0x02, 0x03]));assert_eq!(checksum, frame2.calculate_checksum(), "相同內容的幀應該有相同的校驗和");}#[test]fn test_different_content_different_checksum() {let frame1 = Frame::new(0x01, "LSVNV2182E0200001".to_string(), Bytes::from_static(&[0x01]));let frame2 = Frame::new(0x01, "LSVNV2182E0200001".to_string(), Bytes::from_static(&[0x02]));assert_ne!(frame1.calculate_checksum(),frame2.calculate_checksum(),"不同內容的幀應該有不同的校驗和");}
}
編解碼器(codec.rs):
- 實現了 tokio 的 Decoder 和 Encoder trait
- 負責數據幀的序列化和反序列化
use bytes::{ BytesMut, Buf };
use tokio_util::codec::{ Decoder, Encoder };
use super::{ Frame, CodecError };pub struct Gbt32960Codec;impl Decoder for Gbt32960Codec {type Item = Frame;type Error = CodecError;fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {// 檢查數據長度是否足夠if src.len() < 22 {// 最小幀長度return Ok(None);}// 檢查起始符if src[0] != 0x23 {return Err(CodecError::InvalidStartByte);}// TODO: 實現完整的解碼邏輯// 1. 讀取各個字段// 2. 驗證校驗和// 3. 解析數據單元Ok(None)}
}impl Encoder<Frame> for Gbt32960Codec {type Error = CodecError;fn encode(&mut self, frame: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {// TODO: 實現編碼邏輯// 1. 寫入各個字段// 2. 計算并寫入校驗和Ok(())}
}
實現完整的解碼邏輯
use bytes::{BytesMut, Buf, BufMut};
use tokio_util::codec::{Decoder, Encoder};
use super::{Frame, CodecError};pub struct Gbt32960Codec;impl Decoder for Gbt32960Codec {type Item = Frame;type Error = CodecError;fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {// 檢查數據長度是否足夠if src.len() < 22 { // 最小幀長度return Ok(None);}// 檢查起始符if src[0] != 0x23 {return Err(CodecError::InvalidStartByte);}// 讀取命令標識和應答標志let command_flag = src[1];let response_flag = src[2];// 讀取 VIN 碼(17字節)let vin = String::from_utf8_lossy(&src[3..20]).to_string();// 讀取加密方式let encrypt_method = src[20];// 讀取數據單元長度(2字節)let payload_length = ((src[21] as u16) << 8) | (src[22] as u16);// 檢查是否有足夠的數據let total_length = 23 + payload_length as usize + 1; // 頭部 + 數據單元 + 校驗碼if src.len() < total_length {return Ok(None);}// 讀取數據單元let payload = src.slice(23..23 + payload_length as usize);// 讀取校驗碼let received_checksum = src[total_length - 1];// 創建幀對象進行校驗和計算let frame = Frame {start_byte: 0x23,command_flag,response_flag,vin,encrypt_method,payload_length,payload: payload.freeze(),checksum: received_checksum,};// 驗證校驗和let calculated_checksum = frame.calculate_checksum();if calculated_checksum != received_checksum {return Err(CodecError::ChecksumMismatch);}// 消費已處理的字節src.advance(total_length);Ok(Some(frame))}
}impl Encoder<Frame> for Gbt32960Codec {type Error = CodecError;fn encode(&mut self, frame: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {// TODO: 實現編碼邏輯// 1. 寫入各個字段// 2. 計算并寫入校驗和Ok(())}
}#[cfg(test)]
mod tests {use super::*;use bytes::Bytes;#[test]fn test_decode_valid_frame() {let mut codec = Gbt32960Codec;let mut buffer = BytesMut::new();// 構造測試數據buffer.put_u8(0x23); // 起始符buffer.put_u8(0x01); // 命令標識buffer.put_u8(0xFE); // 應答標志buffer.extend_from_slice(b"LSVNV2182E0200001"); // VIN碼buffer.put_u8(0x01); // 加密方式buffer.put_u16(2); // 數據長度buffer.extend_from_slice(&[0x01, 0x02]); // 數據單元// 計算并添加校驗和let checksum = buffer[1..buffer.len()].iter().fold(0u8, |acc, &x| acc ^ x);buffer.put_u8(checksum);// 解碼let result = codec.decode(&mut buffer).unwrap().unwrap();// 驗證解碼結果assert_eq!(result.command_flag, 0x01);assert_eq!(result.vin, "LSVNV2182E0200001");assert_eq!(result.payload.len(), 2);assert_eq!(buffer.len(), 0); // 確保所有數據都被消費}#[test]fn test_decode_invalid_checksum() {let mut codec = Gbt32960Codec;let mut buffer = BytesMut::new();// 構造測試數據(使用錯誤的校驗和)buffer.put_u8(0x23);buffer.put_u8(0x01);buffer.put_u8(0xFE);buffer.extend_from_slice(b"LSVNV2182E0200001");buffer.put_u8(0x01);buffer.put_u16(0);buffer.put_u8(0xFF); // 錯誤的校驗和// 驗證解碼失敗assert!(matches!(codec.decode(&mut buffer),Err(CodecError::ChecksumMismatch)));}
}
代碼地址
阿里云登錄 - 歡迎登錄阿里云,安全穩定的云計算服務平臺
總結
1. 完整的幀解析邏輯:
? ?- 起始符驗證,根據接口協議驗證是否0x23開頭
? ?- 命令標識和應答標志解析
? ?- VIN碼解析,vin碼17個字節長度
? ?- 加密方式解析,讀取加密方式,測試的時候可以先不使用,上生產環境后要打開
? ?- 數據單元長度解析,表示數據payload的總長度
? ?- 數據單元提取
? ?- 校驗和驗證
2. 數據完整性檢查:
? ?- 最小幀長度檢查
? ?- 完整數據長度檢查
? ?- 校驗和驗證
3. 添加了單元測試:
???- 測試有效幀的解碼
? ?- 測試校驗和錯誤的情況