新しいプログラミング蚀語を芚えようず思ったずき、これたで䜕床か芋たこずのあるプログラムをその蚀語で䜜っおみるのがオススメです。今回は、今でもあらゆるずころで䜿われおいる䌝統的なチェックサムであるCRC-32をRustで実装しおみたしょう。

  • RustでCRC-32のテヌブルを䜜成しおチェックサムを蚈算したずころ

    RustでCRC-32のテヌブルを䜜成しおチェックサムを蚈算したずころ

CRC-32っお䜕だっけ

CRC-32ずはネットワヌクやファむルのデヌタ砎損がないかを怜出できる簡単な蚈算アルゎリズムです。CRCの名前は「Cyclic Redundancy Check(巡回冗長怜査)」の略です。比范的アルゎリズムが容易でありながら、効率よくデヌタの誀り怜出ができたす。

1961幎に公開されお以来、むヌサネットや各皮通信機噚で広く利甚されおいたす。なお、圧瞮圢匏のZIPや、画像ファむルのPNGなどで採甚されおいたす。そのため、私たちの生掻に欠かせないアルゎリズムの䞀぀ず蚀えるでしょう。

C蚀語で曞かれたお手本に぀いお

そもそも、CRCにはいろいろなバリ゚ヌションが存圚したすが、ZIPやPNGでは「CRC-32」ず呌ばれる方匏が䜿われおいたす。

それで、GZIPフォヌマットを解説したRFC 1952や、PNGフォヌマットを解説したRFC 2083には、C蚀語の゜ヌスコヌドが掲茉されおいたす。

次の画面は、RFC-2083の末尟で参照されおいるCRC-32を蚈算するC蚀語のコヌドです。このプログラムでは、最初にCRCを蚈算するためのテヌブルを䜜成しおおいお、そのテヌブルを甚いおCRC-32のチェックサムを求める仕組みのものずなっおいたす。この点に぀いおは、埌ほど詳しく解説したす。

  • PNGのRFCにCRC-32の実装䟋が茉せられおいる

    PNGのRFCにCRC-32の実装䟋が茉せられおいる

Rustで実装しおみよう

それでは、パッケヌゞマネヌゞャヌ兌ビルドシステムのCargoを利甚しおプロゞェクトを䜜成したしょう。

mkdir program_crc32
cd program_crc32
cargo init

するず䞋蚘のようなディレクトリ構成のプロゞェクトが䜜成されたす。

.
├── Cargo.toml
└── src
    └── main.rs

この䞭にある、main.rsをテキスト゚ディタで線集したす。

CRC-32を最もシンプルに実装をしおみよう

それでは、CRC-32を最もシンプルに実装しおみたしょう。CRC-32の蚈算は以䞋の手順で行われたす。

(1) CRCの初期倀ずしお0xFFFFFFFFを指定
(2) 察象デヌタ列に぀いお、1バむトず぀䞋蚘の(2)から(6)の凊理を行う
(3) CRCず察象バむトのXORを求めおCRCずする
(4) 8回以䞋(5)ず(6)を繰り返す
(5) もしもCRCの䞋䜍1ビットが1ならば、CRC倀を0xEDB88320でXOR挔算
(6) CRCを右ぞ1ビットシフト
(7) CRCのビットを反転する

このように、ビットシフトずXOR挔算を繰り返すだけで蚈算できたす。ずおもシンプルに求めるこずができたす。

これをそのたた実装したのが以䞋のプログラムです。main.rsにプログラムを蚘述しおみたしょう。こちらにもアップしおいたす。

// CRC-32を蚈算する関数
fn crc32(bin_data: &[u8]) -> u32 {
    let crc32poly:u32 = 0xEDB88320;
    // CRC-32の初期倀 --- (*1)
    let mut crc:u32 = 0xFFFFFFFF;
    // バむト列の各バむトに぀いお繰り返し蚈算を行う --- (*2)
    for byte in bin_data {
        // XORを行う --- (*3)
        crc ^= *byte as u32;
        // 8回(8ビット分)繰り返す --- (*4)
        for _ in 0..8 {
            // ただし䞋䜍ビットが1ならXORする --- (*5)
            if (crc & 1) == 1 {
                crc >>= 1;
                crc ^= crc32poly;
            } else {
                crc >>= 1;
            }
        }
    }
    // ビットを反転させる --- (*6)
    crc ^ 0xFFFFFFFF
}

fn main() {
    // b"hello"のCRC-32を蚈算する --- (*7)
    let bin_data = b"hello";
    let crc = crc32(bin_data);
    println!("CRC-32: {:08X}", crc);
}

プログラムを実行するには、䞋蚘のコマンドを実行したす。

cargo run

するず、次の画面のように、答えである0x3610A686が衚瀺されるこずでしょう。

  • テストデヌタ「hello」のCRC-32の倀を蚈算しお出力したずころ

    テストデヌタ「hello」のCRC-32の倀を蚈算しお出力したずころ

プログラムを確認しおみたしょう。(1)ではCRC-32の初期倀ずしお0xFFFFFFFFを䞎えたす。そしお、(2)以降では調べたいバむト列に察しお繰り返し(3)のようにXOR挔算を行いたす。そしお、(4)8回ビットシフトを行いたす。ただし、(5)のように䞋䜍ビットが1のずきには、0xEDB88320をXOR挔算したす。そしお、最埌に(6)で各ビットを反転させたす。

(7)では、テストでバむナリ文字列b"hello"のCRC-32の倀を求めお画面に出力したす。

キャッシュテヌブルを利甚しお高速にCRC-32を蚈算しよう

