zlib は DEFLATE とその一般的なラッパーを実装する C ライブラリです。ストリーミング API は z_stream を使うため、
アプリケーションは 1 回の呼び出しでメッセージ全体を消費・生成すると仮定せず、上限付きバッファーで入力と出力を処理できます。
ストリーム状態と初期化 #
アプリケーションは next_in/avail_in と next_out/avail_out を指定します。
呼び出し後、zlib はポインターを進めて利用可能数を減らし、total_in と total_out は累積カウンターを提供します。
アプリケーションは生成されたバイトを消費し、次の呼び出し前にバッファーを補充する必要があります。
その他の状態フィールドは zlib に任せます。
deflateInit() は zlib ラッパー付きストリームを生成します。deflateInit2() は負の windowBits で raw DEFLATE を、
windowBits + 16 で gzip を選択します。初期化前にアロケーターのフィールドを初期化し、正常に初期化されたすべてのストリームで、
エラーパスを含め deflateEnd() を呼び出します。圧縮器は deflateSetDictionary() で共有履歴を事前ロードできます。
zlib ラッパーは Adler-32 の辞書識別子を持ち、inflate() は Z_NEED_DICT を返せます。
raw DEFLATE では外側のプロトコルが辞書を識別する必要があります。gzip ラッパーはプリセット辞書の通知を定義しません。
正しい deflate ループ #
通常の入力には Z_NO_FLUSH を使います。頻繁なフラッシュは圧縮率を下げます。入力が残っているか出力が保留中の間は
deflate() を呼び続けます。終了するには入力の供給を止め、Z_STREAM_END が返るまで deflate(..., Z_FINISH) を呼び、
呼び出しの間に出力バッファーを拡張または排出します。次の上限付き入力の例では、input_len が zlib の uInt カウントに収まると仮定しています。
z_stream strm = {0};
if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) fail();
strm.next_in = input;
strm.avail_in = (uInt)input_len;
for (;;) {
strm.next_out = out;
strm.avail_out = sizeof out;
uInt input_before = strm.avail_in;
int flush = strm.avail_in == 0 ? Z_FINISH : Z_NO_FLUSH;
int rc = deflate(&strm, flush);
size_t produced = sizeof out - strm.avail_out;
write_bytes(out, produced);
if (rc == Z_STREAM_END) break;
if (rc != Z_OK) fail(rc);
if (input_before == strm.avail_in && produced == 0) fail_no_progress();
}
deflateEnd(&strm);実際のコードでは、次の Z_NO_FLUSH 呼び出しの前に next_in を補充し、両方のカーソルにまたがる進捗を追跡します。
Z_BUF_ERROR はバッファーが空または満杯で進捗できなかったことを意味する場合がありますが、それ自体は破損の証拠ではありません。
Z_SYNC_FLUSH はストリームを終了せずにデコーダーからデータを利用可能にし、Z_FINISH はストリームを終了します。
ネットワークまたはファイルリーダーでは、新たに受信した各チャンクを next_in/avail_in に割り当て、
そのカウントが 0 になるか出力バッファーが満杯になるまで呼び出し、出力を排出して続行します。
zlib がまだ avail_in に示している間はチャンクを上書きしないでください。カーソルが所有権の境界です。
上限付き inflate #
inflateInit() は zlib ラッパーを受け入れます。-15 の inflateInit2() は raw DEFLATE、
15 + 16 は gzip、15 + 32 は zlib または gzip の自動検出を有効にします。
出力領域を用意し、入力を消費するか、出力が満杯になるか、zlib が終端状態を返すまで inflate() を呼びます。
Z_NEED_DICT には、ラッパーが識別するプリセット辞書を inflateSetDictionary() で渡して処理します。
無関係な入力から辞書を推測しないでください。
Z_STREAM_END で avail_in が 0 でない場合、そのバイトは完了したストリームの後続データであり、黙って破棄してはいけません。
連結された gzip メンバーでは、残りの入力を記録し、インフレーターをリセットまたは再初期化して、
各メンバーのトレーラーを検証しながら処理します。最初のメンバーが成功しただけでは、後続バイトが有効な gzip データだとは証明できません。
raw DEFLATE にはラッパーチェックサムがなく、zlib は Adler-32、gzip は CRC-32 と ISIZE を検証します。
解凍によってデータが大幅に膨張する可能性があります。信頼できないデータを受け入れる前に、出力、割り当て量、
圧縮比または作業量、期限の制限を適用します。Z_DATA_ERROR、切り詰められた入力、不正な距離、出力上限違反を拒否します。
エラー前に生成されたバイトを、完全または認証済みのアプリケーションデータとして扱ってはいけません。
クリーンアップと高水準 API #
正常な初期化ごとに inflateEnd() または deflateEnd() を正確に 1 回呼びます。
これらの関数は内部割り当てを解放しますが、保留中の出力をフラッシュしません。
compress() と uncompress() は上限付きの一回限りのバッファーに便利であり、gzopen() と gzread() は gzip ファイルアクセスを提供します。
入力サイズが不明、出力に上限が必要、またはプロトコルが明示的なフラッシュ境界を必要とする場合はストリーミング API を使います。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)