Skip to content

Commit

Permalink
prevent overflow writing bytes on stream when incoming bytes are out …
Browse files Browse the repository at this point in the history
…of the chunk range
  • Loading branch information
bezzad committed Nov 18, 2023
1 parent d689cb7 commit 9766a4c
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/Downloader.Test/UnitTests/ChunkDownloaderOnFileTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public ChunkDownloaderOnFileTest()
MinimumSizeOfChunking = 16,
Timeout = 100,
};
Storage = new ConcurrentStream(path, DummyFileHelper.FileSize16Kb);
Storage = new ConcurrentStream(path, 0);
}
}
23 changes: 23 additions & 0 deletions src/Downloader.Test/UnitTests/ChunkDownloaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,27 @@ public async Task CancelReadStreamTest()

chunkDownloader.Chunk.Clear();
}

[Fact]
public async Task OverflowWhenReadStreamTest()
{
// arrange
var randomlyBytes = DummyData.GenerateRandomBytes(Size);
var chunk = new Chunk(0, Size / 2 - 1);
var chunkDownloader = new ChunkDownloader(chunk, Configuration, Storage);
using var memoryStream = new MemoryStream(randomlyBytes);

// act
await chunkDownloader.ReadStream(memoryStream, new PauseTokenSource().Token, new CancellationToken());
Storage.Flush();

// assert
Assert.Equal(expected: Size / 2, actual: chunk.Length);
Assert.Equal(expected: chunk.Length, actual: chunk.Position);
Assert.Equal(expected: 0, actual: chunk.EmptyLength);
Assert.Equal(expected: memoryStream.Position, actual: chunk.Position);
Assert.Equal(expected: chunk.Length, actual: Storage.Length);

Storage.Dispose();
}
}
67 changes: 57 additions & 10 deletions src/Downloader/Chunk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,76 @@
namespace Downloader
{
/// <summary>
/// Chunk data structure
/// Chunk data structure
/// </summary>
public class Chunk
{
/// <summary>
/// Chunk unique identity name
/// </summary>
public string Id { get; set; }

/// <summary>
/// Start offset of the chunk in the file bytes
/// </summary>
public long Start { get; set; }

/// <summary>
/// End offset of the chunk in the file bytes
/// </summary>
public long End { get; set; }

/// <summary>
/// Current write offset of the chunk
/// </summary>
public long Position { get; set; }

/// <summary>
/// How many times to try again after the error
/// </summary>
public int MaxTryAgainOnFailover { get; set; }

/// <summary>
/// How many milliseconds to wait for a response from the server?
/// </summary>
public int Timeout { get; set; }

/// <summary>
/// How many times has downloading the chunk failed?
/// </summary>
public int FailoverCount { get; private set; }

/// <summary>
/// Length of current chunk.
/// When the chunk length is zero, the file is open to receive new bytes
/// until no more bytes are received from the server.
/// </summary>
public long Length => End - Start + 1;

/// <summary>
/// Unused length of current chunk.
/// When the chunk length is zero, the file is open to receive new bytes
/// until no more bytes are received from the server.
/// </summary>
public long EmptyLength => Length > 0 ? Length - Position : long.MaxValue;

/// <summary>
/// Can write more data on this chunk according to the chunk situations?
/// </summary>
public bool CanWrite => Length > 0 ? Start + Position < End : true;


public Chunk()
{
Id = Guid.NewGuid().ToString("N");
}

public Chunk(long start, long end) : this()
{
Start = start;
End = end;
}

public string Id { get; set; }
public long Start { get; set; }
public long End { get; set; }
public long Position { get; set; }
public int MaxTryAgainOnFailover { get; set; }
public int Timeout { get; set; }
public int FailoverCount { get; private set; }
public long Length => End - Start + 1;

public bool CanTryAgainOnFailover()
{
return FailoverCount++ < MaxTryAgainOnFailover;
Expand Down
8 changes: 7 additions & 1 deletion src/Downloader/ChunkDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public async Task<Chunk> Download(Request downloadRequest, PauseToken pause, Can
{
return await ContinueWithDelay(downloadRequest, pause, cancelToken).ConfigureAwait(false);
}
catch(Exception error)

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used

Check warning on line 57 in src/Downloader/ChunkDownloader.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'error' is declared but never used
{
// Can't handle this exception
throw;
}
finally
{
await Task.Yield();
Expand Down Expand Up @@ -125,7 +130,7 @@ internal async Task ReadStream(Stream stream, PauseToken pauseToken, Cancellatio
{
// close stream on cancellation because, it's not work on .Net Framework
using var _ = cancelToken.Register(stream.Close);
while (readSize > 0)
while (readSize > 0 && Chunk.CanWrite)
{
cancelToken.ThrowIfCancellationRequested();
await pauseToken.WaitWhilePausedAsync().ConfigureAwait(false);
Expand All @@ -139,6 +144,7 @@ internal async Task ReadStream(Stream stream, PauseToken pauseToken, Cancellatio
readSize = await stream.ReadAsync(buffer, 0, buffer.Length, innerToken.Value).ConfigureAwait(false);
}

readSize = (int)Math.Min(Chunk.EmptyLength, readSize);
if (readSize > 0)
{
await _storage.WriteAsync(Chunk.Start + Chunk.Position - _configuration.RangeLow, buffer, readSize).ConfigureAwait(false);
Expand Down

0 comments on commit 9766a4c

Please sign in to comment.