2004-02-04 Sebastien Pouliot <spouliot@videotron.ca>
[mono.git] / mcs / class / corlib / System.Security.Cryptography / CryptoStream.cs
1 //
2 // System.Security.Cryptography CryptoStream.cs
3 //
4 // Authors:
5 //      Thomas Neidhart (tome@sbox.tugraz.at)
6 //      Sebastien Pouliot (sebastien@ximian.com)
7 //
8 // Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
9 // (C) 2004 Novell (http://www.novell.com)
10 //
11
12 using System;
13 using System.Globalization;
14 using System.IO;
15 using System.Runtime.InteropServices;
16
17 namespace System.Security.Cryptography {
18
19         public class CryptoStream : Stream {
20                 private Stream _stream;
21                 private ICryptoTransform _transform;
22                 private CryptoStreamMode _mode;
23                 private byte[] _previousBlock;
24                 private byte[] _currentBlock;
25                 private bool _disposed;
26                 private bool _flushedFinalBlock;
27                 private int _blockSize;
28                 private int _partialCount;
29                 private bool _endOfStream;
30         
31                 private byte[] _waitingBlock;
32                 private int _waitingCount;
33         
34                 private byte[] _transformedBlock;
35                 private int _transformedPos;
36                 private int _transformedCount;
37         
38                 private byte[] _workingBlock;
39                 private int _workingCount;
40                 
41                 public CryptoStream (Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
42                 {
43                         if ((mode == CryptoStreamMode.Read) && (!stream.CanRead)) {
44                                 throw new ArgumentException (
45                                         Locale.GetText ("Can't read on stream"));
46                         }
47                         if ((mode == CryptoStreamMode.Write) && (!stream.CanWrite)) {
48                                 throw new ArgumentException (
49                                         Locale.GetText ("Can't write on stream"));
50                         }
51                         _stream = stream;
52                         _transform = transform;
53                         _mode = mode;
54                         _disposed = false;
55                         if (transform != null) {
56                                 if (mode == CryptoStreamMode.Read)
57                                         _blockSize = transform.InputBlockSize;
58                                 else if (mode == CryptoStreamMode.Write)
59                                         _blockSize = transform.OutputBlockSize;
60                                 _workingBlock = new byte [_blockSize];
61                                 _currentBlock = new byte [_blockSize];
62                         }
63                 }
64         
65                 ~CryptoStream () 
66                 {
67                         Dispose (false);
68                 }
69                 
70                 public override bool CanRead {
71                         get { return (_mode == CryptoStreamMode.Read); }
72                 }
73         
74                 public override bool CanSeek {
75                         get { return false; }
76                 }
77         
78                 public override bool CanWrite {
79                         get { return (_mode == CryptoStreamMode.Write); }
80                 }
81                 
82                 public override long Length {
83                         get { throw new NotSupportedException ("Length"); }
84                 }
85         
86                 public override long Position {
87                         get { throw new NotSupportedException ("Position"); }
88                         set { throw new NotSupportedException ("Position"); }
89                 }
90         
91                 public void Clear () 
92                 {
93                         Dispose (true);
94                         GC.SuppressFinalize (this); // not called in Stream.Dispose
95                 }
96         
97                 // LAMESPEC: A CryptoStream can be close in read mode
98                 public override void Close () 
99                 {
100                         // only flush in write mode (bugzilla 46143)
101                         if ((!_flushedFinalBlock) && (_mode == CryptoStreamMode.Write))
102                                 FlushFinalBlock ();
103         
104                         if (_stream != null)
105                                 _stream.Close ();
106                 }
107         
108                 public override int Read ([In,Out] byte[] buffer, int offset, int count)
109                 {
110                         if (_mode != CryptoStreamMode.Read) {
111                                 throw new NotSupportedException (
112                                         Locale.GetText ("not in Read mode"));
113                         }
114                         if (offset < 0) {
115                                 throw new ArgumentOutOfRangeException ("offset", 
116                                         Locale.GetText ("negative"));
117                         }
118                         if (count < 0) {
119                                 throw new ArgumentOutOfRangeException ("count",
120                                         Locale.GetText ("negative"));
121                         }
122                         // yes - buffer.Length will throw a NullReferenceException if buffer is null
123                         // but by doing so we match MS implementation
124                         if (offset + count > buffer.Length) {
125                                 throw new ArgumentException ("(offset+count)", 
126                                         Locale.GetText ("buffer overflow"));
127                         }
128                         // for some strange reason Object_disposedException isn't throw
129                         // instead we get a ArgumentNullException (probably from an internal method)
130                         if (_workingBlock == null) {
131                                 throw new ArgumentNullException (
132                                         Locale.GetText ("object _disposed"));
133                         }
134         
135                         int result = 0;
136                         if ((count == 0) || ((_transformedPos == _transformedCount) && (_endOfStream)))
137                                 return result;
138         
139                         if (_waitingBlock == null) {
140                                 _transformedBlock = new byte [_blockSize << 2];
141                                 _transformedPos = 0;
142                                 _transformedCount = 0;
143                                 _waitingBlock = new byte [_blockSize];
144                                 _waitingCount = _stream.Read (_waitingBlock, 0, _waitingBlock.Length);
145                         }
146                         
147                         while (count > 0) {
148                                 // transformed but not yet returned
149                                 int length = (_transformedCount - _transformedPos);
150         
151                                 // need more data - at least one full block must be available if we haven't reach the end of the stream
152                                 if (length < _blockSize) {
153                                         int transformed = 0;
154         
155                                         // load a new block
156                                         _workingCount = _stream.Read (_workingBlock, 0, _workingBlock.Length);
157                                         _endOfStream = (_workingCount < _blockSize);
158         
159                                         if (!_endOfStream) {
160                                                 // transform the waiting block
161                                                 transformed = _transform.TransformBlock (_waitingBlock, 0, _waitingBlock.Length, _transformedBlock, _transformedCount);
162         
163                                                 // transfer temporary to waiting
164                                                 Buffer.BlockCopy (_workingBlock, 0, _waitingBlock, 0, _workingCount);
165                                                 _waitingCount = _workingCount;
166                                         }
167                                         else {
168                                                 if (_workingCount > 0) {
169                                                         // transform the waiting block
170                                                         transformed = _transform.TransformBlock (_waitingBlock, 0, _waitingBlock.Length, _transformedBlock, _transformedCount);
171         
172                                                         // transfer temporary to waiting
173                                                         Buffer.BlockCopy (_workingBlock, 0, _waitingBlock, 0, _workingCount);
174                                                         _waitingCount = _workingCount;
175         
176                                                         length += transformed;
177                                                         _transformedCount += transformed;
178                                                 }
179                                                 byte[] input = _transform.TransformFinalBlock (_waitingBlock, 0, _waitingCount);
180                                                 transformed = input.Length;
181                                                 Array.Copy (input, 0, _transformedBlock, _transformedCount, input.Length);
182                                                 // zeroize this last block
183                                                 Array.Clear (input, 0, input.Length);
184                                         }
185         
186                                         length += transformed;
187                                         _transformedCount += transformed;
188                                 }
189                                 // compaction
190                                 if (_transformedPos > _blockSize) {
191                                         Buffer.BlockCopy (_transformedBlock, _transformedPos, _transformedBlock, 0, length);
192                                         _transformedCount -= _transformedPos;
193                                         _transformedPos = 0;
194                                 }
195         
196                                 length = ((count < length) ? count : length);
197                                 Buffer.BlockCopy (_transformedBlock, _transformedPos, buffer, offset, length);
198                                 _transformedPos += length;
199         
200                                 result += length;
201                                 offset += length;
202                                 count -= length;
203         
204                                 // there may not be enough data in the stream for a 
205                                 // complete block
206                                 if ((length != _blockSize) || (_endOfStream)) {
207                                         count = 0;      // no more data can be read
208                                 }
209                         }
210                         
211                         return result;
212                 }
213         
214                 public override void Write (byte[] buffer, int offset, int count)
215                 {
216                         if (_mode != CryptoStreamMode.Write) {
217                                 throw new NotSupportedException (
218                                         Locale.GetText ("not in Write mode"));
219                         }
220                         if (offset < 0) { 
221                                 throw new ArgumentOutOfRangeException ("offset", 
222                                         Locale.GetText ("negative"));
223                         }
224                         if (count < 0) {
225                                 throw new ArgumentOutOfRangeException ("count", 
226                                         Locale.GetText ("negative"));
227                         }
228                         if (offset + count > buffer.Length) {
229                                 throw new ArgumentException ("(offset+count)", 
230                                         Locale.GetText ("buffer overflow"));
231                         }
232         
233                         // partial block (in progress)
234                         if ((_partialCount > 0) && (_partialCount != _blockSize)) {
235                                 int remainder = _blockSize - _partialCount;
236                                 remainder = ((count < remainder) ? count : remainder);
237                                 Buffer.BlockCopy (buffer, offset, _workingBlock, _partialCount, remainder);
238                                 _partialCount += remainder;
239                                 offset += remainder;
240                                 count -= remainder;
241                         }
242         
243                         int bufferPos = offset;
244                         while (count > 0) {
245                                 if (_partialCount == _blockSize) {
246                                         _partialCount = 0;
247                                         // use partial block to avoid (re)allocation
248                                         _transform.TransformBlock (_workingBlock, 0, _blockSize, _currentBlock, 0);
249                                         _stream.Write (_currentBlock, 0, _currentBlock.Length);
250                                 }
251         
252                                 if (_transform.CanTransformMultipleBlocks) {
253                                         // transform all except the last block (which may be the last block
254                                         // of the stream and require TransformFinalBlock)
255                                         int numBlock = ((_partialCount + count) / _blockSize);
256                                         if (((_partialCount + count) % _blockSize) == 0) // partial block ?
257                                                 numBlock--; // no then reduce
258                                         int multiSize = (numBlock * _blockSize);
259                                         if (numBlock > 0) {
260                                                 byte[] multiBlocks = new byte [multiSize];
261                                                 _transform.TransformBlock (buffer, offset, multiSize, multiBlocks, 0);
262                                                 _stream.Write (multiBlocks, 0, multiSize); 
263                                                 // copy last block into _currentBlock
264                                                 _partialCount = count - multiSize;
265                                                 Array.Copy (buffer, offset + multiSize, _workingBlock, 0, _partialCount);
266                                         }
267                                         else {
268                                                 Array.Copy (buffer, offset, _workingBlock, _partialCount, count);
269                                                 _partialCount += count;
270                                         }
271                                         count = 0; // the last block, if any, is in _workingBlock
272                                 }
273                                 else {
274                                         int len = Math.Min (_blockSize - _partialCount, count);
275                                         Array.Copy (buffer, bufferPos, _workingBlock, _partialCount, len);
276                                         bufferPos += len;
277                                         _partialCount += len;
278                                         count -= len;
279                                         // here block may be full, but we wont TransformBlock it until next iteration
280                                         // so that the last block will be called in FlushFinalBlock using TransformFinalBlock
281                                 }
282                         }
283                 }
284         
285                 public override void Flush ()
286                 {
287                         if (_stream != null)
288                                 _stream.Flush ();
289                 }
290         
291                 public void FlushFinalBlock ()
292                 {
293                         if (_flushedFinalBlock) {
294                                 throw new NotSupportedException (
295                                         Locale.GetText ("This method cannot be called twice."));
296                         }
297                         if (_mode != CryptoStreamMode.Write) {
298                                 throw new NotSupportedException (
299                                         Locale.GetText ("cannot flush a non-writeable CryptoStream"));
300                         }
301         
302                         _flushedFinalBlock = true;
303                         byte[] finalBuffer = _transform.TransformFinalBlock (_workingBlock, 0, _partialCount);
304                         if (_stream != null) {
305                                 _stream.Write (finalBuffer, 0, finalBuffer.Length);
306                                 _stream.Flush ();
307                         }
308                         // zeroize
309                         Array.Clear (finalBuffer, 0, finalBuffer.Length);
310                 }
311         
312                 public override long Seek (long offset, SeekOrigin origin)
313                 {
314                         throw new NotSupportedException ("Seek");
315                 }
316                 
317                 // LAMESPEC: Exception NotSupportedException not documented
318                 public override void SetLength (long value)
319                 {
320                         throw new NotSupportedException ("SetLength");
321                 }
322         
323                 protected virtual void Dispose (bool disposing) 
324                 {
325                         if (!_disposed) {
326                                 _disposed = true;
327                                 // always cleared for security reason
328                                 if (_workingBlock != null)
329                                         Array.Clear (_workingBlock, 0, _workingBlock.Length);
330                                 if (_currentBlock != null)
331                                         Array.Clear (_currentBlock, 0, _currentBlock.Length);
332                                 if (_previousBlock != null)
333                                         Array.Clear (_previousBlock, 0, _previousBlock.Length);
334                                 if (disposing) {
335                                         _stream = null;
336                                         _workingBlock = null;
337                                         _currentBlock = null;
338                                         _previousBlock = null;
339                                 }
340                         }
341                 }
342         }
343 }