Skip to content

Commit 3e37a05

Browse files
committed
feat: Align Fastboot util & commands with AOSP
Add AOSP-aligned behavior, tests, and packaging - Add new AospFastbootDriverTests with a MockTransport testing GetVar, INFO/TEXT messages and download failure handling. - DownloadData (bytes/stream): expect DATA reply before sending, check for short writes and return Fail on partial write; remove local SHA256 hash computation and ensure progress reporting uses written bytes. - HandleResponse: trim response content, treat FAIL as a terminal state, and use NotifyReceived for INFO/TEXT (resetting timeout accordingly). - ErasePartition: do not auto-append slot (match AOSP behavior). - Reboot: treat empty target as plain "reboot"; map named targets to specific reboot commands. - SnapshotUpdate: emit snapshot text via NotifyReceived and update reboot messaging to use NotifyReceived; call Reboot("fastboot") when device requests it. - FastbootUtil: convert to file-scoped namespace/class layout, add optional GetVar caching (useCache parameter), add preparatory steps for logical partitions (NotifyCurrentStep, CreateLogicalPartition attempt) and minor documentation updates. - Project file: expand TargetFrameworks to multi-target (netstandard2.0/2.1;net6.0;net8.0;net9.0;net10.0), set LangVersion, and add package metadata (Authors, Description, URL, License), enable package generation. These changes align command/response handling and behavior with AOSP fastboot expectations and add unit tests to validate key interactions.
1 parent 2365fe5 commit 3e37a05

9 files changed

Lines changed: 186 additions & 50 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using FirmwareKit.Comm.Fastboot.DataModel;
2+
using FirmwareKit.Comm.Fastboot.Usb;
3+
using System.Text;
4+
using Xunit;
5+
6+
namespace FirmwareKit.Comm.Fastboot.Tests
7+
{
8+
public class AospFastbootDriverTests
9+
{
10+
private class MockTransport : IFastbootTransport
11+
{
12+
private readonly Queue<byte[]> _responses = new Queue<byte[]>();
13+
public List<string> WrittenCommands { get; } = new List<string>();
14+
15+
public void EnqueueResponse(string response)
16+
{
17+
// AOSP fastboot protocol responses are truncated at 256 bytes in some cases,
18+
// but the string itself is what matters for the test.
19+
_responses.Enqueue(Encoding.UTF8.GetBytes(response));
20+
}
21+
22+
public byte[] Read(int length)
23+
{
24+
if (_responses.Count == 0) return Array.Empty<byte>();
25+
return _responses.Dequeue();
26+
}
27+
28+
public long Write(byte[] data, int length)
29+
{
30+
string cmd = Encoding.UTF8.GetString(data, 0, length);
31+
WrittenCommands.Add(cmd);
32+
return length;
33+
}
34+
35+
public void Dispose() { }
36+
}
37+
38+
[Fact]
39+
public void Test_GetVar_Aosp()
40+
{
41+
// Ported from: fastboot_driver_test.cpp -> TEST_F(DriverTest, GetVar)
42+
var transport = new MockTransport();
43+
var util = new FastbootUtil(transport);
44+
45+
transport.EnqueueResponse("OKAY0.4");
46+
47+
string output = util.GetVar("version");
48+
49+
Assert.Equal("0.4", output);
50+
Assert.Contains("getvar:version", transport.WrittenCommands);
51+
}
52+
53+
[Fact]
54+
public void Test_InfoMessage_Aosp()
55+
{
56+
// Ported from: fastboot_driver_test.cpp -> TEST_F(DriverTest, InfoMessage)
57+
var transport = new MockTransport();
58+
var util = new FastbootUtil(transport);
59+
60+
transport.EnqueueResponse("INFOthis is an info line");
61+
transport.EnqueueResponse("OKAY");
62+
63+
var response = util.RawCommand("oem dmesg");
64+
65+
Assert.Equal(FastbootState.Success, response.Result);
66+
Assert.Single(response.Info);
67+
Assert.Equal("this is an info line", response.Info[0]);
68+
Assert.Contains("oem dmesg", transport.WrittenCommands);
69+
}
70+
71+
[Fact]
72+
public void Test_TextMessage_Aosp()
73+
{
74+
// Ported from: fastboot_driver_test.cpp -> TEST_F(DriverTest, TextMessage)
75+
var transport = new MockTransport();
76+
var util = new FastbootUtil(transport);
77+
string capturedText = "";
78+
79+
util.ReceivedFromDevice += (s, e) =>
80+
{
81+
if (e.Type == FastbootState.Text)
82+
{
83+
capturedText += e.NewText;
84+
}
85+
};
86+
87+
transport.EnqueueResponse("TEXTthis is a text line");
88+
transport.EnqueueResponse("TEXT, albeit very long and split over multiple TEXT messages.");
89+
transport.EnqueueResponse("TEXT Indeed we can do that now with a TEXT message whenever we feel like it.");
90+
transport.EnqueueResponse("TEXT Isn't that truly super cool?");
91+
transport.EnqueueResponse("OKAY");
92+
93+
var response = util.RawCommand("oem trusty runtest trusty.hwaes.bench");
94+
95+
Assert.Equal(FastbootState.Success, response.Result);
96+
string expected = "this is a text line" +
97+
", albeit very long and split over multiple TEXT messages." +
98+
" Indeed we can do that now with a TEXT message whenever we feel like it." +
99+
" Isn't that truly super cool?";
100+
Assert.Equal(expected, capturedText);
101+
// Also check the accumulated text in response object
102+
Assert.Equal(expected, response.Text);
103+
}
104+
105+
[Fact]
106+
public void Test_Download_Fail_Aosp()
107+
{
108+
// Logical check: If download command fails, should return FAIL immediately
109+
var transport = new MockTransport();
110+
var util = new FastbootUtil(transport);
111+
112+
transport.EnqueueResponse("FAILdata too large");
113+
114+
var response = util.DownloadData(new byte[1024]);
115+
116+
Assert.Equal(FastbootState.Fail, response.Result);
117+
Assert.Equal("data too large", response.Response);
118+
}
119+
}
120+
}

