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;