1using System.Security.Cryptography;
6internal static class NdsDsiDigestBuilder
18 public static async ValueTask<NdsDsiDigestBuildResult> BuildAsync(
20 NdsImageBuildLayout layout,
21 NdsDsiDigestOptions options,
22 ReadOnlyMemory<byte> key,
23 CancellationToken cancellationToken)
25 if (layout.SectorHashTable.Length > Array.MaxLength || layout.BlockHashTable.Length > Array.MaxLength)
27 throw new IOException(
"DSi digest tables are too large to materialize safely.");
30 byte[] sectorHashes =
new byte[layout.SectorHashTable.Length];
32 hashOffset = await AppendRegionSectorsAsync(
39 cancellationToken).ConfigureAwait(
false);
40 hashOffset = await AppendRegionSectorsAsync(
47 cancellationToken).ConfigureAwait(
false);
48 if (hashOffset != sectorHashes.Length)
50 throw new InvalidDataException(
"DSi sector hash count disagrees with the planned table length.");
53 byte[] blockHashes = BuildBlockHashes(sectorHashes, options.BlockSectorCount, key.Span);
54#pragma warning disable CA5350
55 byte[] masterHmac = HMACSHA1.HashData(key.Span, blockHashes);
56#pragma warning restore CA5350
57 return new(sectorHashes, blockHashes, masterHmac);
69 private static async ValueTask<int> AppendRegionSectorsAsync(
73 ReadOnlyMemory<byte> key,
76 CancellationToken cancellationToken)
78 byte[] buffer =
new byte[Math.Min(sectorSize, 64 * 1024)];
79 long regionOffset = 0;
80 while (regionOffset < region.Length)
82#pragma warning disable CA5350
83 using IncrementalHash hash = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, key.Span);
84#pragma warning restore CA5350
85 long sectorRemaining = Math.Min(sectorSize, region.Length - regionOffset);
86 image.Position = checked(region.Offset + regionOffset);
87 while (sectorRemaining > 0)
89 int count = (int)Math.Min(buffer.Length, sectorRemaining);
90 await image.ReadExactlyAsync(buffer.AsMemory(0, count), cancellationToken).ConfigureAwait(
false);
91 hash.AppendData(buffer, 0, count);
92 sectorRemaining -= count;
93 regionOffset += count;
96 if (!hash.TryGetHashAndReset(destination.AsSpan(hashOffset, 20), out
int written) || written != 20)
98 throw new CryptographicException(
"The platform HMAC provider did not produce a 20-byte SHA-1 result.");
112 private static byte[] BuildBlockHashes(
113 ReadOnlySpan<byte> sectorHashes,
114 int blockSectorCount,
115 ReadOnlySpan<byte> key)
117 int blockInputSize = checked(blockSectorCount * 20);
118 int blockCount = checked((sectorHashes.Length + blockInputSize - 1) / blockInputSize);
119 byte[] blockHashes =
new byte[checked(blockCount * 20)];
120 for (
int index = 0; index < blockCount; index++)
122 int offset = checked(index * blockInputSize);
123 ReadOnlySpan<byte> input = sectorHashes.Slice(offset, Math.Min(blockInputSize, sectorHashes.Length - offset));
124#pragma warning disable CA5350
125 HMACSHA1.HashData(key, input, blockHashes.AsSpan(index * 20, 20));
126#pragma warning restore CA5350