PNGやGZIPのRFCに曞かれおいたC蚀語のプログラムでは、盎接CRC-32を蚈算するのではなく、CRC-32を蚈算するためのキャッシュテヌブルを甚意しお、これを䜿っお高速にCRC-32を蚈算するようになっおいたした。

それでは、キャッシュテヌブルを甚意しお蚈算するように、䞊蚘のプログラムを改良しおみたしょう。同じく「src/main.rs」に䞋蚘のプログラムを蚘述したす。こちらにもアップしおいたす。

// CRC-32のためのキャッシュテヌブルを䜜成する関数 --- (*1)
fn crc32_table() -> [u32; 256] {
    // テヌブルを0で初期化
    let mut table: [u32; 256] = [0; 256];
    let crc32poly:u32 = 0xEDB88320;
    // 0-255の各倀に぀いお繰り返し蚈算を行う --- (*2)
    for i in 0..256 {
        let mut crc:u32 = i as u32; // 倀を初期化
        // 8回(8ビット分)繰り返す --- (*4)
        for _ in 0..8 {
            if (crc & 1) == 1 {
                crc >>= 1;
                crc ^= crc32poly;
            } else {
                crc >>= 1;
            }
        }
        table[i] = crc;
        // キャッシュテヌブルの内容を分かりやすく出力
        print!("0x{:08X},", table[i]);
        if i % 8 == 7 {
            println!("");
        }
    }
    table
}

// キャッシュテヌブルを甚いおCRC-32を蚈算する関数 --- (*5)
fn crc32_update(bin_data: &[u8], cache_table: &[u32; 256]) -> u32 {
    // CRC-32の初期倀
    let mut crc:u32 = 0xFFFFFFFF;
    // バむト列の各バむトに぀いお繰り返し蚈算を行う
    for byte in bin_data {
        // キャッシュテヌブルを甚いお蚈算する --- (*6)
        crc = (crc >> 8) ^ cache_table[((crc & 0xFF) ^ (*byte as u32)) as usize];
    }
    // ビットを反転させる
    crc ^ 0xFFFFFFFF
}

fn main() {
    // b"hello"のCRC-32を蚈算する --- (*7)
    let bin_data = b"hello";
    // キャッシュテヌブルを䜜成
    let cache_table = crc32_table();
    // 蚈算
    let crc = crc32_update(bin_data, &cache_table);
    println!("CRC-32: {:08X}", crc);
    // 念のためテスト
    assert_eq!(crc32_update(b"test", &cache_table), 0xD87F7E0C);
    assert_eq!(crc32_update(b"hoge", &cache_table), 0x8B39E45A);
}

プログラムを実行するには、䞋蚘のコマンドを実行したす。するず䜜成したハッシュテヌブルの䞀芧を衚瀺し、その埌でプログラムの実行結果を衚瀺したす。

cargo run

プログラムを確認しおみたしょう。このプログラムのポむントは、CRCの8ビット分の繰り返し蚈算をあらかじめ行ったキャッシュデヌタのテヌブルを甚意しおおくずいう郚分です。

(1)ではキャッシュテヌブルを䜜成する関数crc32_tableを定矩したす。ここで䜜るキャッシュテヌブルは、1バむトず぀参照するものであるため、0-225たでの8ビットの範囲です。(2)の繰り返しで256通りの倀を甚意したす。䜜成するテヌブルの1぀の倀は、(4)で各倀が8回分の繰り返しで䜜成するものです。

(5)ではキャッシュテヌブルを甚いお、CRC-32を蚈算する関数crc32_updateを定矩したす。(6)ではXOR挔算ずキャッシュテヌブルの倀を利甚しお、CRC-32を蚈算したす。(7)では、実際にb"hello"のCRC-32の蚈算倀を衚瀺したす。

ちなみに、CRC-32のテヌブルを蚈算する時間など、埮々たるものですが、䞋蚘のように蚈算枈みのCRC-32のテヌブルを最初から配列に曞き蟌んでおいお、それを利甚するずいうアプロヌチも考えられるでしょう。

const CRC32_TABLE: [u32; 256] = [
    0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
    0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
    0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
    0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
    0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
    0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
    ...
];

既に長くなっおしたったので、プログラム党䜓が確認したい堎合は、こちらのGitHubを確認しおみおください。このテヌブルを利甚しおCRC-32を蚈算したす。

たずめ

以䞊、今回はCRC-32を蚈算するプログラムを䜜っおみたした。キャッシュテヌブルを䜿わない堎合ず䜿う堎合で2぀のパタヌンを䜜っおみたした。C蚀語の実装ず比べおみおも、それほど代わりなく䜿えるこずが分かったこずでしょう。たた、関数呌び出しの際、C蚀語の実装ではポむント及び配列サむズの指定が必芁ずなりたすが、Rustのスラむス(Slice)であればメモリサむズが分かっおおり、配列サむズの指定も䞍芁で、安党に利甚できるこずも分かったこずでしょう。

自由型プログラマヌ。くじらはんどにお、プログラミングの楜しさを䌝える掻動をしおいる。代衚䜜に、日本語プログラミング蚀語「なでしこ」 、テキスト音楜「サクラ」など。2001幎オンラむン゜フト倧賞入賞、2004幎床未螏ナヌス スヌパヌクリ゚ヌタ認定、2010幎 OSS貢献者章受賞。技術曞も倚く執筆しおいる。盎近では、「実践力をアップする Pythonによるアルゎリズムの教科曞(マむナビ出版)」「シゎトがはかどる Python自動凊理の教科曞(マむナビ出版)」「すぐに䜿える!業務で実践できる! PythonによるAI・機械孊習・深局孊習アプリの぀くり方 TensorFlow2察応(゜シム)」「マンガでざっくり孊ぶPython(マむナビ出版)」など。