m13o.net

2020-12-02 Wed 23:00
Rustでバイナリを読む その2   AdventCalendar2020 Rust programming

前回作成したBinaryReaderはnew()で渡されたbufferに対して特定バイト数読む事しか機能としてありません. 大抵のバイナリファイルは特定のバイト数毎に意味付けがされています. 今回はよくある意味付けとして数値表現を読み出す機能を追加してみます.

まずは整数型の値として読み出す機能を追加します. Rustの整数型は8bitから128bitまで符号有無それぞれありますが, ひとまず32bit整数までをここでは地道に実装していきます.

まずはu8, i8についてのテストから.

#[test]
fn read_u8() {
    let buffer = vec![0xFE, 0xFF, 0x01, 0x80];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_u8().unwrap();
    assert_eq!(value, 254);
    assert_eq!(rest.len(), 3);
    assert_eq!(rest[0], 0xFF);
    assert_eq!(rest[1], 0x01);
    assert_eq!(rest[2], 0x80);
}

#[test]
fn read_i8() {
    let buffer = vec![0xFE, 0xFF, 0x01, 0x80];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_i8().unwrap();
    assert_eq!(value, -2);
    assert_eq!(rest.len(), 3);
    assert_eq!(rest[0], 0xFF);
    assert_eq!(rest[1], 0x01);
    assert_eq!(rest[2], 0x80);
}

既存のread()メソッドとインターフェースを合わせてResult<T, E>を戻し, TとEはread()メソッドに合わせて数値と残りのスライスとのタプルとStringにしました. Tは数値そのものでタプルにする必要はないかなとも思いつつ, インターフェースを統一する事をひとまず優先しています.

read_u8(), read_i8()メソッドの実装は以下のようになります.

fn read_u8(&mut self) -> Result<(u8, &'a [u8]), String> {
    match self.read(1) {
        Ok((value, rest)) => {
            Ok((value[0], rest))
        },
        Err(e) => Err(e)
    }
}

fn read_i8(&mut self) -> Result<(i8, &'a [u8]), String> {
    match self.read(1) {
        Ok((value, rest)) => {
            Ok((value[0] as i8, rest))
        },
        Err(e) => Err(e)
    }
}

内部でread()メソッドを読んでその戻り値を変換する形です. エラーが発生した場合はひとまずread()メソッドのエラーをそのまま返しています.

さて, 8bit整数の場合は深く考えなくても良いのですが, 8bitよりも大きい表現力を持つ整数型については, そのバイナリ表現において, ビッグエンディアンとリトルエンディアンの2種類があるため, 状況に応じて場合分けが必要となります. 今回はひとまずリトルエンディアンとして読み取るメソッドを作る事に注力しようと思います.

さて, u16, i16, u32, i32向けのテストですが, 利用方法は8bit整数向けを踏襲する形で良いのでそのようなテストを書きますが, 注意としては, 符号有りの時は符号bitの境界検査をする必要があるので, i16, i32には境界検査を入れないといけません.

#[test]
fn read_le_u16() {
    let buffer = vec![0x80, 0xFF, 0x01, 0xFE];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_u16().unwrap();
    assert_eq!(value, 65408);
    assert_eq!(rest.len(), 2);
    assert_eq!(rest[0], 0x01);
    assert_eq!(rest[1], 0xFE);
}

#[test]
fn read_le_i16_signed_max() {
    let buffer = vec![0xFF, 0x7F, 0x01, 0xFE];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_i16().unwrap();
    assert_eq!(value, 32767);
    assert_eq!(rest.len(), 2);
    assert_eq!(rest[0], 0x01);
    assert_eq!(rest[1], 0xFE);
}

#[test]
fn read_le_i16_signed_min() {
    let buffer = vec![0x00, 0x80, 0x01, 0xFE];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_i16().unwrap();
    assert_eq!(value, -32768);
    assert_eq!(rest.len(), 2);
    assert_eq!(rest[0], 0x01);
    assert_eq!(rest[1], 0xFE);
}

#[test]
fn read_le_i16_minus_one() {
    let buffer = vec![0xFF, 0xFF, 0x01, 0xFE];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_i16().unwrap();
    assert_eq!(value, -1);
    assert_eq!(rest.len(), 2);
    assert_eq!(rest[0], 0x01);
    assert_eq!(rest[1], 0xFE);
}

#[test]
fn read_le_u32() {
    let buffer = vec![0x80, 0x00, 0xF0, 0xFF, 0x42];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_u32().unwrap();
    assert_eq!(value, 4293918848);
    assert_eq!(rest.len(), 1);
    assert_eq!(rest[0], 0x42);
}

#[test]
fn read_le_i32_signed_max() {
    let buffer = vec![0xFF, 0xFF, 0xFF, 0x7F, 0x42];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_i32().unwrap();
    assert_eq!(value, 2147483647);
    assert_eq!(rest.len(), 1);
    assert_eq!(rest[0], 0x42);
}

#[test]
fn read_le_i32_signed_min() {
    let buffer = vec![0x00, 0x00, 0x00, 0x80, 0x42];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_i32().unwrap();
    assert_eq!(value, -2147483648);
    assert_eq!(rest.len(), 1);
    assert_eq!(rest[0], 0x42);
}

