3 // AsyncProtocolRequest.cs
6 // Martin Baulig <martin.baulig@xamarin.com>
8 // Copyright (c) 2015 Xamarin, Inc.
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;
20 namespace Mono.Net.Security
22 class BufferOffsetSize
27 public int TotalBytes;
30 public int EndOffset {
31 get { return Offset + Size; }
34 public int Remaining {
35 get { return Buffer.Length - Offset - Size; }
38 public BufferOffsetSize (byte[] buffer, int offset, int size)
41 throw new ArgumentNullException (nameof (buffer));
43 throw new ArgumentOutOfRangeException (nameof (offset));
44 if (size < 0 || offset + size > buffer.Length)
45 throw new ArgumentOutOfRangeException (nameof (size));
53 public override string ToString ()
55 return string.Format ("[BufferOffsetSize: {0} {1}]", Offset, Size);
59 class BufferOffsetSize2 : BufferOffsetSize
61 public readonly int InitialSize;
63 public BufferOffsetSize2 (int size)
64 : base (new byte[size], 0, 0)
73 Buffer = new byte[InitialSize];
77 public void MakeRoom (int size)
79 if (Remaining >= size)
82 int missing = size - Remaining;
83 if (Offset == 0 && Size == 0) {
84 Buffer = new byte[size];
88 var buffer = new byte[Buffer.Length + missing];
89 Buffer.CopyTo (buffer, 0);
93 public void AppendData (byte[] buffer, int offset, int size)
96 System.Buffer.BlockCopy (buffer, offset, Buffer, EndOffset, size);
101 enum AsyncOperationStatus
109 class AsyncProtocolResult
111 public int UserResult {
114 public ExceptionDispatchInfo Error {
118 public AsyncProtocolResult (int result)
123 public AsyncProtocolResult (ExceptionDispatchInfo error)
129 abstract class AsyncProtocolRequest
131 public MobileAuthenticatedStream Parent {
135 public bool RunSynchronously {
139 public int ID => ++next_id;
141 public string Name => GetType ().Name;
143 public int UserResult {
151 readonly object locker = new object ();
155 public AsyncProtocolRequest (MobileAuthenticatedStream parent, bool sync)
158 RunSynchronously = sync;
161 [SD.Conditional ("MARTIN_DEBUG")]
162 protected void Debug (string message, params object[] args)
164 Parent.Debug ("{0}({1}:{2}): {3}", Name, Parent.ID, ID, string.Format (message, args));
167 internal void RequestRead (int size)
170 RequestedSize += size;
171 Debug ("RequestRead: {0}", size);
175 internal void RequestWrite ()
180 internal async Task<AsyncProtocolResult> StartOperation (CancellationToken cancellationToken)
182 Debug ("Start Operation: {0}", this);
183 if (Interlocked.CompareExchange (ref Started, 1, 0) != 0)
184 throw new InvalidOperationException ();
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);
195 async Task ProcessOperation (CancellationToken cancellationToken)
197 var status = AsyncOperationStatus.Initialize;
198 while (status != AsyncOperationStatus.Complete) {
199 cancellationToken.ThrowIfCancellationRequested ();
200 Debug ("ProcessOperation: {0}", status);
202 var ret = await InnerRead (cancellationToken).ConfigureAwait (false);
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.");
214 Debug ("ProcessOperation run: {0}", status);
216 AsyncOperationStatus newStatus;
218 case AsyncOperationStatus.Initialize:
219 case AsyncOperationStatus.Continue:
220 case AsyncOperationStatus.ReadDone:
221 newStatus = Run (status);
224 throw new InvalidOperationException ();
227 if (Interlocked.Exchange (ref WriteRequested, 0) != 0) {
228 // Flush the write queue.
229 await Parent.InnerWrite (RunSynchronously, cancellationToken);
232 Debug ("ProcessOperation done: {0} -> {1}", status, newStatus);
238 async Task<int?> InnerRead (CancellationToken cancellationToken)
240 int? totalRead = null;
241 var requestedSize = Interlocked.Exchange (ref RequestedSize, 0);
242 while (requestedSize > 0) {
243 Debug ("ProcessOperation - read inner: {0}", requestedSize);
245 var ret = await Parent.InnerRead (RunSynchronously, requestedSize, cancellationToken).ConfigureAwait (false);
246 Debug ("ProcessOperation - read inner done: {0} - {1}", requestedSize, ret);
250 if (ret > requestedSize)
251 throw new InvalidOperationException ();
254 requestedSize -= ret;
255 var newRequestedSize = Interlocked.Exchange (ref RequestedSize, 0);
256 requestedSize += newRequestedSize;
263 * This will operate on the internal buffers and never block.
265 protected abstract AsyncOperationStatus Run (AsyncOperationStatus status);
267 public override string ToString ()
269 return string.Format ("[{0}]", Name);
273 class AsyncHandshakeRequest : AsyncProtocolRequest
275 public AsyncHandshakeRequest (MobileAuthenticatedStream parent, bool sync)
276 : base (parent, sync)
280 protected override AsyncOperationStatus Run (AsyncOperationStatus status)
282 return Parent.ProcessHandshake (status);
286 abstract class AsyncReadOrWriteRequest : AsyncProtocolRequest
288 protected BufferOffsetSize UserBuffer {
292 protected int CurrentSize {
296 public AsyncReadOrWriteRequest (MobileAuthenticatedStream parent, bool sync, byte[] buffer, int offset, int size)
297 : base (parent, sync)
299 UserBuffer = new BufferOffsetSize (buffer, offset, size);
302 public override string ToString ()
304 return string.Format ("[{0}: {1}]", Name, UserBuffer);
308 class AsyncReadRequest : AsyncReadOrWriteRequest
310 public AsyncReadRequest (MobileAuthenticatedStream parent, bool sync, byte[] buffer, int offset, int size)
311 : base (parent, sync, buffer, offset, size)
315 protected override AsyncOperationStatus Run (AsyncOperationStatus status)
317 Debug ("ProcessRead - read user: {0} {1}", this, status);
319 var (ret, wantMore) = Parent.ProcessRead (UserBuffer);
321 Debug ("ProcessRead - read user done: {0} - {1} {2}", this, ret, wantMore);
325 return AsyncOperationStatus.Complete;
329 UserBuffer.Offset += ret;
330 UserBuffer.Size -= ret;
332 Debug ("Process Read - read user done #1: {0} - {1} {2}", this, CurrentSize, wantMore);
334 if (wantMore && CurrentSize == 0)
335 return AsyncOperationStatus.Continue;
337 UserResult = CurrentSize;
338 return AsyncOperationStatus.Complete;
342 class AsyncWriteRequest : AsyncReadOrWriteRequest
344 public AsyncWriteRequest (MobileAuthenticatedStream parent, bool sync, byte[] buffer, int offset, int size)
345 : base (parent, sync, buffer, offset, size)
349 protected override AsyncOperationStatus Run (AsyncOperationStatus status)
351 Debug ("ProcessWrite - write user: {0} {1}", this, status);
353 if (UserBuffer.Size == 0) {
354 UserResult = CurrentSize;
355 return AsyncOperationStatus.Complete;
358 var (ret, wantMore) = Parent.ProcessWrite (UserBuffer);
360 Debug ("ProcessWrite - write user done: {0} - {1} {2}", this, ret, wantMore);
364 return AsyncOperationStatus.Complete;
368 UserBuffer.Offset += ret;
369 UserBuffer.Size -= ret;
372 return AsyncOperationStatus.Continue;
374 UserResult = CurrentSize;
375 return AsyncOperationStatus.Complete;
379 class AsyncShutdownRequest : AsyncProtocolRequest
381 public AsyncShutdownRequest (MobileAuthenticatedStream parent)
382 : base (parent, false)
386 protected override AsyncOperationStatus Run (AsyncOperationStatus status)
388 return Parent.ProcessShutdown (status);