From 6c9e6e5203455490ff198fef7a87c891233c0406 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 18 Apr 2022 18:01:38 -0700 Subject: [PATCH] Add HierarchicalIntegrityVerificationStorage --- src/LibHac/Crypto/HmacSha256.cs | 17 + src/LibHac/FsSrv/FileSystemServer.cs | 2 + src/LibHac/FsSystem/BitmapUtils.cs | 6 +- .../FsSystem/BlockCacheBufferedStorage.cs | 6 +- ...ierarchicalIntegrityVerificationStorage.cs | 817 ++++++++++++++++++ src/LibHac/Util/Optional.cs | 5 + .../LibHac.Tests/FsSystem/TypeLayoutTests.cs | 75 ++ 7 files changed, 922 insertions(+), 6 deletions(-) create mode 100644 src/LibHac/Crypto/HmacSha256.cs create mode 100644 src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs diff --git a/src/LibHac/Crypto/HmacSha256.cs b/src/LibHac/Crypto/HmacSha256.cs new file mode 100644 index 00000000..8fe9dd2a --- /dev/null +++ b/src/LibHac/Crypto/HmacSha256.cs @@ -0,0 +1,17 @@ +using System; +using System.Security.Cryptography; +using LibHac.Diag; + +namespace LibHac.Crypto; + +public static class HmacSha256 +{ + public const int HashSize = Sha256.DigestSize; + + public static void GenerateHmacSha256(Span outMac, ReadOnlySpan data, ReadOnlySpan key) + { + bool success = HMACSHA256.TryHashData(key, data, outMac, out int bytesWritten); + + Abort.DoAbortUnless(success && bytesWritten == HashSize); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/FileSystemServer.cs b/src/LibHac/FsSrv/FileSystemServer.cs index cf0de519..9667c075 100644 --- a/src/LibHac/FsSrv/FileSystemServer.cs +++ b/src/LibHac/FsSrv/FileSystemServer.cs @@ -44,6 +44,7 @@ internal struct FileSystemServerGlobals : IDisposable public LocationResolverSetGlobals LocationResolverSet; public PooledBufferGlobals PooledBuffer; public GameCardServiceGlobals GameCardService; + public HierarchicalIntegrityVerificationStorageGlobals HierarchicalIntegrityVerificationStorage; public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer) { @@ -55,6 +56,7 @@ internal struct FileSystemServerGlobals : IDisposable LocationResolverSet.Initialize(); PooledBuffer.Initialize(); GameCardService.Initialize(); + HierarchicalIntegrityVerificationStorage.Initialize(fsServer); } public void Dispose() diff --git a/src/LibHac/FsSystem/BitmapUtils.cs b/src/LibHac/FsSystem/BitmapUtils.cs index 0cdb4bfc..95ce63af 100644 --- a/src/LibHac/FsSystem/BitmapUtils.cs +++ b/src/LibHac/FsSystem/BitmapUtils.cs @@ -5,11 +5,11 @@ namespace LibHac.FsSystem; public static class BitmapUtils { // ReSharper disable once InconsistentNaming - public static uint ILog2(uint value) + public static int ILog2(uint value) { Assert.SdkRequiresGreater(value, 0u); - const uint intBitCount = 32; - return intBitCount - 1 - (uint)Util.BitUtil.CountLeadingZeros(value); + const int intBitCount = 32; + return intBitCount - 1 - Util.BitUtil.CountLeadingZeros(value); } } \ No newline at end of file diff --git a/src/LibHac/FsSystem/BlockCacheBufferedStorage.cs b/src/LibHac/FsSystem/BlockCacheBufferedStorage.cs index 1c339870..a572367e 100644 --- a/src/LibHac/FsSystem/BlockCacheBufferedStorage.cs +++ b/src/LibHac/FsSystem/BlockCacheBufferedStorage.cs @@ -59,8 +59,8 @@ public class BlockCacheBufferedStorage : IStorage private int _shiftBytesVerificationBlock; private int _flags; private int _bufferLevel; - private StorageType _storageType; private BlockCacheManager _cacheManager; + private bool _isWritable; public BlockCacheBufferedStorage() { @@ -93,7 +93,7 @@ public class BlockCacheBufferedStorage : IStorage public Result Initialize(IBufferManager bufferManager, SdkRecursiveMutex mutex, IStorage data, long dataSize, int sizeBytesVerificationBlock, int maxCacheEntries, bool useRealDataCache, sbyte bufferLevel, - bool useKeepBurstMode, StorageType storageType) + bool useKeepBurstMode, bool isWritable) { Assert.SdkNotNull(data); Assert.SdkNotNull(mutex); @@ -111,7 +111,7 @@ public class BlockCacheBufferedStorage : IStorage _lastResult = Result.Success; _flags = 0; _bufferLevel = bufferLevel; - _storageType = storageType; + _isWritable = isWritable; _shiftBytesVerificationBlock = (int)BitmapUtils.ILog2((uint)sizeBytesVerificationBlock); Assert.SdkEqual(1 << _shiftBytesVerificationBlock, _sizeBytesVerificationBlock); diff --git a/src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs b/src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs new file mode 100644 index 00000000..67270e15 --- /dev/null +++ b/src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs @@ -0,0 +1,817 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Common.FixedArrays; +using LibHac.Crypto; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.FsSrv; +using LibHac.Os; +using LibHac.Util; +using static LibHac.FsSystem.Constants; +using static LibHac.FsSystem.HierarchicalIntegrityVerificationStorage; +using static LibHac.Util.BitUtil; + +namespace LibHac.FsSystem; + +internal static class Constants +{ + public const int IntegrityMinLayerCount = 2; + public const int IntegrityMaxLayerCount = 7; +} + +internal struct HierarchicalIntegrityVerificationStorageGlobals +{ + public RandomDataGenerator GenerateRandom; + public Semaphore GlobalWriteSemaphore; + public Semaphore GlobalReadSemaphore; + + public void Initialize(FileSystemServer fsServer) + { + GlobalWriteSemaphore = new Semaphore(fsServer.Hos.Os, AccessCountMax, AccessCountMax); + GlobalReadSemaphore = new Semaphore(fsServer.Hos.Os, AccessCountMax, AccessCountMax); + } + + public void Dispose() + { + GlobalReadSemaphore.Dispose(); + } +} + +public class FileSystemBufferManagerSet +{ + public IBufferManager[] Buffers; + + public FileSystemBufferManagerSet() + { + Buffers = new IBufferManager[IntegrityMaxLayerCount]; + } +} + +public struct HierarchicalIntegrityVerificationLevelInformation +{ + public Fs.Int64 Offset; + public Fs.Int64 Size; + public int BlockOrder; + public uint Reserved; +} + +public struct HierarchicalIntegrityVerificationInformation +{ + public uint MaxLayers; + public Array6 Layers; + public HashSalt HashSalt; +} + +public struct HierarchicalIntegrityVerificationMetaInformation +{ + public uint Magic; + public uint Version; + public uint MasterHashSize; + public HierarchicalIntegrityVerificationInformation LevelHashInfo; + + public void Format() + { + var hashSalt = new Optional(); + Format(in hashSalt); + } + + public void Format(in Optional hashSalt) + { + Magic = IntegrityVerificationStorageMagic; + Version = IntegrityVerificationStorageVersion; + MasterHashSize = 0; + LevelHashInfo = default; + + if (hashSalt.HasValue) + { + LevelHashInfo.HashSalt = hashSalt.ValueRo; + } + } +} + +public struct HierarchicalIntegrityVerificationSizeSet +{ + public long ControlSize; + public long MasterHashSize; + public Array5 LayeredHashSizes; +} + +public class HierarchicalIntegrityVerificationStorageControlArea : IDisposable +{ + public struct InputParam + { + public Array6 LevelBlockSizes; + } + + public const int HashSize = Sha256.DigestSize; + + private ValueSubStorage _storage; + private HierarchicalIntegrityVerificationMetaInformation _meta; + + public HierarchicalIntegrityVerificationStorageControlArea() + { + _storage = new ValueSubStorage(); + } + + public void Dispose() + { + _storage.Dispose(); + } + + public static Result QuerySize(out HierarchicalIntegrityVerificationSizeSet outSizeSet, in InputParam inputParam, + int layerCount, long dataSize) + { + UnsafeHelpers.SkipParamInit(out outSizeSet); + + Assert.SdkRequires(layerCount >= IntegrityMinLayerCount && layerCount <= IntegrityMaxLayerCount); + + for (int level = 0; level < layerCount - 1; level++) + { + Assert.SdkRequires(inputParam.LevelBlockSizes[level] > 0 && IsPowerOfTwo(inputParam.LevelBlockSizes[level])); + } + + { + outSizeSet.ControlSize = Unsafe.SizeOf(); + + Span levelSize = stackalloc long[IntegrityMaxLayerCount]; + int level = layerCount - 1; + + levelSize[level] = Alignment.AlignUpPow2(dataSize, (uint)inputParam.LevelBlockSizes[level - 1]); + level--; + + for (; level > 0; level--) + { + // Calculate how much space is needed to store the hashes of the above level, rounding up to the next block size. + levelSize[level] = + Alignment.AlignUpPow2(levelSize[level + 1] / inputParam.LevelBlockSizes[level] * HashSize, + (uint)inputParam.LevelBlockSizes[level - 1]); + } + + // The size of the master hash does not get rounded up to the next block size. + levelSize[0] = levelSize[1] / inputParam.LevelBlockSizes[0] * HashSize; + outSizeSet.MasterHashSize = levelSize[0]; + + // Write the sizes of each level to the output struct. + for (level = 1; level < layerCount - 1; level++) + { + outSizeSet.LayeredHashSizes[level - 1] = levelSize[level]; + } + + return Result.Success; + } + } + + public static Result Format(in ValueSubStorage metaStorage, + in HierarchicalIntegrityVerificationMetaInformation metaInfo) + { + // Ensure the storage is large enough to hold the meta info. + Result rc = metaStorage.GetSize(out long metaSize); + if (rc.IsFailure()) return rc.Miss(); + + if (metaSize < Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + // Validate the meta magic and version. + if (metaInfo.Magic != IntegrityVerificationStorageMagic) + return ResultFs.IncorrectIntegrityVerificationMagicCode.Log(); + + if ((metaInfo.Version & IntegrityVerificationStorageVersionMask) != IntegrityVerificationStorageVersion) + return ResultFs.UnsupportedVersion.Log(); + + // Write the meta info to the storage. + rc = metaStorage.Write(0, SpanHelpers.AsReadOnlyByteSpan(in metaInfo)); + if (rc.IsFailure()) return rc.Miss(); + + rc = metaStorage.Flush(); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result Expand(in ValueSubStorage metaStorage, + in HierarchicalIntegrityVerificationMetaInformation newMeta) + { + // Ensure the storage is large enough to hold the meta info. + Result rc = metaStorage.GetSize(out long metaSize); + if (rc.IsFailure()) return rc.Miss(); + + if (metaSize < Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + // Validate both the previous and new metas. + HierarchicalIntegrityVerificationMetaInformation previousMeta = default; + rc = metaStorage.Read(0, SpanHelpers.AsByteSpan(ref previousMeta)); + if (rc.IsFailure()) return rc.Miss(); + + if (newMeta.Magic != IntegrityVerificationStorageMagic || newMeta.Magic != previousMeta.Magic) + return ResultFs.IncorrectIntegrityVerificationMagicCode.Log(); + + if (newMeta.Version != IntegrityVerificationStorageVersion || newMeta.Version != previousMeta.Version) + return ResultFs.UnsupportedVersion.Log(); + + // Write the new meta. + rc = metaStorage.Write(0, SpanHelpers.AsReadOnlyByteSpan(in newMeta)); + if (rc.IsFailure()) return rc.Miss(); + + rc = metaStorage.Flush(); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public uint GetMasterHashSize() + { + return _meta.MasterHashSize; + } + + public void GetLevelHashInfo(out HierarchicalIntegrityVerificationInformation outInfo) + { + outInfo = _meta.LevelHashInfo; + } + + public Result Initialize(in ValueSubStorage metaStorage) + { + // Ensure the storage is large enough to hold the meta info. + Result rc = metaStorage.GetSize(out long metaSize); + if (rc.IsFailure()) return rc.Miss(); + + if (metaSize < Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + // Set the storage and read the meta. + _storage.Set(in metaStorage); + rc = _storage.Read(0, SpanHelpers.AsByteSpan(ref _meta)); + if (rc.IsFailure()) return rc.Miss(); + + // Validate the meta magic and version. + if (_meta.Magic != IntegrityVerificationStorageMagic) + return ResultFs.IncorrectIntegrityVerificationMagicCode.Log(); + + if ((_meta.Version & IntegrityVerificationStorageVersionMask) != IntegrityVerificationStorageVersion) + return ResultFs.UnsupportedVersion.Log(); + + return Result.Success; + } + + public void FinalizeObject() + { + using var emptySubStorage = new ValueSubStorage(); + _storage.Set(in emptySubStorage); + } +} + +public class HierarchicalIntegrityVerificationStorage : IStorage +{ + [NonCopyableDisposable] + public struct HierarchicalStorageInformation : IDisposable + { + public enum Storage + { + MasterStorage = 0, + Layer1Storage = 1, + Layer2Storage = 2, + Layer3Storage = 3, + Layer4Storage = 4, + Layer5Storage = 5, + DataStorage = 6 + } + + private ValueSubStorage[] _storages; + + public HierarchicalStorageInformation(in HierarchicalStorageInformation other) + { + _storages = new ValueSubStorage[(int)Storage.DataStorage + 1]; + + for (int i = 0; i < _storages.Length; i++) + { + _storages[i] = new ValueSubStorage(in other._storages[i]); + } + } + + public HierarchicalStorageInformation() + { + _storages = new ValueSubStorage[(int)Storage.DataStorage + 1]; + } + + public void Dispose() + { + for (int i = 0; i < _storages.Length; i++) + { + _storages[i].Dispose(); + } + } + + public ref ValueSubStorage this[int index] + { + get + { + Assert.SdkRequiresInRange(index, (int)Storage.MasterStorage, (int)Storage.DataStorage + 1); + return ref _storages[index]; + } + } + + public void SetMasterHashStorage(in ValueSubStorage storage) => _storages[(int)Storage.MasterStorage].Set(in storage); + public void SetLayer1HashStorage(in ValueSubStorage storage) => _storages[(int)Storage.Layer1Storage].Set(in storage); + public void SetLayer2HashStorage(in ValueSubStorage storage) => _storages[(int)Storage.Layer2Storage].Set(in storage); + public void SetLayer3HashStorage(in ValueSubStorage storage) => _storages[(int)Storage.Layer3Storage].Set(in storage); + public void SetLayer4HashStorage(in ValueSubStorage storage) => _storages[(int)Storage.Layer4Storage].Set(in storage); + public void SetLayer5HashStorage(in ValueSubStorage storage) => _storages[(int)Storage.Layer5Storage].Set(in storage); + public void SetDataStorage(in ValueSubStorage storage) => _storages[(int)Storage.DataStorage].Set(in storage); + } + + internal const uint IntegrityVerificationStorageMagic = 0x43465649; // IVFC + internal const uint IntegrityVerificationStorageVersion = 0x00020000; + internal const uint IntegrityVerificationStorageVersionMask = 0xFFFF0000; + + internal const int HashSize = Sha256.DigestSize; + + internal const int AccessCountMax = 5; + internal TimeSpan AccessTimeout => TimeSpan.FromMilliSeconds(10); + + private const sbyte BaseBufferLevel = 0x10; + + private FileSystemBufferManagerSet _bufferManagers; + private SdkRecursiveMutex _mutex; + private IntegrityVerificationStorage[] _integrityStorages; + private BlockCacheBufferedStorage[] _bufferedStorages; + private Semaphore _readSemaphore; + private Semaphore _writeSemaphore; + private long _dataSize; + private int _layerCount; + + // LibHac addition + private FileSystemServer _fsServer; + + private static readonly byte[][] KeyArray = + { MasterKey.ToArray(), L1Key.ToArray(), L2Key.ToArray(), L3Key.ToArray(), L4Key.ToArray(), L5Key.ToArray() }; + + public HierarchicalIntegrityVerificationStorage(FileSystemServer fsServer) + { + _integrityStorages = new IntegrityVerificationStorage[IntegrityMaxLayerCount - 1]; + _bufferedStorages = new BlockCacheBufferedStorage[IntegrityMaxLayerCount - 1]; + + _dataSize = -1; + _fsServer = fsServer; + } + + public override void Dispose() + { + FinalizeObject(); + + if (_integrityStorages is not null) + { + foreach (IntegrityVerificationStorage storage in _integrityStorages) + { + storage.Dispose(); + } + } + + if (_bufferedStorages is not null) + { + foreach (BlockCacheBufferedStorage storage in _bufferedStorages) + { + storage.Dispose(); + } + } + + base.Dispose(); + } + + public FileSystemBufferManagerSet GetBuffers() + { + return _bufferManagers; + } + + public void GetParameters(out HierarchicalIntegrityVerificationStorageControlArea.InputParam outParam) + { + outParam = default; + + for (int i = 0; i < _layerCount - 2; i++) + { + outParam.LevelBlockSizes[i] = _integrityStorages[i].GetBlockSize(); + } + } + + public bool IsInitialized() + { + return _dataSize >= 0; + } + + public ValueSubStorage GetL1HashStorage() + { + return new ValueSubStorage(_bufferedStorages[_layerCount - 3], 0, + DivideUp(_dataSize, GetL1HashVerificationBlockSize())); + } + + public long GetL1HashVerificationBlockSize() + { + return _integrityStorages[_layerCount - 2].GetBlockSize(); + } + + public Result Initialize(in HierarchicalIntegrityVerificationInformation info, + ref HierarchicalStorageInformation storageInfo, FileSystemBufferManagerSet buffers, + IHash256GeneratorFactory hashGeneratorFactory, bool isHashSaltEnabled, SdkRecursiveMutex mutex, + int maxDataCacheEntries, int maxHashCacheEntries, sbyte bufferLevel, bool isWritable, bool allowClearedBlocks) + { + return Initialize(in info, ref storageInfo, buffers, hashGeneratorFactory, isHashSaltEnabled, mutex, null, null, + maxDataCacheEntries, maxHashCacheEntries, bufferLevel, isWritable, allowClearedBlocks); + } + + public Result Initialize(in HierarchicalIntegrityVerificationInformation info, + ref HierarchicalStorageInformation storageInfo, FileSystemBufferManagerSet buffers, + IHash256GeneratorFactory hashGeneratorFactory, bool isHashSaltEnabled, SdkRecursiveMutex mutex, + Semaphore readSemaphore, Semaphore writeSemaphore, int maxDataCacheEntries, int maxHashCacheEntries, + sbyte bufferLevel, bool isWritable, bool allowClearedBlocks) + { + // Validate preconditions. + Assert.SdkNotNull(buffers); + Assert.SdkAssert(info.MaxLayers >= IntegrityMinLayerCount && info.MaxLayers <= IntegrityMaxLayerCount); + + // Set member variables. + _layerCount = (int)info.MaxLayers; + _bufferManagers = buffers; + _mutex = mutex; + _readSemaphore = readSemaphore; + _writeSemaphore = writeSemaphore; + + { + // If hash salt is enabled, generate it. + var mac = new Optional(); + if (isHashSaltEnabled) + { + mac.Set(); + HmacSha256.GenerateHmacSha256(mac.Value.Hash, info.HashSalt.HashRo, KeyArray[0]); + } + + // Initialize the top level verification storage. + _integrityStorages[0] = new IntegrityVerificationStorage(); + _integrityStorages[0].Initialize(in storageInfo[(int)HierarchicalStorageInformation.Storage.MasterStorage], + in storageInfo[(int)HierarchicalStorageInformation.Storage.Layer1Storage], + 1 << info.Layers[0].BlockOrder, HashSize, _bufferManagers.Buffers[_layerCount - 2], + hashGeneratorFactory, in mac, false, isWritable, allowClearedBlocks); + } + + // Initialize the top level buffer storage. + _bufferedStorages[0] = new BlockCacheBufferedStorage(); + Result rc = _bufferedStorages[0].Initialize(_bufferManagers.Buffers[0], _mutex, _integrityStorages[0], + info.Layers[0].Size, 1 << info.Layers[0].BlockOrder, maxHashCacheEntries, false, BaseBufferLevel, false, + isWritable); + + if (!rc.IsFailure()) + { + int level; + + // Initialize the level storages. + for (level = 0; level < _layerCount - 3; level++) + { + // If hash salt is enabled, generate it. + var mac = new Optional(); + if (isHashSaltEnabled) + { + mac.Set(); + HmacSha256.GenerateHmacSha256(mac.Value.Hash, info.HashSalt.HashRo, KeyArray[level + 1]); + } + + // Initialize the verification storage. + using (var hashStorage = new ValueSubStorage(_bufferedStorages[level], 0, info.Layers[level].Size)) + { + _integrityStorages[level + 1] = new IntegrityVerificationStorage(); + _integrityStorages[level + 1].Initialize(in hashStorage, in storageInfo[level + 2], + 1 << info.Layers[level + 1].BlockOrder, 1 << info.Layers[level].BlockOrder, + _bufferManagers.Buffers[_layerCount - 2], hashGeneratorFactory, in mac, false, isWritable, + allowClearedBlocks); + } + + // Initialize the buffer storage. + _bufferedStorages[level + 1] = new BlockCacheBufferedStorage(); + rc = _bufferedStorages[level + 1].Initialize(_bufferManagers.Buffers[level + 1], _mutex, + _integrityStorages[level + 1], info.Layers[level + 1].Size, 1 << info.Layers[level + 1].BlockOrder, + maxHashCacheEntries, false, (sbyte)(BaseBufferLevel + (level + 1)), false, isWritable); + + if (rc.IsFailure()) + { + // Cleanup initialized storages if we failed. + _integrityStorages[level + 1].FinalizeObject(); + + for (; level > 0; level--) + { + _bufferedStorages[level].FinalizeObject(); + _integrityStorages[level].FinalizeObject(); + } + + break; + } + } + + if (!rc.IsFailure()) + { + // Initialize the final level storage. + // If hash salt is enabled, generate it. + var mac = new Optional(); + if (isHashSaltEnabled) + { + mac.Set(); + HmacSha256.GenerateHmacSha256(mac.Value.Hash, info.HashSalt.HashRo, KeyArray[level + 1]); + } + + // Initialize the verification storage. + using (var hashStorage = new ValueSubStorage(_bufferedStorages[level], 0, info.Layers[level].Size)) + { + _integrityStorages[level + 1] = new IntegrityVerificationStorage(); + _integrityStorages[level + 1].Initialize(in hashStorage, + in storageInfo[(int)HierarchicalStorageInformation.Storage.DataStorage], + 1 << info.Layers[level + 1].BlockOrder, 1 << info.Layers[level].BlockOrder, + _bufferManagers.Buffers[_layerCount - 2], hashGeneratorFactory, in mac, true, isWritable, + allowClearedBlocks); + } + + // Initialize the buffer storage. + _bufferedStorages[level + 1] = new BlockCacheBufferedStorage(); + rc = _bufferedStorages[level + 1].Initialize(_bufferManagers.Buffers[level + 1], _mutex, + _integrityStorages[level + 1], info.Layers[level + 1].Size, 1 << info.Layers[level + 1].BlockOrder, + maxDataCacheEntries, true, bufferLevel, true, isWritable); + + if (!rc.IsFailure()) + { + _dataSize = info.Layers[level + 1].Size; + return Result.Success; + } + + // Cleanup initialized storages if we failed. + _integrityStorages[level + 1].FinalizeObject(); + + for (; level > 0; level--) + { + _bufferedStorages[level].FinalizeObject(); + _integrityStorages[level].FinalizeObject(); + } + } + + _bufferedStorages[0].FinalizeObject(); + } + + _integrityStorages[0].FinalizeObject(); + + // Ensure we're uninitialized if we failed. + _dataSize = -1; + _bufferManagers = null; + _mutex = null; + + return rc; + } + + public void FinalizeObject() + { + if (_dataSize >= 0) + { + _dataSize = 0; + _bufferManagers = null; + _mutex = null; + + for (int level = _layerCount - 2; level >= 0; level--) + { + _bufferedStorages[level].FinalizeObject(); + _integrityStorages[level].FinalizeObject(); + } + + _dataSize = -1; + } + } + + public override Result GetSize(out long size) + { + Assert.SdkRequires(_dataSize >= 0); + + size = _dataSize; + return Result.Success; + } + + public override Result SetSize(long size) + { + return ResultFs.UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage.Log(); + } + + public override Result Read(long offset, Span destination) + { + Assert.SdkRequires(_dataSize >= 0); + + if (destination.Length == 0) + return Result.Success; + + ref HierarchicalIntegrityVerificationStorageGlobals g = + ref _fsServer.Globals.HierarchicalIntegrityVerificationStorage; + + _readSemaphore?.Acquire(); + + try + { + if (!g.GlobalReadSemaphore.TimedAcquire(AccessTimeout)) + { + for (int level = _layerCount - 2; level >= 0; level--) + { + Result rc = _bufferedStorages[level].Flush(); + if (rc.IsFailure()) return rc.Miss(); + } + + g.GlobalReadSemaphore.Acquire(); + } + + try + { + Result rc = _bufferedStorages[_layerCount - 2].Read(offset, destination); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + finally + { + g.GlobalReadSemaphore.Release(); + } + } + finally + { + _readSemaphore?.Release(); + } + } + + public override Result Write(long offset, ReadOnlySpan source) + { + Assert.SdkRequires(_dataSize >= 0); + + if (source.Length == 0) + return Result.Success; + + ref HierarchicalIntegrityVerificationStorageGlobals g = + ref _fsServer.Globals.HierarchicalIntegrityVerificationStorage; + + _writeSemaphore?.Acquire(); + + try + { + if (!g.GlobalWriteSemaphore.TimedAcquire(AccessTimeout)) + { + for (int level = _layerCount - 2; level >= 0; level--) + { + Result rc = _bufferedStorages[level].Flush(); + if (rc.IsFailure()) return rc.Miss(); + } + + g.GlobalWriteSemaphore.Acquire(); + } + + try + { + Result rc = _bufferedStorages[_layerCount - 2].Write(offset, source); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + finally + { + g.GlobalWriteSemaphore.Release(); + } + } + finally + { + _writeSemaphore?.Release(); + } + } + + public override Result Flush() + { + return Result.Success; + } + + public override Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + switch (operationId) + { + case OperationId.FillZero: + case OperationId.DestroySignature: + { + Result rc = _bufferedStorages[_layerCount - 2] + .OperateRange(outBuffer, operationId, offset, size, inBuffer); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + case OperationId.InvalidateCache: + case OperationId.QueryRange: + { + Result rc = _bufferedStorages[_layerCount - 2] + .OperateRange(outBuffer, operationId, offset, size, inBuffer); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + default: + return ResultFs.UnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage.Log(); + } + } + + public Result Commit() + { + for (int level = _layerCount - 2; level >= 0; level--) + { + Result rc = _bufferedStorages[level].Commit(); + if (rc.IsFailure()) return rc.Miss(); + } + + return Result.Success; + } + + public Result OnRollback() + { + for (int level = _layerCount - 2; level >= 0; level--) + { + Result rc = _bufferedStorages[level].OnRollback(); + if (rc.IsFailure()) return rc.Miss(); + } + + return Result.Success; + } + + public static void SetGenerateRandomFunction(FileSystemServer fsServer, RandomDataGenerator function) + { + fsServer.Globals.HierarchicalIntegrityVerificationStorage.GenerateRandom = function; + } + + public static sbyte GetDefaultDataCacheBufferLevel(int maxLayers) + { + return (sbyte)(BaseBufferLevel + maxLayers - 2); + } + + /// "HierarchicalIntegrityVerificationStorage::Master" + public static ReadOnlySpan MasterKey => // "HierarchicalIntegrityVerificationStorage::Master" + new[] + { + (byte)'H', (byte)'i', (byte)'e', (byte)'r', (byte)'a', (byte)'r', (byte)'c', (byte)'h', + (byte)'i', (byte)'c', (byte)'a', (byte)'l', (byte)'I', (byte)'n', (byte)'t', (byte)'e', + (byte)'g', (byte)'r', (byte)'i', (byte)'t', (byte)'y', (byte)'V', (byte)'e', (byte)'r', + (byte)'i', (byte)'f', (byte)'i', (byte)'c', (byte)'a', (byte)'t', (byte)'i', (byte)'o', + (byte)'n', (byte)'S', (byte)'t', (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', + (byte)':', (byte)':', (byte)'M', (byte)'a', (byte)'s', (byte)'t', (byte)'e', (byte)'r' + }; + + /// "HierarchicalIntegrityVerificationStorage::L1" + public static ReadOnlySpan L1Key => // "HierarchicalIntegrityVerificationStorage::L1" + new[] + { + (byte)'H', (byte)'i', (byte)'e', (byte)'r', (byte)'a', (byte)'r', (byte)'c', (byte)'h', + (byte)'i', (byte)'c', (byte)'a', (byte)'l', (byte)'I', (byte)'n', (byte)'t', (byte)'e', + (byte)'g', (byte)'r', (byte)'i', (byte)'t', (byte)'y', (byte)'V', (byte)'e', (byte)'r', + (byte)'i', (byte)'f', (byte)'i', (byte)'c', (byte)'a', (byte)'t', (byte)'i', (byte)'o', + (byte)'n', (byte)'S', (byte)'t', (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', + (byte)':', (byte)':', (byte)'L', (byte)'1' + }; + + /// "HierarchicalIntegrityVerificationStorage::L2" + public static ReadOnlySpan L2Key => // "HierarchicalIntegrityVerificationStorage::L2" + new[] + { + (byte)'H', (byte)'i', (byte)'e', (byte)'r', (byte)'a', (byte)'r', (byte)'c', (byte)'h', + (byte)'i', (byte)'c', (byte)'a', (byte)'l', (byte)'I', (byte)'n', (byte)'t', (byte)'e', + (byte)'g', (byte)'r', (byte)'i', (byte)'t', (byte)'y', (byte)'V', (byte)'e', (byte)'r', + (byte)'i', (byte)'f', (byte)'i', (byte)'c', (byte)'a', (byte)'t', (byte)'i', (byte)'o', + (byte)'n', (byte)'S', (byte)'t', (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', + (byte)':', (byte)':', (byte)'L', (byte)'2' + }; + + /// "HierarchicalIntegrityVerificationStorage::L3" + public static ReadOnlySpan L3Key => // "HierarchicalIntegrityVerificationStorage::L3" + new[] + { + (byte)'H', (byte)'i', (byte)'e', (byte)'r', (byte)'a', (byte)'r', (byte)'c', (byte)'h', + (byte)'i', (byte)'c', (byte)'a', (byte)'l', (byte)'I', (byte)'n', (byte)'t', (byte)'e', + (byte)'g', (byte)'r', (byte)'i', (byte)'t', (byte)'y', (byte)'V', (byte)'e', (byte)'r', + (byte)'i', (byte)'f', (byte)'i', (byte)'c', (byte)'a', (byte)'t', (byte)'i', (byte)'o', + (byte)'n', (byte)'S', (byte)'t', (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', + (byte)':', (byte)':', (byte)'L', (byte)'3' + }; + + /// "HierarchicalIntegrityVerificationStorage::L4" + public static ReadOnlySpan L4Key => // "HierarchicalIntegrityVerificationStorage::L4" + new[] + { + (byte)'H', (byte)'i', (byte)'e', (byte)'r', (byte)'a', (byte)'r', (byte)'c', (byte)'h', + (byte)'i', (byte)'c', (byte)'a', (byte)'l', (byte)'I', (byte)'n', (byte)'t', (byte)'e', + (byte)'g', (byte)'r', (byte)'i', (byte)'t', (byte)'y', (byte)'V', (byte)'e', (byte)'r', + (byte)'i', (byte)'f', (byte)'i', (byte)'c', (byte)'a', (byte)'t', (byte)'i', (byte)'o', + (byte)'n', (byte)'S', (byte)'t', (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', + (byte)':', (byte)':', (byte)'L', (byte)'4' + }; + + /// "HierarchicalIntegrityVerificationStorage::L5" + public static ReadOnlySpan L5Key => // "HierarchicalIntegrityVerificationStorage::L5" + new[] + { + (byte)'H', (byte)'i', (byte)'e', (byte)'r', (byte)'a', (byte)'r', (byte)'c', (byte)'h', + (byte)'i', (byte)'c', (byte)'a', (byte)'l', (byte)'I', (byte)'n', (byte)'t', (byte)'e', + (byte)'g', (byte)'r', (byte)'i', (byte)'t', (byte)'y', (byte)'V', (byte)'e', (byte)'r', + (byte)'i', (byte)'f', (byte)'i', (byte)'c', (byte)'a', (byte)'t', (byte)'i', (byte)'o', + (byte)'n', (byte)'S', (byte)'t', (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', + (byte)':', (byte)':', (byte)'L', (byte)'5' + }; +} \ No newline at end of file diff --git a/src/LibHac/Util/Optional.cs b/src/LibHac/Util/Optional.cs index e1549330..3dc6c74b 100644 --- a/src/LibHac/Util/Optional.cs +++ b/src/LibHac/Util/Optional.cs @@ -43,6 +43,11 @@ public struct Optional public static implicit operator Optional(in T value) => new Optional(in value); + public void Set() + { + _hasValue = true; + } + public void Set(T value) { _value = value; diff --git a/tests/LibHac.Tests/FsSystem/TypeLayoutTests.cs b/tests/LibHac.Tests/FsSystem/TypeLayoutTests.cs index 3e518524..c344430e 100644 --- a/tests/LibHac.Tests/FsSystem/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/FsSystem/TypeLayoutTests.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using LibHac.FsSystem; using Xunit; using static LibHac.Tests.Common.Layout; @@ -279,4 +280,78 @@ public class TypeLayoutTests Assert.Equal(0x9, GetOffset(in s, in s.Reserved)); Assert.Equal(0xC, GetOffset(in s, in s.Generation)); } + + [Fact] + public static void HierarchicalIntegrityVerificationLevelInformation_Layout() + { + HierarchicalIntegrityVerificationLevelInformation s = default; + + Assert.Equal(0x18, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Offset)); + Assert.Equal(0x08, GetOffset(in s, in s.Size)); + Assert.Equal(0x10, GetOffset(in s, in s.BlockOrder)); + Assert.Equal(0x14, GetOffset(in s, in s.Reserved)); + } + + [StructLayout(LayoutKind.Sequential)] + private struct HierarchicalIntegrityVerificationLevelInformationAlignmentTest + { + public byte A; + public HierarchicalIntegrityVerificationLevelInformation B; + } + + [Fact] + public static void HierarchicalIntegrityVerificationLevelInformation_Alignment() + { + var s = new HierarchicalIntegrityVerificationLevelInformationAlignmentTest(); + + Assert.Equal(0x1C, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.A)); + Assert.Equal(4, GetOffset(in s, in s.B)); + } + + [Fact] + public static void HierarchicalIntegrityVerificationInformation_Layout() + { + HierarchicalIntegrityVerificationInformation s = default; + + Assert.Equal(0xB4, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.MaxLayers)); + Assert.Equal(0x04, GetOffset(in s, in s.Layers)); + Assert.Equal(0x94, GetOffset(in s, in s.HashSalt)); + + Assert.Equal(Constants.IntegrityMaxLayerCount - 1, s.Layers.ItemsRo.Length); + } + + [Fact] + public static void HierarchicalIntegrityVerificationMetaInformation_Layout() + { + HierarchicalIntegrityVerificationMetaInformation s = default; + + Assert.Equal(0xC0, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Magic)); + Assert.Equal(0x04, GetOffset(in s, in s.Version)); + Assert.Equal(0x08, GetOffset(in s, in s.MasterHashSize)); + Assert.Equal(0x0C, GetOffset(in s, in s.LevelHashInfo)); + } + + [Fact] + public static void HierarchicalIntegrityVerificationSizeSet_Layout() + { + HierarchicalIntegrityVerificationSizeSet s = default; + + Assert.Equal(Constants.IntegrityMaxLayerCount - 2, s.LayeredHashSizes.ItemsRo.Length); + } + + [Fact] + public static void HierarchicalIntegrityVerificationStorageControlArea_InputParam_Layout() + { + HierarchicalIntegrityVerificationStorageControlArea.InputParam s = default; + + Assert.Equal(Constants.IntegrityMaxLayerCount - 1, s.LevelBlockSizes.ItemsRo.Length); + } } \ No newline at end of file