Skip to content
ブログに戻る
チュートリアル

CRC-16 の変種:MODBUS・CCITT・XMODEM で結果が違う理由

同じバイト列なのに CRC-16 チェックサムの結果が四通り。poly・init・refin・refout・xorout の五つが MODBUS と CCITT-FALSE、XMODEM を分ける仕組みをオンラインで確認できる。

13 分で読める

CRC-16 の変種:MODBUS・CCITT・XMODEM で結果が違う理由

CRC-16 はアルゴリズムの名前ではなく、一族の名前だ。しかも一族の中身は互いに食い違う。同じバイト列を MODBUS に、CCITT-FALSE に、XMODEM に渡すと、互いに何の関係もない 16 ビットの数値が三つ返ってくる。

いま自分がどの変種を動かしているかを決めるのは、五つの定数だ。polyinitrefinrefoutxorout。このうち一つを変えるだけで出力は完全に別物になり、「惜しい」という形の警告は一切出ない。「自分の CRC が機器の値と合わない」という問いへの答えは、それで全部である。あなたと機器は違う変種を動かしていて、どちらの側もそれを書き残していない。

各パラメータが何をしているのか、手元にあるのがどの変種かをどう特定するのか、Modbus RTU がワイヤ上に置くバイト順はどうなっているのか。最後に、回線ノイズを止める CRC がなぜ攻撃者を止められないのかを見る。

このガイドに出てくる値はすべて、macOS 上の Python 3.14.7 で動かしたパラメータ化 CRC モデルの出力である。突き合わせた先は四つ。標準ライブラリの zlib.crc32、同じく binascii.crc_hqx、RevEng CRC カタログが公開する check 値、そして各パラメータセットについてのベンダー資料。

1. 同じフレーム、四通りの CRC-16

実際の Modbus RTU リクエストを見てみよう。スレーブ 01、ファンクションコード 03(保持レジスタ読み出し)、開始アドレス 0x0000、数量 0x000A

01 03 00 00 00 0A

同じ 6 バイトを四つの変種に通すと、答えは四つに割れる:

変種結果ワイヤ上の並び(下位バイト先行)
CRC-16/MODBUS0xCDC5C5 CD
CRC-16/IBM-3740(CCITT-FALSE)0x042828 04
CRC-16/XMODEM0x0A3838 0A
CRC-16/ARC0xD6C5C5 D6

この四つの値をつなぐものは何もない。ニブルのパターンが共通しているわけでも、一定値だけずれているわけでもない。うち二つはワイヤ上の先頭バイトがたまたま一致しているが、そこから片方をもう片方に変える並べ替えの小技が出てくるわけではない。

実務上はこうなる。データシートに書かれた「CRC-16」という文字列が保証しているのは、出力幅が 16 ビットだということだけだ。2 進数を表示する機器と突き合わせながら別の基数で結果を眺めたいなら、進数変換ツールで 16 ビット値を 16 進と 2 進の間で行き来させられる。桁埋めはツール側が合わせる。

2. 五つのパラメータがすべてを決める

どの変種の下にも同じ機械がある。1 ビットずつデータを飲み込むシフトレジスタがあり、最上位から 1 がこぼれ落ちるたびに定数を XOR する。パラメータが変えるのは、レジスタに何が入るか、ビットがどちら向きに進むか、そして出口で何が起きるかだ。

エンジン全体は Python 14 行に収まる。1 ビットずつ処理する素朴な実装で、速度は出ないが、この記事に出てくる値はすべてこれで再現できる:

def crc(data, width, poly, init, refin, refout, xorout):
    top = 1 << (width - 1)
    mask = (1 << width) - 1
    reg = init
    for byte in data:
        if refin:
            byte = int(f"{byte:08b}"[::-1], 2)
        reg ^= byte << (width - 8)
        for _ in range(8):
            reg = ((reg << 1) ^ poly) if reg & top else (reg << 1)
            reg &= mask
    if refout:
        reg = int(f"{reg:0{width}b}"[::-1], 2)
    return reg ^ xorout

このエンジンの出力は Python 標準ライブラリと突き合わせられる。zlib.crc32binascii.crc_hqx がそれぞれ独立に答えを出し、三つとも一致する。

