[System]: Correctly implement close and shutdown in SslStream. (#4969)
[mono.git] / mcs / class / System / Mono.Net.Security / AsyncProtocolRequest.cs
1 #if SECURITY_DEP
2 //
3 // AsyncProtocolRequest.cs
4 //
5 // Author:
6 //       Martin Baulig <martin.baulig@xamarin.com>
7 //
8 // Copyright (c) 2015 Xamarin, Inc.
9 //
10 using System;
11 using System.IO;
12 using System.Net;
13 using System.Net.Security;
14 using System.Security.Authentication;
15 using SD = System.Diagnostics;
16 using System.Threading;
17 using System.Threading.Tasks;
18 using System.Runtime.ExceptionServices;
19
20 namespace Mono.Net.Security
21 {
22         class BufferOffsetSize
23         {
24                 public byte[] Buffer;
25                 public int Offset;
26                 public int Size;
27                 public int TotalBytes;
28                 public bool Complete;
29
30                 public int EndOffset {
31                         get { return Offset + Size; }
32                 }
33
34                 public int Remaining {
35                         get { return Buffer.Length - Offset - Size; }
36                 }
37
38                 public BufferOffsetSize (byte[] buffer, int offset, int size)
39                 {
40                         if (buffer == null)
41                                 throw new ArgumentNullException (nameof (buffer));
42                         if (offset < 0)
43                                 throw new ArgumentOutOfRangeException (nameof (offset));
44                         if (size < 0 || offset + size > buffer.Length)
45                                 throw new ArgumentOutOfRangeException (nameof (size));
46
47                         Buffer = buffer;
48                         Offset = offset;
49                         Size = size;
50                         Complete = false;
51                 }
52
53                 public override string ToString ()
54                 {
55                         return string.Format ("[BufferOffsetSize: {0} {1}]", Offset, Size);
56                 }
57         }
58
59         class BufferOffsetSize2 : BufferOffsetSize
60         {
61                 public readonly int InitialSize;
62
63                 public BufferOffsetSize2 (int size)
64                         : base (new byte[size], 0, 0)
65                 {
66                         InitialSize = size;
67                 }
68
69                 public void Reset ()
70                 {
71                         Offset = Size = 0;
72                         TotalBytes = 0;
73                         Buffer = new byte[InitialSize];
74                         Complete = false;
75                 }
76
77                 public void MakeRoom (int size)
78                 {
79                         if (Remaining >= size)
80                                 return;
81
82                         int missing = size - Remaining;
83                         if (Offset == 0 && Size == 0) {
84                                 Buffer = new byte[size];
85                                 return;
86                         }
87
88                         var buffer = new byte[Buffer.Length + missing];
89                         Buffer.CopyTo (buffer, 0);
90                         Buffer = buffer;
91                 }
92
93                 public void AppendData (byte[] buffer, int offset, int size)
94                 {
95                         MakeRoom (size);
96                         System.Buffer.BlockCopy (buffer, offset, Buffer, EndOffset, size);
97                         Size += size;
98                 }
99         }
100
101         enum AsyncOperationStatus
102         {
103                 Initialize,
104                 Continue,
105                 ReadDone,
106                 Complete
107         }
108
109         class AsyncProtocolResult
110         {
111                 public int UserResult {
112                         get;
113                 }
114                 public ExceptionDispatchInfo Error {
115                         get;
116                 }
117
118                 public AsyncProtocolResult (int result)
119                 {
120                         UserResult = result;
121                 }
122
123                 public AsyncProtocolResult (ExceptionDispatchInfo error)
124                 {
125                         Error = error;
126                 }
127         }
128
129         abstract class AsyncProtocolRequest
130         {
131                 public MobileAuthenticatedStream Parent {
132                         get;
133                 }
134
135                 public bool RunSynchronously {
136                         get;
137                 }
138
139                 public int ID => ++next_id;
140
141                 public string Name => GetType ().Name;
142
143                 public int UserResult {
144                         get;
145                         protected set;
146                 }
147
148                 int Started;
149                 int RequestedSize;
150                 int WriteRequested;
151                 readonly object locker = new object ();
152
153                 static int next_id;
154
155                 public AsyncProtocolRequest (MobileAuthenticatedStream parent, bool sync)
156                 {
157                         Parent = parent;
158                         RunSynchronously = sync;
159                 }
160
161                 [SD.Conditional ("MARTIN_DEBUG")]
162                 protected void Debug (string message, params object[] args)
163                 {
164                         Parent.Debug ("{0}({1}:{2}): {3}", Name, Parent.ID, ID, string.Format (message, args));
165                 }
166
167                 internal void RequestRead (int size)
168                 {
169                         lock (locker) {
170                                 RequestedSize += size;
171                                 Debug ("RequestRead: {0}", size);
172                         }
173                 }
174
175                 internal void RequestWrite ()
176                 {
177                         WriteRequested = 1;
178                 }
179
180                 internal async Task<AsyncProtocolResult> StartOperation (CancellationToken cancellationToken)
181                 {
182                         Debug ("Start Operation: {0}", this);
183                         if (Interlocked.CompareExchange (ref Started, 1, 0) != 0)
184                                 throw new InvalidOperationException ();
185
186                         try {
187                                 await ProcessOperation (cancellationToken).ConfigureAwait (false);
188                                 return new AsyncProtocolResult (UserResult);
189                         } catch (Exception ex) {
190                                 var info = Parent.SetException (MobileAuthenticatedStream.GetSSPIException (ex));
191                                 return new AsyncProtocolResult (info);
192                         }
193                 }
194
195                 async Task ProcessOperation (CancellationToken cancellationToken)
196                 {
197                         var status = AsyncOperationStatus.Initialize;
198                         while (status != AsyncOperationStatus.Complete) {
199                                 cancellationToken.ThrowIfCancellationRequested ();
200                                 Debug ("ProcessOperation: {0}", status);
201
202                                 var ret = await InnerRead (cancellationToken).ConfigureAwait (false);
203                                 if (ret != null) {
204                                         if (ret == 0) {
205                                                 // End-of-stream
206                                                 Debug ("END OF STREAM!");
207                                                 status = AsyncOperationStatus.ReadDone;
208                                         } else if (ret < 0) {
209                                                 // remote prematurely closed connection.
210                                                 throw new IOException ("Remote prematurely closed connection.");
211                                         }
212                                 }
213
214                                 Debug ("ProcessOperation run: {0}", status);
215
216                                 AsyncOperationStatus newStatus;
217                                 switch (status) {
218                                 case AsyncOperationStatus.Initialize:
219                                 case AsyncOperationStatus.Continue:
220                                 case AsyncOperationStatus.ReadDone:
221                                         newStatus = Run (status);
222                                         break;
223                                 default:
224                                         throw new InvalidOperationException ();
225                                 }
226
227                                 if (Interlocked.Exchange (ref WriteRequested, 0) != 0) {
228                                         // Flush the write queue.
229                                         await Parent.InnerWrite (RunSynchronously, cancellationToken);
230                                 }
231
232                                 Debug ("ProcessOperation done: {0} -> {1}", status, newStatus);
233
234                                 status = newStatus;
235                         }
236                 }
237
238                 async Task<int?> InnerRead (CancellationToken cancellationToken)
239                 {
240                         int? totalRead = null;
241                         var requestedSize = Interlocked.Exchange (ref RequestedSize, 0);
242                         while (requestedSize > 0) {
243                                 Debug ("ProcessOperation - read inner: {0}", requestedSize);
244
245                                 var ret = await Parent.InnerRead (RunSynchronously, requestedSize, cancellationToken).ConfigureAwait (false);
246                                 Debug ("ProcessOperation - read inner done: {0} - {1}", requestedSize, ret);
247
248                                 if (ret <= 0)
249                                         return ret;
250                                 if (ret > requestedSize)
251                                         throw new InvalidOperationException ();
252
253                                 totalRead += ret;
254                                 requestedSize -= ret;
255                                 var newRequestedSize = Interlocked.Exchange (ref RequestedSize, 0);
256                                 requestedSize += newRequestedSize;
257                         }
258
259                         return totalRead;
260                 }
261
262                 /*
263                  * This will operate on the internal buffers and never block.
264                  */
265                 protected abstract AsyncOperationStatus Run (AsyncOperationStatus status);
266
267                 public override string ToString ()
268                 {
269                         return string.Format ("[{0}]", Name);
270                 }
271         }
272
273         class AsyncHandshakeRequest : AsyncProtocolRequest
274         {
275                 public AsyncHandshakeRequest (MobileAuthenticatedStream parent, bool sync)
276                         : base (parent, sync)
277                 {
278                 }
279
280                 protected override AsyncOperationStatus Run (AsyncOperationStatus status)
281                 {
282                         return Parent.ProcessHandshake (status);
283                 }
284         }
285
286         abstract class AsyncReadOrWriteRequest : AsyncProtocolRequest
287         {
288                 protected BufferOffsetSize UserBuffer {
289                         get;
290                 }
291
292                 protected int CurrentSize {
293                         get; set;
294                 }
295
296                 public AsyncReadOrWriteRequest (MobileAuthenticatedStream parent, bool sync, byte[] buffer, int offset, int size)
297                         : base (parent, sync)
298                 {
299                         UserBuffer = new BufferOffsetSize (buffer, offset, size);
300                 }
301
302                 public override string ToString ()
303                 {
304                         return string.Format ("[{0}: {1}]", Name, UserBuffer);
305                 }
306         }
307
308         class AsyncReadRequest : AsyncReadOrWriteRequest
309         {
310                 public AsyncReadRequest (MobileAuthenticatedStream parent, bool sync, byte[] buffer, int offset, int size)
311                         : base (parent, sync, buffer, offset, size)
312                 {
313                 }
314
315                 protected override AsyncOperationStatus Run (AsyncOperationStatus status)
316                 {
317                         Debug ("ProcessRead - read user: {0} {1}", this, status);
318
319                         var (ret, wantMore) = Parent.ProcessRead (UserBuffer);
320
321                         Debug ("ProcessRead - read user done: {0} - {1} {2}", this, ret, wantMore);
322
323                         if (ret < 0) {
324                                 UserResult = -1;
325                                 return AsyncOperationStatus.Complete;
326                         }
327
328                         CurrentSize += ret;
329                         UserBuffer.Offset += ret;
330                         UserBuffer.Size -= ret;
331
332                         Debug ("Process Read - read user done #1: {0} - {1} {2}", this, CurrentSize, wantMore);
333
334                         if (wantMore && CurrentSize == 0)
335                                 return AsyncOperationStatus.Continue;
336
337                         UserResult = CurrentSize;
338                         return AsyncOperationStatus.Complete;
339                 }
340         }
341
342         class AsyncWriteRequest : AsyncReadOrWriteRequest
343         {
344                 public AsyncWriteRequest (MobileAuthenticatedStream parent, bool sync, byte[] buffer, int offset, int size)
345                         : base (parent, sync, buffer, offset, size)
346                 {
347                 }
348
349                 protected override AsyncOperationStatus Run (AsyncOperationStatus status)
350                 {
351                         Debug ("ProcessWrite - write user: {0} {1}", this, status);
352
353                         if (UserBuffer.Size == 0) {
354                                 UserResult = CurrentSize;
355                                 return AsyncOperationStatus.Complete;
356                         }
357
358                         var (ret, wantMore) = Parent.ProcessWrite (UserBuffer);
359
360                         Debug ("ProcessWrite - write user done: {0} - {1} {2}", this, ret, wantMore);
361
362                         if (ret < 0) {
363                                 UserResult = -1;
364                                 return AsyncOperationStatus.Complete;
365                         }
366
367                         CurrentSize += ret;
368                         UserBuffer.Offset += ret;
369                         UserBuffer.Size -= ret;
370
371                         if (wantMore)
372                                 return AsyncOperationStatus.Continue;
373
374                         UserResult = CurrentSize;
375                         return AsyncOperationStatus.Complete;
376                 }
377         }
378
379         class AsyncShutdownRequest : AsyncProtocolRequest
380         {
381                 public AsyncShutdownRequest (MobileAuthenticatedStream parent)
382                         : base (parent, false)
383                 {
384                 }
385
386                 protected override AsyncOperationStatus Run (AsyncOperationStatus status)
387                 {
388                         return Parent.ProcessShutdown (status);
389                 }
390         }
391
392 }
393 #endif