mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add HierarchicalIntegrityVerificationStorage
This commit is contained in:
parent
b54f5d17fa
commit
6c9e6e5203
7 changed files with 922 additions and 6 deletions
17
src/LibHac/Crypto/HmacSha256.cs
Normal file
17
src/LibHac/Crypto/HmacSha256.cs
Normal file
|
@ -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<byte> outMac, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
|
||||
{
|
||||
bool success = HMACSHA256.TryHashData(key, data, outMac, out int bytesWritten);
|
||||
|
||||
Abort.DoAbortUnless(success && bytesWritten == HashSize);
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -59,8 +59,8 @@ public class BlockCacheBufferedStorage : IStorage
|
|||
private int _shiftBytesVerificationBlock;
|
||||
private int _flags;
|
||||
private int _bufferLevel;
|
||||
private StorageType _storageType;
|
||||
private BlockCacheManager<CacheEntry, AccessRange> _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);
|
||||
|
|
817
src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs
Normal file
817
src/LibHac/FsSystem/HierarchicalIntegrityVerificationStorage.cs
Normal file
|
@ -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<HierarchicalIntegrityVerificationLevelInformation> 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<HashSalt>();
|
||||
Format(in hashSalt);
|
||||
}
|
||||
|
||||
public void Format(in Optional<HashSalt> 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<long> LayeredHashSizes;
|
||||
}
|
||||
|
||||
public class HierarchicalIntegrityVerificationStorageControlArea : IDisposable
|
||||
{
|
||||
public struct InputParam
|
||||
{
|
||||
public Array6<int> 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<HierarchicalIntegrityVerificationMetaInformation>();
|
||||
|
||||
Span<long> 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<HierarchicalIntegrityVerificationMetaInformation>())
|
||||
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<HierarchicalIntegrityVerificationMetaInformation>())
|
||||
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<HierarchicalIntegrityVerificationMetaInformation>())
|
||||
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<HashSalt>();
|
||||
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<HashSalt>();
|
||||
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<HashSalt>();
|
||||
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<byte> 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<byte> 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<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> 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);
|
||||
}
|
||||
|
||||
/// <summary>"<c>HierarchicalIntegrityVerificationStorage::Master</c>"</summary>
|
||||
public static ReadOnlySpan<byte> 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'
|
||||
};
|
||||
|
||||
/// <summary>"<c>HierarchicalIntegrityVerificationStorage::L1</c>"</summary>
|
||||
public static ReadOnlySpan<byte> 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'
|
||||
};
|
||||
|
||||
/// <summary>"<c>HierarchicalIntegrityVerificationStorage::L2</c>"</summary>
|
||||
public static ReadOnlySpan<byte> 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'
|
||||
};
|
||||
|
||||
/// <summary>"<c>HierarchicalIntegrityVerificationStorage::L3</c>"</summary>
|
||||
public static ReadOnlySpan<byte> 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'
|
||||
};
|
||||
|
||||
/// <summary>"<c>HierarchicalIntegrityVerificationStorage::L4</c>"</summary>
|
||||
public static ReadOnlySpan<byte> 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'
|
||||
};
|
||||
|
||||
/// <summary>"<c>HierarchicalIntegrityVerificationStorage::L5</c>"</summary>
|
||||
public static ReadOnlySpan<byte> 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'
|
||||
};
|
||||
}
|
|
@ -43,6 +43,11 @@ public struct Optional<T>
|
|||
|
||||
public static implicit operator Optional<T>(in T value) => new Optional<T>(in value);
|
||||
|
||||
public void Set()
|
||||
{
|
||||
_hasValue = true;
|
||||
}
|
||||
|
||||
public void Set(T value)
|
||||
{
|
||||
_value = value;
|
||||
|
|
|
@ -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<HierarchicalIntegrityVerificationLevelInformation>());
|
||||
|
||||
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<HierarchicalIntegrityVerificationLevelInformationAlignmentTest>());
|
||||
|
||||
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<HierarchicalIntegrityVerificationInformation>());
|
||||
|
||||
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<HierarchicalIntegrityVerificationMetaInformation>());
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue