@ -0,0 +1,623 @@
using System ;
using System.Runtime.CompilerServices ;
using LibHac.Common ;
using LibHac.Common.FixedArrays ;
using LibHac.Crypto ;
using LibHac.Diag ;
using LibHac.Fs ;
using LibHac.Util ;
namespace LibHac.FsSystem ;
/// <summary>
/// An <see cref="IStorage"/> that can verify the integrity of its data by using a second <see cref="IStorage"/>
/// that contains hash digests of the main data.
/// </summary>
/// <remarks><para>An <see cref="IntegrityVerificationStorage"/> consists of a data <see cref="IStorage"/>
/// and a hash <see cref="IStorage"/>. The main storage is split into blocks of a provided size and the hashes
/// of all these blocks are stored sequentially in the hash storage. Each time a data block is read its hash is
/// also read and used to verify the integrity of the main data.</para>
/// <para>An <see cref="IntegrityVerificationStorage"/> may be writable, updating the hash storage as required
/// when written to. Writable storages have some additional features compared to read-only storages:<br/>
/// If the hash for a data block is all zeros then that block is treated as if the actual data is all zeros.<br/>
/// To avoid collisions in the case where a block's actual hash is all zeros, a certain bit in all writable storage
/// hashes is always set to 1.<br/>
/// An optional <see cref="HashSalt"/> may be provided. This salt will be added to the beginning of each block of
/// data before it is hashed.</para>
/// <para>Based on FS 14.1.0 (nnSdk 14.3.0)</para></remarks>
public class IntegrityVerificationStorage : IStorage
{
public struct BlockHash
{
public Array32 < byte > Hash ;
}
public const int HashSize = Sha256 . DigestSize ;
private ValueSubStorage _hashStorage ;
private ValueSubStorage _dataStorage ;
private int _verificationBlockSize ;
private int _verificationBlockOrder ;
private int _upperLayerVerificationBlockSize ;
private int _upperLayerVerificationBlockOrder ;
private IBufferManager _bufferManager ;
private Optional < HashSalt > _hashSalt ;
private bool _isRealData ;
private IHash256GeneratorFactory _hashGeneratorFactory ;
private bool _isWritable ;
private bool _allowClearedBlocks ;
public IntegrityVerificationStorage ( )
{
_hashStorage = new ValueSubStorage ( ) ;
_dataStorage = new ValueSubStorage ( ) ;
}
public override void Dispose ( )
{
FinalizeObject ( ) ;
_dataStorage . Dispose ( ) ;
_hashStorage . Dispose ( ) ;
base . Dispose ( ) ;
}
/// <summary>
/// Sets the validation bit on the provided <see cref="BlockHash"/>. The hashes of all writable blocks
/// have a certain bit set so we can tell the difference between a cleared block and a hash of all zeros.
/// </summary>
/// <param name="hash">The <see cref="BlockHash"/> to have its validation bit set.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SetValidationBit ( ref BlockHash hash )
{
hash . Hash . Items [ HashSize - 1 ] | = 0x80 ;
}
/// <summary>
/// Checks if the provided <see cref="BlockHash"/> has its validation bit set. The hashes of all writable blocks
/// have a certain bit set so we can tell the difference between a cleared block and a hash of all zeros.
/// </summary>
/// <param name="hash">The <see cref="BlockHash"/> to check.</param>
/// <returns><see langword="true"/> if the validation bit is set; otherwise <see langword="false"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsValidationBit ( in BlockHash hash )
{
return ( hash . Hash . ItemsRo [ HashSize - 1 ] & 0x80 ) ! = 0 ;
}
public int GetBlockSize ( )
{
return _verificationBlockSize ;
}
public void Initialize ( in ValueSubStorage hashStorage , in ValueSubStorage dataStorage , int sizeVerificationBlock ,
int sizeUpperLayerVerificationBlock , IBufferManager bufferManager ,
IHash256GeneratorFactory hashGeneratorFactory , in Optional < HashSalt > hashSalt , bool isRealData , bool isWritable ,
bool allowClearedBlocks )
{
Assert . SdkRequiresGreaterEqual ( sizeVerificationBlock , HashSize ) ;
Assert . SdkRequiresNotNull ( bufferManager ) ;
Assert . SdkRequiresNotNull ( hashGeneratorFactory ) ;
_hashStorage . Set ( in hashStorage ) ;
_dataStorage . Set ( in dataStorage ) ;
_hashGeneratorFactory = hashGeneratorFactory ;
_verificationBlockSize = sizeVerificationBlock ;
_verificationBlockOrder = BitmapUtils . ILog2 ( ( uint ) sizeVerificationBlock ) ;
Assert . SdkRequiresEqual ( 1 < < _verificationBlockOrder , _verificationBlockSize ) ;
_bufferManager = bufferManager ;
sizeUpperLayerVerificationBlock = Math . Max ( sizeUpperLayerVerificationBlock , HashSize ) ;
_upperLayerVerificationBlockSize = sizeUpperLayerVerificationBlock ;
_upperLayerVerificationBlockOrder = BitmapUtils . ILog2 ( ( uint ) sizeUpperLayerVerificationBlock ) ;
Assert . SdkRequiresEqual ( 1 < < _upperLayerVerificationBlockOrder , _upperLayerVerificationBlockSize ) ;
Assert . SdkAssert ( _dataStorage . GetSize ( out long dataSize ) . IsSuccess ( ) ) ;
Assert . SdkAssert ( _hashStorage . GetSize ( out long hashSize ) . IsSuccess ( ) ) ;
Assert . SdkAssert ( hashSize / HashSize * _verificationBlockSize > = dataSize ) ;
_hashSalt = hashSalt ;
_isRealData = isRealData ;
_isWritable = isWritable ;
_allowClearedBlocks = allowClearedBlocks ;
}
public void FinalizeObject ( )
{
if ( _bufferManager is not null )
{
using ( var emptySubStorage = new ValueSubStorage ( ) )
{
_hashStorage . Set ( in emptySubStorage ) ;
}
using ( var emptySubStorage = new ValueSubStorage ( ) )
{
_dataStorage . Set ( in emptySubStorage ) ;
}
_bufferManager = null ;
}
}
public override Result GetSize ( out long size )
{
return _dataStorage . GetSize ( out size ) ;
}
public override Result SetSize ( long size )
{
return ResultFs . UnsupportedSetSizeForIntegrityVerificationStorage . Log ( ) ;
}
public override Result Read ( long offset , Span < byte > destination )
{
Assert . SdkRequiresNotEqual ( 0 , destination . Length ) ;
Assert . SdkRequiresAligned ( offset , _verificationBlockSize ) ;
Assert . SdkRequiresAligned ( destination . Length , _verificationBlockSize ) ;
if ( destination . Length = = 0 )
return Result . Success ;
Result rc = _dataStorage . GetSize ( out long dataSize ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
if ( dataSize < offset )
return ResultFs . InvalidOffset . Log ( ) ;
long alignedDataSize = Alignment . AlignUpPow2 ( dataSize , ( uint ) _verificationBlockSize ) ;
rc = CheckAccessRange ( offset , destination . Length , alignedDataSize ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
int readSize = destination . Length ;
if ( offset + readSize > dataSize )
{
// All reads to this storage must be aligned to the block size, but if the last data block is a partial block
// it will not be written to the base data storage. If that's the case, fill the unused portion of the block
// with zeros. The hash for the partial block is calculated using the padded, complete block.
int paddingOffset = ( int ) ( dataSize - offset ) ;
int paddingSize = _verificationBlockSize - ( paddingOffset & ( _verificationBlockSize - 1 ) ) ;
Assert . SdkLess ( paddingSize , _verificationBlockSize ) ;
// Clear the padding.
destination . Slice ( paddingOffset , paddingSize ) . Clear ( ) ;
// Set the new in-bounds size.
readSize = ( int ) ( dataSize - offset ) ;
}
// Read all of the data to be validated.
rc = _dataStorage . Read ( offset , destination . Slice ( 0 , readSize ) ) ;
if ( rc . IsFailure ( ) )
{
destination . Clear ( ) ;
return rc . Log ( ) ;
}
// Validate the hashes of the read data blocks.
Result verifyHashResult = Result . Success ;
using var hashGenerator = new UniqueRef < IHash256Generator > ( ) ;
rc = _hashGeneratorFactory . Create ( ref hashGenerator . Ref ( ) ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
int signatureCount = destination . Length > > _verificationBlockOrder ;
using var signatureBuffer =
new PooledBuffer ( signatureCount * Unsafe . SizeOf < BlockHash > ( ) , Unsafe . SizeOf < BlockHash > ( ) ) ;
int bufferCount = ( int ) Math . Min ( signatureCount , signatureBuffer . GetSize ( ) / ( uint ) Unsafe . SizeOf < BlockHash > ( ) ) ;
// Loop over each block while validating their signatures
int verifiedCount = 0 ;
while ( verifiedCount < signatureCount )
{
int currentCount = Math . Min ( bufferCount , signatureCount - verifiedCount ) ;
Result currentResult = ReadBlockSignature ( signatureBuffer . GetBuffer ( ) ,
offset + ( verifiedCount < < _verificationBlockOrder ) , currentCount < < _verificationBlockOrder ) ;
using var changePriority = new ScopedThreadPriorityChanger ( 1 , ScopedThreadPriorityChanger . Mode . Relative ) ;
for ( int i = 1 ; i < currentCount & & currentResult . IsSuccess ( ) ; i + + )
{
int verifiedSize = ( verifiedCount + i ) < < _verificationBlockOrder ;
ref BlockHash blockHash = ref signatureBuffer . GetBuffer < BlockHash > ( ) [ i ] ;
currentResult = VerifyHash ( destination . Slice ( verifiedCount ) , ref blockHash , in hashGenerator ) ;
if ( ResultFs . IntegrityVerificationStorageCorrupted . Includes ( currentResult ) )
{
// Don't output the corrupted block to the destination buffer
destination . Slice ( verifiedSize , _verificationBlockSize ) . Clear ( ) ;
if ( ! ResultFs . ClearedRealDataVerificationFailed . Includes ( currentResult ) & & ! _allowClearedBlocks )
{
verifyHashResult = currentResult ;
}
currentResult = Result . Success ;
}
}
if ( currentResult . IsFailure ( ) )
{
destination . Clear ( ) ;
return currentResult ;
}
verifiedCount + = currentCount ;
}
return verifyHashResult ;
}
public override Result Write ( long offset , ReadOnlySpan < byte > source )
{
if ( source . Length = = 0 )
return Result . Success ;
Result rc = CheckOffsetAndSize ( offset , source . Length ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
rc = _dataStorage . GetSize ( out long dataSize ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
if ( offset > = dataSize )
return ResultFs . InvalidOffset . Log ( ) ;
rc = CheckAccessRange ( offset , source . Length , Alignment . AlignUpPow2 ( dataSize , ( uint ) _verificationBlockSize ) ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
Assert . SdkRequiresAligned ( offset , _verificationBlockSize ) ;
Assert . SdkRequiresAligned ( source . Length , _verificationBlockSize ) ;
Assert . SdkLessEqual ( offset , dataSize ) ;
Assert . SdkLess ( offset + source . Length , dataSize + _verificationBlockSize ) ;
// When writing to a partial final block, the data past the end of the partial block should be all zeros.
if ( offset + source . Length > dataSize )
{
Assert . SdkAssert ( source . Slice ( ( int ) ( dataSize - offset ) ) . IsZeros ( ) ) ;
}
// Determine the size of the unpadded data we're writing to the base data storage
int writeSize = source . Length ;
if ( offset + writeSize > dataSize )
{
writeSize = ( int ) ( dataSize - offset ) ;
if ( writeSize = = 0 )
return Result . Success ;
}
int alignedWriteSize = Alignment . AlignUpPow2 ( writeSize , ( uint ) _verificationBlockSize ) ;
Result updateResult = Result . Success ;
int updatedSignatureCount = 0 ;
{
int signatureCount = alignedWriteSize > > _verificationBlockOrder ;
using var signatureBuffer =
new PooledBuffer ( signatureCount * Unsafe . SizeOf < BlockHash > ( ) , Unsafe . SizeOf < BlockHash > ( ) ) ;
int bufferCount = Math . Min ( signatureCount , signatureBuffer . GetSize ( ) / Unsafe . SizeOf < BlockHash > ( ) ) ;
using var hashGenerator = new UniqueRef < IHash256Generator > ( ) ;
rc = _hashGeneratorFactory . Create ( ref hashGenerator . Ref ( ) ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
while ( updatedSignatureCount < signatureCount )
{
int remainingCount = signatureCount - updatedSignatureCount ;
int currentCount = Math . Min ( bufferCount , remainingCount ) ;
using ( new ScopedThreadPriorityChanger ( 1 , ScopedThreadPriorityChanger . Mode . Relative ) )
{
// Calculate the new hashes for the current set of blocks
for ( int i = 0 ; i < currentCount ; i + + )
{
int updatedSize = ( updatedSignatureCount + i ) < < _verificationBlockOrder ;
CalcBlockHash ( out signatureBuffer . GetBuffer < BlockHash > ( ) [ i ] , source . Slice ( updatedSize ) ,
in hashGenerator ) ;
}
}
// Write the new block signatures.
updateResult = WriteBlockSignature ( signatureBuffer . GetBuffer ( ) ,
offset : offset + ( updatedSignatureCount < < _verificationBlockOrder ) ,
size : currentCount < < _verificationBlockOrder ) ;
if ( updateResult . IsFailure ( ) )
break ;
updatedSignatureCount + = currentCount ;
}
}
// The updated hash values have all been written. Now write the actual data.
// If there was an error writing the updated hashes, only the data for the blocks that were
// successfully updated will be written.
int dataWriteSize = Math . Min ( writeSize , updatedSignatureCount < < _verificationBlockOrder ) ;
rc = _dataStorage . Write ( offset , source . Slice ( 0 , dataWriteSize ) ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
return updateResult ;
}
public override Result Flush ( )
{
Result rc = _hashStorage . Flush ( ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
rc = _dataStorage . Flush ( ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
return Result . Success ;
}
public override Result OperateRange ( Span < byte > outBuffer , OperationId operationId , long offset , long size ,
ReadOnlySpan < byte > inBuffer )
{
if ( operationId ! = OperationId . InvalidateCache )
{
Assert . SdkRequiresAligned ( offset , _verificationBlockSize ) ;
Assert . SdkRequiresAligned ( size , _verificationBlockSize ) ;
}
switch ( operationId )
{
case OperationId . FillZero :
{
Assert . SdkRequires ( _isWritable ) ;
Result rc = _dataStorage . GetSize ( out long dataStorageSize ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
if ( offset < 0 | | dataStorageSize < offset )
return ResultFs . InvalidOffset . Log ( ) ;
// Get the range of the signatures for the blocks that will be cleared
long signOffset = ( offset > > _verificationBlockOrder ) * Unsafe . SizeOf < BlockHash > ( ) ;
long signSize = Math . Min ( size , dataStorageSize - offset ) * Unsafe . SizeOf < BlockHash > ( ) ;
// Allocate a work buffer up to 4 times the size of the hash storage's verification block size.
int bufferSize = ( int ) Math . Min ( signSize , 1 < < ( _upperLayerVerificationBlockOrder + 2 ) ) ;
using var workBuffer = new RentedArray < byte > ( bufferSize ) ;
if ( workBuffer . Array is null )
return ResultFs . AllocationMemoryFailedInIntegrityVerificationStorageA . Log ( ) ;
workBuffer . Span . Clear ( ) ;
long remainingSize = signSize ;
// Clear the hash storage in chunks.
while ( remainingSize > 0 )
{
int currentSize = ( int ) Math . Min ( remainingSize , bufferSize ) ;
rc = _hashStorage . Write ( signOffset + signSize - remainingSize ,
workBuffer . Span . Slice ( 0 , currentSize ) ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
remainingSize - = currentSize ;
}
return Result . Success ;
}
case OperationId . DestroySignature :
{
Assert . SdkRequires ( _isWritable ) ;
Result rc = _dataStorage . GetSize ( out long dataStorageSize ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
if ( offset < 0 | | dataStorageSize < offset )
return ResultFs . InvalidOffset . Log ( ) ;
// Get the range of the signatures for the blocks that will be cleared
long signOffset = ( offset > > _verificationBlockOrder ) * Unsafe . SizeOf < BlockHash > ( ) ;
long signSize = Math . Min ( size , dataStorageSize - offset ) * Unsafe . SizeOf < BlockHash > ( ) ;
using var workBuffer = new RentedArray < byte > ( ( int ) signSize ) ;
if ( workBuffer . Array is null )
return ResultFs . AllocationMemoryFailedInIntegrityVerificationStorageB . Log ( ) ;
// Read the existing signature.
rc = _hashStorage . Read ( signOffset , workBuffer . Span ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
// Clear the signature.
// This flips all bits, leaving the verification bit cleared.
for ( int i = 0 ; i < workBuffer . Span . Length ; i + + )
{
workBuffer . Span [ i ] ^ = ( byte ) ( ( i + 1 ) % ( uint ) HashSize = = 0 ? 0x7F : 0xFF ) ;
}
// Write the cleared signature.
return _hashStorage . Write ( signOffset , workBuffer . Span ) ;
}
case OperationId . InvalidateCache :
{
// Only allow cache invalidation for read-only storages.
if ( _isWritable )
return ResultFs . UnsupportedOperateRangeForWritableIntegrityVerificationStorage . Log ( ) ;
Result rc = _hashStorage . OperateRange ( operationId , 0 , long . MaxValue ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
rc = _dataStorage . OperateRange ( operationId , offset , size ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
return Result . Success ;
}
case OperationId . QueryRange :
{
Result rc = _dataStorage . GetSize ( out long dataStorageSize ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
if ( offset < 0 | | dataStorageSize < offset )
return ResultFs . InvalidOffset . Log ( ) ;
long actualSize = Math . Min ( size , dataStorageSize - offset ) ;
rc = _dataStorage . OperateRange ( outBuffer , operationId , offset , actualSize , inBuffer ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
return Result . Success ;
}
default :
return ResultFs . UnsupportedOperateRangeForIntegrityVerificationStorage . Log ( ) ;
}
}
private Result ReadBlockSignature ( Span < byte > destination , long offset , int size )
{
Assert . SdkRequiresAligned ( offset , _verificationBlockSize ) ;
Assert . SdkRequiresAligned ( size , _verificationBlockSize ) ;
// Calculate the range that contains the signatures.
long offsetSignData = ( offset > > _verificationBlockOrder ) * HashSize ;
long sizeSignData = ( size > > _verificationBlockOrder ) * HashSize ;
Assert . SdkGreaterEqual ( destination . Length , sizeSignData ) ;
// Validate the hash storage contains the calculated range.
Result rc = _hashStorage . GetSize ( out long sizeHash ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
Assert . SdkLessEqual ( offsetSignData + sizeSignData , sizeHash ) ;
if ( offsetSignData + sizeSignData > sizeHash )
return ResultFs . OutOfRange . Log ( ) ;
// Read the signature.
rc = _hashStorage . Read ( offsetSignData , destination . Slice ( 0 , ( int ) sizeSignData ) ) ;
if ( rc . IsFailure ( ) )
{
// Clear any read signature data if something goes wrong.
destination . Slice ( 0 , ( int ) sizeSignData ) ;
return rc . Miss ( ) ;
}
return Result . Success ;
}
private Result WriteBlockSignature ( ReadOnlySpan < byte > source , long offset , int size )
{
Assert . SdkRequiresAligned ( offset , _verificationBlockSize ) ;
long offsetSignData = ( offset > > _verificationBlockOrder ) * HashSize ;
long sizeSignData = ( size > > _verificationBlockOrder ) * HashSize ;
Assert . SdkGreaterEqual ( source . Length , sizeSignData ) ;
Result rc = _hashStorage . Write ( offsetSignData , source . Slice ( 0 , ( int ) sizeSignData ) ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
return Result . Success ;
}
public Result CalcBlockHash ( out BlockHash outHash , ReadOnlySpan < byte > buffer , int verificationBlockSize )
{
UnsafeHelpers . SkipParamInit ( out outHash ) ;
using var hashGenerator = new UniqueRef < IHash256Generator > ( ) ;
Result rc = _hashGeneratorFactory . Create ( ref hashGenerator . Ref ( ) ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
CalcBlockHash ( out outHash , buffer , verificationBlockSize , in hashGenerator ) ;
return Result . Success ;
}
private void CalcBlockHash ( out BlockHash outHash , ReadOnlySpan < byte > buffer ,
in UniqueRef < IHash256Generator > hashGenerator )
{
CalcBlockHash ( out outHash , buffer , _verificationBlockSize , in hashGenerator ) ;
}
private void CalcBlockHash ( out BlockHash outHash , ReadOnlySpan < byte > buffer , int verificationBlockSize ,
in UniqueRef < IHash256Generator > hashGenerator )
{
UnsafeHelpers . SkipParamInit ( out outHash ) ;
if ( _isWritable )
{
if ( _hashSalt . HasValue )
{
// Calculate the hash using the salt if enabled.
hashGenerator . Get . Initialize ( ) ;
hashGenerator . Get . Update ( _hashSalt . ValueRo . HashRo ) ;
hashGenerator . Get . Update ( buffer . Slice ( 0 , verificationBlockSize ) ) ;
hashGenerator . Get . GetHash ( SpanHelpers . AsByteSpan ( ref outHash ) ) ;
}
else
{
// Otherwise calculate the hash of just the data.
_hashGeneratorFactory . GenerateHash ( SpanHelpers . AsByteSpan ( ref outHash ) ,
buffer . Slice ( 0 , verificationBlockSize ) ) ;
}
// The hashes of all writable blocks have the validation bit set.
SetValidationBit ( ref outHash ) ;
}
else
{
// Nothing special needed for read-only blocks. Just calculate the hash.
_hashGeneratorFactory . GenerateHash ( SpanHelpers . AsByteSpan ( ref outHash ) ,
buffer . Slice ( 0 , verificationBlockSize ) ) ;
}
}
private Result VerifyHash ( ReadOnlySpan < byte > buffer , ref BlockHash hash ,
in UniqueRef < IHash256Generator > hashGenerator )
{
Assert . SdkRequiresGreaterEqual ( buffer . Length , HashSize ) ;
// Writable storages allow using an all-zeros hash to indicate an empty block.
if ( _isWritable )
{
Result rc = IsCleared ( out bool isCleared , in hash ) ;
if ( rc . IsFailure ( ) ) return rc . Miss ( ) ;
if ( isCleared )
return ResultFs . ClearedRealDataVerificationFailed . Log ( ) ;
}
CalcBlockHash ( out BlockHash actualHash , buffer , hashGenerator ) ;
if ( ! CryptoUtil . IsSameBytes ( SpanHelpers . AsReadOnlyByteSpan ( in hash ) ,
SpanHelpers . AsReadOnlyByteSpan ( in actualHash ) , Unsafe . SizeOf < BlockHash > ( ) ) )
{
hash = default ;
if ( _isRealData )
{
return ResultFs . UnclearedRealDataVerificationFailed . Log ( ) ;
}
else
{
return ResultFs . NonRealDataVerificationFailed . Log ( ) ;
}
}
return Result . Success ;
}
private Result IsCleared ( out bool isCleared , in BlockHash hash )
{
Assert . SdkRequires ( _isWritable ) ;
isCleared = false ;
if ( IsValidationBit ( in hash ) )
return Result . Success ;
for ( int i = 0 ; i < hash . Hash . ItemsRo . Length ; i + + )
{
if ( hash . Hash . ItemsRo [ i ] ! = 0 )
return ResultFs . InvalidZeroHash . Log ( ) ;
}
isCleared = true ;
return Result . Success ;
}
}