Bug 20583

Summary: ReadAsStreamAsync returns 0 length stream over SSL
Product: [Mono] Class Libraries Reporter: John Miller [MSFT] <john.miller>
Component: System.WebAssignee: Martin Baulig <martin.baulig>
Status: RESOLVED FIXED    
Severity: major CC: akirby, grendel, jim_hanmer, mono-bugs+monodroid, mono-bugs+mono, shrutis, valentin
Priority: High    
Version: master   
Target Milestone: Untriaged   
Hardware: PC   
OS: Mac OS   
Tags: Is this bug a regression?: ---
Last known good build:
Attachments: desktop_test.cs

Description John Miller [MSFT] 2014-06-13 12:08:12 UTC
## Overview: 

    When calling the same service over HTTPS / SSL a response is received, but the response content is empty (0 bytes)

## Steps to Reproduce: 

    1. Open the attached Test Case.
    2. Make sure that all NuGet packages are restored. 
    3. Set SOAPErrorDemo.Droid as the Startup Project.
    4. Run the project on an android device / emulator.
    5. Toggle SSL and press the Get Results button.
    6. Observe the output "Zero-length content stream" (found at SOAPErrorDemo.GetAppointmentsService:80)

## Actual Results: 

    ReadAsStreamAsync() returns a 0 length stream.

## Expected Results: 

    Return a content stream that is not 0 length.

## Build Date & Platform: 

    XA 4.12.4
    Mono 3.4.0

## Additional Information: 

    There is one subtle complexity that may or may not be relevant - the SSL certificate is self-generated, so System.Net.ServicePointManager.ServerCertificateValidationCallback was used to tell the app to trust the certificate.

Test Case is private.
Comment 2 Shruti 2014-06-16 06:07:50 UTC
I have tried this issue but not able to reproduce it. I performed same steps as given in description. I also added debugger at ReadAsStreamAsync() method and observed that this method is returning content having length of 2296. 
Screencast regarding same is : http://screencast.com/t/frxWYTvdi

Environment Info :
=== Xamarin Studio ===

Version 5.0.1 (build 3)
Installation UUID: 1151d3d9-29c1-4339-b53c-a54856b47901
Runtime:
    Mono 3.4.0 ((no/c3fc3ba)
    GTK+ 2.24.23 (Raleigh theme)

    Package version: 304000204

=== Apple Developer Tools ===

Xcode 5.1 (5084)
Build 5B130a

=== Xamarin.iOS ===

Version: 7.2.3.39 (Business Edition)
Hash: fc6f56b
Branch: 
Build date: 2014-05-19 19:10:29-0400

=== Xamarin.Android ===

Version: 4.12.4 (Trial Edition)
Android SDK: /Users/shruti/Desktop/android-sdk-macosx
    Supported Android versions:
        2.1   (API level 7)
        2.2   (API level 8)
        2.3   (API level 10)
        3.1   (API level 12)
        3.2   (API level 13)
        4.0   (API level 14)
        4.0.3 (API level 15)
        4.1   (API level 16)
        4.2   (API level 17)
        4.3   (API level 18)
        4.4   (API level 19)
Java SDK: /usr
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)

=== Xamarin.Mac ===

Xamarin.Mac: Not Installed

=== Build Information ===

Release ID: 500010003
Git revision: f94ee866936d25105704eb63728ad5a981eda0a4
Build date: 2014-06-04 12:19:12-04
Xamarin addins: 1a6044e8321ea07e03a56b5381951686c82fed8b

=== Operating System ===

Mac OS X 10.9.2
Darwin Shrutis-Mac-mini.local 13.1.0 Darwin Kernel Version 13.1.0
    Thu Jan 16 19:40:37 PST 2014
    root:xnu-2422.90.20~2/RELEASE_X86_64 x86_64

Please let me know If I missed anything to reproduce this issue.
Comment 3 Jim Hanmer 2014-06-16 07:03:53 UTC
Hi Shruti

I am using Visual Studio not Xamarin Studio.

However, I have now tried debugging with Xamarin Studio, and I can sometimes (2 out of 3 times) get a response with 'short wait' selected. I can never get a response when 'long wait' is selected.

Could you please retry with an additional step between 5 and 6 to 'Toggle 'Long Wait''? This will call a web service that is slower to respond (typically between 10 and 20s) and will return a larger payload.

Also could you please attempt to reproduce when deploying / debugging from within Visual Studio?

Many thanks

Jim
Comment 4 Shruti 2014-06-17 03:25:26 UTC
@Jim, Thanks for providing more detail. It help us to reproduce the issue.

 I have tried this issue on Visual Studio.  I have put breakpoints and debug the application with both cases 
Short Wait and Long Wait.  I observed that When first I debug the application with Short Wait, it is working fine as per expectations. But When debug it with Long Wait,  giving 0 length output. 

Screencast regarding same : http://www.screencast.com/t/4aapAxIQ

I again debug the app with Short wait after debugging with Long Wait. Here I observed that ContentStream contains no content (0 length).

Build Output Log : https://gist.github.com/saurabh360/d23422508cb17331ffeb
Adb Logcat : https://gist.github.com/Mohit-Kheterpal/c0760e498acda8cb91ac

Environment Info :
Windows 8.1 
Microsoft Visual Studio Professional 2013
Xamarin.Android   4.12.4.20 (b5dc5ce91305e19de51d71a1122c109719c4bc34)
Comment 5 Marek Habersack 2014-06-27 06:54:23 UTC
Created attachment 7214 [details]
desktop_test.cs

The issue is reproducible with desktop mono as well. Attached is a distilled destkop case that reproduces it reliably for me with the tip of Mono master.
Comment 6 Marek Habersack 2014-06-27 07:36:32 UTC
Martin, assigning it to you since it appears to be a race condition in how WebConnection reads the SSL stream. Marek Safar determined that putting a sleep in WebConnection.BeginRead allows him to get the data from the stream.
Comment 8 Martin Baulig 2014-07-23 16:08:38 UTC
Good news!  I finally figure out what's causing this and got a test case in my new async web-test suite.

The bug actually has nothing at all to do with SSL - but the circumstances that you need to trigger this are so obscure that they may never happen without SSL.

Here's my test case:
https://github.com/xamarin/web-tests/blob/605a991133a0beb3c81f932ac2ec9bc9ed0923d6/Xamarin.WebTests/Tests/TestHttpClient.cs#L89

If you look at that Write() method, removing the AutoFlush, any of the Thread.Sleep()'s, joining the writer.Write()'s or using a chunk-size that does not start with a zero will make this bug go away.

The web server needs to send a chunk-size that starts with a trailing zero (which is valid) and the stream must be buffered in a way that we read all the headers up to and including this zero.

In my test case, the server sends:

    "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked:\r\n\r\n04\r\nAAAA\r\n0\r\n\r\n\r\n"

and the first Read() returns "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked:\r\n\r\n0", so the ChunkStream gets constructed with a single byte - the training "0" - which is incorrectly considers as an empty chunk.

My guess is that SSL makes this more likely to happen because it seems to be using smaller packet sizes.  Using a real Apache web server without SSL and it's always sending the first 4000+ bytes of a long response.

As a work-around, if you have access to that server, turn off chunked encoding or make it send the chunk size without a trailing zero.  Adding some random HTTP headers could also work if you're lucky.

I should have a real fix tomorrow.
Comment 9 Martin Baulig 2014-07-24 19:05:43 UTC
Fixed; mono/master 288489d and mono-3.6.0-branch 80b4f74.