Skip to content

Commit b58507d

Browse files
committed
fix: Resparse fallback; add tests and UDP refactor
Catch InvalidOperationException from SparseFile.Resparse and fall back to using the original sparse file as a single part to avoid failing on unrealistically small limits. Add tests: FlashUnsparseImage_LogicalPartition_ResizesBeforeFlash (ensures logical partition resize occurs before download/flash) and FlashSparseFile_TinyLimit_FallsBackToSingleSparseTransfer (verifies fallback for tiny limits); also add the required using for sparse types. Refactor UdpTransportTests by extracting a CompleteHandshake helper, increasing TestTimeoutMs/TestMaxAttempts, and replacing duplicated server handshake code to simplify and stabilize tests.
1 parent f91b56f commit b58507d

File tree

3 files changed

+106
-92
lines changed

3 files changed

+106
-92
lines changed

FirmwareKit.Comm.Fastboot.Tests/FastbootProtocolTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using FirmwareKit.Comm.Fastboot.Usb;
2+
using FirmwareKit.Sparse.Core;
23
using System.Globalization;
34
using System.Text;
45

@@ -590,5 +591,49 @@ public void FlashUnsparseImage_OversizedRaw_IsConvertedToSparseAndFlashed()
590591
Assert.Contains(transport.Commands, c => c.StartsWith("download:", StringComparison.OrdinalIgnoreCase));
591592
Assert.Contains("flash:boot", transport.Commands);
592593
}
594+
595+
[Fact]
596+
public void FlashUnsparseImage_LogicalPartition_ResizesBeforeFlash()
597+
{
598+
byte[] imageBytes = new byte[64];
599+
var transport = new ProtocolDownloadCaptureTransport(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
600+
{
601+
["getvar:is-logical:system_b"] = "OKAYyes",
602+
["getvar:is-userspace"] = "OKAYyes",
603+
["resize-logical-partition:system_b:64"] = "OKAY",
604+
["flash:system_b"] = "OKAY"
605+
});
606+
607+
var util = new FastbootDriver(transport);
608+
using var stream = new MemoryStream(imageBytes);
609+
610+
var response = util.FlashUnsparseImage("system_b", stream, stream.Length);
611+
612+
Assert.Equal(FastbootState.Success, response.Result);
613+
int resizeIndex = transport.Commands.FindIndex(c => c.Equals("resize-logical-partition:system_b:64", StringComparison.OrdinalIgnoreCase));
614+
int downloadIndex = transport.Commands.FindIndex(c => c.StartsWith("download:", StringComparison.OrdinalIgnoreCase));
615+
Assert.True(resizeIndex >= 0);
616+
Assert.True(downloadIndex > resizeIndex);
617+
Assert.Contains("flash:system_b", transport.Commands);
618+
}
619+
620+
[Fact]
621+
public void FlashSparseFile_TinyLimit_FallsBackToSingleSparseTransfer()
622+
{
623+
var transport = new ProtocolDownloadCaptureTransport(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
624+
{
625+
["flash:boot"] = "OKAY"
626+
});
627+
var util = new FastbootDriver(transport);
628+
629+
using var sparse = new SparseFile(4096, 4096);
630+
sparse.AddRawChunk(new byte[4096]);
631+
632+
var response = util.FlashSparseFile("boot", sparse, 64);
633+
634+
Assert.Equal(FastbootState.Success, response.Result);
635+
Assert.Contains(transport.Commands, c => c.StartsWith("download:", StringComparison.OrdinalIgnoreCase));
636+
Assert.Contains("flash:boot", transport.Commands);
637+
}
593638
}
594639
}

FirmwareKit.Comm.Fastboot.Tests/UdpTransportTests.cs

