4
4
5
5
import 'dart:typed_data' ;
6
6
7
- import 'package:typed_data/typed_data.dart' ;
8
-
9
7
import 'digest.dart' ;
10
8
import 'utils.dart' ;
11
9
@@ -19,11 +17,24 @@ abstract class HashSink implements Sink<List<int>> {
19
17
/// Whether the hash function operates on big-endian words.
20
18
final Endian _endian;
21
19
22
- /// The words in the current chunk.
20
+ /// A [ByteData] view of the current chunk of data.
21
+ ///
22
+ /// This is an instance variable to avoid re-allocating.
23
+ ByteData ? _byteDataView;
24
+
25
+ /// The actual chunk of bytes currently accumulating.
26
+ ///
27
+ /// The same allocation will be reused over and over again; once full it is
28
+ /// passed to the underlying hashing algorithm for processing.
29
+ final Uint8List _chunk;
30
+
31
+ /// The index of the next insertion into the chunk.
32
+ int _chunkNextIndex;
33
+
34
+ /// A [Uint32List] (in specified endian) copy of the chunk.
23
35
///
24
- /// This is an instance variable to avoid re-allocating, but its data isn't
25
- /// used across invocations of [_iterate] .
26
- final Uint32List _currentChunk;
36
+ /// This is an instance variable to avoid re-allocating.
37
+ final Uint32List _chunk32;
27
38
28
39
/// Messages with more than 2^53-1 bits are not supported.
29
40
///
@@ -35,9 +46,6 @@ abstract class HashSink implements Sink<List<int>> {
35
46
/// The length of the input data so far, in bytes.
36
47
int _lengthInBytes = 0 ;
37
48
38
- /// Data that has yet to be processed by the hash function.
39
- final _pendingData = Uint8Buffer ();
40
-
41
49
/// Whether [close] has been called.
42
50
bool _isClosed = false ;
43
51
@@ -66,7 +74,9 @@ abstract class HashSink implements Sink<List<int>> {
66
74
}) : _endian = endian,
67
75
assert (signatureBytes >= 8 ),
68
76
_signatureBytes = signatureBytes,
69
- _currentChunk = Uint32List (chunkSizeInWords);
77
+ _chunk = Uint8List (chunkSizeInWords * bytesPerWord),
78
+ _chunkNextIndex = 0 ,
79
+ _chunk32 = Uint32List (chunkSizeInWords);
70
80
71
81
/// Runs a single iteration of the hash computation, updating [digest] with
72
82
/// the result.
@@ -79,18 +89,47 @@ abstract class HashSink implements Sink<List<int>> {
79
89
void add (List <int > data) {
80
90
if (_isClosed) throw StateError ('Hash.add() called after close().' );
81
91
_lengthInBytes += data.length;
82
- _pendingData.addAll (data);
83
- _iterate ();
92
+ _addData (data);
93
+ }
94
+
95
+ void _addData (List <int > data) {
96
+ var dataIndex = 0 ;
97
+ var chunkNextIndex = _chunkNextIndex;
98
+ final size = _chunk.length;
99
+ _byteDataView ?? = _chunk.buffer.asByteData ();
100
+ while (true ) {
101
+ // Check if there is enough data left in [data] for a full chunk.
102
+ var restEnd = chunkNextIndex + data.length - dataIndex;
103
+ if (restEnd < size) {
104
+ // There is not enough data, so just add into [_chunk].
105
+ _chunk.setRange (chunkNextIndex, restEnd, data, dataIndex);
106
+ _chunkNextIndex = restEnd;
107
+ return ;
108
+ }
109
+
110
+ // There is enough data to fill the chunk. Fill it and process it.
111
+ _chunk.setRange (chunkNextIndex, size, data, dataIndex);
112
+ dataIndex += size - chunkNextIndex;
113
+
114
+ // Now do endian conversion to words.
115
+ var j = 0 ;
116
+ do {
117
+ _chunk32[j] = _byteDataView! .getUint32 (j * bytesPerWord, _endian);
118
+ j++ ;
119
+ } while (j < _chunk32.length);
120
+
121
+ updateHash (_chunk32);
122
+ chunkNextIndex = 0 ;
123
+ }
84
124
}
85
125
86
126
@override
87
127
void close () {
88
128
if (_isClosed) return ;
89
129
_isClosed = true ;
90
130
91
- _finalizeData ();
92
- _iterate ();
93
- assert (_pendingData.isEmpty);
131
+ _finalizeAndProcessData ();
132
+ assert (_chunkNextIndex == 0 );
94
133
_sink.add (Digest (_byteDigest ()));
95
134
_sink.close ();
96
135
}
@@ -108,65 +147,38 @@ abstract class HashSink implements Sink<List<int>> {
108
147
return byteDigest;
109
148
}
110
149
111
- /// Iterates through [_pendingData] , updating the hash computation for each
112
- /// chunk.
113
- void _iterate () {
114
- var pendingDataBytes = _pendingData.buffer.asByteData ();
115
- var pendingDataChunks = _pendingData.length ~ / _currentChunk.lengthInBytes;
116
- for (var i = 0 ; i < pendingDataChunks; i++ ) {
117
- // Copy words from the pending data buffer into the current chunk buffer.
118
- for (var j = 0 ; j < _currentChunk.length; j++ ) {
119
- _currentChunk[j] = pendingDataBytes.getUint32 (
120
- i * _currentChunk.lengthInBytes + j * bytesPerWord,
121
- _endian,
122
- );
123
- }
124
-
125
- // Run the hash function on the current chunk.
126
- updateHash (_currentChunk);
127
- }
128
-
129
- // Remove all pending data up to the last clean chunk break.
130
- _pendingData.removeRange (
131
- 0 ,
132
- pendingDataChunks * _currentChunk.lengthInBytes,
133
- );
134
- }
135
-
136
- /// Finalizes [_pendingData] .
150
+ /// Finalizes the data and finishes the hash.
137
151
///
138
152
/// This adds a 1 bit to the end of the message, and expands it with 0 bits to
139
153
/// pad it out.
140
- void _finalizeData () {
141
- // Pad out the data with 0x80, eight or sixteen 0s, and as many more 0s
142
- // as we need to land cleanly on a chunk boundary.
143
- _pendingData.add (0x80 );
154
+ void _finalizeAndProcessData () {
155
+ if (_lengthInBytes > _maxMessageLengthInBytes) {
156
+ throw UnsupportedError (
157
+ 'Hashing is unsupported for messages with more than 2^53 bits.' ,
158
+ );
159
+ }
144
160
145
161
final contentsLength = _lengthInBytes + 1 /* 0x80 */ + _signatureBytes;
146
162
final finalizedLength = _roundUp (
147
163
contentsLength,
148
- _currentChunk .lengthInBytes,
164
+ _chunk .lengthInBytes,
149
165
);
150
166
151
- for (var i = 0 ; i < finalizedLength - contentsLength; i++ ) {
152
- _pendingData.add (0 );
153
- }
167
+ // Prepare the finalization data.
168
+ var padding = Uint8List (finalizedLength - _lengthInBytes);
169
+ // Pad out the data with 0x80, eight or sixteen 0s, and as many more 0s
170
+ // as we need to land cleanly on a chunk boundary.
171
+ padding[0 ] = 0x80 ;
154
172
155
- if (_lengthInBytes > _maxMessageLengthInBytes) {
156
- throw UnsupportedError (
157
- 'Hashing is unsupported for messages with more than 2^53 bits.' ,
158
- );
159
- }
173
+ // The rest is already 0-bytes.
160
174
161
175
var lengthInBits = _lengthInBytes * bitsPerByte;
162
176
163
177
// Add the full length of the input data as a 64-bit value at the end of the
164
178
// hash. Note: we're only writing out 64 bits, so skip ahead 8 if the
165
179
// signature is 128-bit.
166
- final offset = _pendingData.length + (_signatureBytes - 8 );
167
-
168
- _pendingData.addAll (Uint8List (_signatureBytes));
169
- var byteData = _pendingData.buffer.asByteData ();
180
+ final offset = padding.length - 8 ;
181
+ var byteData = padding.buffer.asByteData ();
170
182
171
183
// We're essentially doing byteData.setUint64(offset, lengthInBits, _endian)
172
184
// here, but that method isn't supported on dart2js so we implement it
@@ -180,6 +192,8 @@ abstract class HashSink implements Sink<List<int>> {
180
192
byteData.setUint32 (offset, lowBits, _endian);
181
193
byteData.setUint32 (offset + bytesPerWord, highBits, _endian);
182
194
}
195
+
196
+ _addData (padding);
183
197
}
184
198
185
199
/// Rounds [val] up to the next multiple of [n] , as long as [n] is a power of
0 commit comments