diff --git a/Duplicati/UnitTest/DiskImage/DiskImageUnitTests.cs b/Duplicati/UnitTest/DiskImage/DiskImageUnitTests.cs index 46f5fc69d..0a0b33416 100644 --- a/Duplicati/UnitTest/DiskImage/DiskImageUnitTests.cs +++ b/Duplicati/UnitTest/DiskImage/DiskImageUnitTests.cs @@ -202,6 +202,197 @@ namespace Duplicati.UnitTest.DiskImage #endregion - } + #region IRawDisk Unaligned Read Tests -} \ No newline at end of file + [Test] + public async Task Test_RawDisk_ReadUnalignedOffset_ReturnsCorrectData() + { + // First write aligned data, then read at an unaligned offset + var sectorSize = _rawDisk.SectorSize; + + // Write known pattern at sector 1 + var writeBuffer = new byte[sectorSize]; + for (int i = 0; i < sectorSize; i++) + writeBuffer[i] = (byte)(i & 0xFF); + + await _rawDisk.WriteSectorsAsync(1, writeBuffer, CancellationToken.None); + + // Read at offset = sector_size + 1 (unaligned) + // The implementation should handle this by padding to sector boundaries + var offset = sectorSize + 1; + var length = sectorSize - 2; + + using var stream = await _rawDisk.ReadBytesAsync(offset, length, CancellationToken.None); + var readBuffer = new byte[length]; + var bytesRead = await stream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None); + + // Should return the data we wrote at sector 1, starting from byte offset 1 + Assert.AreEqual(length, bytesRead, "Should have read the requested length."); + for (int i = 0; i < length; i++) + Assert.AreEqual((byte)((i + 1) & 0xFF), readBuffer[i], $"Byte at position {i} should match."); + } + + [Test] + public async Task Test_RawDisk_ReadUnalignedLength_ReturnsCorrectData() + { + // Test reading with a length that's not a multiple of sector size + var sectorSize = _rawDisk.SectorSize; + + // Write known pattern at sector 0 + var writeBuffer = new byte[sectorSize]; + for (int i = 0; i < sectorSize; i++) + writeBuffer[i] = (byte)(i & 0xFF); + + await _rawDisk.WriteSectorsAsync(0, writeBuffer, CancellationToken.None); + + // Read with unaligned length (sectorSize - 1) + var length = sectorSize - 1; + using var stream = await _rawDisk.ReadBytesAsync(0, length, CancellationToken.None); + var readBuffer = new byte[length]; + var bytesRead = await stream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None); + + Assert.AreEqual(length, bytesRead, "Should have read the requested length."); + for (int i = 0; i < length; i++) + Assert.AreEqual((byte)(i & 0xFF), readBuffer[i], $"Byte at position {i} should match."); + } + + [Test] + public async Task Test_RawDisk_ReadShortLength_ReturnsCorrectData() + { + // Read at offset 0 with length = sector_size - 1 (short read) + var sectorSize = _rawDisk.SectorSize; + + // Write known pattern at sector 0 + var writeBuffer = new byte[sectorSize]; + for (int i = 0; i < sectorSize; i++) + writeBuffer[i] = (byte)(i & 0xFF); + + await _rawDisk.WriteSectorsAsync(0, writeBuffer, CancellationToken.None); + + // Read short length + var length = sectorSize - 1; + using var stream = await _rawDisk.ReadBytesAsync(0, length, CancellationToken.None); + var readBuffer = new byte[length]; + var bytesRead = await stream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None); + + Assert.AreEqual(length, bytesRead, "Should have read the requested short length."); + for (int i = 0; i < length; i++) + Assert.AreEqual((byte)(i & 0xFF), readBuffer[i], $"Byte at position {i} should match."); + } + + [Test] + public async Task Test_RawDisk_ReadStraddlingSectors_ReturnsCorrectData() + { + // Read at offset = sector_size / 2 with length = sector_size (straddles two sectors) + var sectorSize = _rawDisk.SectorSize; + + // Write different patterns at sectors 0 and 1 + var sector0Data = new byte[sectorSize]; + var sector1Data = new byte[sectorSize]; + for (int i = 0; i < sectorSize; i++) + { + sector0Data[i] = (byte)(i | 0xF0); // Pattern 0xF0-0xFF + sector1Data[i] = (byte)(i | 0x0F); // Pattern 0x0F-0x1E + } + + await _rawDisk.WriteSectorsAsync(0, sector0Data, CancellationToken.None); + await _rawDisk.WriteSectorsAsync(1, sector1Data, CancellationToken.None); + + // Read at half-sector offset with full sector length + var offset = sectorSize / 2; + using var stream = await _rawDisk.ReadBytesAsync(offset, sectorSize, CancellationToken.None); + var readBuffer = new byte[sectorSize]; + var bytesRead = await stream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None); + + Assert.AreEqual(sectorSize, bytesRead, "Should have read one sector."); + + // First half should be from end of sector 0 + for (int i = 0; i < sectorSize / 2; i++) + { + var expected = (byte)((sectorSize / 2 + i) | 0xF0); + Assert.AreEqual(expected, readBuffer[i], $"Byte at position {i} should match sector 0 data."); + } + + // Second half should be from start of sector 1 + for (int i = sectorSize / 2; i < sectorSize; i++) + { + var expected = (byte)((i - sectorSize / 2) | 0x0F); + Assert.AreEqual(expected, readBuffer[i], $"Byte at position {i} should match sector 1 data."); + } + } + + [Test] + public async Task Test_RawDisk_ReadNearEndOfDisk_ReturnsCorrectData() + { + // Read at the very end of the disk where remaining bytes < sector_size + var sectorSize = _rawDisk.SectorSize; + var diskSize = _rawDisk.Size; + + // Calculate the last sector and write data there + var lastSector = (diskSize / sectorSize) - 1; + var writeBuffer = new byte[sectorSize]; + new Random().NextBytes(writeBuffer); + + await _rawDisk.WriteSectorsAsync(lastSector, writeBuffer, CancellationToken.None); + + // Read from near the end + var offset = diskSize - sectorSize; + using var stream = await _rawDisk.ReadBytesAsync(offset, sectorSize, CancellationToken.None); + var readBuffer = new byte[sectorSize]; + var bytesRead = await stream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None); + + Assert.AreEqual(sectorSize, bytesRead, "Should have read a full sector."); + Assert.AreEqual(writeBuffer, readBuffer, "Data should match what was written."); + } + + [Test] + public async Task Test_RawDisk_ReadUnalignedOffsetWithMemory_ReturnsCorrectData() + { + // Test the Memory-based ReadBytesAsync with unaligned offset + var sectorSize = _rawDisk.SectorSize; + + // Write known pattern at sector 1 + var writeBuffer = new byte[sectorSize]; + for (int i = 0; i < sectorSize; i++) + writeBuffer[i] = (byte)((i * 2) & 0xFF); + + await _rawDisk.WriteSectorsAsync(1, writeBuffer, CancellationToken.None); + + // Read at unaligned offset + var offset = sectorSize + 4; + var length = sectorSize - 8; + var readBuffer = new byte[length]; + var bytesRead = await _rawDisk.ReadBytesAsync(offset, readBuffer.AsMemory(), CancellationToken.None); + + Assert.AreEqual(length, bytesRead, "Should have read the requested length."); + for (int i = 0; i < length; i++) + Assert.AreEqual((byte)(((i + 4) * 2) & 0xFF), readBuffer[i], $"Byte at position {i} should match."); + } + + [Test] + public async Task Test_RawDisk_ReadUnalignedLengthWithMemory_ReturnsCorrectData() + { + // Test the Memory-based ReadBytesAsync with unaligned length + var sectorSize = _rawDisk.SectorSize; + + // Write known pattern at sector 0 + var writeBuffer = new byte[sectorSize]; + for (int i = 0; i < sectorSize; i++) + writeBuffer[i] = (byte)((i + 100) & 0xFF); + + await _rawDisk.WriteSectorsAsync(0, writeBuffer, CancellationToken.None); + + // Read with unaligned length + var length = sectorSize - 5; + var readBuffer = new byte[length]; + var bytesRead = await _rawDisk.ReadBytesAsync(0, readBuffer.AsMemory(), CancellationToken.None); + + Assert.AreEqual(length, bytesRead, "Should have read the requested length."); + for (int i = 0; i < length; i++) + Assert.AreEqual((byte)((i + 100) & 0xFF), readBuffer[i], $"Byte at position {i} should match."); + } + + #endregion + + } +}