zlib.crc32(b"123456789")               = 0xCBF43926   engine above = 0xCBF43926   MATCH
binascii.crc_hqx(b"123456789", 0x0000) = 0x31C3       CRC-16/XMODEM = 0x31C3      MATCH
binascii.crc_hqx(b"123456789", 0xFFFF) = 0x29B1       CCITT-FALSE   = 0x29B1      MATCH

poly0x80050x1021 の二大陣営

多項式は、レジスタに XOR で戻される定数だ。最上位ビットを暗黙とする「ノーマル」形式で書かれるので、0x8005x^16 + x^15 + x^2 + 1 を、0x1021x^16 + x^12 + x^5 + 1 を意味する。シフトと XOR のループの正体は GF(2) 上の多項式の筆算による割り算だ。メッセージの各ビットが割り算の一手であり、多項式が除数、レジスタが現在の余りを保持する。

実際に出会う CRC-16 のほぼすべてが、このどちらかを使う。0x8005 は ARC、MODBUS、USB をカバーする。0x1021 は CCITT まわりの混乱一式に加えて、XMODEM、KERMIT、RFID 系の変種をカバーする。多項式だけでは変種を特定できない理由がこれだ。

init:レジスタの初期値

支配的なのは 0x00000xFFFF の二つ。この違いは見た目より重い。ゼロから始めると、ゼロバイトを食わせてもレジスタはゼロのままなので、00 00 0101 が同じ CRC になる。伝送の途中で先頭のゼロが増えたり減ったりしたメッセージが、検査を素通りしてしまう。0xFFFF から始めればこの死角は消える。Modbus も CCITT-FALSE も USB もこれを使うのはそのためだ。

refinrefout:バイト順ではなくビット順

refin は入力バイトの 8 ビットを、レジスタに入る前に反転する。refout は最後の XOR の前に、最終的なレジスタを反転する。データを LSB 先行で送り出すハードウェアはこの反転をタダで手に入れられるので、反転ありの変種はシリアル通信プロトコル出身のものが多い。

エンディアンと最も頻繁に混同されるのがこのパラメータ対だ。両者は別の階層にある別物で、6 節で切り分ける。

xorout:最後の XOR

refout の後にレジスタへ適用される最終ステップ。CRC-16 ではたいてい 0x00000xFFFF、CRC-32 一族では一律 0xFFFFFFFF だ。取り違えても他のパラメータの間違いとまったく同じ見え方をするので、いちばん安く間違えられるパラメータでもある。

width:8・16・32 ビット

幅が検出能力の天井を決める。幅 n の CRC は n ビット以内のバースト誤りをすべて捕まえ、ランダムな破損を見逃す確率はおよそ 2^-n になる。CRC-16 なら 65,536 分の 1、CRC-32 なら 42.9 億分の 1 だ。

CRC-16/XMODEM を出発点に、標準の検査入力 123456789 に対して、パラメータを一度に一つだけ変えていく:

baseline CRC-16/XMODEM                        = 0x31C3
init 0x0000 -> 0xFFFF   (becomes CCITT-FALSE) = 0x29B1
refin/refout -> true    (becomes KERMIT)      = 0x2189
xorout 0x0000 -> 0xFFFF                       = 0xCE3C
poly 0x1021 -> 0x8005                         = 0xFEE8

フラグを一つ倒しただけで、出力はまるきり別物になる。CRC に「近い」という概念はない。二つの結果は一致するかしないかのどちらかで、外れた値は入力がどれだけ離れていたかを何も語らない。不一致をにらんでいても原因の場所が絞れないのは、そのせいだ。内側のループがやっているのは XOR とシフトだけなので、演算子そのものについてはビット演算完全ガイドに譲る。

3.「CCITT」は名前として出来が悪い

2026 年 9 月時点で RevEng の CRC カタログには 16 ビット CRC の定義が 31 種類あり、そのうち三つが CCITT の名を背負っている。その三つは互いに一致しない。多項式も入力も同じなのに、下の表は無関係な四つの結果を並べることになる:

通称カタログ名checkinitrefin/refoutxorout
CCITT-FALSECRC-16/IBM-37400x29B10xFFFFfalse0x0000
XMODEMCRC-16/XMODEM0x31C30x0000false0x0000
「本物の」CCITTCRC-16/KERMIT0x21890x0000true0x0000
CRC-16/MCRF4XX0x6F910xFFFFtrue0x0000

