根据架构设计,实现编解码层的代码设计
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, // 起始符 0x23
- pub 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(), // vin
- payload
- );
- 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. 添加了单元测试:
- 测试有效帧的解码
- 测试校验和错误的情况
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |