1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 using System.Collections;
29 using System.Net.Sockets;
30 using System.Security.Cryptography;
31 using System.Security.Cryptography.X509Certificates;
32 using System.Threading;
34 using Mono.Security.Interface;
36 namespace Mono.Security.Protocol.Tls
43 abstract class SslStreamBase: Stream, IDisposable
45 private delegate void AsyncHandshakeDelegate(InternalAsyncResult asyncResult, bool fromWrite);
49 static ManualResetEvent record_processing = new ManualResetEvent (true);
51 internal Stream innerStream;
52 internal MemoryStream inputBuffer;
53 internal Context context;
54 internal RecordProtocol protocol;
55 internal bool ownsStream;
56 private volatile bool disposed;
57 private bool checkCertRevocationStatus;
58 private object negotiate;
61 private ManualResetEvent negotiationComplete;
68 protected SslStreamBase(
74 throw new ArgumentNullException("stream is null.");
76 if (!stream.CanRead || !stream.CanWrite)
78 throw new ArgumentNullException("stream is not both readable and writable.");
81 this.inputBuffer = new MemoryStream();
82 this.innerStream = stream;
83 this.ownsStream = ownsStream;
84 this.negotiate = new object();
85 this.read = new object();
86 this.write = new object();
87 this.negotiationComplete = new ManualResetEvent(false);
93 private void AsyncHandshakeCallback(IAsyncResult asyncResult)
95 InternalAsyncResult internalResult = asyncResult.AsyncState as InternalAsyncResult;
101 this.EndNegotiateHandshake(asyncResult);
105 this.protocol.SendAlert(ref ex);
106 throw new IOException("The authentication or decryption has failed.", ex);
109 if (internalResult.ProceedAfterHandshake)
111 //kick off the read or write process (whichever called us) after the handshake is complete
112 if (internalResult.FromWrite)
114 InternalBeginWrite(internalResult);
118 InternalBeginRead(internalResult);
120 negotiationComplete.Set();
124 negotiationComplete.Set();
125 internalResult.SetComplete();
131 negotiationComplete.Set();
132 internalResult.SetComplete(ex);
136 internal bool MightNeedHandshake
140 if (this.context.HandshakeState == HandshakeState.Finished)
146 lock (this.negotiate)
148 return (this.context.HandshakeState != HandshakeState.Finished);
154 internal void NegotiateHandshake()
156 if (this.MightNeedHandshake)
158 InternalAsyncResult ar = new InternalAsyncResult(null, null, null, 0, 0, false, false);
160 //if something already started negotiation, wait for it.
161 //otherwise end it ourselves.
162 if (!BeginNegotiateHandshake(ar))
164 this.negotiationComplete.WaitOne();
168 this.EndNegotiateHandshake(ar);
175 #region Abstracts/Virtuals
177 internal abstract IAsyncResult BeginNegotiateHandshake (AsyncCallback callback, object state);
178 internal abstract void EndNegotiateHandshake (IAsyncResult result);
180 internal abstract X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates,
181 X509Certificate serverCertificate,
183 X509CertificateCollection serverRequestedCertificates);
185 internal abstract bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors);
186 internal abstract ValidationResult OnRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection);
187 internal abstract bool HaveRemoteValidation2Callback { get; }
189 internal abstract AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost);
193 #region Event Methods
195 internal X509Certificate RaiseLocalCertificateSelection(X509CertificateCollection certificates,
196 X509Certificate remoteCertificate,
198 X509CertificateCollection requestedCertificates)
200 return OnLocalCertificateSelection(certificates, remoteCertificate, targetHost, requestedCertificates);
203 internal bool RaiseRemoteCertificateValidation(X509Certificate certificate, int[] errors)
205 return OnRemoteCertificateValidation(certificate, errors);
208 internal ValidationResult RaiseRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
210 return OnRemoteCertificateValidation2 (collection);
213 internal AsymmetricAlgorithm RaiseLocalPrivateKeySelection(
214 X509Certificate certificate,
217 return OnLocalPrivateKeySelection(certificate, targetHost);
221 #region Security Properties
223 public bool CheckCertRevocationStatus
225 get { return this.checkCertRevocationStatus; }
226 set { this.checkCertRevocationStatus = value; }
229 public CipherAlgorithmType CipherAlgorithm
233 if (this.context.HandshakeState == HandshakeState.Finished)
235 return this.context.Current.Cipher.CipherAlgorithmType;
238 return CipherAlgorithmType.None;
242 public int CipherStrength
246 if (this.context.HandshakeState == HandshakeState.Finished)
248 return this.context.Current.Cipher.EffectiveKeyBits;
255 public HashAlgorithmType HashAlgorithm
259 if (this.context.HandshakeState == HandshakeState.Finished)
261 return this.context.Current.Cipher.HashAlgorithmType;
264 return HashAlgorithmType.None;
268 public int HashStrength
272 if (this.context.HandshakeState == HandshakeState.Finished)
274 return this.context.Current.Cipher.HashSize * 8;
281 public int KeyExchangeStrength
285 if (this.context.HandshakeState == HandshakeState.Finished)
287 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
294 public ExchangeAlgorithmType KeyExchangeAlgorithm
298 if (this.context.HandshakeState == HandshakeState.Finished)
300 return this.context.Current.Cipher.ExchangeAlgorithmType;
303 return ExchangeAlgorithmType.None;
307 public SecurityProtocolType SecurityProtocol
311 if (this.context.HandshakeState == HandshakeState.Finished)
313 return this.context.SecurityProtocol;
320 public X509Certificate ServerCertificate
324 if (this.context.HandshakeState == HandshakeState.Finished)
326 if (this.context.ServerSettings.Certificates != null &&
327 this.context.ServerSettings.Certificates.Count > 0)
329 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
337 // this is used by Mono's certmgr tool to download certificates
338 internal Mono.Security.X509.X509CertificateCollection ServerCertificates
340 get { return context.ServerSettings.Certificates; }
345 #region Internal Async Result/State Class
347 private class InternalAsyncResult : IAsyncResult
349 private object locker = new object ();
350 private AsyncCallback _userCallback;
351 private object _userState;
352 private Exception _asyncException;
353 private ManualResetEvent handle;
354 private bool completed;
355 private int _bytesRead;
356 private bool _fromWrite;
357 private bool _proceedAfterHandshake;
359 private byte[] _buffer;
363 public InternalAsyncResult(AsyncCallback userCallback, object userState, byte[] buffer, int offset, int count, bool fromWrite, bool proceedAfterHandshake)
365 _userCallback = userCallback;
366 _userState = userState;
370 _fromWrite = fromWrite;
371 _proceedAfterHandshake = proceedAfterHandshake;
374 public bool ProceedAfterHandshake
376 get { return _proceedAfterHandshake; }
379 public bool FromWrite
381 get { return _fromWrite; }
386 get { return _buffer; }
391 get { return _offset; }
396 get { return _count; }
401 get { return _bytesRead; }
404 public object AsyncState
406 get { return _userState; }
409 public Exception AsyncException
411 get { return _asyncException; }
414 public bool CompletedWithError
417 if (IsCompleted == false)
419 return null != _asyncException;
423 public WaitHandle AsyncWaitHandle
428 handle = new ManualResetEvent (completed);
434 public bool CompletedSynchronously
436 get { return false; }
439 public bool IsCompleted
447 private void SetComplete(Exception ex, int bytesRead)
454 _asyncException = ex;
455 _bytesRead = bytesRead;
459 if (_userCallback != null)
460 _userCallback.BeginInvoke (this, null, null);
463 public void SetComplete(Exception ex)
468 public void SetComplete(int bytesRead)
470 SetComplete(null, bytesRead);
473 public void SetComplete()
475 SetComplete(null, 0);
480 #region Stream Overrides and Async Stream Operations
482 private bool BeginNegotiateHandshake(InternalAsyncResult asyncResult)
486 lock (this.negotiate)
488 if (this.context.HandshakeState == HandshakeState.None)
490 this.BeginNegotiateHandshake(new AsyncCallback(AsyncHandshakeCallback), asyncResult);
502 this.negotiationComplete.Set();
503 this.protocol.SendAlert(ref ex);
505 throw new IOException("The authentication or decryption has failed.", ex);
509 private void EndNegotiateHandshake(InternalAsyncResult asyncResult)
511 if (asyncResult.IsCompleted == false)
512 asyncResult.AsyncWaitHandle.WaitOne();
514 if (asyncResult.CompletedWithError)
516 throw asyncResult.AsyncException;
520 public override IAsyncResult BeginRead(
524 AsyncCallback callback,
527 this.checkDisposed();
531 throw new ArgumentNullException("buffer is a null reference.");
535 throw new ArgumentOutOfRangeException("offset is less than 0.");
537 if (offset > buffer.Length)
539 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
543 throw new ArgumentOutOfRangeException("count is less than 0.");
545 if (count > (buffer.Length - offset))
547 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
550 InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, false, true);
552 if (this.MightNeedHandshake)
554 if (! BeginNegotiateHandshake(asyncResult))
556 //we made it down here so the handshake was not started.
557 //another thread must have started it in the mean time.
558 //wait for it to complete and then perform our original operation
559 this.negotiationComplete.WaitOne();
561 InternalBeginRead(asyncResult);
566 InternalBeginRead(asyncResult);
572 // bigger than max record length for SSL/TLS
573 private byte[] recbuf = new byte[16384];
575 private void InternalBeginRead(InternalAsyncResult asyncResult)
583 // If actual buffer is fully read, reset it
584 bool shouldReset = this.inputBuffer.Position == this.inputBuffer.Length && this.inputBuffer.Length > 0;
586 // If the buffer isn't fully read, but does have data, we need to immediately
587 // read the info from the buffer and let the user know that they have more data.
588 bool shouldReadImmediately = (this.inputBuffer.Length > 0) && (asyncResult.Count > 0);
594 else if (shouldReadImmediately)
596 preReadSize = this.inputBuffer.Read(asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
600 // This is explicitly done outside the synclock to avoid
601 // any potential deadlocks in the delegate call.
604 asyncResult.SetComplete(preReadSize);
606 else if (recordStream.Position < recordStream.Length) {
607 InternalReadCallback_inner (asyncResult, recbuf, new object[] { recbuf, asyncResult }, false, 0);
609 else if (!this.context.ReceivedConnectionEnd)
611 // this will read data from the network until we have (at least) one
612 // record to send back to the caller
613 this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
614 new AsyncCallback(InternalReadCallback), new object[] { recbuf, asyncResult });
618 // We're done with the connection so we need to let the caller know with 0 bytes read
619 asyncResult.SetComplete(0);
624 this.protocol.SendAlert(ref ex);
625 throw new IOException("The authentication or decryption has failed.", ex);
630 private MemoryStream recordStream = new MemoryStream();
632 // read encrypted data until we have enough to decrypt (at least) one
633 // record and return are the records (may be more than one) we have
634 private void InternalReadCallback(IAsyncResult result)
639 object[] state = (object[])result.AsyncState;
640 byte[] recbuf = (byte[])state[0];
641 InternalAsyncResult internalResult = (InternalAsyncResult)state[1];
645 int n = innerStream.EndRead(result);
648 // Add the just received data to the waiting data
649 recordStream.Write(recbuf, 0, n);
653 // 0 length data means this read operation is done (lost connection in the case of a network stream).
654 internalResult.SetComplete(0);
658 InternalReadCallback_inner(internalResult, recbuf, state, true, n);
662 internalResult.SetComplete(ex);
667 // read encrypted data until we have enough to decrypt (at least) one
668 // record and return are the records (may be more than one) we have
669 private void InternalReadCallback_inner(InternalAsyncResult internalResult, byte[] recbuf, object[] state, bool didRead, int n)
676 bool dataToReturn = false;
677 long pos = recordStream.Position;
679 recordStream.Position = 0;
680 byte[] record = null;
682 // don't try to decode record unless we have at least 5 bytes
683 // i.e. type (1), protocol (2) and length (2)
684 if (recordStream.Length >= 5)
686 record = this.protocol.ReceiveRecord(recordStream);
689 // a record of 0 length is valid (and there may be more record after it)
690 while (record != null)
692 // we probably received more stuff after the record, and we must keep it!
693 long remainder = recordStream.Length - recordStream.Position;
694 byte[] outofrecord = null;
697 outofrecord = new byte[remainder];
698 recordStream.Read(outofrecord, 0, outofrecord.Length);
703 long position = this.inputBuffer.Position;
705 if (record.Length > 0)
707 // Write new data to the inputBuffer
708 this.inputBuffer.Seek(0, SeekOrigin.End);
709 this.inputBuffer.Write(record, 0, record.Length);
711 // Restore buffer position
712 this.inputBuffer.Seek(position, SeekOrigin.Begin);
717 recordStream.SetLength(0);
722 recordStream.Write(outofrecord, 0, outofrecord.Length);
723 // type (1), protocol (2) and length (2)
724 if (recordStream.Length >= 5)
726 // try to see if another record is available
727 recordStream.Position = 0;
728 record = this.protocol.ReceiveRecord(recordStream);
730 pos = recordStream.Length;
739 if (!dataToReturn && (!didRead || (n > 0)))
741 if (context.ReceivedConnectionEnd) {
742 internalResult.SetComplete (0);
744 // there is no record to return to caller and (possibly) more data waiting
745 // so continue reading from network (and appending to stream)
746 recordStream.Position = recordStream.Length;
747 this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
748 new AsyncCallback(InternalReadCallback), state);
753 // we have record(s) to return -or- no more available to read from network
754 // reset position for further reading
755 recordStream.Position = pos;
760 bytesRead = this.inputBuffer.Read(internalResult.Buffer, internalResult.Offset, internalResult.Count);
763 internalResult.SetComplete(bytesRead);
768 internalResult.SetComplete(ex);
772 private void InternalBeginWrite(InternalAsyncResult asyncResult)
776 // Send the buffer as a TLS record
780 byte[] record = this.protocol.EncodeRecord(
781 ContentType.ApplicationData, asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
783 this.innerStream.BeginWrite(
784 record, 0, record.Length, new AsyncCallback(InternalWriteCallback), asyncResult);
789 this.protocol.SendAlert (ref ex);
792 throw new IOException("The authentication or decryption has failed.", ex);
796 private void InternalWriteCallback(IAsyncResult ar)
801 InternalAsyncResult internalResult = (InternalAsyncResult)ar.AsyncState;
805 this.innerStream.EndWrite(ar);
806 internalResult.SetComplete();
810 internalResult.SetComplete(ex);
814 public override IAsyncResult BeginWrite(
818 AsyncCallback callback,
821 this.checkDisposed();
825 throw new ArgumentNullException("buffer is a null reference.");
829 throw new ArgumentOutOfRangeException("offset is less than 0.");
831 if (offset > buffer.Length)
833 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
837 throw new ArgumentOutOfRangeException("count is less than 0.");
839 if (count > (buffer.Length - offset))
841 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
845 InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, true, true);
847 if (this.MightNeedHandshake)
849 if (! BeginNegotiateHandshake(asyncResult))
851 //we made it down here so the handshake was not started.
852 //another thread must have started it in the mean time.
853 //wait for it to complete and then perform our original operation
854 this.negotiationComplete.WaitOne();
856 InternalBeginWrite(asyncResult);
861 InternalBeginWrite(asyncResult);
867 public override int EndRead(IAsyncResult asyncResult)
869 this.checkDisposed();
871 InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
872 if (internalResult == null)
874 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
877 // Always wait until the read is complete
878 if (!asyncResult.IsCompleted)
880 if (!asyncResult.AsyncWaitHandle.WaitOne ())
881 throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndRead");
884 if (internalResult.CompletedWithError)
886 throw internalResult.AsyncException;
889 return internalResult.BytesRead;
892 public override void EndWrite(IAsyncResult asyncResult)
894 this.checkDisposed();
896 InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
897 if (internalResult == null)
899 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginWrite.");
903 if (!asyncResult.IsCompleted)
905 if (!internalResult.AsyncWaitHandle.WaitOne ())
906 throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndWrite");
909 if (internalResult.CompletedWithError)
911 throw internalResult.AsyncException;
915 public override void Close()
920 public override void Flush()
922 this.checkDisposed();
924 this.innerStream.Flush();
927 public int Read(byte[] buffer)
929 return this.Read(buffer, 0, buffer.Length);
932 public override int Read(byte[] buffer, int offset, int count)
934 this.checkDisposed ();
938 throw new ArgumentNullException ("buffer");
942 throw new ArgumentOutOfRangeException("offset is less than 0.");
944 if (offset > buffer.Length)
946 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
950 throw new ArgumentOutOfRangeException("count is less than 0.");
952 if (count > (buffer.Length - offset))
954 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
957 if (this.context.HandshakeState != HandshakeState.Finished)
959 this.NegotiateHandshake (); // Handshake negotiation
964 record_processing.Reset ();
965 // do we already have some decrypted data ?
966 if (this.inputBuffer.Position > 0) {
967 // or maybe we used all the buffer before ?
968 if (this.inputBuffer.Position == this.inputBuffer.Length) {
969 this.inputBuffer.SetLength (0);
971 int n = this.inputBuffer.Read (buffer, offset, count);
973 record_processing.Set ();
979 bool needMoreData = false;
981 // we first try to process the read with the data we already have
982 if ((recordStream.Position == 0) || needMoreData) {
983 needMoreData = false;
984 // if we loop, then it either means we need more data
985 byte[] recbuf = new byte[16384];
988 int value = innerStream.ReadByte ();
990 recbuf[0] = (byte) value;
994 n = innerStream.Read (recbuf, 0, recbuf.Length);
997 // Add the new received data to the waiting data
998 if ((recordStream.Length > 0) && (recordStream.Position != recordStream.Length))
999 recordStream.Seek (0, SeekOrigin.End);
1000 recordStream.Write (recbuf, 0, n);
1002 // or that the read operation is done (lost connection in the case of a network stream).
1003 record_processing.Set ();
1008 bool dataToReturn = false;
1010 recordStream.Position = 0;
1011 byte[] record = null;
1013 // don't try to decode record unless we have at least 5 bytes
1014 // i.e. type (1), protocol (2) and length (2)
1015 if (recordStream.Length >= 5) {
1016 record = this.protocol.ReceiveRecord (recordStream);
1017 needMoreData = (record == null);
1020 // a record of 0 length is valid (and there may be more record after it)
1021 while (record != null) {
1022 // we probably received more stuff after the record, and we must keep it!
1023 long remainder = recordStream.Length - recordStream.Position;
1024 byte[] outofrecord = null;
1025 if (remainder > 0) {
1026 outofrecord = new byte[remainder];
1027 recordStream.Read (outofrecord, 0, outofrecord.Length);
1030 long position = this.inputBuffer.Position;
1032 if (record.Length > 0) {
1033 // Write new data to the inputBuffer
1034 this.inputBuffer.Seek (0, SeekOrigin.End);
1035 this.inputBuffer.Write (record, 0, record.Length);
1037 // Restore buffer position
1038 this.inputBuffer.Seek (position, SeekOrigin.Begin);
1039 dataToReturn = true;
1042 recordStream.SetLength (0);
1045 if (remainder > 0) {
1046 recordStream.Write (outofrecord, 0, outofrecord.Length);
1047 recordStream.Position = 0;
1051 // we have record(s) to return -or- no more available to read from network
1052 // reset position for further reading
1053 int i = inputBuffer.Read (buffer, offset, count);
1054 record_processing.Set ();
1060 catch (TlsException ex)
1062 throw new IOException("The authentication or decryption has failed.", ex);
1064 catch (Exception ex)
1066 throw new IOException("IO exception during read.", ex);
1071 public override long Seek(long offset, SeekOrigin origin)
1073 throw new NotSupportedException();
1076 public override void SetLength(long value)
1078 throw new NotSupportedException();
1081 public void Write(byte[] buffer)
1083 this.Write(buffer, 0, buffer.Length);
1086 public override void Write(byte[] buffer, int offset, int count)
1088 this.checkDisposed ();
1092 throw new ArgumentNullException ("buffer");
1096 throw new ArgumentOutOfRangeException("offset is less than 0.");
1098 if (offset > buffer.Length)
1100 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
1104 throw new ArgumentOutOfRangeException("count is less than 0.");
1106 if (count > (buffer.Length - offset))
1108 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
1111 if (this.context.HandshakeState != HandshakeState.Finished)
1113 this.NegotiateHandshake ();
1120 // Send the buffer as a TLS record
1121 byte[] record = this.protocol.EncodeRecord (ContentType.ApplicationData, buffer, offset, count);
1122 this.innerStream.Write (record, 0, record.Length);
1124 catch (Exception ex)
1126 this.protocol.SendAlert(ref ex);
1128 throw new IOException("The authentication or decryption has failed.", ex);
1133 public override bool CanRead
1135 get { return this.innerStream.CanRead; }
1138 public override bool CanSeek
1140 get { return false; }
1143 public override bool CanWrite
1145 get { return this.innerStream.CanWrite; }
1148 public override long Length
1150 get { throw new NotSupportedException(); }
1153 public override long Position
1157 throw new NotSupportedException();
1161 throw new NotSupportedException();
1166 #region IDisposable Members and Finalizer
1170 this.Dispose(false);
1173 protected override void Dispose (bool disposing)
1179 if (this.innerStream != null)
1181 if (this.context.HandshakeState == HandshakeState.Finished &&
1182 !this.context.SentConnectionEnd)
1184 // Write close notify
1186 this.protocol.SendAlert(AlertDescription.CloseNotify);
1191 if (this.ownsStream)
1193 // Close inner stream
1194 this.innerStream.Close();
1197 this.ownsStream = false;
1198 this.innerStream = null;
1201 this.disposed = true;
1202 base.Dispose (disposing);
1208 #region Misc Methods
1210 private void resetBuffer()
1212 this.inputBuffer.SetLength(0);
1213 this.inputBuffer.Position = 0;
1216 internal void checkDisposed()
1220 throw new ObjectDisposedException("The Stream is closed.");