基于 Rust 与 GBT32960 规范的编解码层

根据架构设计,实现编解码层的代码设计

Cargo.toml 加入二进制序列化支持

复制代码
# 序列化支持
...
bincode = "1.3"           # 添加二进制序列化支持
bytes-utils = "0.1"       # 添加字节处理工具

开始编码

错误处理(error.rs):

定义了编解码过程中可能遇到的错误类型,使用枚举定义

rust 复制代码
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

rust 复制代码
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

  • 负责数据帧的序列化和反序列化

rust 复制代码
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(())
    }
}

实现完整的解码逻辑

rust 复制代码
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的总长度

  • 数据单元提取

  • 校验和验证

  1. 数据完整性检查:
  • 最小帧长度检查

  • 完整数据长度检查

  • 校验和验证

  1. 添加了单元测试:
  • 测试有效帧的解码

  • 测试校验和错误的情况

相关推荐
Ray Liang2 分钟前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
Java水解17 分钟前
Java 中间件:Dubbo 服务降级(Mock 机制)
java·后端
蚂蚁背大象1 小时前
Rust 所有权系统是为了解决什么问题
后端·rust
布列瑟农的星空2 小时前
前端都能看懂的rust入门教程(五)—— 所有权
rust
SimonKing4 小时前
OpenCode AI辅助编程,不一样的编程思路,不写一行代码
java·后端·程序员
FastBean5 小时前
Jackson View Extension Spring Boot Starter
java·后端
Seven976 小时前
剑指offer-79、最⻓不含重复字符的⼦字符串
java
皮皮林55115 小时前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java
冰_河15 小时前
QPS从300到3100:我靠一行代码让接口性能暴涨10倍,系统性能原地起飞!!
java·后端·性能优化