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