#[test]
fn read_le_i32_minus_one() {
    let buffer = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x42];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_i32().unwrap();
    assert_eq!(value, -1);
    assert_eq!(rest.len(), 1);
    assert_eq!(rest[0], 0x42);
}

後は各々ビットシフトを頑張って実装……と思いきや, Rustの整数型にはバイト列をエンディアンによって適切に変換してくれるメソッドがあるので, ありがたくそれを利用します. ポイントとしては, from_le_bytes()は配列を受け取るようになっており, u8のスライスのままでは渡す事ができないので, try_into()で型変換をします.

fn read_le_u16(&mut self) -> Result<(u16, &'a [u8]), String> {
    match self.read(2) {
        Ok((u16_bytes, rest)) => {
            Ok((u16::from_le_bytes(u16_bytes.try_into().unwrap()), rest))
        },
        Err(e) => Err(e)
    }
}

fn read_le_i16(&mut self) -> Result<(i16, &'a [u8]), String> {
    match self.read(2) {
        Ok((i16_bytes, rest)) => {
            Ok((i16::from_le_bytes(i16_bytes.try_into().unwrap()), rest))
        },
        Err(e) => Err(e)
    }
}

fn read_le_u32(&mut self) -> Result<(u32, &'a [u8]), String> {
    match self.read(4) {
        Ok((u32_bytes, rest)) => {
            Ok((u32::from_le_bytes(u32_bytes.try_into().unwrap()), rest))
        },
        Err(e) => Err(e)
    }
}

fn read_le_i32(&mut self) -> Result<(i32, &'a [u8]), String> {
    match self.read(4) {
        Ok((i32_bytes, rest)) => {
            Ok((i32::from_le_bytes(i32_bytes.try_into().unwrap()), rest))
        },
        Err(e) => Err(e)
    }
}

処で, 24bit幅の整数というRustでは用意されていないbit幅を利用したくなるケースが良くあります. これの変換に対応できていた方が良いので, 他の整数値同様にバイト列から読み取れるようにします.

#[test]
fn read_le_u24() {
    let buffer = vec![0x56, 0x34, 0x12, 0x80];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_u24().unwrap();
    assert_eq!(value, 1193046);
    assert_eq!(rest.len(), 1);
    assert_eq!(rest[0], 128);
}

#[test]
fn read_le_i24_signed_max() {
    let buffer = vec![0xFF, 0xFF, 0x7F, 0xFD];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_i24().unwrap();
    assert_eq!(value, 8388607);
    assert_eq!(rest.len(), 1);
    assert_eq!(rest[0], 253);
}

#[test]
fn read_le_i24_signed_min() {
    let buffer = vec![0x00, 0x00, 0x80, 0xFD];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_i24().unwrap();
    assert_eq!(value, -8388608);
    assert_eq!(rest.len(), 1);
    assert_eq!(rest[0], 253);
}

#[test]
fn read_le_i24_minus_one() {
    let buffer = vec![0xFF, 0xFF, 0xFF, 0xFD];
    let mut reader = BinaryReader::new(&buffer);
    let (value, rest) = reader.read_le_i24().unwrap();
    assert_eq!(value, -1);
    assert_eq!(rest.len(), 1);
    assert_eq!(rest[0], 253);
}

テストそのものは符号の有無にかかわらず, 24bit整数として読み取れていればOKという形です. まず考える事の少ない符号無24bit整数について実装します.

fn read_le_u24(&mut self) -> Result<(u32, &'a [u8]), String> {
    match self.read(3) {
        Ok((u24_bytes, rest)) => {
            let value = (u24_bytes[2] as u32) << 16 | (u24_bytes[1] as u32) << 8 | u24_bytes[0] as u32;
            Ok((value, rest))
        },
        Err(e) => Err(e),
    }
}

24bit整数といいながら, u32として処理した方が楽なので値の戻り値はu32としています. 後はリトルエンディアンとして扱いつつビットシフトで値を構築していく感じです.

次に, 符号有24bit整数については, 当然符号について考慮する必要があるため, 少し小細工を要します. ビット演算でi32の値を構築し, 読み取ったバイト列のMSBが0x80以上であればそれは負数であるとして, i32として構築した値から0x1000000(24bit幅+1)を引く処理を行います.

fn read_le_i24(&mut self) -> Result<(i32, &'a [u8]), String> {
    match self.read(3) {
        Ok((i24_bytes, rest)) => {
            let mut value = (i24_bytes[2] as i32) << 16 | (i24_bytes[1] as i32) << 8 | i24_bytes[0] as i32;
            if i24_bytes[2] >= 0x80 {
                value -= 0x1000000;
            }
            Ok((value, rest))
        },
        Err(e) => Err(e),
    }
}

これで境界テストもパスするようになりました.

64bitや128bit幅の整数の取得に対しても同様の処理を行えば良いので, 必要であればそのような処理を追加しようと思います.

長くなってきたので, 数値表現の内, 浮動小数については次回に回します.