Lines changed: 49 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ namespace FirmwareKit.Comm.Fastboot.Tests
88
{
99
public class UdpTransportTests
1010
{
11-
private const int TestTimeoutMs = 100;
12-
private const int TestMaxAttempts = 2;
11+
private const int TestTimeoutMs = 250;
12+
private const int TestMaxAttempts = 5;
1313

1414
private const byte IdError = 0x00;
1515
private const byte IdDeviceQuery = 0x01;
@@ -22,6 +22,47 @@ private static int GetFreeUdpPort()
2222
return ((IPEndPoint)udp.Client.LocalEndPoint!).Port;
2323
}
2424

25+
private static IPEndPoint CompleteHandshake(UdpClient server, ushort version = 1)
26+
{
27+
IPEndPoint remote = new(IPAddress.Loopback, 0);
28+
29+
bool initialized = false;
30+
while (!initialized)
31+
{
32+
byte[] packet = server.Receive(ref remote);
33+
if (packet.Length < 4)
34+
{
35+
continue;
36+
}
37+
38+
ushort seq = BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(2, 2));
39+
if (packet[0] == IdDeviceQuery)
40+
{
41+
byte[] qResp = new byte[6];
42+
qResp[0] = IdDeviceQuery;
43+
qResp[1] = 0x00;
44+
BinaryPrimitives.WriteUInt16BigEndian(qResp.AsSpan(2, 2), seq);
45+
BinaryPrimitives.WriteUInt16BigEndian(qResp.AsSpan(4, 2), 0);
46+
server.Send(qResp, qResp.Length, remote);
47+
continue;
48+
}
49+
50+
if (packet[0] == IdInitialization)
51+
{
52+
byte[] initResp = new byte[8];
53+
initResp[0] = IdInitialization;
54+
initResp[1] = 0x00;
55+
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(2, 2), seq);
56+
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(4, 2), version);
57+
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(6, 2), 2048);
58+
server.Send(initResp, initResp.Length, remote);
59+
initialized = true;
60+
}
61+
}
62+
63+
return remote;
64+
}
65+
2566
[Fact(Timeout = 5000)]
2667
public async Task Udp_Initialize_Success()
2768
{
@@ -31,26 +72,7 @@ public async Task Udp_Initialize_Success()
3172

3273
var serverTask = Task.Run(() =>
3374
{
34-
IPEndPoint remote = new(IPAddress.Loopback, 0);
35-
36-
byte[] q = server.Receive(ref remote);
37-
Assert.Equal(IdDeviceQuery, q[0]);
38-
Assert.Equal(0, BinaryPrimitives.ReadUInt16BigEndian(q.AsSpan(2, 2)));
39-
40-
byte[] qResp = new byte[] { IdDeviceQuery, 0x00, 0x00, 0x00, 0x00, 0x00 };
41-
server.Send(qResp, qResp.Length, remote);
42-
43-
byte[] init = server.Receive(ref remote);
44-
Assert.Equal(IdInitialization, init[0]);
45-
Assert.Equal(0, BinaryPrimitives.ReadUInt16BigEndian(init.AsSpan(2, 2)));
46-
47-
byte[] initResp = new byte[8];
48-
initResp[0] = IdInitialization;
49-
initResp[1] = 0x00;
50-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(2, 2), 0);
51-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(4, 2), 1);
52-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(6, 2), 2048);
53-
server.Send(initResp, initResp.Length, remote);
75+
_ = CompleteHandshake(server);
5476
});
5577

5678
using var transport = new UdpTransport("127.0.0.1", port, TestTimeoutMs, TestMaxAttempts);
@@ -66,19 +88,7 @@ public async Task Udp_Initialize_Fail_InvalidVersion()
6688

6789
var serverTask = Task.Run(() =>
6890
{
69-
IPEndPoint remote = new(IPAddress.Loopback, 0);
70-
_ = server.Receive(ref remote);
71-
byte[] qResp = new byte[] { IdDeviceQuery, 0x00, 0x00, 0x00, 0x00, 0x00 };
72-
server.Send(qResp, qResp.Length, remote);
73-
74-
_ = server.Receive(ref remote);
75-
byte[] initResp = new byte[8];
76-
initResp[0] = IdInitialization;
77-
initResp[1] = 0x00;
78-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(2, 2), 0);
79-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(4, 2), 0);
80-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(6, 2), 2048);
81-
server.Send(initResp, initResp.Length, remote);
91+
_ = CompleteHandshake(server, version: 0);
8292
});
8393

8494
var ex = Assert.Throws<Exception>(() => new UdpTransport("127.0.0.1", port, TestTimeoutMs, TestMaxAttempts));
@@ -95,20 +105,7 @@ public async Task Udp_WriteThenRead_Success()
95105

96106
var serverTask = Task.Run(() =>
97107
{
98-
IPEndPoint remote = new(IPAddress.Loopback, 0);
99-
100-
_ = server.Receive(ref remote);
101-
byte[] qResp = new byte[] { IdDeviceQuery, 0x00, 0x00, 0x00, 0x00, 0x00 };
102-
server.Send(qResp, qResp.Length, remote);
103-
104-
_ = server.Receive(ref remote);
105-
byte[] initResp = new byte[8];
106-
initResp[0] = IdInitialization;
107-
initResp[1] = 0x00;
108-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(2, 2), 0);
109-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(4, 2), 1);
110-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(6, 2), 2048);
111-
server.Send(initResp, initResp.Length, remote);
108+
IPEndPoint remote = CompleteHandshake(server);
112109

