Skip to content

Commit 2b954f2

Browse files
author
Bas Visscher
committed
Merge branch 'copilot/fix-34'
2 parents 9d8296a + 1f68195 commit 2b954f2

File tree

5 files changed

+190
-49
lines changed

5 files changed

+190
-49
lines changed

ESPTool/Communication/Communicator.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,18 @@ public async Task<int> ReadRawAsync(byte[] buffer, CancellationToken token)
130130
return await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, token);
131131
}
132132

133+
/// <summary>
134+
/// Writes raw data asynchronously to the serial port.
135+
/// </summary>
136+
/// <param name="data">The data to write.</param>
137+
/// <param name="token">Cancellation token to cancel the operation.</param>
138+
/// <returns>A task representing the asynchronous operation.</returns>
139+
/// <exception cref="OperationCanceledException">Thrown if the operation is canceled via the cancellation token.</exception>
140+
public async Task WriteAsync(byte[] data, CancellationToken token)
141+
{
142+
await _serialPort.BaseStream.WriteAsync(data, 0, data.Length, token);
143+
}
144+
133145

134146
/// <summary>
135147
/// Disposes the serial port and associated resources.
@@ -142,5 +154,10 @@ public void Dispose()
142154
}
143155
_serialPort.Dispose();
144156
}
157+
158+
internal async Task FlushAsync(CancellationToken token)
159+
{
160+
await _serialPort.BaseStream.FlushAsync(token);
161+
}
145162
}
146163
}

ESPTool/ESPToolbox.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ public async Task<byte[]> ReadEfuseAsync(ILoader loader, ChipTypes chipType, EFl
9090
return await tool.ReadAsync(key, token);
9191
}
9292

