@@ -5,156 +5,234 @@ This Source Code Form is subject to the terms of the
55at http://mozilla.org/MPL/2.0/.
66----------------------------------------------------------*/
77
8- using System ;
9- using System . IO ;
10- using System . Security . Cryptography ;
11- using System . Text ;
128using OneScript . Contexts ;
139using OneScript . Exceptions ;
1410using OneScript . StandardLibrary . Binary ;
1511using OneScript . Values ;
1612using ScriptEngine . Machine ;
1713using ScriptEngine . Machine . Contexts ;
14+ using System ;
15+ using System . IO ;
16+ using System . Security . Cryptography ;
17+ using System . Text ;
1818
1919namespace OneScript . StandardLibrary . Hash
20- {
20+ {
21+ /// <summary>
22+ /// Реализует инкрементальный расчет хеш-суммы по добавленным данным.
23+ /// Тип вычисляемого значения определяются типом хеш-функции.
24+ /// </summary>
2125 [ ContextClass ( "ХешированиеДанных" , "DataHashing" ) ]
2226 public class HashImpl : AutoContext < HashImpl > , IDisposable
23- {
24- private HashAlgorithm _provider ;
25- private HashFunctionEnum _enumValue ;
26- private CombinedStream _toCalculate = new CombinedStream ( ) ;
27- private bool _calculated ;
27+ {
28+ private const int BUFFER_SIZE = ( 1024 * 32 ) ;
29+
30+ private readonly Crc32 _crc32 ;
31+ private readonly IncrementalHash _provider ;
32+ private readonly HashFunctionEnum _enumValue ;
2833 private byte [ ] _hash ;
2934
30- public HashImpl ( HashAlgorithm provider , HashFunctionEnum enumValue )
35+ public HashImpl ( IncrementalHash provider , HashFunctionEnum enumValue )
3136 {
3237 _provider = provider ;
3338 _enumValue = enumValue ;
34- _calculated = false ;
35- }
36-
37- public byte [ ] InternalHash
38- {
39- get
40- {
41- if ( ! _calculated )
42- {
43- _hash = _provider . ComputeHash ( _toCalculate ) ;
44- _calculated = true ;
45- }
46- return _hash ;
47- }
48- }
49-
39+ if ( enumValue == HashFunctionEnum . CRC32 )
40+ {
41+ _crc32 = new Crc32 ( ) ;
42+ }
43+ else ArgumentNullException . ThrowIfNull ( provider ) ;
44+
45+ AppendData ( Array . Empty < byte > ( ) ) ;
46+ }
47+
48+ /// <summary>
49+ /// Вид хеш-функции, определяющий способ вычисления хеш-суммы.
50+ /// Только для чтения
51+ /// </summary>
52+ /// <value>Перечисление ХешФункция</value>
5053 [ ContextProperty ( "ХешФункция" , "HashFunction" ) ]
51- public HashFunctionEnum Extension => _enumValue ;
52-
54+ public HashFunctionEnum HashFunction => _enumValue ;
55+
56+ /// <summary>
57+ /// Текущее значение хеш-суммы. Только для чтения
58+ /// </summary>
59+ /// <value>Для хеш-функции CRC32 - Число, для остальных - ДвоичныеДанные
60+ /// </value>
5361 [ ContextProperty ( "ХешСумма" , "HashSum" ) ]
5462 public IValue Hash
5563 {
5664 get
5765 {
58- if ( _provider is Crc32 )
59- {
60- var buffer = new byte [ 4 ] ;
61- Array . Copy ( InternalHash , buffer , 4 ) ;
62- if ( BitConverter . IsLittleEndian )
63- Array . Reverse ( buffer ) ;
64- var ret = BitConverter . ToUInt32 ( buffer , 0 ) ;
65- return ValueFactory . Create ( ( decimal ) ret ) ;
66- }
67- return new BinaryDataContext ( InternalHash ) ;
66+ if ( _crc32 != null )
67+ return ValueFactory . Create ( _crc32 . GetCurrentHashAsUInt32 ( ) ) ;
68+
69+ return new BinaryDataContext ( _hash ) ;
6870 }
69- }
70-
71+ }
72+
73+ /// <summary>
74+ /// Нестандартное расширение!
75+ /// Строковое представление текущего значения хеш-суммы. Только для чтения
76+ /// </summary>
77+ /// <value>Для хеш-функции CRC32 - Число, для остальных - ДвоичныеДанные</value>
7178 [ ContextProperty ( "ХешСуммаСтрокой" , "HashSumOfString" ) ]
7279 public string HashString
7380 {
7481 get
75- {
82+ {
83+ if ( _crc32 != null )
84+ return _crc32 . GetCurrentHashAsUInt32 ( ) . ToString ( "X8" ) ;
85+
7686 var sb = new StringBuilder ( ) ;
77- for ( int i = 0 ; i < InternalHash . Length ; i ++ )
78- sb . Append ( InternalHash [ i ] . ToString ( "X2" ) ) ;
87+ for ( int i = 0 ; i < _hash . Length ; i ++ )
88+ sb . Append ( _hash [ i ] . ToString ( "X2" ) ) ;
7989 return sb . ToString ( ) ;
8090 }
8191 }
8292
93+ private void AppendData ( byte [ ] data )
94+ {
95+ AppendData ( data , data . Length ) ;
96+ }
8397
98+ private void AppendData ( byte [ ] data , int count )
99+ {
100+ if ( _crc32 != null )
101+ {
102+ _crc32 . AppendData ( data , 0 , count ) ;
103+ }
104+ else
105+ {
106+ _provider . AppendData ( data , 0 , count ) ;
107+ _hash = _provider . GetCurrentHash ( ) ;
108+ }
109+ }
110+
111+ private void AppendStream ( Stream stream )
112+ {
113+ var buffer = new byte [ BUFFER_SIZE ] ;
114+ while ( true )
115+ {
116+ var read = stream . Read ( buffer , 0 , BUFFER_SIZE ) ;
117+ if ( read == 0 )
118+ break ;
119+
120+ AppendData ( buffer , read ) ;
121+ }
122+ }
123+
124+ private void AppendStream ( Stream stream , int count )
125+ {
126+ if ( count <= 0 )
127+ {
128+ AppendStream ( stream ) ;
129+ return ;
130+ }
131+
132+ int bufSize = Math . Min ( BUFFER_SIZE , count ) ;
133+ var buffer = new byte [ bufSize ] ;
134+ int toRead = count ;
135+ while ( toRead > 0 )
136+ {
137+ var read = stream . Read ( buffer , 0 , Math . Min ( toRead , bufSize ) ) ;
138+ if ( read == 0 )
139+ break ;
140+
141+ AppendData ( buffer , read ) ;
142+ toRead -= read ;
143+ }
144+ }
145+
146+ /// <summary>
147+ /// Добавляет данные и обновляет хеш-сумму
148+ /// </summary>
149+ /// <param name="toAdd">Источник данных. Строка, ДвоичныеДанные или Поток</param>
150+ /// <param name="count">Для источника данных типов Строка или ДвоичныеДанные - игнорируется.
151+ /// Для источника данных типа Поток - Количество байтов, которые читаются из потока.
152+ /// Если количество не задано, нулевое <b>или отрицательное</b>, то читаются все данные до конца потока.
153+ /// </param>
84154 [ ContextMethod ( "Добавить" , "Append" ) ]
85- public void Append ( BslValue toAdd , uint count = 0 )
155+ public void Append ( BslValue toAdd , BslValue count = null )
86156 {
87157 switch ( toAdd )
88158 {
89159 case BslStringValue s :
90- AddStream ( new MemoryStream ( Encoding . UTF8 . GetBytes ( ( string ) s ) ) ) ;
160+ AppendData ( Encoding . UTF8 . GetBytes ( ( string ) s ) ) ;
91161 break ;
92- case BslObjectValue obj when obj is IStreamWrapper wrapper :
93- var stream = wrapper . GetUnderlyingStream ( ) ;
94- var readByte = ( int ) Math . Min ( count == 0 ? stream . Length : count , stream . Length - stream . Position ) ;
95- var buffer = new byte [ readByte ] ;
96- stream . Read ( buffer , 0 , readByte ) ;
97- AddStream ( new MemoryStream ( buffer ) ) ;
162+ case BinaryDataContext binaryData :
163+ AppendStream ( binaryData . GetStream ( ) ) ;
98164 break ;
99- case BslObjectValue obj when obj is BinaryDataContext binaryData :
100- AddStream ( binaryData . GetStream ( ) ) ;
165+
166+ case IStreamWrapper wrapper :
167+ var stream = wrapper . GetUnderlyingStream ( ) ;
168+ if ( count == null )
169+ {
170+ AppendStream ( stream ) ;
171+ }
172+ else
173+ {
174+ int cnt ;
175+ try
176+ {
177+ cnt = ( int ) count ;
178+ }
179+ catch
180+ {
181+ if ( count is BslStringValue )
182+ throw RuntimeException . InvalidNthArgumentValue ( 2 ) ;
183+ else
184+ throw RuntimeException . InvalidNthArgumentType ( 2 ) ;
185+ }
186+
187+ AppendStream ( stream , cnt ) ;
188+ }
101189 break ;
190+
102191 default :
103- throw RuntimeException . InvalidArgumentType ( nameof ( toAdd ) ) ;
192+ throw RuntimeException . InvalidNthArgumentType ( 1 ) ;
104193 }
105194 }
106195
196+ /// <summary>
197+ /// Добавляет двоичные данные из файла и обновляет хеш-сумму
198+ /// </summary>
199+ /// <param name="path">Имя файла, из которого читаются данные. Тип: Строка</param>
107200 [ ContextMethod ( "ДобавитьФайл" , "AppendFile" ) ]
108201 public void AppendFile ( string path )
109202 {
110203 if ( ! File . Exists ( path ) )
111- throw RuntimeException . InvalidArgumentType ( ) ;
112- AddStream ( new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ) ;
204+ throw RuntimeException . InvalidArgumentType ( ) ;
205+
206+ using var stream = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ;
207+ AppendStream ( stream ) ;
113208 }
114209
115- [ ContextMethod ( "Очистить" , "Clear" ) ]
116- public void Clear ( )
117- {
118- _toCalculate . Close ( ) ;
119- _toCalculate . Dispose ( ) ;
120- _toCalculate = new CombinedStream ( ) ;
121- _calculated = false ;
122- }
123-
124-
125210 [ ScriptConstructor ( Name = "По указанной хеш-функции" ) ]
126211 public static HashImpl Constructor ( HashFunctionEnum providerEnum )
127- {
128- var objectProvider = GetProvider ( providerEnum ) ;
212+ {
213+ if ( providerEnum == HashFunctionEnum . CRC32 )
214+ return new HashImpl ( null , providerEnum ) ;
215+
216+ var objectProvider = IncrementalHash . CreateHash ( GetAlgorithmName ( providerEnum ) ) ;
129217 return new HashImpl ( objectProvider , providerEnum ) ;
130218 }
131219
132- private static HashAlgorithm GetProvider ( HashFunctionEnum algo )
220+ private static HashAlgorithmName GetAlgorithmName ( HashFunctionEnum algo )
133221 {
134- switch ( algo )
135- {
136- case HashFunctionEnum . CRC32 :
137- return new Crc32 ( ) ;
138- default :
139- var ret = HashAlgorithm . Create ( algo . ToString ( ) ) ;
140- if ( ret == null )
141- throw RuntimeException . InvalidArgumentType ( ) ;
142- return ret ;
143- }
144- }
145-
222+ return algo switch
223+ {
224+ HashFunctionEnum . MD5 => HashAlgorithmName . MD5 ,
225+ HashFunctionEnum . SHA1 => HashAlgorithmName . SHA1 ,
226+ HashFunctionEnum . SHA256 => HashAlgorithmName . SHA256 ,
227+ HashFunctionEnum . SHA384 => HashAlgorithmName . SHA384 ,
228+ HashFunctionEnum . SHA512 => HashAlgorithmName . SHA512 ,
229+ _ => throw RuntimeException . InvalidArgumentValue ( )
230+ } ;
231+ }
232+
146233 public void Dispose ( )
147234 {
148- _toCalculate . Close ( ) ;
149- _toCalculate . Dispose ( ) ;
150- }
151-
152- private void AddStream ( Stream stream )
153- {
154- _toCalculate . AddStream ( stream ) ;
155- _toCalculate . Seek ( 0 , SeekOrigin . Begin ) ;
156- _calculated = false ;
157-
235+ _provider ? . Dispose ( ) ;
158236 }
159237 }
160238}
0 commit comments