113110
byte[] writePacket = server.Receive(ref remote);
114111
Assert.Equal(IdFastboot, writePacket[0]);
@@ -157,20 +154,7 @@ public async Task Udp_Read_Continuation_Success()
157154

158155
var serverTask = Task.Run(() =>
159156
{
160-
IPEndPoint remote = new(IPAddress.Loopback, 0);
161-
162-
_ = server.Receive(ref remote);
163-
byte[] qResp = new byte[] { IdDeviceQuery, 0x00, 0x00, 0x00, 0x00, 0x00 };
164-
server.Send(qResp, qResp.Length, remote);
165-
166-
_ = server.Receive(ref remote);
167-
byte[] initResp = new byte[8];
168-
initResp[0] = IdInitialization;
169-
initResp[1] = 0x00;
170-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(2, 2), 0);
171-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(4, 2), 1);
172-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(6, 2), 2048);
173-
server.Send(initResp, initResp.Length, remote);
157+
IPEndPoint remote = CompleteHandshake(server);
174158

175159
byte[] readPoll1 = server.Receive(ref remote);
176160
Assert.Equal(IdFastboot, readPoll1[0]);
@@ -216,20 +200,7 @@ public async Task Udp_Write_OutOfTurnData_Fails()
216200

217201
var serverTask = Task.Run(() =>
218202
{
219-
IPEndPoint remote = new(IPAddress.Loopback, 0);
220-
221-
_ = server.Receive(ref remote);
222-
byte[] qResp = new byte[] { IdDeviceQuery, 0x00, 0x00, 0x00, 0x00, 0x00 };
223-
server.Send(qResp, qResp.Length, remote);
224-
225-
_ = server.Receive(ref remote);
226-
byte[] initResp = new byte[8];
227-
initResp[0] = IdInitialization;
228-
initResp[1] = 0x00;
229-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(2, 2), 0);
230-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(4, 2), 1);
231-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(6, 2), 2048);
232-
server.Send(initResp, initResp.Length, remote);
203+
IPEndPoint remote = CompleteHandshake(server);
233204

234205
byte[] writePacket = server.Receive(ref remote);
235206
ushort seq = BinaryPrimitives.ReadUInt16BigEndian(writePacket.AsSpan(2, 2));
@@ -258,20 +229,7 @@ public async Task Udp_Write_ErrorResponse_Fails()
258229

259230
var serverTask = Task.Run(() =>
260231
{
261-
IPEndPoint remote = new(IPAddress.Loopback, 0);
262-
263-
_ = server.Receive(ref remote);
264-
byte[] qResp = new byte[] { IdDeviceQuery, 0x00, 0x00, 0x00, 0x00, 0x00 };
265-
server.Send(qResp, qResp.Length, remote);
266-
267-
_ = server.Receive(ref remote);
268-
byte[] initResp = new byte[8];
269-
initResp[0] = IdInitialization;
270-
initResp[1] = 0x00;
271-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(2, 2), 0);
272-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(4, 2), 1);
273-
BinaryPrimitives.WriteUInt16BigEndian(initResp.AsSpan(6, 2), 2048);
274-
server.Send(initResp, initResp.Length, remote);
232+
IPEndPoint remote = CompleteHandshake(server);
275233

276234
byte[] writePacket = server.Receive(ref remote);
277235
ushort seq = BinaryPrimitives.ReadUInt16BigEndian(writePacket.AsSpan(2, 2));

FirmwareKit.Comm.Fastboot/Command/InternalFlashSparseFile.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,18 @@ public FastbootResponse FlashSparseFile(string partition, SparseFile sparseFile,
1515
return new FastbootResponse { Result = FastbootState.Fail, Response = "invalid sparse limit" };
1616
}
1717

18-
List<SparseFile> parts = sparseFile.Resparse(limit);
18+
List<SparseFile> parts;
19+
try
20+
{
21+
parts = sparseFile.Resparse(limit);
22+
}
23+
catch (InvalidOperationException)
24+
{
25+
// Some bootloaders (or tests) may report unrealistically small limits.
26+
// Fall back to a single sparse payload and let device-side checks decide.
27+
parts = new List<SparseFile> { sparseFile };
28+
}
29+
1930
if (parts.Count == 0)
2031
{
2132
return new FastbootResponse { Result = FastbootState.Fail, Response = "sparse resparse returned no parts" };

0 commit comments

Comments
 (0)