経緯は短く、そして役に立たない。カタログは CRC-16/KERMIT に CRC-CCITT と CRC-16/CCITT-TRUE という別名を載せていて、こちらは反転ありだ。一方で init が 0xFFFF の反転なし実装も CCITT の名で広く出回った。カタログが「CCITT-FALSE」を CRC-16/IBM-3740 の別名として記録しているのはそのためである。XMODEM はその中間に位置する。多項式は同じ、反転なし、init はゼロ。

つまりデータシートに CCITT と書いてあったら、それは多項式が 0x1021 だと言っているだけで、他には何も言っていない。候補は四つ残ったままで、外れを引いたときの値は正解と同じくらいもっともらしく見える。

だから、名前ではなくパラメータを書く癖をつけたほうがいい。プロトコル仕様書に poly=0x1021, init=0xFFFF, refin=false, refout=false, xorout=0x0000 と一行書けば、曖昧さは永久に消える。「CRC-16/CCITT」と書いても消えない。

4. 手元の CRC-16 がどの変種か見分ける方法

カタログはこれを指紋で解決している。すべてのエントリが check 値を公開しているのだ。ASCII 9 バイト 123456789 に対する CRC である。目の前の実装にこの文字列を通し、出てきた値を表で引けばいい。

変種checkpolyinitrefinrefoutxorout
CRC-16/ARC(IBM/LHA)0xBB3D0x80050x0000truetrue0x0000
CRC-16/MODBUS0x4B370x80050xFFFFtruetrue0x0000
CRC-16/USB0xB4C80x80050xFFFFtruetrue0xFFFF
CRC-16/IBM-3740(CCITT-FALSE)0x29B10x10210xFFFFfalsefalse0x0000
CRC-16/XMODEM0x31C30x10210x0000falsefalse0x0000
CRC-16/KERMIT(本物の CCITT)0x21890x10210x0000truetrue0x0000
CRC-16/GENIBUS0xD64E0x10210xFFFFfalsefalse0xFFFF
CRC-16/MCRF4XX0x6F910x10210xFFFFtruetrue0x0000
CRC-32/ISO-HDLC(zip、PNG、zlib)0xCBF439260x04C11DB70xFFFFFFFFtruetrue0xFFFFFFFF
CRC-32/BZIP20xFC8919180x04C11DB70xFFFFFFFFfalsefalse0xFFFFFFFF
CRC-32C(Castagnoli、iSCSI)0xE30692830x1EDC6F410xFFFFFFFFtruetrue0xFFFFFFFF
CRC-8/SMBUS0xF40x070x00falsefalse0x00
CRC-8/MAXIM-DOW(1-Wire)0xA10x310x00truetrue0x00

この照合を意外なほど多く台無しにする細部が一つある。123456789 とは 9 バイトの 31 32 33 34 35 36 37 38 39 のことであって、数値の 123456789 でもなければ NUL 終端付きの文字列でもない。使っている言語が関数にワイド文字列を渡したり終端文字を付け足したりしていれば、違う入力をハッシュしていることになり、どの行にも当たらない。非 ASCII のペイロードにはもう一つ罠がある。同じ文字列でも UTF-8 と Windows-1252 では別のバイト列になり、したがって CRC も別になる。ASCII コード表と変換ツールを開けば各文字のバイト値がわかるので、10 秒でこの可能性を潰せる。

check 値が表のどの行とも一致しないなら、独自多項式を疑う前に、ありふれた原因を三つ潰しておく。

  1. バイト順:ワイヤ上の結果を前後逆に読んでいる可能性がある。
  2. フィールドの範囲:送信側がアドレスバイト、長さバイト、あるいは CRC フィールド自体を計算に含めているか、含めていないか。
  3. ベンダー独自の init0x0000 でも 0xFFFF でもない値。独自の電力量計プロトコルには実在し、たいてい脚注に埋もれている。

パラメータを総当たりする

ベンダーが教えてくれず、ファームウェアも読めないなら、代わりに一つだけ頼めばいい。キャプチャしたフレームと、その機器がそれに対して出した CRC のペアだ。あとは列挙するだけである。多項式 2 通り、init 2 通り、refinrefout が独立に、xorout 2 通りで 32 通り。何ということもない数だ:

