先日、GZipStream は破損したデータを検出しない (CRC32 パスでも)という質問に出くわしました。(これは「重複」である可能性が非常に高いですが、私はこの件について複雑な気持ちを持っています。タイトルにCRC32を追加したのも私でしたが、振り返ってみると、投稿の残りの部分では場違いに感じます)。自分で問題を少し調べてみたところ、この問題は最初に説明した他の質問よりもはるかに大きいと思います。
私は他の質問を拡張し、テスト コードを LINQPad で実行できるようにし、CRC32 (Cyclic Redundancy Check)の問題が実際に存在する場合は、それをより適切に紹介しようとしました。(コードはオリジナルに基づいたわずかな変更にすぎないため、テストのセットアップ/方法論に欠陥があるか、別の奇妙な癖/PEBCAK の両方が存在する可能性があります。)
破損したデータが常に(何らかの!) 例外を発生させるとは限らないため、結果は奇妙です。CRC32 チェックが実際に「機能している」ように見える場合があることに注意してください。index-out-of-range/bad header/bad footer の原因となる破損バイトは無視できます。これは、これらがCRC32 チェックの前に解凍を強制終了していると想定できるためです ( IndexOutOfRangeException がラップされる可能性が高い場合でも、これは完全に理解できます)。 InvalidDataException による) そのため、
CRC32 チェックの信頼性が本来よりも大幅に低いのはなぜですか? (下に「無効なデータ(例外なし)」とあるのはなぜですか?)
GZip フッターには CRC32 と 圧縮されていないデータの長さの両方が含まれているため、エラー検出率は「大幅に高く」なるはずです。 . (もちろん、破損したストリームをできるだけ早く検出するのは良いことですが、場合によっては、最終的なセーフガード チェックサムが完全に無視されるようです。)
形式はCorruptByteIndex+FailedDetections: Message
次のとおりです。
0+0: System.IO.InvalidDataException: GZip ヘッダーのマジック ナンバーが正しくありません。GZip ストリームを渡していることを確認してください。 1+0: System.IO.InvalidDataException: GZip ヘッダーのマジック ナンバーが正しくありません。GZip ストリームを渡していることを確認してください。 2+0: System.IO.InvalidDataException: GZip ヘッダーで指定された圧縮モードが不明です。 3+0: 良好なデータ (例外なし) 4+0: 良好なデータ (例外なし) 5+0: 良好なデータ (例外なし) 6+0: 良好なデータ (例外なし) 7+0: 良好なデータ (例外なし) 8+0: 良好なデータ (例外なし) 9+0: 良好なデータ (例外なし) 10+0: System.IO.InvalidDataException: 不明なブロック タイプ。ストリームが破損している可能性があります。 11+1: 無効なデータ (例外なし) 12+1: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 13+1: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 14+1: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 15+1: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 16+1: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 17+2: 無効なデータ (例外なし) 18+2: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 19+2: System.IndexOutOfRangeException:Index が配列の範囲外でした。 20+2: System.IndexOutOfRangeException:Index が配列の範囲外でした。 21+3: 無効なデータ (例外なし) 22+3: System.IndexOutOfRangeException:Index が配列の範囲外でした。 23+3: System.IndexOutOfRangeException:Index が配列の範囲外でした。 24+4: 無効なデータ (例外なし) 25+4: System.IndexOutOfRangeException:Index が配列の範囲外でした。 26+4: System.IndexOutOfRangeException:Index が配列の範囲外でした。 27+4: System.IndexOutOfRangeException:Index が配列の範囲外でした。 28+4: System.IndexOutOfRangeException:Index が配列の範囲外でした。 29+5: 無効なデータ (例外なし) 30+5: System.IndexOutOfRangeException:Index が配列の範囲外でした。 31+6: 無効なデータ (例外なし) 32+7: 無効なデータ (例外なし) 33+7: System.IndexOutOfRangeException:Index が配列の範囲外でした。 34+7: System.IndexOutOfRangeException:Index が配列の範囲外でした。 35+7: System.IndexOutOfRangeException:Index が配列の範囲外でした。 36+8: 無効なデータ (例外なし) 37+8: System.IndexOutOfRangeException:Index が配列の範囲外でした。 38+8: System.IndexOutOfRangeException:Index が配列の範囲外でした。 39+9: 無効なデータ (例外なし) 40+9: System.IndexOutOfRangeException:Index が配列の範囲外でした。 41+9: System.IndexOutOfRangeException:Index が配列の範囲外でした。 42+10: 無効なデータ (例外なし) 43+10: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 44+10: System.IndexOutOfRangeException:Index が配列の範囲外でした。 45+10: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 46+11: 無効なデータ (例外なし) 47+11: System.IndexOutOfRangeException:Index が配列の範囲外でした。 48+11: System.IndexOutOfRangeException:Index が配列の範囲外でした。 49+11: System.IndexOutOfRangeException:Index が配列の範囲外でした。 50+12: 無効なデータ (例外なし) 51+12: System.IndexOutOfRangeException:Index が配列の範囲外でした。 52+12: System.IndexOutOfRangeException:Index が配列の範囲外でした。 53+13: 無効なデータ (例外なし) 54+13: System.IndexOutOfRangeException:Index が配列の範囲外でした。 55+14: 無効なデータ (例外なし) 56+14: System.IndexOutOfRangeException:Index が配列の範囲外でした。 57+15: 無効なデータ (例外なし) 58+15: System.IndexOutOfRangeException:Index が配列の範囲外でした。 59+15: System.IndexOutOfRangeException:Index が配列の範囲外でした。 60+16: 無効なデータ (例外なし) 61+17: 無効なデータ (例外なし) 62+18: 無効なデータ (例外なし) 63+19: 無効なデータ (例外なし) 64+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 65+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 66+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 67+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 68+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 69+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 70+19: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 71+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 72+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 73+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 74+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 75+19: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 76+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 77+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 78+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 79+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 80+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 81+19: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 82+19: System.IndexOutOfRangeException:Index が配列の範囲外でした。 83+20: 無効なデータ (例外なし) 84+21: 無効なデータ (例外なし) 85+22: 無効なデータ (例外なし) 86+22: System.IndexOutOfRangeException:Index が配列の範囲外でした。 87+23: 無効なデータ (例外なし) 88+24: 無効なデータ (例外なし) 89+25: 無効なデータ (例外なし) 90+25: System.IndexOutOfRangeException:Index が配列の範囲外でした。 91+26: 無効なデータ (例外なし) 92+26: System.IO.InvalidDataException: デコード中に無効なデータが見つかりました。 93+26: System.IndexOutOfRangeException:Index が配列の範囲外でした。 94+27: 無効なデータ (例外なし) 95+27: System.IndexOutOfRangeException:Index が配列の範囲外でした。 96+27: System.IndexOutOfRangeException:Index が配列の範囲外でした。 97+28: 無効なデータ (例外なし) 98+28: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 99+28: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 100+29: 無効なデータ (例外なし) 101+30: 無効なデータ (例外なし) 102+31: 無効なデータ (例外なし) 103+32: 無効なデータ (例外なし) 104+32: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 105+33: 無効なデータ (例外なし) 106+34: 無効なデータ (例外なし) 107+35: 無効なデータ (例外なし) 108+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 109+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 110+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 111+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 112+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 113+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 114+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 115+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 116+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 117+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 118+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 119+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 120+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 121+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 122+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 123+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 124+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 125+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 126+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 127+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 128+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 129+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 130+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 131+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 132+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 133+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 134+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 135+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 136+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 137+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 138+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 139+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 140+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 141+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 142+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 143+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 144+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 145+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 146+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 147+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 148+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 149+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 150+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 151+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 152+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 153+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 154+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 155+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 156+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 157+35: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 158+36: 無効なデータ (例外なし) 159+36: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 160+36: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 161+37: 無効なデータ (例外なし) 162+38: 無効なデータ (例外なし) 163+39: 無効なデータ (例外なし) 164+40: 無効なデータ (例外なし) 165+41: 無効なデータ (例外なし) 166+41: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 167+41: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 168+41: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 169+41: System.IO.InvalidDataException: GZip フッターの CRC が、解凍されたデータから計算された CRC と一致しません。 170+41: System.IO.InvalidDataException: GZip フッターのストリーム サイズが実際のストリーム サイズと一致しません。 171+41: System.IO.InvalidDataException: GZip フッターのストリーム サイズが実際のストリーム サイズと一致しません。 172+41: System.IO.InvalidDataException: GZip フッターのストリーム サイズが実際のストリーム サイズと一致しません。 173+41: System.IO.InvalidDataException: GZip フッターのストリーム サイズが実際のストリーム サイズと一致しません。
LINQPad で実行可能なコピー アンド ペーストのテストを次に示します (.NET 3.5 および 4 の場合は、「C# ステートメントとして」モードを使用します)。
string sample = "This is a compression test of microsoft .net gzip compression method and decompression methods";
var encoding = new ASCIIEncoding();
var data = encoding.GetBytes(sample);
string sampleOut = null;
byte[] cmpData;
// Compress
using (var cmpStream = new MemoryStream())
{
using (var hgs = new System.IO.Compression.GZipStream(cmpStream, System.IO.Compression.CompressionMode.Compress))
{
hgs.Write(data, 0, data.Length);
}
cmpData = cmpStream.ToArray();
}
int corruptBytesNotDetected = 0;
// corrupt data byte by byte
for (var byteToCorrupt = 0; byteToCorrupt < cmpData.Length; byteToCorrupt++)
{
var corruptData = new List<byte>(cmpData).ToArray();
// corrupt the data
corruptData[byteToCorrupt]++;
using (var decomStream = new MemoryStream(corruptData))
{
using (var hgs = new System.IO.Compression.GZipStream(decomStream, System.IO.Compression.CompressionMode.Decompress))
{
using (var reader = new StreamReader(hgs))
{
string message;
try
{
sampleOut = reader.ReadToEnd();
// if we get here, the corrupt data was not detected by GZipStream
// ... okay so long as the correct data is extracted
if (!sample.SequenceEqual(sampleOut)) {
corruptBytesNotDetected++;
message = "Invalid data (No Exception)";
} else {
message = "Good data (No Exception)";
}
}
catch(Exception ex)
{
message = (ex.GetType() + ":" + ex.Message);
}
string.Format("{0}+{1}: {2}",
byteToCorrupt, corruptBytesNotDetected, message).Dump();
}
}
}
}
.NET 3.5の圧縮データを次に示します(GZipStream は小さなペイロードの "圧縮" が苦手なことで有名ですが、ストリームは技術的にまだ有効であるため、"修正されません" という問題です)。
1F 8B 08 00 00 00 00 00 04 00 ED BD 07 60 1C 49 96 25 26 2F 6D CA 7B 7F 4A F5 4A D7 E0 74 A1 08 80 60 13 24 D8 90 40 10 EC C1 88 CD E6 92 EC 1D 69 47 23 29 AB 2A 81 CA 65 56 65 5D 66 16 40 CC ED 9D BC F7 DE 7B EF BD F7 DE 7B EF BD F7 BA 3B 9D 4E 27 F7 DF FF 3F 5C 66 64 01 6C F6 CE 4A DA C9 9E 21 80 AA C8 1F 3F 7E 7C 1F 3F 22 DE CC 8B 26 A5 FF 65 E9 B4 5A AC EA BC 69 8A 6A 99 B6 79 D3 A6 D5 79 BA 28 A6 75 D5 54 E7 6D 3A 5E E6 6D 7A F1 83 62 15 B4 5B E4 ED BC 9A A5 D9 72 96 CE F2 FE 17 CD FF 03 5C 51 5E 27 5E 00 00 00
(笑)
1F 8B 08 00 00 00 00 00 04 00 EC BD 07 60 1C 49 96 25 26 2F 6D CA 7B 7F 4A F5 4A D7 E0 74 A1 08 80 60 13 24 D8 90 40 10 EC C1 88 CD E6 92 EC 1D 69 47 23 29 AB 2A 81 CA 65 56 65 5D 66 16 40 CC ED 9D BC F7 DE 7B EF BD F7 DE 7B EF BD F7 BA 3B 9D 4E 27 F7 DF FF 3F 5C 66 64 01 6C F6 CE 4A DA C9 9E 21 80 AA C8 1F 3F 7E 7C 1F 3F 22 DE CC 8B 26 A5 FF 65 E9 B4 5A AC EA BC 69 8A 6A 99 B6 79 D3 A6 D5 79 BA 28 A6 75 D5 54 E7 6D 3A 5E E6 6D 7A F1 83 62 15 B4 5B E4 ED BC 9A A5 D9 72 96 CE F2 FE 17 CD FF 13 00 00 FF FF 5C 51 5E 27 5E 00 00 00
その他の注意事項:
この場合、テストに微妙な欠陥がある可能性があります。GZipStream が「破損の検出に失敗した」(例外なし) 場合、StreamReader から読み取られたデータは「」(空の文字列) です。その場合、例外 (IOException など)を発生させReadToEnd()
ないのはなぜですか?
したがって、GZipStreamではなく、ここで「風変わりな」StreamReaderですか、それともGZipStreamの問題ですか(例外をスローしないため)? このユースケースを確実に処理する正しい方法はありますか? (現在の位置からの入力ストリームが実際に空である場合を考慮してください。)