Hellstorm
Hellstorm

Reputation: 670

How can I take N bits of byte in nom?

I am trying to write a HTTP2 parser with nom. I'm implementing the HPACK header compression, but having trouble understanding how to work with bit fields in nom.

For example, the Indexed Header Field Representation starts with the first bit as 1.

fn indexed_header_field_tag(i: &[u8]) -> IResult<&[u8], ()> {
    nom::bits::streaming::tag(1, 1)(i)
}

This gives me a compiler warning I don't really understand (To be honest, I'm having some problems with the types in nom):

error[E0308]: mismatched types
   --> src/parser.rs:179:41
    |
179 |         nom::bits::streaming::tag(1, 1)(i)
    |                                         ^ expected tuple, found `&[u8]`
    |
    = note:  expected tuple `(_, usize)`
            found reference `&[u8]`

Wwhat should I put here?

Another example is:

fn take_2_bits(input: &[u8]) -> IResult<&[u8], u64> {
    nom::bits::bits(nom::bits::streaming::take::<_, _, _, (_, _)>(2usize))(input)
}

Here, my problem is that the remaining bits of the first byte are discarded, even though I want to further work on them.

I guess I can do it manually with bitwise-ands, but doing it with nom would be nicer.

I've tried with the following approach, but this gives me many compiler warnings:

fn check_tag(input: &[u8]) -> IResult<&[u8], ()> {
    use nom::bits::{bits, bytes, complete::take_bits, complete::tag};
    let converted_bits = bits(take_bits(2usize))(2)?;
    let something = tag(0x80, 2)(converted_bits);
    nom::bits::bytes(something)
}

(Inspired from https://docs.rs/nom/5.1.2/nom/bits/fn.bytes.html).

It tells me, that there is no complete::take_bits (I guess only the documentation is a bit off there), but it also tells me:

368 |         let converted_bits = bits(take_bits(2usize))(2)?;
    |                                                      ^ the trait `nom::traits::Slice<std::ops::RangeFrom<usize>>` is not implemented for `{integer}`

and other errors, but which just result due to the first errors.

Upvotes: 4

Views: 2227

Answers (1)

Solomon Ucko
Solomon Ucko

Reputation: 6109

The bit-oriented interfaces (e.g. take) accept a tuple (I, usize), representing (input, bit_offset), so you need to use a function such as bits to convert the input from i to (i, 0), then convert the output back to bytes by ignoring any remaining bits in the current byte.

For the second question, see the comments on How can I combine nom parsers to get a more bit-oriented interface to the data? : use bits only when you need to switch between bits and bytes, and make bit-oriented functions use bit-oriented input.

Example code

use nom::{IResult, bits::{bits, complete::{take, tag}}};

fn take_2_bits(i: (&[u8], usize)) -> IResult<(&[u8], usize), u8> {
    take(2usize)(i)
}

fn check_tag(i: (&[u8], usize)) -> IResult<(&[u8], usize), u8> {
    tag(0x01, 1usize)(i)
}

fn do_everything_bits(i: (&[u8], usize)) -> IResult<(&[u8], usize), (u8, u8)> {
    let (i, a) = take_2_bits(i)?;
    let (i, b) = check_tag(i)?;
    Ok((i, (a, b)))
}

fn do_everything_bytes(i: &[u8]) -> IResult<&[u8], (u8, u8)> {
    bits(do_everything_bits)(i)
}

Upvotes: 2

Related Questions