FirmwareKit.Comm.Fastboot/Command/DownloadDataBytes.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,16 @@ public partial class FastbootUtil
1313
public FastbootResponse DownloadData(byte[] data)
1414
{
1515
FastbootResponse response = RawCommand("download:" + data.Length.ToString("x8"));
16-
if (response.Result == FastbootState.Fail)
16+
if (response.Result != FastbootState.Data)
1717
return response;
18-
Transport.Write(data, data.Length);
19-
var res = HandleResponse();
20-
if (res.Result == FastbootState.Success)
18+
19+
long written = Transport.Write(data, data.Length);
20+
if (written != data.Length)
2121
{
22-
using var sha256 = SHA256.Create();
23-
byte[] hash = sha256.ComputeHash(data);
24-
res.Hash = BitConverter.ToString(hash).Replace("-", "").ToLower();
22+
return new FastbootResponse { Result = FastbootState.Fail, Response = $"Short write: {written}/{data.Length}" };
2523
}
26-
return res;
24+
25+
return HandleResponse();
2726
}
2827
}
2928
}

FirmwareKit.Comm.Fastboot/Command/DownloadDataStream.cs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,30 @@ public partial class FastbootUtil
1212
/// </summary>
1313
public FastbootResponse DownloadData(Stream stream, long length, bool onEvent = true)
1414
{
15+
// AOSP uses %08" PRIx32 which is 8 chars hex with leading zeros
1516
FastbootResponse response = RawCommand("download:" + length.ToString("x8"));
16-
if (response.Result == FastbootState.Fail)
17+
if (response.Result != FastbootState.Data)
1718
return response;
1819

19-
using var sha256 = SHA256.Create();
2020
byte[] buffer = new byte[OnceSendDataSize];
21-
long bytesRead = 0;
22-
while (bytesRead < length)
21+
long bytesWritten = 0;
22+
while (bytesWritten < length)
2323
{
24-
int toRead = (int)Math.Min(OnceSendDataSize, length - bytesRead);
24+
int toRead = (int)Math.Min(OnceSendDataSize, length - bytesWritten);
2525
int readSize = stream.Read(buffer, 0, toRead);
2626
if (readSize <= 0) break;
2727

28-
sha256.TransformBlock(buffer, 0, readSize, null, 0);
29-
Transport.Write(buffer, readSize);
30-
bytesRead += readSize;
28+
long written = Transport.Write(buffer, readSize);
29+
if (written != readSize)
30+
{
31+
return new FastbootResponse { Result = FastbootState.Fail, Response = $"Short write: {written}/{readSize}" };
32+
}
33+
bytesWritten += written;
3134
if (onEvent)
32-
NotifyProgress(bytesRead, length);
35+
NotifyProgress(bytesWritten, length);
3336
}
34-
sha256.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
3537

36-
var res = HandleResponse();
37-
if (res.Result == FastbootState.Success && sha256.Hash != null)
38-
{
39-
res.Hash = BitConverter.ToString(sha256.Hash).Replace("-", "").ToLower();
40-
}
41-
return res;
38+
return HandleResponse();
4239
}
4340
}
4441
}