93+
public ReadFlashTool CreateReadFlashTool(Communicator communicator, SoftLoader softLoader, ChipTypes chipType)
94+
{
95+
var deviceConfig = GetDeviceConfig(chipType);
96+
return new ReadFlashTool(softLoader, communicator);
97+
}
98+
9399
public IUploadTool CreateUploadRamTool(ILoader loader, ChipTypes chipType)
94100
{
95101
return new UploadRamTool(loader)

ESPTool/Example.cs

Lines changed: 0 additions & 45 deletions
This file was deleted.

ESPTool/Loaders/SoftLoader/SoftLoader.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
using EspDotNet.Commands;
22
using EspDotNet.Communication;
3-
using EspDotNet.Loaders;
4-
using System;
5-
using System.Threading;
6-
using System.Threading.Tasks;
3+
using System.Diagnostics;
74

85
namespace EspDotNet.Loaders.SoftLoader
96
{
@@ -60,6 +57,29 @@ public async Task<byte[]> SPI_FLASH_MD5(uint address, uint size, CancellationTok
6057
return response.Payload.Take(16).ToArray(); // Return the MD5 checksum (first 16 bytes)
6158
}
6259

60+
public async Task FlashReadBeginAsync(uint address, uint size, uint blockSize, uint inflight, CancellationToken token)
61+
{
62+
var request = new RequestCommandBuilder()
63+
.WithCommand(0xD2)
64+
.AppendPayload(BitConverter.GetBytes(address))
65+
.AppendPayload(BitConverter.GetBytes(size))
66+
.AppendPayload(BitConverter.GetBytes(blockSize))
67+
.AppendPayload(BitConverter.GetBytes(inflight))
68+
.Build();
69+
70+
71+
var response = await _commandExecutor.ExecuteCommandAsync(request, token);
72+
if (!response.Success)
73+
throw new InvalidOperationException("Failed to begin flash read.");
74+
}
75+
76+
public async Task FlashReadAckAsync(uint totalBytesReceived, CancellationToken token)
77+
{
78+
var ackData = BitConverter.GetBytes(totalBytesReceived);
79+
await _communicator.WriteFrameAsync(new Frame(ackData), token);
80+
await _communicator.FlushAsync(token);
81+
}
82+
6383
/// <summary>
6484
/// Begins the flash process using compressed data.
6585
/// </summary>

ESPTool/Tools/ReadFlashTool.cs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using EspDotNet.Communication;
2+
using EspDotNet.Loaders.SoftLoader;
3+
using System.Security.Cryptography;
4+
5+
namespace EspDotNet.Tools
6+
{
7+
public class ReadFlashTool
8+
{
9+
public IProgress<float> Progress { get; set; } = new Progress<float>();
10+
public uint SectorSize { get; set; } = 4096;
11+
public uint BlockSize { get; set; } = 4096;
12+
public uint MaxInFlight { get; set; } = 1;
13+
14+
private readonly SoftLoader _softLoader;
15+
private readonly Communicator _communicator;
16+
17+
public ReadFlashTool(SoftLoader softLoader, Communicator communicator)
18+
{
19+
_softLoader = softLoader;
20+
_communicator = communicator;
21+
}
22+
23+
/// <summary>
24+
/// A wrapper stream that computes MD5 hash while writing to the underlying stream.
25+
/// </summary>
26+
private class MD5Stream : Stream
27+
{
28+
private readonly Stream _baseStream;
29+
private readonly MD5 _md5;
30+
31+
public MD5Stream(Stream baseStream)
32+
{
33+
_baseStream = baseStream;
34+
_md5 = MD5.Create();
35+
}
36+
37+
public byte[] FinalizeAndGetHash()
38+
{
39+
_md5.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
40+
return _md5.Hash ?? throw new InvalidOperationException("Hash not computed yet.");
41+
}
42+
43+
public override bool CanRead => false;
44+
public override bool CanSeek => false;
45+
public override bool CanWrite => _baseStream.CanWrite;
46+
public override long Length => _baseStream.Length;
47+
public override long Position { get => _baseStream.Position; set => _baseStream.Position = value; }
48+
49+
public override void Flush() => _baseStream.Flush();
50+
51+
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
52+
53+
public override long Seek(long offset, SeekOrigin origin) => _baseStream.Seek(offset, origin);
54+
55+
public override void SetLength(long value) => _baseStream.SetLength(value);
56+
57+
public override void Write(byte[] buffer, int offset, int count)
58+
{
59+
_md5.TransformBlock(buffer, offset, count, null, 0);
60+
_baseStream.Write(buffer, offset, count);
61+
}
62+
63+
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
64+
{
65+
_md5.TransformBlock(buffer, offset, count, null, 0);
66+
await _baseStream.WriteAsync(buffer, offset, count, cancellationToken);
67+
}
68+
69+
protected override void Dispose(bool disposing)
70+
{
71+
_md5.Dispose();
72+
base.Dispose(disposing);
73+
}
74+
}
75+
76+
77+
public async Task ReadFlashAsync(uint address, uint size, Stream outputStream, CancellationToken token)
78+
{
79+
if (outputStream == null)
80+
throw new ArgumentNullException(nameof(outputStream));
81+
if (!outputStream.CanWrite)
82+
throw new ArgumentException("Output stream must be writable.", nameof(outputStream));
83+
84+
using var md5Stream = new MD5Stream(outputStream);
85+
86+
await _softLoader.FlashReadBeginAsync(address, size, BlockSize, MaxInFlight, token);
87+
88+
uint totalReceived = 0;
89+
90+
while (totalReceived < size)
91+
{
92+
totalReceived += await ReadFlashBlockAsync(md5Stream, totalReceived, token);
93+
Progress.Report((float)totalReceived / size);
94+
}
95+
96+
await VerifyMd5Async(address, size, md5Stream, token);
97+
}
98+
99+
private async Task<uint> ReadFlashBlockAsync(MD5Stream md5Stream, uint totalReceived, CancellationToken token)
100+
{
101+
var frame = await _communicator.ReadFrameAsync(token);
102+
if (frame?.Data == null)
103+
throw new InvalidOperationException("Failed to receive flash data frame.");
104+
105+
await md5Stream.WriteAsync(frame.Data, 0, frame.Data.Length, token);
106+
107+
await _softLoader.FlashReadAckAsync(totalReceived + (uint)frame.Data.Length, token);
108+
109+
return (uint)frame.Data.Length;
110+
}
111+
112+
113+
114+
115+
116+
117+
118+
private async Task VerifyMd5Async(uint address, uint size, MD5Stream stream, CancellationToken token)
119+
{
120+
var hashFrame = await _communicator.ReadFrameAsync(token);
121+
var computedHash = stream.FinalizeAndGetHash();
122+
var expectedHash = hashFrame?.Data ?? throw new Exception("Expected hash frame");
123+
124+
for (int i = 0; i < 16; i++)
125+
{
126+
if (computedHash[i] != expectedHash[i])
127+
{
128+
var expected = BitConverter.ToString(expectedHash).Replace("-", "");
129+
var computed = BitConverter.ToString(computedHash).Replace("-", "");
130+
throw new InvalidOperationException(
131+
$"MD5 verification failed: expected {expected}, got {computed}");
132+
}
133+
}
134+
}
135+
136+
137+
138+
139+
140+
141+
142+
}
143+
}

0 commit comments

Comments
 (0)