根据架构设计,实现编解码层的代码设计
Cargo.toml 加入二进制序列化支持
# 序列化支持
...
bincode = "1.3" # 添加二进制序列化支持
bytes-utils = "0.1" # 添加字节处理工具
定义了编解码过程中可能遇到的错误类型,使用枚举定义
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),
}
- 定义了符合 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(),
"不同内容的帧应该有不同的校验和"
);
}
}
- 实现了 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
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
阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
1. 完整的帧解析逻辑:
- 起始符验证,根据接口协议验证是否0x23开头
- 命令标识和应答标志解析
- VIN码解析,vin码17个字节长度
- 加密方式解析,读取加密方式,测试的时候可以先不使用,上生产环境后要打开
- 数据单元长度解析,表示数据payload的总长度
- 数据单元提取
- 校验和验证
2. 数据完整性检查:
- 最小帧长度检查
- 完整数据长度检查
- 校验和验证
3. 添加了单元测试:
- 测试有效帧的解码
- 测试校验和错误的情况