target = 0x4B37                    # 相手の実装が返してきた値
for poly in (0x8005, 0x1021):
    for init in (0x0000, 0xFFFF):
        for refin in (False, True):
            for refout in (False, True):
                for xorout in (0x0000, 0xFFFF):
                    if crc(b"123456789", 16, poly, init, refin, refout, xorout) == target:
                        print(f"poly=0x{poly:04X} init=0x{init:04X} "
                              f"refin={refin} refout={refout} xorout=0x{xorout:04X}")
poly=0x8005 init=0xFFFF refin=True refout=True xorout=0x0000

ヒットは一つ、CRC-16/MODBUS だ。123456789 の代わりに実フレームを食わせれば、同じ 32 通りの走査が、正解の CRC がわかっているどんなメッセージにも使える。標本一つで複数の組み合わせが生き残ったら、二つ目のフレームで走らせて結果の積を取る。

5. 実務での Modbus RTU

Modbus RTU が使うのは CRC-16/MODBUS だ。多項式 0x8005、init 0xFFFF、入出力とも反転あり、最終 XOR なし。先ほどの 6 バイトのリクエストなら CRC は 0xCDC5 で、完成したフレームはこうなる:

01 03 00 00 00 0A C5 CD

ワイヤ上の並びは、値を書くときの並びの逆

値は 0xCDC5。フレームに追加されるバイトは C5 CD。Modbus は CRC の下位バイトを先に送ると定めていて、これは 16 進数を読むときの順序とは逆であり、毎回必ず誰かが引っかかる。同じフレームの中の他のマルチバイトフィールドは、さっきのレジスタ数 00 0A も含めてすべてビッグエンディアンだ。CRC だけが例外になっている。

ここから便利な帰結が一つ出てくる。CRC バイトまで含めたフレーム全体に対して CRC-16/MODBUS を計算すると、結果はこうなる:

0x0000

これが受信側の検証ルーチンのすべてだ。末尾 2 バイトを切り離し、バイトを入れ替え、ローカルで計算した値と比べる必要はない。届いたもの全部に CRC を通して、ゼロかどうかを見るだけでいい。手順が減れば、バイトを逆順にしくじる場所も減る。

Modbus のドキュメントに 0xA001 が出てくる理由

Modbus の実装をほぼどれでも開けば、ソース中の定数は 0x8005 ではなく 0xA001 になっている。どちらも正しい。0xA0010x8005 の 16 ビットを反転したもので、アルゴリズムの反転形に属している。反転形ではレジスタが左ではなく右にシフトし、入力バイトごとの反転が不要になる。二つの実装の出力は同一で、違うのは内部だけだ。コード中に 0xA001 を見つけたら、それは別の多項式ではなく反転形の CRC-16 を見ている、という確かな印になる。

キャプチャを読む人向けに余談を一つ。Modbus ASCII は別のフレーミングで、各バイトを 16 進 2 文字として運び、先頭に 3A:)、末尾に 0D 0A を置く。しかも CRC ではなく LRC を使う。01 03 があるはずの場所にトレースが 3A 30 31 30 33 を示していたら、それは ASCII モードであって、どの CRC 変種も永遠に一致しない。ASCII コード表を使えば、そのバイト列をそのまま文字に戻せる。

同じアルゴリズムを C で書くと

Modbus のファームウェアは上の Python モデルをまず使わない。反射形式をそのまま使い、右シフトして 0xA001 と XOR する。

uint16_t crc16_modbus(const uint8_t *buf, size_t len) {
    uint16_t crc = 0xFFFF;
    for (size_t i = 0; i < len; i++) {
        crc ^= buf[i];
        for (int b = 0; b < 8; b++)
            crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
    }
    return crc;
}

フレーム 01 03 00 00 00 0A を渡すと 0xCDC5 が返り、Python エンジンの値と一致する。check 文字列 1234567890x4B37 になる。どちらも Apple clang 21 で実行し、第 4 節の表とビット単位で一致した。

6. refin/refout はエンディアンではない

この二つは別の階層に住んでいて、その混同は高くつく。Modbus には両方が同時に含まれているからだ。

エンディアンが扱うのはマルチバイト値の中でのバイトの並びだ。0xCDC5CD C5 として格納・送信するのか、C5 CD とするのか。バイトの中身は一つも動かない。その階層の話はビッグエンディアンとリトルエンディアンが丸ごと扱っているので、ここでは繰り返さない。