FirmwareKit.Comm.Fastboot/Command/ErasePartition.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ public partial class FastbootUtil
66
{
77
public FastbootResponse ErasePartition(string partition)
88
{
9-
if (HasSlot(partition))
10-
{
11-
partition += "_" + GetCurrentSlot();
12-
}
9+
// AOSP erase does not automatically append slot.
10+
// FastbootDriver::Erase(const std::string& partition, ...)
11+
// return RawCommand(FB_CMD_ERASE ":" + partition, ...);
1312
return RawCommand("erase:" + partition);
1413
}
1514
}

FirmwareKit.Comm.Fastboot/Command/HandleResponse.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,31 @@ public FastbootResponse HandleResponse()
4444
}
4545

4646
string prefix = devStatus.Substring(0, 4);
47-
string content = devStatus.Substring(4);
47+
string content = devStatus.Trim().Substring(4);
4848

49-
if (prefix == "OKAY" || prefix == "FAIL")
49+
if (prefix == "OKAY")
5050
{
51-
response.Result = prefix == "OKAY" ? FastbootState.Success : FastbootState.Fail;
51+
response.Result = FastbootState.Success;
5252
response.Response = content;
5353
return response;
5454
}
55+
else if (prefix == "FAIL")
56+
{
57+
response.Result = FastbootState.Fail;
58+
response.Response = content;
59+
// AOSP: FAIL is a terminal state
60+
return response;
61+
}
5562
else if (prefix == "INFO")
5663
{
5764
response.Info.Add(content);
58-
ReceivedFromDevice?.Invoke(this, new FastbootReceivedFromDeviceEventArgs(FastbootState.Info, content));
59-
start = DateTime.Now;
65+
NotifyReceived(FastbootState.Info, content);
66+
start = DateTime.Now; // Reset timeout for progress
6067
}
6168
else if (prefix == "TEXT")
6269
{
6370
response.Text += content;
64-
ReceivedFromDevice?.Invoke(this, new FastbootReceivedFromDeviceEventArgs(FastbootState.Text, null, content));
71+
NotifyReceived(FastbootState.Text, null, content);
6572
start = DateTime.Now;
6673
}
6774
else if (prefix == "DATA")

