diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyChunkedStream.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyChunkedStream.cs new file mode 100644 index 0000000..a794d6c --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyChunkedStream.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; + +namespace AdvancedSharpAdbClient.Tests +{ + /// + /// A forward-only stream that returns its content in the given chunk sizes, like a pipe or network stream may. + /// + internal class DummyChunkedStream(byte[] content, params int[] chunks) : Stream + { + private int position; + private int chunkIndex; + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + int chunk = chunkIndex < chunks.Length ? chunks[chunkIndex++] : count; + int read = Math.Min(Math.Min(chunk, count), content.Length - position); + Array.Copy(content, position, buffer, offset, read); + position += read; + return read; + } + + public override void Flush() { } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + } +} diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs index b973817..8764882 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -664,6 +664,45 @@ await RunTestAsync( TestContext.Current.CancellationToken); } + /// + /// Tests the method + /// with a stream that returns a short read followed by full reads. + /// + [Fact] + public async Task PushShortReadAsyncTest() + { + // A 24 byte buffer leaves 16 bytes of data per message + byte[] content = [.. Enumerable.Range(0, 40).Select(x => (byte)x)]; + DummyChunkedStream stream = new(content, 5, 16, 16); + byte[][] contentMessages = + [ + [.. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(5), .. content[..5]], + [.. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(16), .. content[5..21]], + [.. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(16), .. content[21..37]], + [.. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(3), .. content[37..]], + ]; + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.SEND, "/sdcard/test,644"), + (SyncCommand.DONE, "1446505200") + ], + [SyncCommand.OKAY], + null, + contentMessages, + async (ctx) => + { + using SyncService service = new(Socket, Device) { MaxBufferSize = 24 }; + await service.PushAsync(stream, "/sdcard/test", UnixFileStatus.StickyBit | UnixFileStatus.UserWrite | UnixFileStatus.OtherRead, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), cancellationToken: ctx); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }, + TestContext.Current.CancellationToken); + } + /// /// Tests the field. /// @@ -818,6 +857,7 @@ .. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(content.Length), .. content, ]; + SyncProgressChangedEventArgs progress = null; await RunTestAsync( OkResponses(2), @@ -833,11 +873,16 @@ await RunTestAsync( async (ctx) => { using SyncService service = new(Socket, Device); - await service.PushAsync(stream, "/sdcard/test", (UnixFileStatus)644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), cancellationToken: ctx); + await service.PushAsync(stream, "/sdcard/test", (UnixFileStatus)644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), args => progress = args, cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); }, TestContext.Current.CancellationToken); + + // A random access stream knows its size, so the progress has a total and ends on it + Assert.NotNull(progress); + Assert.Equal((ulong)content.Length, progress.ReceivedBytesSize); + Assert.Equal((ulong)content.Length, progress.TotalBytesToReceive); } /// diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs index 82926c8..859d122 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs @@ -462,6 +462,44 @@ .. BitConverter.GetBytes(content.Length), }); } + /// + /// Tests the method + /// with a stream that returns a short read followed by full reads. + /// + [Fact] + public void PushShortReadTest() + { + // A 24 byte buffer leaves 16 bytes of data per message + byte[] content = [.. Enumerable.Range(0, 40).Select(x => (byte)x)]; + DummyChunkedStream stream = new(content, 5, 16, 16); + byte[][] contentMessages = + [ + [.. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(5), .. content[..5]], + [.. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(16), .. content[5..21]], + [.. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(16), .. content[21..37]], + [.. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(3), .. content[37..]], + ]; + + RunTest( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.SEND, "/sdcard/test,644"), + (SyncCommand.DONE, "1446505200") + ], + [SyncCommand.OKAY], + null, + contentMessages, + () => + { + using SyncService service = new(Socket, Device) { MaxBufferSize = 24 }; + service.Push(stream, "/sdcard/test", (UnixFileStatus)644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc)); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }); + } + /// /// Tests the field. /// diff --git a/AdvancedSharpAdbClient/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index d59d823..b221dcd 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -96,12 +96,10 @@ await stream.ReadAsync(buffer, headerSize, maxDataSize, cancellationToken).Confi // read up to SYNC_DATA_MAX totalBytesRead += read; - if (read != maxDataSize) - { - // At the end of the line, so we need to recalculate the length of the header - lengthBytes = BitConverter.GetBytes(read); - headerSize = dataBytes.Length + lengthBytes.Length; - } + // A stream may return a short read before its end (pipes, network streams), + // so the header must always carry the length of this read. + lengthBytes = BitConverter.GetBytes(read); + headerSize = dataBytes.Length + lengthBytes.Length; int startPosition = reservedHeaderSize - headerSize; @@ -283,7 +281,7 @@ public virtual async Task PushAsync(IInputStream stream, string remotePath, Unix { if (stream is IRandomAccessStream random) { - totalBytesRead = random.Size; + totalBytesToProcess = random.Size; } } catch { } @@ -300,9 +298,10 @@ public virtual async Task PushAsync(IInputStream stream, string remotePath, Unix { break; } - else if (read != maxDataSize) + else { - // At the end of the line, so we need to recalculate the length of the header + // A stream may return a short read before its end (pipes, network streams), + // so the header must always carry the length of this read. lengthBytes = BitConverter.GetBytes(read); headerSize = dataBytes.Length + lengthBytes.Length; } diff --git a/AdvancedSharpAdbClient/SyncService.cs b/AdvancedSharpAdbClient/SyncService.cs index af96759..d61c7ae 100644 --- a/AdvancedSharpAdbClient/SyncService.cs +++ b/AdvancedSharpAdbClient/SyncService.cs @@ -206,12 +206,10 @@ public virtual void Push(Stream stream, string remotePath, UnixFileStatus permis // read up to SYNC_DATA_MAX totalBytesRead += read; - if (read != maxDataSize) - { - // At the end of the line, so we need to recalculate the length of the header - lengthBytes = BitConverter.GetBytes(read); - headerSize = dataBytes.Length + lengthBytes.Length; - } + // A stream may return a short read before its end (pipes, network streams), + // so the header must always carry the length of this read. + lengthBytes = BitConverter.GetBytes(read); + headerSize = dataBytes.Length + lengthBytes.Length; int startPosition = reservedHeaderSize - headerSize;