acdream/tests/AcDream.Content.Tests/Crc32Tests.cs
Erik 84d1956d84 fix(pipeline): MP1b review - pak format/reader hardening
Adversarially-verified review findings 2,3,4,5,6,9:

(2) PakFormat.CurrentFormatVersion=1 constant; PakWriter stamps it
unconditionally (caller header templates can no longer produce a
version-0 pak — the default-0 footgun); PakReader refuses any other
version with a message naming found/expected. Tests: versions 0 and 2
both rejected; writer stamps even when the template omits the field.

(3) Reader robustness, all under the documented corrupt-=-missing
CONTRACT (external-file input — surface loudly once, then behave as
absent; never garbage, never throw from a lookup): (a) per-TOC-entry
bounds validation at open (offset/length outside [header, toc) or
past EOF -> logged once, entry missing, siblings unaffected); (b)
TryReadObjectMeshData catches deserialization failures (malformed
structure behind a matching CRC) -> logged once per entry, false; (c)
structurally unopenable files throw at OPEN with a clear message:
unfinalized header (tocOffset < header size — the placeholder-header
crash signature) and TOC-past-EOF truncation. Tests: corrupt TOC
entry, truncated pak, half-written pak, malformed-blob-behind-valid-
CRC (tamper + CRC recompute) — each verifying sibling blobs still read.

(4) Single-pass read: TryReadObjectMeshData now does ONE ReadArray out
of the map; CRC and deserialization run over the same buffer (was: a
separate VerifyCrc traversal + a second ReadArray + a ToArray copy
inside Serializer.Read). New Serializer.Read(byte[]) overload avoids
the defensive copy. Verdict set stays a ConcurrentDictionary (MP1c
calls this from 4 decode workers). True span-over-mmap zero-copy is
deferred to MP1c profiling per the class doc comment.

(5) PakWriter.Dispose restored to try/finally: Finish() does real I/O
and can throw (disk full) — the stream must ALWAYS close so no file
handle leaks mid-unwind. (Reverts the a5926ebc simplification, which
was wrong about this.) Test: dispose after an AddBlob exception leaves
the file deletable.

(6) CRC-32 known-answer vectors: "123456789" -> 0xCBF43926, empty ->
0x00000000, single zero byte -> 0xD202EF8D. The suite was previously
blind to a self-consistent-but-wrong CRC.

(9) Equality comparator floats switched to bit-equality
(SingleToInt32Bits/DoubleToInt64Bits, incl. Vector2/3 + Matrix4x4
components): for byte-identity round-trip purposes `==` was both too
strict (NaN==NaN false — a surviving NaN payload would wrongly FAIL)
and too lax (-0.0==+0.0 true — a sign-bit flip would wrongly PASS).

Content.Tests: 54/54 green (was 43).
2026-07-05 22:08:59 +02:00

31 lines
1.1 KiB
C#

using System.Text;
using AcDream.Content.Pak;
namespace AcDream.Content.Tests;
/// <summary>
/// Known-answer vectors for the hand-rolled CRC-32 (IEEE 802.3 / zip / PNG
/// variant). Without these the suite is blind to a self-consistent-but-wrong
/// implementation: writer and reader would agree with each other while
/// producing values no external tool could reproduce (review finding 6).
/// </summary>
public class Crc32Tests {
[Fact]
public void KnownAnswer_123456789_IsCBF43926() {
// THE standard CRC-32 check value: CRC of the ASCII string
// "123456789" is 0xCBF43926 for the reflected 0xEDB88320 polynomial.
var input = Encoding.ASCII.GetBytes("123456789");
Assert.Equal(0xCBF43926u, Crc32.Compute(input));
}
[Fact]
public void KnownAnswer_EmptyInput_IsZero() {
Assert.Equal(0x00000000u, Crc32.Compute(ReadOnlySpan<byte>.Empty));
}
[Fact]
public void KnownAnswer_SingleZeroByte() {
// CRC-32 of a single 0x00 byte is 0xD202EF8D (standard vector).
Assert.Equal(0xD202EF8Du, Crc32.Compute(new byte[] { 0x00 }));
}
}