From 8391518f3a5aec98364f110fe432a516cdcb6523 Mon Sep 17 00:00:00 2001 From: alexSSB Date: Sat, 24 Oct 2026 20:57:04 +0300 Subject: [PATCH 1/2] Fix Push sending a wrong DATA length after a short read Push only rewrote the DATA header length when a read returned less than a full chunk, assuming that happens only at the end of the stream. A stream that returns a short read followed by a full one (pipes, network or COM streams) then sent the full chunk under the short read's length, and adbd aborted the transfer with "invalid data message". The length is now set from every read, in Push, PushAsync and the WinRT PushAsync. Co-Authored-By: Claude Opus 5.5 --- .../Dummys/DummyChunkedStream.cs | 45 +++++++++++++++++++ .../SyncServiceTests.Async.cs | 39 ++++++++++++++++ .../SyncServiceTests.cs | 38 ++++++++++++++++ AdvancedSharpAdbClient/SyncService.Async.cs | 15 +++---- AdvancedSharpAdbClient/SyncService.cs | 10 ++--- 5 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 AdvancedSharpAdbClient.Tests/Dummys/DummyChunkedStream.cs 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..7339bb8 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. /// 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..3a5dfd7 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; @@ -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; From f50d313fd322e18d77344aceda691ff37d909dec Mon Sep 17 00:00:00 2001 From: alexSSB Date: Sat, 24 Oct 2026 21:29:29 +0300 Subject: [PATCH 2/2] Fix WinRT PushAsync progress counting the stream size as already sent The size of a random access stream went into totalBytesRead instead of totalBytesToProcess, so progress started at the full size, ended at twice it, and never reported a total. Co-Authored-By: Claude Opus 5.5 --- AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs | 8 +++++++- AdvancedSharpAdbClient/SyncService.Async.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs index 7339bb8..8764882 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -857,6 +857,7 @@ .. SyncCommand.DATA.GetBytes(), .. BitConverter.GetBytes(content.Length), .. content, ]; + SyncProgressChangedEventArgs progress = null; await RunTestAsync( OkResponses(2), @@ -872,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/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index 3a5dfd7..b221dcd 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -281,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 { }