I just need to read up to N bytes from a SslStream but if no byte has been received before a timeout, cancel, while leaving the stream in a valid state in order to try again later. (*)
This can be done easily for non-SSL streams i.e. NetworkStream simply by using its ReadTimeout property which will make the stream throw an exception on timeout. Unfortunately this approach doesn't work on SslStream per the official docs:
SslStream assumes that a timeout along with any other IOException when one is thrown from the inner stream will be treated as fatal by its caller. Reusing a SslStream instance after a timeout will return garbage. An application should Close the SslStream and throw an exception in these cases.
[Updated 1] I tried a different approach like this:
task = stream->ReadAsync(buffer, 0, buffer->Length);
if (task->Wait(timeout_ms)) {
count = task->Result;
...
}
But this doesn't work if Wait() returned false: when calling ReadAsync() again later it throws an exception:
Exception thrown: 'System.NotSupportedException' in System.dll Tests.exe Warning: 0 : Failed reading from socket: System.NotSupportedException: The BeginRead method cannot be called when another read operation is pending.
[Update 2] I tried yet another approach to implement timeouts by calling Poll(timeout, ...READ) on the underlying TcpClient socket: if it returns true, then call Read() on the SSlStream, or if it returns false then we have a timeout. This doesn't work either: because SslStream presumably uses its own internal intermediary buffers, Poll() can return false even if there's data left to be read in the SslStream.
[Update 3] Another possibility would be to write a custom Stream subclass that would sit between NetworkStream and SslStream and capture the timeout exception and return 0 bytes instead to SslStream. I'm not sure how to do this, and more importantly, I have no idea if returning a 0 bytes read to SslStream would still not corrupt it somehow.
(*) The reason I'm trying to do this is that reading synchronously with a timeout from a non-secure or secure socket is the pattern I'm already using on iOS, OS X, Linux and Android for some cross-platform code. It works for non-secure sockets in .NET so the only case remaining is SslStream.