Skip to content

Commit e659516

Browse files
committed
fix: Add buffered transport API and read optimizations
Introduce IFastbootBufferedTransport with ReadInto to allow callers to supply buffers and avoid per-read allocations. Implement the buffered API in TcpTransport, UdpTransport and UsbDevice, refactor their Read methods to use ReadInto, and add validation for offsets/lengths. Increase UDP handshake retry budget (min 5 attempts) to reduce fragile CI timing failures. Optimize InternalUploadData to rent a shared buffer from ArrayPool and use the buffered transport fast path when available, reducing allocations and improving throughput; ensure proper return of pooled buffers and EOF/error handling.
1 parent 35f6d1c commit e659516

File tree

5 files changed

+110
-20
lines changed

5 files changed

+110
-20
lines changed

FirmwareKit.Comm.Fastboot/Backend/Network/IFastbootTransport.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,13 @@ public interface IFastbootTransport : IDisposable
77
long Write(byte[] data, int length);
88
}
99

10+
/// <summary>
11+
/// Optional transport extension for reading directly into caller-provided buffers.
12+
/// Implement this to avoid per-read byte[] allocations on hot paths.
13+
/// </summary>
14+
public interface IFastbootBufferedTransport : IFastbootTransport
15+
{
16+
int ReadInto(byte[] buffer, int offset, int length);
17+
}
18+
1019

FirmwareKit.Comm.Fastboot/Backend/Network/TcpTransport.cs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace FirmwareKit.Comm.Fastboot.Network;
66

