3fd8a2dd1805725401ac094d0fbc807dd68cbc0b
[mono.git] / mcs / class / referencesource / System / net / System / Net / SecureProtocols / _SslStream.cs
1 /*++
2 Copyright (c) Microsoft Corporation
3
4 Module Name:
5
6     _SslStream.cs
7
8 Abstract:
9     Internal helper to support public SslStream class.
10     It would be nice to make it a partial class file once compiler gets this supported
11
12 Author:
13     Alexei Vopilov    22-Aug-2003
14
15 Revision History:
16     22-Aug-2003 New design that has obsoleted SslClientStream and SslServerStream class
17
18 --*/
19 #if MONO_FEATURE_NEW_TLS && SECURITY_DEP
20 namespace System.Net.Security {
21     using System;
22     using System.IO;
23     using System.Security;
24     using System.Security.Principal;
25     using System.Security.Permissions;
26     using System.Threading;
27     using System.Collections.Generic;
28     using System.Net.Sockets;
29
30     //
31     // This is a wrapping stream that does data encryption/decryption based on a successfully authenticated SSPI context.
32     //
33     internal partial class _SslStream
34     {
35         private static AsyncCallback _WriteCallback         = new AsyncCallback(WriteCallback);
36         private static AsyncCallback _MulitpleWriteCallback = new AsyncCallback(MulitpleWriteCallback);
37         private static AsyncProtocolCallback _ResumeAsyncWriteCallback = new AsyncProtocolCallback(ResumeAsyncWriteCallback);
38         private static AsyncProtocolCallback _ResumeAsyncReadCallback = new AsyncProtocolCallback(ResumeAsyncReadCallback);
39         private static AsyncProtocolCallback _ReadHeaderCallback  = new AsyncProtocolCallback(ReadHeaderCallback);
40         private static AsyncProtocolCallback _ReadFrameCallback   = new AsyncProtocolCallback(ReadFrameCallback);
41
42         private const int PinnableReadBufferSize = 4096*4 + 32;         // We like to read in 16K chunks + headers
43         private static PinnableBufferCache s_PinnableReadBufferCache = new PinnableBufferCache("System.Net.SslStream", PinnableReadBufferSize);
44         private const int PinnableWriteBufferSize = 4096 + 1024;        // We like to write in 4K chunks + encryption overhead
45         private static PinnableBufferCache s_PinnableWriteBufferCache = new PinnableBufferCache("System.Net.SslStream", PinnableWriteBufferSize);
46
47         private SslState    _SslState;
48         private int         _NestedWrite;
49         private int         _NestedRead;
50
51         // never updated directly, special properties are used.  This is the read buffer
52         private byte[]      _InternalBuffer;
53         private bool        _InternalBufferFromPinnableCache;
54
55         private byte[]      _PinnableOutputBuffer;                        // Used for writes when we can do it. 
56         private byte[]      _PinnableOutputBufferInUse;                   // remembers what UNENCRYPTED buffer is using _PinnableOutputBuffer
57
58         private int         _InternalOffset;
59         private int         _InternalBufferCount;
60
61         FixedSizeReader     _Reader;
62
63         internal _SslStream(SslState sslState)
64         {
65             if (PinnableBufferCacheEventSource.Log.IsEnabled())
66             {
67                 PinnableBufferCacheEventSource.Log.DebugMessage1("CTOR: In System.Net._SslStream.SslStream", this.GetHashCode());
68             }
69             _SslState = sslState;
70             _Reader = new FixedSizeReader(_SslState.InnerStream);
71         }
72
73         // if we have a read buffer from the pinnable cache, return it
74         void FreeReadBuffer()
75         {
76             if (_InternalBufferFromPinnableCache)
77             {
78                 s_PinnableReadBufferCache.FreeBuffer(_InternalBuffer);
79                 _InternalBufferFromPinnableCache = false;
80             }
81             _InternalBuffer = null;
82         }
83
84         ~_SslStream()
85         {        
86             if (_InternalBufferFromPinnableCache)  
87             {
88                 if (PinnableBufferCacheEventSource.Log.IsEnabled())
89                 {
90                     PinnableBufferCacheEventSource.Log.DebugMessage2("DTOR: In System.Net._SslStream.~SslStream Freeing Read Buffer", this.GetHashCode(), PinnableBufferCacheEventSource.AddressOfByteArray(_InternalBuffer));
91                 }
92                 FreeReadBuffer();
93             }
94             if (_PinnableOutputBuffer != null)
95             {
96                 if (PinnableBufferCacheEventSource.Log.IsEnabled())
97                 {
98                     PinnableBufferCacheEventSource.Log.DebugMessage2("DTOR: In System.Net._SslStream.~SslStream Freeing Write Buffer", this.GetHashCode(), PinnableBufferCacheEventSource.AddressOfByteArray(_PinnableOutputBuffer));
99                 }
100                 s_PinnableWriteBufferCache.FreeBuffer(_PinnableOutputBuffer);
101             }
102         }
103
104         //
105         // Some of the Public Stream class contract
106         //
107         //
108         //
109         internal int Read(byte[] buffer, int offset, int count)
110         {
111             return ProcessRead(buffer, offset, count, null);
112         }
113         //
114         //
115         internal void Write(byte[] buffer, int offset, int count)
116         {
117             ProcessWrite(buffer, offset, count, null);
118         }
119         //
120         //
121         internal void Write(BufferOffsetSize[] buffers)
122         {
123             ProcessWrite(buffers, null);
124         }
125         //
126         //
127         internal IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
128         {
129             BufferAsyncResult bufferResult = new BufferAsyncResult(this, buffer, offset, count, asyncState, asyncCallback);
130             AsyncProtocolRequest asyncRequest = new AsyncProtocolRequest(bufferResult);
131             ProcessRead(buffer, offset, count, asyncRequest );
132             return bufferResult;
133         }
134         //
135         //
136         internal int EndRead(IAsyncResult asyncResult)
137         {
138             if (asyncResult == null)
139             {
140                 throw new ArgumentNullException("asyncResult");
141             }
142
143             BufferAsyncResult bufferResult = asyncResult as BufferAsyncResult;
144             if (bufferResult == null)
145             {
146                 throw new ArgumentException(SR.GetString(SR.net_io_async_result, asyncResult.GetType().FullName), "asyncResult");
147             }
148
149             if (Interlocked.Exchange(ref _NestedRead, 0) == 0)
150             {
151                 throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndRead"));
152             }
153
154             // No "artificial" timeouts implemented so far, InnerStream controls timeout.
155             bufferResult.InternalWaitForCompletion();
156
157             if (bufferResult.Result is Exception)
158             {
159                 if (bufferResult.Result is IOException)
160                 {
161                     throw (Exception)bufferResult.Result;
162                 }
163                 throw new IOException(SR.GetString(SR.net_io_read), (Exception)bufferResult.Result);
164             }
165             return (int) bufferResult.Result;
166         }
167         //
168         //
169         internal IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
170         {
171             LazyAsyncResult lazyResult = new LazyAsyncResult(this, asyncState, asyncCallback);
172             AsyncProtocolRequest asyncRequest = new AsyncProtocolRequest(lazyResult);
173             ProcessWrite(buffer, offset, count, asyncRequest);
174             return lazyResult;
175         }
176         //
177         //  Assumes that InnerStream type == typeof(NetworkStream)
178         //
179         internal IAsyncResult BeginWrite(BufferOffsetSize[] buffers, AsyncCallback asyncCallback, object asyncState)
180         {
181             LazyAsyncResult lazyResult = new LazyAsyncResult(this, asyncState, asyncCallback);
182             SplitWriteAsyncProtocolRequest asyncRequest = new SplitWriteAsyncProtocolRequest(lazyResult);
183             ProcessWrite(buffers, asyncRequest);
184             return lazyResult;
185         }
186         //
187         //
188         internal void EndWrite(IAsyncResult asyncResult)
189         {
190             if (asyncResult == null)
191             {
192                 throw new ArgumentNullException("asyncResult");
193             }
194
195             LazyAsyncResult lazyResult = asyncResult as LazyAsyncResult;
196             if (lazyResult == null)
197             {
198                 throw new ArgumentException(SR.GetString(SR.net_io_async_result, asyncResult.GetType().FullName), "asyncResult");
199             }
200
201             if (Interlocked.Exchange(ref _NestedWrite, 0) == 0)
202             {
203                 throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndWrite"));
204             }
205
206             // No "artificial" timeouts implemented so far, InnerStream controls timeout.
207             lazyResult.InternalWaitForCompletion();
208
209             if (lazyResult.Result is Exception)
210             {
211                 if (lazyResult.Result is IOException)
212                 {
213                     throw (Exception)lazyResult.Result;
214                 }
215                 throw new IOException(SR.GetString(SR.net_io_write), (Exception)lazyResult.Result);
216             }
217         }
218         //
219         // Internal implemenation
220         //
221
222         //
223         //
224         internal bool DataAvailable {
225             get { return  InternalBufferCount != 0;}
226         }
227         //
228         //
229         private byte[] InternalBuffer {
230             get {
231                 return _InternalBuffer;
232             }
233         }
234         //
235         //
236         private int InternalOffset {
237             get {
238                 return _InternalOffset;
239             }
240         }
241         //
242         //
243         private int InternalBufferCount {
244             get {
245                 return _InternalBufferCount;
246             }
247         }
248         //
249         //
250         private void DecrementInternalBufferCount(int decrCount)
251         {
252             _InternalOffset += decrCount;
253             _InternalBufferCount -= decrCount;
254         }
255         //
256         // This will set the internal offset to "curOffset" and ensure internal buffer.
257         // If not enough, reallocate and copy up to "curOffset"
258         //
259         private void EnsureInternalBufferSize(int curOffset, int addSize)
260         {
261             if (_InternalBuffer == null || _InternalBuffer.Length < addSize + curOffset)
262             {
263
264                 bool wasPinnable = _InternalBufferFromPinnableCache;
265                 byte[] saved = _InternalBuffer;
266
267                 int newSize = addSize + curOffset;
268                 if (newSize <= PinnableReadBufferSize)
269                 {
270                     if (PinnableBufferCacheEventSource.Log.IsEnabled())
271                     {
272                         PinnableBufferCacheEventSource.Log.DebugMessage2("In System.Net._SslStream.EnsureInternalBufferSize IS pinnable", this.GetHashCode(), newSize);
273                     }
274                     _InternalBufferFromPinnableCache = true;
275                     _InternalBuffer = s_PinnableReadBufferCache.AllocateBuffer();
276                 }
277                 else  
278                 {
279                     if (PinnableBufferCacheEventSource.Log.IsEnabled())
280                     {
281                         PinnableBufferCacheEventSource.Log.DebugMessage2("In System.Net._SslStream.EnsureInternalBufferSize NOT pinnable", this.GetHashCode(), newSize);
282                     }
283                     _InternalBufferFromPinnableCache = false;
284                     _InternalBuffer = new byte[newSize];
285                 }
286
287                 if (saved != null && curOffset != 0)
288                 {
289                     Buffer.BlockCopy(saved, 0, _InternalBuffer, 0, curOffset);
290                 }
291
292                 if (wasPinnable)
293                 {
294                     s_PinnableReadBufferCache.FreeBuffer(saved);
295                 }
296             }
297             _InternalOffset = curOffset;
298             _InternalBufferCount = curOffset + addSize;
299         }
300         //
301         // Validates user parameteres for all Read/Write methods
302         //
303         private void ValidateParameters(byte[] buffer, int offset, int count)
304         {
305             if (buffer == null)
306                 throw new ArgumentNullException("buffer");
307
308             if (offset < 0)
309                 throw new ArgumentOutOfRangeException("offset");
310
311             if (count < 0)
312                 throw new ArgumentOutOfRangeException("count");
313
314             if (count > buffer.Length-offset)
315                 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.net_offset_plus_count));
316         }
317         //
318         // Combined sync/async write method. For sync case asyncRequest==null
319         //
320         private void ProcessWrite(BufferOffsetSize[] buffers, SplitWriteAsyncProtocolRequest asyncRequest)
321         {
322             foreach (BufferOffsetSize buffer in buffers)
323             {
324                 ValidateParameters(buffer.Buffer, buffer.Offset, buffer.Size);
325             }
326
327             if (Interlocked.Exchange(ref _NestedWrite, 1) == 1)
328             {
329                 throw new NotSupportedException(SR.GetString(SR.net_io_invalidnestedcall, (asyncRequest != null? "BeginWrite":"Write"), "write"));
330             }
331
332             bool failed = false;
333             try
334             {
335                 SplitWritesState splitWrite = new SplitWritesState(buffers);
336                 if (asyncRequest != null)
337                     asyncRequest.SetNextRequest(splitWrite, _ResumeAsyncWriteCallback);
338
339                 StartWriting(splitWrite, asyncRequest);
340             }
341             catch (Exception e)
342             {
343                 _SslState.FinishWrite();
344
345                 failed = true;
346                 if (e is IOException) {
347                     throw;
348                 }
349                 throw new IOException(SR.GetString(SR.net_io_write), e);
350             }
351             finally
352             {
353                 if (asyncRequest == null || failed)
354                 {
355                     _NestedWrite = 0;
356                 }
357             }
358         }
359         //
360         // Combined sync/async write method. For sync case asyncRequest==null
361         //
362         private void ProcessWrite(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
363         {
364             if (_SslState.LastPayload != null)
365             {
366                 //
367                 // !!! LastPayload Only used in TlsStream  for HTTP and it needs re-work for a general case !!!
368                 //
369                 BufferOffsetSize[] buffers = new BufferOffsetSize[1];
370                 buffers[0] = new BufferOffsetSize(buffer, offset, count, false);
371                 if (asyncRequest != null)
372                     ProcessWrite(buffers, new SplitWriteAsyncProtocolRequest(asyncRequest.UserAsyncResult));
373                 else
374                     ProcessWrite(buffers, null);
375                 return;
376             }
377
378             ValidateParameters(buffer, offset, count);
379
380             if (Interlocked.Exchange(ref _NestedWrite, 1) == 1)
381             {
382                 throw new NotSupportedException(SR.GetString(SR.net_io_invalidnestedcall, (asyncRequest != null? "BeginWrite":"Write"), "write"));
383             }
384
385             bool failed = false;
386             try
387             {
388                 StartWriting(buffer, offset, count, asyncRequest);
389             }
390             catch (Exception e)
391             {
392                 _SslState.FinishWrite();
393
394                 failed = true;
395                 if (e is IOException) {
396                     throw;
397                 }
398                 throw new IOException(SR.GetString(SR.net_io_write), e);
399             }
400             finally
401             {
402                 if (asyncRequest == null || failed)
403                 {
404                     _NestedWrite = 0;
405                 }
406             }
407         }
408         //
409         // This method assumes that InnerStream type == typeof(NetwokrStream)
410         // It will produce a set of buffers for one MultipleWrite call
411         //
412         private void StartWriting(SplitWritesState splitWrite, SplitWriteAsyncProtocolRequest asyncRequest)
413         {
414             while (!splitWrite.IsDone)
415             {
416                 // request a write IO slot
417                 if (_SslState.CheckEnqueueWrite(asyncRequest))
418                 {
419                     // operation is async and has been queued, return.
420                     return;
421                 }
422
423                 byte[] lastHandshakePayload = null;
424                 if (_SslState.LastPayload != null)
425                 {
426                     //
427                     // !!! LastPayload Only used in TlsStream for HTTP and it needs re-work for a general case !!!
428                     //
429                     lastHandshakePayload = _SslState.LastPayload;
430                     _SslState.LastPayloadConsumed();
431                 }
432
433                 BufferOffsetSize[] buffers = splitWrite.GetNextBuffers();
434                 buffers = EncryptBuffers(buffers, lastHandshakePayload);
435
436                 if (asyncRequest != null)
437                 {
438                     // prepare for the next request
439                     IAsyncResult ar = ((NetworkStream)(_SslState.InnerStream)).BeginMultipleWrite(buffers, _MulitpleWriteCallback, asyncRequest);
440                     if (!ar.CompletedSynchronously)
441                         return;
442
443                     ((NetworkStream)(_SslState.InnerStream)).EndMultipleWrite(ar);
444                 }
445                 else
446                 {
447                     ((NetworkStream)(_SslState.InnerStream)).MultipleWrite(buffers);
448                 }
449
450                 // release write IO slot
451                 _SslState.FinishWrite();
452
453             }
454
455             if (asyncRequest != null)
456                 asyncRequest.CompleteUser();
457         }
458
459         //
460         // Performs encryption of an array of buffers, proceeds buffer by buffer, if the individual
461         // buffer size exceeds a SSL limit of SecureChannel.MaxDataSize,the buffers are then split into smaller ones.
462         // Returns the same array that is encrypted or a new array of encrypted buffers.
463         //
464         private BufferOffsetSize[] EncryptBuffers(BufferOffsetSize[] buffers, byte[] lastHandshakePayload)
465         {
466
467             List<BufferOffsetSize> arrayList = null;
468             SecurityStatus status = SecurityStatus.OK;
469
470             foreach(BufferOffsetSize buffer in buffers)
471             {
472                 int chunkBytes = Math.Min(buffer.Size, _SslState.MaxDataSize);
473
474                 byte[] outBuffer = null;
475                 int    outSize;
476
477                 status = _SslState.EncryptData(buffer.Buffer, buffer.Offset, chunkBytes, ref outBuffer, out outSize);
478                 if (status != SecurityStatus.OK)
479                     break;
480
481                 if (chunkBytes != buffer.Size || arrayList != null)
482                 {
483                     if (arrayList == null)
484                     {
485                         arrayList = new List<BufferOffsetSize>(buffers.Length * (buffer.Size/chunkBytes+1));
486                         if (lastHandshakePayload != null)
487                             arrayList.Add(new BufferOffsetSize(lastHandshakePayload, false));
488
489                         foreach(BufferOffsetSize oldBuffer in buffers)
490                         {
491                             if (oldBuffer == buffer)
492                                 break;
493                             arrayList.Add(oldBuffer);
494                         }
495                     }
496                     arrayList.Add(new BufferOffsetSize(outBuffer, 0, outSize, false));
497                     while ((buffer.Size-=chunkBytes) != 0)
498                     {
499                         buffer.Offset += chunkBytes;
500                         chunkBytes = Math.Min(buffer.Size, _SslState.MaxDataSize);
501                         outBuffer = null;
502                         status = _SslState.EncryptData(buffer.Buffer, buffer.Offset, chunkBytes, ref outBuffer, out outSize);
503                         if (status != SecurityStatus.OK)
504                             break;
505                         arrayList.Add(new BufferOffsetSize(outBuffer, 0, outSize, false));
506                     }
507                 }
508                 else
509                 {
510                     buffer.Buffer = outBuffer;
511                     buffer.Offset = 0;
512                     buffer.Size   = outSize;
513                 }
514                 if (status != SecurityStatus.OK)
515                     break;
516             }
517
518             if (status != SecurityStatus.OK)
519             {
520                 //
521                 ProtocolToken message = new ProtocolToken(null, status);
522                 throw new IOException(SR.GetString(SR.net_io_encrypt), message.GetException());
523             }
524
525             if (arrayList != null)
526                 buffers = arrayList.ToArray();
527             else if (lastHandshakePayload != null)
528             {
529                 BufferOffsetSize[] result = new BufferOffsetSize[buffers.Length+1];
530                 Array.Copy(buffers, 0, result, 1, buffers.Length);
531                 result[0] = new BufferOffsetSize(lastHandshakePayload, false);
532                 buffers = result;
533             }
534
535             return buffers;
536         }
537         //
538         private void StartWriting(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
539         {
540             if (asyncRequest != null)
541             {
542                 asyncRequest.SetNextRequest(buffer, offset, count, _ResumeAsyncWriteCallback);
543             }
544
545             // We loop to this method from the callback
546             // If the last chunk was just completed from async callback (count < 0), we complete user request
547             if (count >= 0 )
548             {
549                 byte[] outBuffer = null;
550                 if (_PinnableOutputBufferInUse == null)                // The output buffer is not in use
551                 {
552                     if (_PinnableOutputBuffer == null)                // Create one if needed
553                     {
554                         _PinnableOutputBuffer = s_PinnableWriteBufferCache.AllocateBuffer();
555                     }
556                     _PinnableOutputBufferInUse = buffer;        // put it in use
557                     outBuffer = _PinnableOutputBuffer;
558                     if (PinnableBufferCacheEventSource.Log.IsEnabled())
559                     {
560                         PinnableBufferCacheEventSource.Log.DebugMessage3("In System.Net._SslStream.StartWriting Trying Pinnable", this.GetHashCode(), count, PinnableBufferCacheEventSource.AddressOfByteArray(outBuffer));
561                     }
562                 }
563                 else
564                 {
565                     if (PinnableBufferCacheEventSource.Log.IsEnabled())
566                     {
567                         PinnableBufferCacheEventSource.Log.DebugMessage2("In System.Net._SslStream.StartWriting BufferInUse", this.GetHashCode(), count);
568                     }
569                 }
570
571                 do
572                 {
573                     // request a write IO slot
574                     if (_SslState.CheckEnqueueWrite(asyncRequest))
575                     {
576                         // operation is async and has been queued, return.
577                         return;
578                     }
579
580                     int chunkBytes = Math.Min(count, _SslState.MaxDataSize);
581                     int encryptedBytes;
582                     SecurityStatus errorCode =  _SslState.EncryptData(buffer, offset, chunkBytes, ref outBuffer, out encryptedBytes);
583                     if (errorCode != SecurityStatus.OK)
584                     {
585                         //
586                         ProtocolToken message = new ProtocolToken(null, errorCode);
587                         throw new IOException(SR.GetString(SR.net_io_encrypt), message.GetException());
588                     }
589
590                     if (PinnableBufferCacheEventSource.Log.IsEnabled())
591                     {
592                         PinnableBufferCacheEventSource.Log.DebugMessage3("In System.Net._SslStream.StartWriting Got Encrypted Buffer", 
593                             this.GetHashCode(), encryptedBytes, PinnableBufferCacheEventSource.AddressOfByteArray(outBuffer));
594                     }
595                     if (asyncRequest != null)
596                     {
597                         // prepare for the next request
598                         asyncRequest.SetNextRequest(buffer, offset+chunkBytes, count-chunkBytes, _ResumeAsyncWriteCallback);
599                         IAsyncResult ar = _SslState.InnerStream.BeginWrite(outBuffer, 0, encryptedBytes, _WriteCallback, asyncRequest);
600                         if (!ar.CompletedSynchronously)
601                         {
602                             return;
603                         }
604                         _SslState.InnerStream.EndWrite(ar);
605
606                     }
607                     else
608                     {
609                         _SslState.InnerStream.Write(outBuffer, 0, encryptedBytes);
610                     }
611                     offset += chunkBytes;
612                     count  -= chunkBytes;
613
614                     // release write IO slot
615                     _SslState.FinishWrite();
616
617                 } while (count != 0);
618         
619             }
620
621             if (asyncRequest != null) {
622                 asyncRequest.CompleteUser();
623             }
624
625             if (buffer == _PinnableOutputBufferInUse)                // Did we put it in use?
626             {
627                 // Then free it 
628                 _PinnableOutputBufferInUse = null;
629                 if (PinnableBufferCacheEventSource.Log.IsEnabled())
630                 {
631                     PinnableBufferCacheEventSource.Log.DebugMessage1("In System.Net._SslStream.StartWriting Freeing buffer.", this.GetHashCode());
632                 }
633             }
634         }
635
636         //
637         // Combined sync/async read method. For sync requet asyncRequest==null
638         // There is a little overheader because we need to pass buffer/offset/count used only in sync.
639         // Still the benefit is that we have a common sync/async code path.
640         //
641         private int ProcessRead(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
642         {
643             ValidateParameters(buffer, offset, count);
644
645             if (Interlocked.Exchange(ref _NestedRead, 1) == 1)
646             {
647                 throw new NotSupportedException(SR.GetString(SR.net_io_invalidnestedcall, (asyncRequest!=null? "BeginRead":"Read"), "read"));
648             }
649
650             bool failed = false;
651             try
652             {
653                 int copyBytes;
654                 if (InternalBufferCount != 0)
655                 {
656                     copyBytes = InternalBufferCount > count? count: InternalBufferCount;
657                     if (copyBytes != 0)
658                     {
659                         Buffer.BlockCopy(InternalBuffer, InternalOffset, buffer, offset, copyBytes);
660                         DecrementInternalBufferCount(copyBytes);
661                     }
662                     if (asyncRequest != null) {
663                         asyncRequest.CompleteUser((object) copyBytes);
664                     }
665                     return copyBytes;
666                 }
667                 // going into real IO
668                 return StartReading(buffer, offset, count, asyncRequest);
669             }
670             catch (Exception e)
671             {
672                 _SslState.FinishRead(null);
673                 failed = true;
674                 if (e is IOException) {
675                     throw;
676                 }
677                 throw new IOException(SR.GetString(SR.net_io_read), e);
678             }
679             finally
680             {
681                 // if sync request or exception
682                 if (asyncRequest == null || failed)
683                 {
684                     _NestedRead = 0;
685                 }
686             }
687         }
688         //
689         // To avoid recursion when decrypted 0 bytes this method will loop until a decrypted result at least 1 byte.
690         //
691         private int StartReading(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
692         {
693             int result = 0;
694
695             GlobalLog.Assert(InternalBufferCount == 0, "SslStream::StartReading()|Previous frame was not consumed. InternalBufferCount:{0}", InternalBufferCount);
696
697             do
698             {
699                 if (asyncRequest != null)
700                 {
701                     asyncRequest.SetNextRequest(buffer, offset, count, _ResumeAsyncReadCallback);
702                 }
703                 int copyBytes = _SslState.CheckEnqueueRead(buffer, offset, count, asyncRequest);
704                 if (copyBytes == 0)
705                 {
706                     //queued but not completed!
707                     return 0;
708                 }
709
710                 if (copyBytes != -1)
711                 {
712                     if (asyncRequest != null)
713                     {
714                         asyncRequest.CompleteUser((object) copyBytes);
715                     }
716                     return copyBytes;
717                 }
718             }
719             // When we read -1 bytes means we have decrypted 0 bytes or rehandshaking, need looping.
720             while ((result = StartFrameHeader(buffer, offset, count, asyncRequest)) == -1);
721
722             return result;
723         }
724         //
725         // Need read frame size first
726         //
727         private int StartFrameHeader(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
728         {
729             int readBytes = 0;
730
731             //
732             // Always pass InternalBuffer for SSPI "in place" decryption.
733             // A user buffer can be shared by many threads in that case decryption/integrity check may fail cause of data corruption.
734             //
735
736             // reset internal buffer for a new frame
737             EnsureInternalBufferSize(0, SecureChannel.ReadHeaderSize);
738
739             if (asyncRequest != null)
740             {
741                 asyncRequest.SetNextRequest(InternalBuffer, 0, SecureChannel.ReadHeaderSize, _ReadHeaderCallback);
742                 _Reader.AsyncReadPacket(asyncRequest);
743                 if (!asyncRequest.MustCompleteSynchronously)
744                 {
745                     return 0;
746                 }
747                 readBytes = asyncRequest.Result;
748             }
749             else
750             {
751                 readBytes = _Reader.ReadPacket(InternalBuffer, 0, SecureChannel.ReadHeaderSize);
752             }
753             return StartFrameBody(readBytes, buffer, offset, count, asyncRequest);
754         }
755         //
756         //
757         //
758         private int StartFrameBody(int readBytes, byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
759         {
760             if (readBytes == 0)
761             {
762                 //EOF
763                 //Reset the buffer as we did not read anything into it
764                 DecrementInternalBufferCount(InternalBufferCount);
765                 if (asyncRequest != null)
766                 {
767                     asyncRequest.CompleteUser((object)0);
768                 }
769                 return 0;
770             }
771
772             // Now readBytes is a payload size
773             readBytes = _SslState.GetRemainingFrameSize(InternalBuffer, readBytes);
774             //
775             // And the payload size must be >= 0
776             //
777             if (readBytes  < 0)
778             {
779                 throw new IOException(SR.GetString(SR.net_frame_read_size));
780             }
781
782             EnsureInternalBufferSize(SecureChannel.ReadHeaderSize, readBytes);
783
784             if (asyncRequest != null) //Async
785             {
786                 asyncRequest.SetNextRequest(InternalBuffer, SecureChannel.ReadHeaderSize, readBytes, _ReadFrameCallback);
787
788                 _Reader.AsyncReadPacket(asyncRequest);
789
790                 if (!asyncRequest.MustCompleteSynchronously)
791                 {
792                     return 0;
793                 }
794                 readBytes = asyncRequest.Result;
795             }
796             else //Sync
797             {
798                 readBytes = _Reader.ReadPacket(InternalBuffer, SecureChannel.ReadHeaderSize, readBytes);
799             }
800             return ProcessFrameBody(readBytes, buffer, offset, count, asyncRequest);
801         }
802         //
803         // readBytes == SSL Data Payload size on input or 0 on EOF
804         //
805         private int ProcessFrameBody(int readBytes, byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
806         {
807             if (readBytes == 0)
808             {
809                 // Eof
810                 throw new IOException(SR.GetString(SR.net_io_eof));
811             }
812
813             //Set readBytes to total number of received bytes
814             readBytes += SecureChannel.ReadHeaderSize;
815
816             //Decrypt into internal buffer, change "readBytes" to count now _Decrypted Bytes_
817             int data_offset = 0;
818
819             SecurityStatus errorCode = _SslState.DecryptData(InternalBuffer, ref data_offset, ref readBytes);
820
821             if (errorCode != SecurityStatus.OK)
822             {
823                 byte[] extraBuffer = null;
824                 if (readBytes != 0)
825                 {
826                     extraBuffer = new byte[readBytes];
827                     Buffer.BlockCopy(InternalBuffer, data_offset, extraBuffer, 0, readBytes);
828                 }
829                 // Reset internal buffer count
830                 DecrementInternalBufferCount(InternalBufferCount);
831                 return ProcessReadErrorCode(errorCode, buffer, offset, count, asyncRequest, extraBuffer);
832             }
833
834
835             if (readBytes == 0 && count != 0)
836             {
837                 //Read again since remote side has sent encrypted 0 bytes
838                 DecrementInternalBufferCount(InternalBufferCount);
839                 return -1;
840             }
841
842             // Decrypted data start from "data_offset" offset, the total count can be shrinked after decryption
843             EnsureInternalBufferSize(0, data_offset + readBytes);
844             DecrementInternalBufferCount(data_offset);
845
846             if (readBytes > count)
847             {
848                 readBytes = count;
849             }
850             Buffer.BlockCopy(InternalBuffer, InternalOffset, buffer, offset, readBytes);
851
852             // This will adjust both the remaining internal buffer count and the offset
853             DecrementInternalBufferCount(readBytes);
854
855             _SslState.FinishRead(null);
856             if (asyncRequest != null)
857             {
858                 asyncRequest.CompleteUser((object)readBytes);
859             }
860
861             return readBytes;
862         }
863         //
864         // Codes we process (Anything else - fail)
865         //
866         // - SEC_I_RENEGOTIATE
867         //
868         private int ProcessReadErrorCode(SecurityStatus errorCode, byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest, byte[] extraBuffer)
869         {
870             // ERROR - examine what kind
871             ProtocolToken message = new ProtocolToken(null, errorCode);
872
873             GlobalLog.Print("SecureChannel#" + ValidationHelper.HashString(this) + "::***Processing an error Status = " + message.Status.ToString());
874
875             if (message.Renegotiate)
876             {
877                 _SslState.ReplyOnReAuthentication(extraBuffer);
878                 // loop on read
879                 return -1;
880             }
881             if (message.CloseConnection) {
882                 _SslState.FinishRead(null);
883                 if (asyncRequest != null)
884                 {
885                     asyncRequest.CompleteUser((object)0);
886                 }
887                 return 0;
888             }
889             // Otherwise bail out.
890             throw new IOException(SR.GetString(SR.net_io_decrypt), message.GetException());
891         }
892         //
893         //
894         //
895         private static void WriteCallback(IAsyncResult transportResult)
896         {
897             if (transportResult.CompletedSynchronously)
898             {
899                 return;
900             }
901
902             GlobalLog.Assert(transportResult.AsyncState is AsyncProtocolRequest , "SslStream::WriteCallback|State type is wrong, expected AsyncProtocolRequest.");
903             AsyncProtocolRequest asyncRequest = (AsyncProtocolRequest) transportResult.AsyncState;
904
905             _SslStream sslStream = (_SslStream)asyncRequest.AsyncObject;
906             try {
907                 sslStream._SslState.InnerStream.EndWrite(transportResult);
908                 sslStream._SslState.FinishWrite();
909
910                 if (asyncRequest.Count == 0) {
911                     // this was the last chunk
912                     asyncRequest.Count = -1;
913                 }
914                 sslStream.StartWriting(asyncRequest.Buffer, asyncRequest.Offset, asyncRequest.Count, asyncRequest);
915
916             }
917             catch (Exception e) {
918                 if (asyncRequest.IsUserCompleted) {
919                     // This will throw on a worker thread.
920                     throw;
921                 }
922                 sslStream._SslState.FinishWrite();
923                 asyncRequest.CompleteWithError(e);
924             }
925         }
926         //
927         // Assuming InnerStream type == typeof(NetworkStream)
928         //
929         private static void MulitpleWriteCallback(IAsyncResult transportResult)
930         {
931             if (transportResult.CompletedSynchronously)
932             {
933                 return;
934             }
935
936             GlobalLog.Assert(transportResult.AsyncState is AsyncProtocolRequest, "SslStream::MulitpleWriteCallback|State type is wrong, expected AsyncProtocolRequest.");
937
938             SplitWriteAsyncProtocolRequest asyncRequest = (SplitWriteAsyncProtocolRequest)transportResult.AsyncState;
939
940             _SslStream sslStream = (_SslStream)asyncRequest.AsyncObject;
941             try {
942                 ((NetworkStream)(sslStream._SslState.InnerStream)).EndMultipleWrite(transportResult);
943                 sslStream._SslState.FinishWrite();
944                 sslStream.StartWriting(asyncRequest.SplitWritesState, asyncRequest);
945             }
946             catch (Exception e) {
947                 if (asyncRequest.IsUserCompleted) {
948                     // This will throw on a worker thread.
949                     throw;
950                 }
951                 sslStream._SslState.FinishWrite();
952                 asyncRequest.CompleteWithError(e);
953             }
954         }
955
956         //
957         // This is used in a rare situation when async Read is resumed from completed handshake
958         //
959         private static void ResumeAsyncReadCallback(AsyncProtocolRequest request)
960         {
961             try {
962                 ((_SslStream)request.AsyncObject).StartReading(request.Buffer, request.Offset, request.Count, request);
963             }
964             catch (Exception e) {
965                 if (request.IsUserCompleted) {
966                     // This will throw on a worker thread.
967                     throw;
968                 }
969                 ((_SslStream)request.AsyncObject)._SslState.FinishRead(null);
970                 request.CompleteWithError(e);
971             }
972         }
973
974         //
975         // This is used in a rare situation when async Write is resumed from completed handshake
976         //
977         private static void ResumeAsyncWriteCallback(AsyncProtocolRequest asyncRequest)
978         {
979             try {
980                 SplitWriteAsyncProtocolRequest splitWriteRequest = asyncRequest as SplitWriteAsyncProtocolRequest;
981                 if (splitWriteRequest != null)
982                     ((_SslStream)asyncRequest.AsyncObject).StartWriting(splitWriteRequest.SplitWritesState, splitWriteRequest);
983                 else
984                     ((_SslStream)asyncRequest.AsyncObject).StartWriting(asyncRequest.Buffer, asyncRequest.Offset, asyncRequest.Count, asyncRequest);
985             }
986             catch (Exception e) {
987                 if (asyncRequest.IsUserCompleted) {
988                     // This will throw on a worker thread.
989                     throw;
990                 }
991                 ((_SslStream)asyncRequest.AsyncObject)._SslState.FinishWrite();
992                 asyncRequest.CompleteWithError(e);
993             }
994         }
995         //
996         //
997         private static void ReadHeaderCallback(AsyncProtocolRequest asyncRequest)
998         {
999             // Async ONLY completion
1000             try
1001             {
1002                 _SslStream sslStream = (_SslStream)asyncRequest.AsyncObject;
1003                 BufferAsyncResult bufferResult = (BufferAsyncResult) asyncRequest.UserAsyncResult;
1004                 if (-1 == sslStream.StartFrameBody(asyncRequest.Result, bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, asyncRequest)) 
1005                 {
1006                     // in case we decrypted 0 bytes start another reading.
1007                     sslStream.StartReading(bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, asyncRequest);
1008                 }
1009             }
1010             catch (Exception e)
1011             {
1012                 if (asyncRequest.IsUserCompleted) {
1013                     // This will throw on a worker thread.
1014                     throw;
1015                 }
1016                 asyncRequest.CompleteWithError(e);
1017             }
1018         }
1019         //
1020         //
1021         private static void ReadFrameCallback(AsyncProtocolRequest asyncRequest)
1022         {
1023             // Async ONLY completion
1024             try
1025             {
1026                 _SslStream sslStream = (_SslStream)asyncRequest.AsyncObject;
1027                 BufferAsyncResult bufferResult = (BufferAsyncResult) asyncRequest.UserAsyncResult;
1028                 if (-1 == sslStream.ProcessFrameBody(asyncRequest.Result, bufferResult.Buffer, bufferResult.Offset, bufferResult.Count,asyncRequest))
1029                 {
1030                     // in case we decrypted 0 bytes start another reading.
1031                     sslStream.StartReading(bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, asyncRequest);
1032                 }
1033             }
1034             catch (Exception e)
1035             {
1036                 if (asyncRequest.IsUserCompleted) {
1037                     // This will throw on a worker thread.
1038                     throw;
1039                 }
1040                 asyncRequest.CompleteWithError(e);
1041             }
1042         }
1043
1044         private class SplitWriteAsyncProtocolRequest: AsyncProtocolRequest
1045         {
1046             internal SplitWritesState SplitWritesState;  // If one buffer is no enough (such as for multiple writes)
1047
1048             internal SplitWriteAsyncProtocolRequest(LazyAsyncResult userAsyncResult): base (userAsyncResult)
1049             {
1050             }
1051
1052             internal void SetNextRequest(SplitWritesState splitWritesState, AsyncProtocolCallback callback)
1053             {
1054                 SplitWritesState = splitWritesState;
1055                 SetNextRequest(null, 0, 0,callback);
1056             }
1057         }
1058         //
1059     }
1060 }
1061 #endif