反転が扱うのは1 バイトの中でのビットの並びだ。refin はレジスタが見る前に各バイトの 8 ビットを反転する。ビット 0 がビット 7 になる。メッセージの中でのそのバイトの位置は変わらない。エンディアンからはこれが見えず、反転からはエンディアンが見えない。

Modbus のフレームでは、この二つが独立に同時に起きている:

  • アルゴリズムの内側では refinrefout が true なので、バイトの中でビットが反転する。
  • ワイヤ上では、完成した CRC が下位バイト先行で追加される。これはプロトコルが決めたバイト順の話だ。

バイト順の問題を「直す」ために反転を切れば、まったく別の変種ができあがる。反転の食い違いを埋め合わせようとフレーム末尾のバイトを入れ替えれば、新しい種類の間違った値ができる。切り分けは一つずつ。まずアルゴリズム単体で check 値を合わせ、それからフレームの並びに取りかかる。

7. CRC-32 と CRC-16、どちらを使うべきか

CRC-32 は CRC-16 と同じ問題を抱えている。ただ見えにくいだけだ。一つの変種があまりに支配的なせいで、他にも存在することを大半の開発者が知らないまま終わる。

変種checkpolyrefin/refout
CRC-32/ISO-HDLC0xCBF439260x04C11DB7true
CRC-32/BZIP20xFC8919180x04C11DB7false
CRC-32C(Castagnoli)0xE30692830x1EDC6F41true

ISO-HDLC は zlib.crc32 が計算するもので、スタックのウェブ寄りの層ならどこにでもいる。Zip はセントラルディレクトリにエントリごとに保存している。PNG のチャンクはどれも、チャンク種別とデータを対象にした CRC-32 で終わる。イーサネットのフレームチェックシーケンスも、サーバが送り出す全フレームの末尾で同じパラメータセットを使っている。BZIP2 は同じ多項式で反転を切ったもので、出てくる値は ISO-HDLC と何も共有しない。

CRC-16 も同じ界隈に顔を出す。Redis Cluster は CRC16(key) mod 16384 でキーを 16,384 個のスロットのどれかに割り当てる。同じハッシュタグを持つキーが同じノードに乗るのはそのためだ。

CRC-32C がハードウェアの籤を引き当てた理由

CRC-32C は別の多項式を使っていて、ストレージやネットワークのプロトコルが実際に送るような短いブロックに対して誤り検出能力が高い。それが iSCSI、ext4 のメタデータチェックサム、SCTP、Btrfs を勝ち取らせた。そこへ Intel がシリコンに載せた。SSE4.2 の crc32 命令は CRC-32C を直接計算するので、現代の x86 では実質タダの完全性チェックになる。互換性の制約がない新規コードで CRC を選ぶなら、いま選ぶべきはこれだ。

ただしこれらのどれも、ダウンロードを検証するためのハッシュではない。ファイルが無事に届いたかを確かめるチェックサムが欲しいなら MD5 ハッシュ生成ツールが定番だし、どのダイジェストを何に信頼すべきかは MD5 と SHA-256 の比較が扱っている。押さえるべき境界は単純だ。CRC が答えるのは「これは壊れたか」であり、暗号学的ハッシュが答えるのは「これは自分が期待している中身そのものか」である。

8. CRC は改ざんを止めない

意味が正反対で、CRC-32 が同一になる JSON メッセージを二つ挙げる:

message A = b'{"to":"alice","amount":100}  H\xf6\xc0E'
message B = b'{"to":"mallory","amount":999}\xb2\xb2\xc2\xc2'
CRC-32(A) = 0x12345678
CRC-32(B) = 0x12345678

同じ二つに対する SHA-256:

A -> a83b90f5e3f21dd32039e0e693a8b15864eda83e258c115deeee50a60c09ae23
B -> f9319ee0507d719c7260535dff33df721ddd9c0e1857690f3d8e02f92601b742

衝突そのものより、その作り方のほうが問題だ。ここでは何も総当たりしていない。末尾のバイトは代数的に解いて求めた。CRC は線形関数であり、メッセージに 4 バイト追加する操作は CRC-32 の出力への全単射になる。だから任意のメッセージと任意の目標値に対して、そこへ到達させる 4 バイトの接尾辞はちょうど一つ存在し、それを見つける作業はただの算術だ。

実演として、解いて求めた 4 バイト 46 C9 6E 0Bhello world に追加する:

CRC-32(b'hello world' + 46 C9 6E 0B) = 0xDEADBEEF   target 0xDEADBEEF   MATCH

つまり、ペイロードを改変できる攻撃者は、あなたの CRC も定数時間で辻褄合わせできる。何かを探索する必要すらない。メッセージの先頭に秘密値を置いても救われない。長さを変えない改変であれば、CRC に加えるべき補正は変化したビットだけで決まるので、秘密値を一切知らずに計算できてしまう。WEP を破ったのはこの形の攻撃だ。

CRC が守るのは伝送ノイズに対してだ。ノイズはランダムで、目的を持たない。敵対者はそのどちらでもないので、CRC の守備範囲から外れる。求めているものが完全性ではなく真正性なら、鍵付きの構成が要る。HMAC ジェネレーターは任意のペイロードと鍵からそれを生成するし、実際のエンドポイントに組み込むときにつまずく箇所は Webhook 署名検証が失敗する原因が順に説明している。

9. FAQ

CRC-16/CCITT と CRC-16/CCITT-FALSE は同じものか?

違う。CRC-16/CCITT-FALSE と CRC-16/CCITT は別の変種だ。CCITT-FALSE は CRC-16/IBM-3740 のことで、init 0xFFFF、反転なし、check 値は 0x29B1。ただ CCITT とだけ言ったときにたいてい指されるのは CRC-16/KERMIT のほうで、init 0x0000、反転あり、check は 0x2189。共通なのは多項式 0x1021 だけで、それ以外は何も一致しない。

オンライン計算機と自分の機器で結果が食い違うのはなぜか?

ほぼ確実にパラメータの違いだ。ツールがある変種をデフォルトにしていて、機器は別の変種を実装している。ASCII バイト 123456789 を両方に通し、二つの check 値を 4 節の表と突き合わせれば、食い違っているパラメータはたいてい 1 分もかからず自分から名乗り出る。

Modbus が CRC の下位バイトを先に置くのはなぜか?

仕様がそう定めているからで、フレーム中でそう振る舞うのはこのフィールドだけだ。レジスタアドレスや個数はビッグエンディアン、末尾の CRC はそうではない。自分でバイトを並べ替えるより、その 2 バイトまで含めたフレーム全体に CRC-16/MODBUS を計算して 0x0000 になるかを見るほうがいい。

refinrefout は正確には何を反転するのか?

バイトの中のビットであって、メッセージの中のバイトではない。refin は処理前に各入力バイトの 8 ビットを反転し、refout は最後の XOR の前に最終レジスタを反転する。どちらもエンディアンとは独立している。エンディアンが決めるのは、マルチバイト値がワイヤ上でどう並ぶかだ。

CRC-16 と CRC-32 のどちらを使うべきか?

CRC-16 がランダムな破損を見逃すのはおよそ 65,536 回に 1 回、CRC-32 は 42.9 億回に 1 回。数十バイト程度の短いシリアルフレームなら CRC-16 で十分だし、そもそもプロトコル側で指定されていることが多い。ファイルやネットワークフレーム、数キロバイトを超えるものには CRC-32 を使う。

CRC を API の署名に使えるか?

使えない。CRC は API 署名の役目を果たせない。線形なので、ペイロードを書き換えた者は一致する値を計算し直せるし、選んだ 4 バイトを追加すれば、指定したどんな CRC-32 の目標値にも到達できる。署名には秘密鍵と非線形な構成が要る。HMAC を SHA-256 と組み合わせて使うこと。

CRC 値から元のデータを復元できるか?

できない。CRC 値から元のデータは復元できない。CRC-16 はどんな入力も 16 ビットに圧縮するので、同じ値を共有するメッセージは無限に存在する。ただし逆方向は攻撃者にとって依然として有用だ。目標値さえ与えられれば、それを生むメッセージを構成できる。CRC が何も認証しないのは、まさにこのためである。

ベンダーの資料に「CRC-16」としか書かれていない。パラメータをどう突き止めるか?

キャプチャしたフレームを一つと、その機器がそれに対して計算した CRC を一緒にもらい、そのペアに対して 4 節の 32 通り走査を回す。パラメータセットが複数生き残ったら、二つ目のフレームで繰り返して積を取る。標本が二つあれば、ほぼ確実に決着する。

タグ: crc checksum modbus embedded data-integrity