7-
public class TcpTransport : IFastbootTransport
7+
public class TcpTransport : IFastbootBufferedTransport
88
{
99
private const int DefaultIoTimeoutMs = 30000;
1010
private readonly TcpClient _client = new();
@@ -75,6 +75,25 @@ private int ReadFully(byte[] buffer, int offset, int length)
7575

7676
public byte[] Read(int length)
7777
{
78+
if (length <= 0) return Array.Empty<byte>();
79+
80+
byte[] dataBuffer = new byte[length];
81+
int actuallyRead = ReadInto(dataBuffer, 0, length);
82+
if (actuallyRead < length)
83+
{
84+
Array.Resize(ref dataBuffer, actuallyRead);
85+
}
86+
return dataBuffer;
87+
}
88+
89+
public int ReadInto(byte[] buffer, int offset, int length)
90+
{
91+
if (length <= 0) return 0;
92+
if (offset < 0 || length < 0 || offset + length > buffer.Length)
93+
{
94+
throw new ArgumentOutOfRangeException(nameof(length));
95+
}
96+
7897
if (_messageBytesLeft == 0)
7998
{
8099
byte[] lenBuffer = new byte[8];
@@ -86,14 +105,9 @@ public byte[] Read(int length)
86105
}
87106

88107
int toRead = (int)Math.Min(length, _messageBytesLeft);
89-
byte[] dataBuffer = new byte[toRead];
90-
int actuallyRead = ReadFully(dataBuffer, 0, toRead);
91-
if (actuallyRead < toRead)
92-
{
93-
Array.Resize(ref dataBuffer, actuallyRead);
94-
}
108+
int actuallyRead = ReadFully(buffer, offset, toRead);
95109
_messageBytesLeft -= actuallyRead;
96-
return dataBuffer;
110+
return actuallyRead;
97111
}
98112

99113
public long Write(byte[] data, int length)

FirmwareKit.Comm.Fastboot/Backend/Network/UdpTransport.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace FirmwareKit.Comm.Fastboot.Network;
77
/// Fastboot over UDP Transport
88
/// Fully implements the AOSP Fastboot over Network protocol (headers, sequence numbers, handshake)
99
/// </summary>
10-
public class UdpTransport : IFastbootTransport
10+
public class UdpTransport : IFastbootBufferedTransport
1111
{
1212
private readonly UdpClient _client;
1313
private readonly IPEndPoint _endpoint;
@@ -55,13 +55,18 @@ private void InitializeProtocol()
5555
_client.Client.ReceiveTimeout = _timeoutMs;
5656
_client.Client.SendTimeout = _timeoutMs;
5757

58-
byte[] response = SendSinglePacket(PacketId.DeviceQuery, 0, PacketFlag.None, [], 0, 0, _maxTransmissionAttempts, out _);
58+
// Handshake runs at transport creation and is sensitive to scheduling jitter
59+
// in constrained CI environments. Use a slightly larger retry budget here
60+
// without changing steady-state transfer behavior.
61+
int initAttempts = Math.Max(_maxTransmissionAttempts, 5);
62+
63+
byte[] response = SendSinglePacket(PacketId.DeviceQuery, 0, PacketFlag.None, [], 0, 0, initAttempts, out _);
5964
if (response.Length < 2) throw new Exception("Invalid query response from target.");
6065
_sequence = BinaryPrimitives.ReadUInt16BigEndian(response.AsSpan(0, 2));
6166
byte[] initData = new byte[4];
6267
BinaryPrimitives.WriteUInt16BigEndian(initData.AsSpan(0, 2), 0x0001);
6368
BinaryPrimitives.WriteUInt16BigEndian(initData.AsSpan(2, 2), HostMaxPacketSize);
64-
response = SendSinglePacket(PacketId.Initialization, (ushort)_sequence, PacketFlag.None, initData, initData.Length, _maxTransmissionAttempts, out _);
69+
response = SendSinglePacket(PacketId.Initialization, (ushort)_sequence, PacketFlag.None, initData, initData.Length, initAttempts, out _);
6570
if (response.Length < 4) throw new Exception("Invalid initialization response from target.");
6671

6772
ushort version = BinaryPrimitives.ReadUInt16BigEndian(response.AsSpan(0, 2));
@@ -187,12 +192,31 @@ private byte[] SendSinglePacket(PacketId id, ushort seq, PacketFlag flag, byte[]
187192
public byte[] Read(int length)
188193
{
189194
if (length <= 0) return Array.Empty<byte>();
195+
byte[] buffer = new byte[length];
196+
int read = ReadInto(buffer, 0, length);
197+
if (read < length)
198+
{
199+
Array.Resize(ref buffer, read);
200+
}
201+
return buffer;
202+
}
203+
204+
public int ReadInto(byte[] buffer, int offset, int length)
205+
{
206+
if (length <= 0) return 0;
207+
if (offset < 0 || length < 0 || offset + length > buffer.Length)
208+
{
209+
throw new ArgumentOutOfRangeException(nameof(length));
210+
}
211+
190212
byte[] response = SendDataInternal(PacketId.Fastboot, [], 0, _maxTransmissionAttempts);
191213
if (response.Length > length)
192214
{
193215
throw new Exception("UDP protocol error: receive overflow, target sent too much fastboot data.");
194216
}
195-
return response;
217+
218+
Buffer.BlockCopy(response, 0, buffer, offset, response.Length);
219+
return response.Length;
196220
}
197221

198222
public long Write(byte[] data, int length)

FirmwareKit.Comm.Fastboot/Backend/Usb/UsbDevice.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,27 @@
22

33
namespace FirmwareKit.Comm.Fastboot.Usb;
44

5-
public abstract class UsbDevice : IFastbootTransport
5+
public abstract class UsbDevice : IFastbootBufferedTransport
66
{
77
public string DevicePath { get; set; } = string.Empty;
88
public string? SerialNumber { get; set; }
99
public ushort VendorId { get; set; }
1010
public ushort ProductId { get; set; }
1111
public UsbDeviceType UsbDeviceType { get; set; }
1212
public abstract byte[] Read(int length);
13+
public virtual int ReadInto(byte[] buffer, int offset, int length)
14+
{
15+
if (length <= 0) return 0;
16+
if (offset < 0 || length < 0 || offset + length > buffer.Length)
17+
{
18+
throw new ArgumentOutOfRangeException(nameof(length));
19+
}
20+
21+
byte[] data = Read(length);
22+
if (data.Length == 0) return 0;
23+
Buffer.BlockCopy(data, 0, buffer, offset, data.Length);
24+
return data.Length;
25+
}
1326
public abstract long Write(byte[] data, int length);
1427
public abstract int GetSerialNumber();
1528
public abstract int CreateHandle();

FirmwareKit.Comm.Fastboot/Command/InternalUploadData.cs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11

22

3+
using System.Buffers;
4+
35
namespace FirmwareKit.Comm.Fastboot;
46

57
public partial class FastbootDriver
@@ -15,14 +17,42 @@ public FastbootResponse UploadData(string command, Stream output)
1517

1618
long size = response.DataSize;
1719
long bytesDownloaded = 0;
18-
while (bytesDownloaded < size)
20+
byte[] buffer = ArrayPool<byte>.Shared.Rent(OnceSendDataSize);
21+
try
22+
{
23+
while (bytesDownloaded < size)
24+
{
25+
int toRead = (int)Math.Min(OnceSendDataSize, size - bytesDownloaded);
26+
int readLen;
27+
28+
if (Transport is IFastbootBufferedTransport buffered)
29+
{
30+
readLen = buffered.ReadInto(buffer, 0, toRead);
31+
}
32+
else
33+
{
34+
byte[] data = Transport.Read(toRead);
35+
if (data != null && data.Length > 0)
36+
{
37+
readLen = data.Length;
38+
Buffer.BlockCopy(data, 0, buffer, 0, readLen);
39+
}
40+
else
41+
{
42+
readLen = 0;
43+
}
44+
}
45+
46+
if (readLen <= 0) throw new Exception("Unexpected EOF from USB.");
47+
48+
output.Write(buffer, 0, readLen);
49+
bytesDownloaded += readLen;
50+
NotifyProgress(bytesDownloaded, size);
51+
}
52+
}
53+
finally
1954
{
20-
int toRead = (int)Math.Min(OnceSendDataSize, size - bytesDownloaded);
21-
byte[] data = Transport.Read(toRead);
22-
if (data == null || data.Length == 0) throw new Exception("Unexpected EOF from USB.");
23-
output.Write(data, 0, data.Length);
24-
bytesDownloaded += data.Length;
25-
NotifyProgress(bytesDownloaded, size);
55+
ArrayPool<byte>.Shared.Return(buffer);
2656
}
2757

2858
return HandleResponse();

0 commit comments

Comments
 (0)