← 中文资源

简体中文版本

端序感知的二进制读取

解析并构造大端序长度前缀帧,包含版本、标志与校验和,并对截断或校验失败给出明确错误。

阅读英文原文

功能说明

网络协议和文件格式通常规定固定字节序,Rust 的整数在内存中按本机序存储,直接做指针转换会在大端机器上得到错误结果。这个示例使用 from_be_bytes 与 to_be_bytes 显式转换,保证同一份数据在任何平台解析一致。

帧结构为八字节头加变长载荷:版本一字节、标志两字节、载荷长度四字节、简单校验和一字节。解析时先检查头部完整,再校验版本,随后按长度截取载荷,最后核对校验和。编码方向顺序写入各字段,长度字段由调用方提供。

关键代码

u16::from_be_bytes 接收固定长度数组,因此需要手动取出对应字节;u32 同理。长度转换为 usize 后与总长度比较,防止越界切片。校验和用 wrapping_add 折叠,允许溢出而不 panic。main 构造 hello 帧,展示编码字节、正常解析、截断报错和篡改载荷后的校验失败。

使用注意

长度字段应设置业务上限,避免恶意帧声明超大长度导致分配或长时间等待。简单累加校验只能发现部分错误,生产协议应使用 CRC32 或加密校验。解析器假设输入恰好包含一帧,多帧流需要循环并返回消耗的字节数。版本不匹配直接拒绝,为兼容性可保留旧解析分支。切片越界已被长度检查覆盖,但仍需 fuzz 测试。

验证方式

编码结果为八个头部字节加 hello 五个字节,解析回来与原始结构相等。截掉末尾两个字节会返回截断错误,打印为 true。把载荷第一个字节取反后,校验和不匹配,unwrap_err 输出 checksum mismatch。可以修改长度字段,确认截断检查先于校验和生效。

代码示例

#[derive(Debug, PartialEq)]
struct Frame {
    version: u8,
    flags: u16,
    length: u32,
    payload: Vec<u8>,
}

const HEADER_LEN: usize = 8;

fn simple_sum(payload: &[u8]) -> u8 {
    payload
        .iter()
        .fold(0u8, |accumulator, byte| accumulator.wrapping_add(*byte))
}

fn parse_frame(bytes: &[u8]) -> Result<Frame, String> {
    if bytes.len() < HEADER_LEN {
        return Err(format!(
            "need {HEADER_LEN} header bytes, got {}",
            bytes.len()
        ));
    }
    let version = bytes[0];
    let flags = u16::from_be_bytes([bytes[1], bytes[2]]);
    let length = u32::from_be_bytes([bytes[3], bytes[4], bytes[5], bytes[6]]);
    let checksum = bytes[7];
    if version != 1 {
        return Err(format!("unsupported version {version}"));
    }
    let end = HEADER_LEN + length as usize;
    if bytes.len() < end {
        return Err(format!("truncated payload: need {end} bytes"));
    }
    let payload = bytes[HEADER_LEN..end].to_vec();
    if simple_sum(&payload) != checksum {
        return Err("checksum mismatch".to_string());
    }
    Ok(Frame {
        version,
        flags,
        length,
        payload,
    })
}

fn encode_frame(frame: &Frame) -> Vec<u8> {
    let mut bytes = Vec::with_capacity(HEADER_LEN + frame.payload.len());
    bytes.push(frame.version);
    bytes.extend_from_slice(&frame.flags.to_be_bytes());
    bytes.extend_from_slice(&frame.length.to_be_bytes());
    bytes.push(simple_sum(&frame.payload));
    bytes.extend_from_slice(&frame.payload);
    bytes
}

fn main() {
    let frame = Frame {
        version: 1,
        flags: 0b1010,
        length: 5,
        payload: b"hello".to_vec(),
    };
    let encoded = encode_frame(&frame);
    println!("encoded: {:?}", encoded);
    println!("decoded: {:?}", parse_frame(&encoded));
    println!(
        "truncated: {}",
        parse_frame(&encoded[..encoded.len() - 2]).is_err()
    );

    let mut corrupted = encoded.clone();
    corrupted[HEADER_LEN] ^= 0xff;
    println!("checksum error: {:?}", parse_frame(&corrupted).unwrap_err());
}