FirmwareKit.Comm.Fastboot/Command/Reboot.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ namespace FirmwareKit.Comm.Fastboot
55
{
66
public partial class FastbootUtil
77
{
8-
public FastbootResponse Reboot(string target = "system")
8+
public FastbootResponse Reboot(string target = "")
99
{
10+
if (string.IsNullOrEmpty(target)) return RawCommand("reboot");
1011
if (target == "recovery") return RawCommand("reboot-recovery");
1112
if (target == "bootloader") return RawCommand("reboot-bootloader");
1213
if (target == "fastboot") return RawCommand("reboot-fastboot");
13-
if (target == "system") return RawCommand("reboot");
1414
return RawCommand("reboot-" + target);
1515
}
1616
}

FirmwareKit.Comm.Fastboot/Command/SnapshotUpdate.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ public partial class FastbootUtil
1010
/// </summary>
1111
public FastbootResponse SnapshotUpdate(string action = "cancel")
1212
{
13-
if (action != "cancel" && action != "merge")
14-
throw new ArgumentException("SnapshotUpdate action must be 'cancel' or 'merge'");
13+
// AOSP SnapshotUpdateCommand: prolog_("Snapshot %s", command.c_str()); RawCommand(FB_CMD_SNAPSHOT_UPDATE ":" + command, ...);
14+
NotifyReceived(FastbootState.Text, $"Snapshot {action}");
1515
var res = RawCommand("snapshot-update:" + action);
1616
if (res.Response.Contains("reboot fastboot", StringComparison.OrdinalIgnoreCase))
1717
{
18-
NotifyCurrentStep("Device requested reboot to fastbootd to finish snapshot action...");
18+
NotifyReceived(FastbootState.Text, "Device requested reboot to fastbootd to finish snapshot action...");
1919
Reboot("fastboot");
2020
}
2121
return res;

FirmwareKit.Comm.Fastboot/FastbootUtil.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
using System.Security.Cryptography;
99
using System.Text;
1010

11-
namespace FirmwareKit.Comm.Fastboot
11+
namespace FirmwareKit.Comm.Fastboot;
12+
13+
public partial class FastbootUtil
1214
{
13-
public partial class FastbootUtil
14-
{
15-
/// <summary>
16-
/// Determines whether the device is in fastbootd (userspace) mode.
17-
/// </summary>
15+
/// <summary>
16+
/// Determines whether the device is in fastbootd (userspace) mode.
17+
/// </summary>
1818
public bool IsUserspace()
1919
{
2020
try
@@ -200,13 +200,13 @@ public Dictionary<string, string> GetVarAll()
200200
}
201201

202202
/// <summary>
203-
/// Gets a single attribute (with caching)
203+
/// Gets a single attribute (with caching if enabled)
204204
/// </summary>
205-
public string GetVar(string key)
205+
public string GetVar(string key, bool useCache = true)
206206
{
207-
if (_varCache.TryGetValue(key, out string? cached)) return cached;
207+
if (useCache && _varCache.TryGetValue(key, out string? cached)) return cached;
208208
var res = RawCommand("getvar:" + key).ThrowIfError().Response;
209-
_varCache[key] = res;
209+
if (useCache) _varCache[key] = res;
210210
return res;
211211
}
212212

@@ -1318,6 +1318,14 @@ public void FlashAll(string productOutDir, bool wipe = false, bool skipSecondary
13181318
string part = Path.GetFileNameWithoutExtension(logImg);
13191319
if (IsLogicalOptimized(part))
13201320
{
1321+
NotifyCurrentStep($"Preparing logical partition {part}...");
1322+
try
1323+
{
1324+
// To align with AOSP behavior, we ensure the partition exists and is cleared
1325+
CreateLogicalPartition(part, 0);
1326+
}
1327+
catch { /* Ignore if already exists or not supported */ }
1328+
13211329
try { ResizeLogicalPartition(part, 0); } catch { }
13221330
}
13231331
}

FirmwareKit.Comm.Fastboot/FirmwareKit.Comm.Fastboot.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net10.0</TargetFramework>
4+
<TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0;net10.0</TargetFrameworks>
5+
<LangVersion>latest</LangVersion>
56
<ImplicitUsings>enable</ImplicitUsings>
67
<Nullable>enable</Nullable>
78
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
89
<IsAotCompatible>true</IsAotCompatible>
10+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
11+
<Authors>SharpFastboot Contributors</Authors>
12+
<Description>A high-performance C# implementation of the Android Fastboot protocol, aligned with AOSP.</Description>
13+
<PackageProjectUrl>https://github.com/your-repo/SharpFastboot</PackageProjectUrl>
14+
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
915
</PropertyGroup>
1016

1117
<ItemGroup>

0 commit comments

Comments
 (0)