You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用nom解析器构建面向比特的AIS消息解码接口?

用Rust的nom库解码AIS消息的两个关键问题解决

我正在用Rust的nom库解码AIS消息,先明确下AIS消息的核心编码规则:

  • AIS消息基于任意长度的比特向量,字段长度不固定,且通常不对齐字节边界
  • 该比特向量通过ASCII编码嵌入NMEA语句,解码规则如下:

    每个ASCII字符对应6比特数据;恢复6比特值的方法:从ASCII字符值减48,若结果大于40则再减8。
    有效ASCII字符范围是"0"(ASCII值64)到"w"(ASCII值87),其中"X"(88)到"_"(95)的字符不会被使用。

举个实际例子:

NMEA语句:!AIVDM,1,1,,A,D03Ovk1T1N>5N8ffqMhNfp0,0*68
编码后的AIS数据:D03Ovk1T1N>5N8ffqMhNfp0
解码后的比特向量:010100000000000011011111111110110011000001100100000001011110001110000101011110001000101110101110111001011101110000011110101110111000000000

遇到的两个相关问题

问题1:在nom内完成ASCII到比特向量的转换,无需中间字节数组

我能手动实现ASCII到比特流的转换,但希望直接在nom的解析器链中完成这一步,把ASCII字符流直接转换成可处理的比特流,避免生成中间字节数组,和后续的AIS字段解析器无缝链式调用。

问题2:读取任意比特数的字段,避免nom强制字节对齐

使用nom的bits!宏读取比特时,发现如果用take_bits!(u8, n),似乎会一次性消费整字节的比特?比如我定义:

named!(take_3_bits<u8>, bits!(take_bits!(u8, 3)));

调用两次这个解析器,实际会消耗16比特流而非预期的6比特;即使把多个读取操作组合,nom还是会强制前进到整字节边界,手动管理比特指针又会增加额外工作量。


解决方案

针对问题1:在nom中直接将ASCII字符流转换为比特流

我们可以编写自定义nom解析器,把输入的ASCII字符逐个转换成6比特值,再拼接成连续比特流,作为bits!宏的输入,全程无需额外中间字节数组(除了必要的比特打包步骤)。

首先实现单个ASCII字符转6比特值的解析器:

use nom::{
    bytes::complete::take,
    combinator::map_res,
    error::{Error, ErrorKind},
    IResult,
};

/// 将单个AIS编码的ASCII字符转换为6比特数值
fn decode_ais_char(c: char) -> Result<u8, Error<&str>> {
    let ascii_val = c as u8;
    let mut val = ascii_val - 48;
    if val > 40 {
        val -= 8;
    }
    // 验证是否在6比特有效范围(0-63)内
    if val > 63 {
        Err(Error::new("", ErrorKind::Char))
    } else {
        Ok(val)
    }
}

/// 解析单个AIS编码的ASCII字符
fn parse_ais_char(input: &str) -> IResult<&str, u8> {
    map_res(take(1usize), |s: &str| {
        s.chars().next()
            .ok_or_else(|| Error::new("", ErrorKind::Char))
            .and_then(decode_ais_char)
    })(input)
}

接着把所有ASCII字符解码后的6比特值打包成完整字节流,供bits!宏处理:

use nom::{bits::bits, multi::fold_many0};

/// 把AIS的ASCII编码字符串转换为可被bits!处理的字节流
fn parse_ais_payload(input: &str) -> IResult<&str, &[u8]> {
    let (remaining, six_bit_vals) = fold_many0(
        parse_ais_char,
        Vec::new(),
        |mut acc, val| {
            acc.push(val);
            acc
        }
    )(input)?;

    // 将6比特值数组打包成连续字节流
    let mut bytes = Vec::new();
    let mut current_byte = 0u8;
    let mut bit_pos = 0;

    for &val in &six_bit_vals {
        let bits_remaining = 8 - bit_pos;
        if bits_remaining >= 6 {
            current_byte |= val << (bits_remaining - 6);
            bit_pos += 6;
        } else {
            current_byte |= val >> (6 - bits_remaining);
            bytes.push(current_byte);
            current_byte = (val << bits_remaining) as u8;
            bit_pos = 6 - bits_remaining;
        }
    }
    // 处理剩余未填满字节的比特
    if bit_pos > 0 {
        bytes.push(current_byte);
    }

    Ok((remaining, bytes.as_slice()))
}

针对问题2:读取任意比特数的字段,避免字节对齐

其实nom的bits!宏本身支持任意比特数的连续读取,问题大概率出在用法上——要把所有比特读取操作放在同一个bits!宏内部,这样nom会维护连续的比特偏移量,不会强制字节对齐。

比如我们可以定义解析器读取两次3比特,总共消耗6比特:

use nom::bits::complete::take_bits;

// 读取3比特的解析器
fn take_3_bits(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> {
    take_bits(3usize)(input)
}

// 连续读取两次3比特的解析器
fn take_two_3_bits(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
    bits(|input| {
        let (input, first) = take_3_bits(input)?;
        let (input, second) = take_3_bits(input)?;
        Ok((input, (first, second)))
    })(input)
}

结合上面的payload解析器,我们可以完整解析AIS消息的字段,比如解析消息类型(6比特)和重复指示器(2比特):

// 解析AIS消息头部:消息类型(6比特)+ 重复指示器(2比特)
fn parse_ais_header(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
    bits(|input| {
        let (input, msg_type) = take_bits(6usize)(input)?;
        let (input, repeat_indicator) = take_bits(2usize)(input)?;
        Ok((input, (msg_type, repeat_indicator)))
    })(input)
}

// 完整的AIS消息解析链:ASCII payload → 比特流 → 字段解析
fn parse_ais_message(input: &str) -> IResult<&str, (u8, u8)> {
    let (remaining, payload_bytes) = parse_ais_payload(input)?;
    let (_, header) = parse_ais_header(payload_bytes)?;
    Ok((remaining, header))
}

这样整个流程都在nom的解析器链中完成,既避免了多余的中间数组,又能自由读取任意比特数的字段,不会出现强制字节对齐的问题。

内容的提问来源于stack exchange,提问作者squidpickles

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:45:10