From 6c6ea19b09244f4f3eff85cb05db84fe0fcb1cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Habinshuti?= Date: Mon, 7 Aug 2023 10:19:31 +0300 Subject: [PATCH] Remove sync I/O from JsonValueUtils async write methods (#2707) * Remove sync I/O in JSON string escaping * Update src/Microsoft.OData.Core/Json/JsonValueUtilsAsync.cs Co-authored-by: Elizabeth Okerio * Address review comments --------- Co-authored-by: Elizabeth Okerio --- .../Json/JsonValueUtils.cs | 19 ++ .../Json/JsonValueUtilsAsync.cs | 162 ++++++++++++++++-- .../Json/NonIndentedTextWriter.cs | 12 ++ .../Json/JsonValueUtilsAsyncTests.cs | 83 ++++++--- 4 files changed, 236 insertions(+), 40 deletions(-) diff --git a/src/Microsoft.OData.Core/Json/JsonValueUtils.cs b/src/Microsoft.OData.Core/Json/JsonValueUtils.cs index 49339695e8..cc5f7a49c2 100644 --- a/src/Microsoft.OData.Core/Json/JsonValueUtils.cs +++ b/src/Microsoft.OData.Core/Json/JsonValueUtils.cs @@ -787,6 +787,25 @@ private static void WriteSubstringToBuffer(string inputString, ref int currentIn currentIndex += substrLength; } + /// + /// Writes a substring starting at a specified position on the string to the buffer. + /// This method is intended for use in async methods, where you cannot pass ref parameters. + /// + /// Input string value. + /// The index in the string at which the substring begins. + /// Char buffer to use for streaming data. + /// Current position in the buffer after the substring has been written. + /// The length of the substring to be copied. + private static void WriteSubstringToBuffer(string inputString, Ref currentIndex, char[] buffer, Ref bufferIndex, int substrLength) + { + Debug.Assert(inputString != null, "inputString != null"); + Debug.Assert(buffer != null, "buffer != null"); + + inputString.CopyTo(currentIndex.Value, buffer, 0, substrLength); + bufferIndex.Value = substrLength; + currentIndex.Value += substrLength; + } + /// /// Writes an escaped string to the buffer. /// diff --git a/src/Microsoft.OData.Core/Json/JsonValueUtilsAsync.cs b/src/Microsoft.OData.Core/Json/JsonValueUtilsAsync.cs index 0f48d0cdf1..41955fc21c 100644 --- a/src/Microsoft.OData.Core/Json/JsonValueUtilsAsync.cs +++ b/src/Microsoft.OData.Core/Json/JsonValueUtilsAsync.cs @@ -412,13 +412,13 @@ async Task WriteEscapedJsonStringValueInnerAsync( { innerBuffer.Value = BufferUtils.InitializeBufferIfRequired(innerBufferPool, innerBuffer.Value); int bufferLength = innerBuffer.Value.Length; - int bufferIndex = 0; - int currentIndex = 0; + Ref bufferIndex = new Ref(0); + Ref currentIndex = new Ref(0); // Let's copy and flush strings up to the first index of the special char - while (currentIndex < innerFirstIndex) + while (currentIndex.Value < innerFirstIndex) { - int substrLength = innerFirstIndex - currentIndex; + int substrLength = innerFirstIndex - currentIndex.Value; Debug.Assert(substrLength > 0, "SubStrLength should be greater than 0 always"); @@ -427,23 +427,24 @@ async Task WriteEscapedJsonStringValueInnerAsync( // Otherwise copy to the buffer and go on from there. if (substrLength >= bufferLength) { - innerInputString.CopyTo(currentIndex, innerBuffer.Value, 0, bufferLength); + innerInputString.CopyTo(currentIndex.Value, innerBuffer.Value, 0, bufferLength); await innerWriter.WriteAsync(innerBuffer.Value, 0, bufferLength).ConfigureAwait(false); - currentIndex += bufferLength; + currentIndex.Value += bufferLength; } else { - WriteSubstringToBuffer(innerInputString, ref currentIndex, innerBuffer.Value, ref bufferIndex, substrLength); + WriteSubstringToBuffer(innerInputString, currentIndex, innerBuffer.Value, bufferIndex, substrLength); } } // Write escaped string to buffer - WriteEscapedStringToBuffer(innerWriter, innerInputString, ref currentIndex, innerBuffer.Value, ref bufferIndex, innerStringEscapeOption); + await WriteEscapedStringToBufferAsync(innerWriter, innerInputString, currentIndex, innerBuffer.Value, bufferIndex, innerStringEscapeOption) + .ConfigureAwait(false); // write any remaining chars to the writer - if (bufferIndex > 0) + if (bufferIndex.Value > 0) { - await innerWriter.WriteAsync(innerBuffer.Value, 0, bufferIndex).ConfigureAwait(false); + await innerWriter.WriteAsync(innerBuffer.Value, 0, bufferIndex.Value).ConfigureAwait(false); } } } @@ -468,15 +469,18 @@ internal static async Task WriteEscapedCharArrayAsync( Ref buffer, ICharArrayPool bufferPool) { - int bufferIndex = 0; + Ref bufferIndex = new Ref(0); + Ref inputArrayOffsetRef = new Ref(inputArrayOffset); buffer.Value = BufferUtils.InitializeBufferIfRequired(bufferPool, buffer.Value); - WriteEscapedCharArrayToBuffer(writer, inputArray, ref inputArrayOffset, inputArrayCount, buffer.Value, ref bufferIndex, stringEscapeOption); + await WriteEscapedCharArrayToBufferAsync(writer, inputArray, inputArrayOffsetRef, inputArrayCount, buffer.Value, bufferIndex, stringEscapeOption) + .ConfigureAwait(false); + // write remaining bytes in buffer - if (bufferIndex > 0) + if (bufferIndex.Value > 0) { - await writer.WriteAsync(buffer.Value, 0, bufferIndex).ConfigureAwait(false); + await writer.WriteAsync(buffer.Value, 0, bufferIndex.Value).ConfigureAwait(false); } } @@ -490,5 +494,135 @@ private static Task WriteQuotedAsync(this TextWriter writer, string text) return writer.WriteAsync( string.Concat(JsonConstants.QuoteCharacter, text, JsonConstants.QuoteCharacter)); } + + /// + /// Writes an escaped string to the buffer. + /// + /// The text writer to write the output. + /// Input string value. + /// The index in the string at which copying should begin. + /// Char buffer to use for streaming data. + /// Current position in the buffer after the string has been written. + /// The string escape option. + /// + /// IMPORTANT: After all characters have been written, + /// caller is responsible for writing the final buffer contents to the writer. + /// + private static async Task WriteEscapedStringToBufferAsync( + TextWriter writer, + string inputString, + Ref currentIndex, + char[] buffer, + Ref bufferIndex, + ODataStringEscapeOption stringEscapeOption) + { + Debug.Assert(inputString != null, "inputString != null"); + Debug.Assert(buffer != null, "buffer != null"); + + for (; currentIndex.Value < inputString.Length; currentIndex.Value++) + { + bufferIndex.Value = await EscapeAndWriteCharToBufferAsync( + writer, + inputString[currentIndex.Value], + buffer, + bufferIndex.Value, + stringEscapeOption + ) + .ConfigureAwait(false); + } + } + + /// + /// Escapes and writes a character buffer, flushing to the writer as the buffer fills. + /// + /// The text writer to write the output to. + /// The character to write to the buffer. + /// Char buffer to use for streaming data. + /// The index into the buffer in which to write the character. + /// The string escape option. + /// Current position in the buffer after the character has been written. + /// + /// IMPORTANT: After all characters have been written, + /// caller is responsible for writing the final buffer contents to the writer. + /// + private static async Task EscapeAndWriteCharToBufferAsync(TextWriter writer, char character, char[] buffer, int bufferIndex, ODataStringEscapeOption stringEscapeOption) + { + int bufferLength = buffer.Length; + string escapedString = null; + + if (stringEscapeOption == ODataStringEscapeOption.EscapeNonAscii || character <= 0x7F) + { + escapedString = JsonValueUtils.SpecialCharToEscapedStringMap[character]; + } + + // Append the unhandled characters (that do not require special treatment) + // to the buffer. + if (escapedString == null) + { + buffer[bufferIndex] = character; + bufferIndex++; + } + else + { + // Okay, an unhandled character was detected. + // First lets check if we can fit it in the existing buffer, if not, + // flush the current buffer and reset. Add the escaped string to the buffer + // and continue. + int escapedStringLength = escapedString.Length; + Debug.Assert(escapedStringLength <= bufferLength, "Buffer should be larger than the escaped string"); + + if ((bufferIndex + escapedStringLength) > bufferLength) + { + await writer.WriteAsync(buffer, 0, bufferIndex).ConfigureAwait(false); + bufferIndex = 0; + } + + escapedString.CopyTo(0, buffer, bufferIndex, escapedStringLength); + bufferIndex += escapedStringLength; + } + + if (bufferIndex >= bufferLength) + { + Debug.Assert(bufferIndex == bufferLength, + "We should never encounter a situation where the buffer index is greater than the buffer length"); + await writer.WriteAsync(buffer, 0, bufferIndex).ConfigureAwait(false); + bufferIndex = 0; + } + + return bufferIndex; + } + + /// + /// Writes an escaped char array to the buffer. + /// + /// The text writer to write the output to. + /// Character array to write. + /// How many characters to skip in the input array. + /// How many characters to write from the input array. + /// Char buffer to use for streaming data. + /// Current position in the buffer after the string has been written. + /// The string escape option. + /// + /// IMPORTANT: After all characters have been written, + /// caller is responsible for writing the final buffer contents to the writer. + /// + private static async Task WriteEscapedCharArrayToBufferAsync( + TextWriter writer, + char[] inputArray, + Ref inputArrayOffset, + int inputArrayCount, + char[] buffer, + Ref bufferIndex, + ODataStringEscapeOption stringEscapeOption) + { + Debug.Assert(inputArray != null, "inputArray != null"); + Debug.Assert(buffer != null, "buffer != null"); + + for (; inputArrayOffset.Value < inputArrayCount; inputArrayOffset.Value++) + { + bufferIndex.Value = await EscapeAndWriteCharToBufferAsync(writer, inputArray[inputArrayOffset.Value], buffer, bufferIndex.Value, stringEscapeOption) + .ConfigureAwait(false); + } + } } } diff --git a/src/Microsoft.OData.Core/Json/NonIndentedTextWriter.cs b/src/Microsoft.OData.Core/Json/NonIndentedTextWriter.cs index cc8655f519..d2c428d11b 100644 --- a/src/Microsoft.OData.Core/Json/NonIndentedTextWriter.cs +++ b/src/Microsoft.OData.Core/Json/NonIndentedTextWriter.cs @@ -62,6 +62,18 @@ public override Task WriteAsync(char value) return this.writer.WriteAsync(value); } + /// + public override void Write(char[] buffer, int index, int count) + { + this.writer.Write(buffer, index, count); + } + + /// + public override Task WriteAsync(char[] buffer, int index, int count) + { + return this.writer.WriteAsync(buffer, index, count); + } + /// /// Writes a new line. /// diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonValueUtilsAsyncTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonValueUtilsAsyncTests.cs index ab3fcc0ba6..a850a5ac37 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonValueUtilsAsyncTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonValueUtilsAsyncTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Threading.Tasks; using Microsoft.OData.Json; using Xunit; @@ -16,7 +17,7 @@ namespace Microsoft.OData.Tests.Json { public class JsonValueUtilsAsyncTests { - private MemoryStream stream; + private Stream stream; private NonIndentedTextWriter writer; private Ref buffer; private IDictionary escapedCharMap; @@ -58,7 +59,7 @@ public JsonValueUtilsAsyncTests() public async Task WriteEmptyStringShouldWork(ODataStringEscapeOption stringEscapeOption) { await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Empty, stringEscapeOption, this.buffer); - Assert.Equal("\"\"", this.StreamToString()); + Assert.Equal("\"\"", await this.StreamToStringAsync()); } [Theory] @@ -67,7 +68,7 @@ public async Task WriteEmptyStringShouldWork(ODataStringEscapeOption stringEscap public async Task WriteNonSpecialCharactersShouldWork(ODataStringEscapeOption stringEscapeOption) { await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, "abcdefg123", stringEscapeOption, this.buffer); - Assert.Equal("\"abcdefg123\"", this.StreamToString()); + Assert.Equal("\"abcdefg123\"", await this.StreamToStringAsync()); } [Theory] @@ -76,21 +77,21 @@ public async Task WriteNonSpecialCharactersShouldWork(ODataStringEscapeOption st public async Task WriteLowSpecialCharactersShouldWorkForEscapeOption(ODataStringEscapeOption stringEscapeOption) { await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, "cA_\n\r\b", stringEscapeOption, this.buffer); - Assert.Equal("\"cA_\\n\\r\\b\"", this.StreamToString()); + Assert.Equal("\"cA_\\n\\r\\b\"", await this.StreamToStringAsync()); } [Fact] public async Task WriteHighSpecialCharactersShouldWorkForEscapeNonAsciiOption() { await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, "cA_Россия", ODataStringEscapeOption.EscapeNonAscii, this.buffer); - Assert.Equal("\"cA_\\u0420\\u043e\\u0441\\u0441\\u0438\\u044f\"", this.StreamToString()); + Assert.Equal("\"cA_\\u0420\\u043e\\u0441\\u0441\\u0438\\u044f\"", await this.StreamToStringAsync()); } [Fact] public async Task WriteHighSpecialCharactersShouldWorkForEscapeOnlyControlsOption() { await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, "cA_Россия", ODataStringEscapeOption.EscapeOnlyControls, this.buffer); - Assert.Equal("\"cA_Россия\"", this.StreamToString()); + Assert.Equal("\"cA_Россия\"", await this.StreamToStringAsync()); } [Fact] @@ -101,7 +102,7 @@ public async Task WriteSpecialCharactersShouldWork() this.Reset(); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Format("{0}", specialChar), ODataStringEscapeOption.EscapeNonAscii, this.buffer); - Assert.Equal(string.Format("\"{0}\"", this.escapedCharMap[specialChar]), this.StreamToString()); + Assert.Equal(string.Format("\"{0}\"", this.escapedCharMap[specialChar]), await this.StreamToStringAsync()); } } @@ -113,7 +114,7 @@ public async Task WriteSpecialCharactersAtStartOfStringShouldWork() this.Reset(); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Format("{0}MiddleEnd", specialChar), ODataStringEscapeOption.EscapeNonAscii, this.buffer); - Assert.Equal(string.Format("\"{0}MiddleEnd\"", this.escapedCharMap[specialChar]), this.StreamToString()); + Assert.Equal(string.Format("\"{0}MiddleEnd\"", this.escapedCharMap[specialChar]), await this.StreamToStringAsync()); } } @@ -125,7 +126,7 @@ public async Task WriteSpecialCharactersAtMiddleOfStringShouldWork() this.Reset(); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Format("Start{0}End", specialChar), ODataStringEscapeOption.EscapeNonAscii, this.buffer); - Assert.Equal(string.Format("\"Start{0}End\"", this.escapedCharMap[specialChar]), this.StreamToString()); + Assert.Equal(string.Format("\"Start{0}End\"", this.escapedCharMap[specialChar]), await this.StreamToStringAsync()); } } @@ -137,7 +138,7 @@ public async Task WriteSpecialCharactersAtEndOfStringShouldWork() this.Reset(); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Format("StartMiddle{0}", specialChar), ODataStringEscapeOption.EscapeNonAscii, this.buffer); - Assert.Equal(string.Format("\"StartMiddle{0}\"", this.escapedCharMap[specialChar]), this.StreamToString()); + Assert.Equal(string.Format("\"StartMiddle{0}\"", this.escapedCharMap[specialChar]), await this.StreamToStringAsync()); } } @@ -149,7 +150,7 @@ public async Task WriteMultipleSpecialCharactersShouldWork() this.Reset(); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Format("{0}Start{0}Middle{0}End", specialChar), ODataStringEscapeOption.EscapeNonAscii, this.buffer); - Assert.Equal(string.Format("\"{0}Start{0}Middle{0}End\"", this.escapedCharMap[specialChar]), this.StreamToString()); + Assert.Equal(string.Format("\"{0}Start{0}Middle{0}End\"", this.escapedCharMap[specialChar]), await this.StreamToStringAsync()); } } @@ -162,7 +163,7 @@ public async Task WriteSpecialCharactersAtStartOfBufferLengthShouldWork() Ref charBuffer = new Ref(new char[10]); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Format("StartMiddle{0}End", specialChar), ODataStringEscapeOption.EscapeNonAscii, charBuffer); - Assert.Equal(string.Format("\"StartMiddle{0}End\"", this.escapedCharMap[specialChar]), this.StreamToString()); + Assert.Equal(string.Format("\"StartMiddle{0}End\"", this.escapedCharMap[specialChar]), await this.StreamToStringAsync()); } } @@ -175,7 +176,7 @@ public async Task WriteMultipleSpecialCharactersAtEndOfBufferLengthShouldWork() Ref charBuffer = new Ref(new char[6]); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Format("Start{0}Middle{0}End{0}", specialChar), ODataStringEscapeOption.EscapeNonAscii, charBuffer); - Assert.Equal(string.Format("\"Start{0}Middle{0}End{0}\"", this.escapedCharMap[specialChar]), this.StreamToString()); + Assert.Equal(string.Format("\"Start{0}Middle{0}End{0}\"", this.escapedCharMap[specialChar]), await this.StreamToStringAsync()); } } @@ -188,7 +189,7 @@ public async Task WriteSpecialCharactersAtEndOfBufferLengthShouldWork() Ref charBuffer = new Ref(new char[6]); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Format("Start{0}", specialChar), ODataStringEscapeOption.EscapeNonAscii, charBuffer); - Assert.Equal(string.Format("\"Start{0}\"", this.escapedCharMap[specialChar]), this.StreamToString()); + Assert.Equal(string.Format("\"Start{0}\"", this.escapedCharMap[specialChar]), await this.StreamToStringAsync()); } } @@ -203,10 +204,39 @@ public async Task WriteControllCharactersWithStringEscapeOptionShouldWork(ODataS Ref charBuffer = new Ref(new char[6]); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, string.Format("S{0}M{0}E{0}", specialChar), escapeOption, charBuffer); - Assert.Equal(string.Format("\"S{0}M{0}E{0}\"", this.controlCharsMap[specialChar]), this.StreamToString()); + Assert.Equal(string.Format("\"S{0}M{0}E{0}\"", this.controlCharsMap[specialChar]), await this.StreamToStringAsync()); } } + [Fact] + public async Task WriteLongEscapedStringShouldWork() + { + this.Reset(); + Ref charBuffer = new Ref(new char[6]); + string value = string.Join("\t", Enumerable.Repeat('\n', 95000)); + await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, value, + ODataStringEscapeOption.EscapeNonAscii, charBuffer); + string expected = "\"" + string.Join("\\t", Enumerable.Repeat("\\n", 95000)) + "\""; + Assert.Equal(expected, await this.StreamToStringAsync()); + } + + [Fact] + public async Task WriteLongEscapedCharArrayShouldWork() + { + Ref charBuffer = new Ref(new char[6]); + string value = string.Join("\t", Enumerable.Repeat('\n', 95000)); + await JsonValueUtils.WriteEscapedCharArrayAsync( + this.writer, + value.ToCharArray(), + 0, + value.Length, + ODataStringEscapeOption.EscapeNonAscii, + charBuffer, + null); + string expected = string.Join("\\t", Enumerable.Repeat("\\n", 95000)); + Assert.Equal(expected, await this.StreamToStringAsync()); + } + [Theory] [InlineData(ODataStringEscapeOption.EscapeOnlyControls)] [InlineData(ODataStringEscapeOption.EscapeNonAscii)] @@ -215,7 +245,7 @@ public async Task WriteStringWithNoSpecialCharShouldLeaveBufferUntouched(ODataSt this.Reset(); Ref charBuffer = new Ref(null); await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, "StartMiddleEnd", stringEscapeOption, charBuffer); - Assert.Equal("\"StartMiddleEnd\"", this.StreamToString()); + Assert.Equal("\"StartMiddleEnd\"", await this.StreamToStringAsync()); Assert.Null(charBuffer.Value); } @@ -231,7 +261,7 @@ public async Task WriteStringShouldIgnoreExistingContentsOfTheBuffer(ODataString } await JsonValueUtils.WriteEscapedJsonStringAsync(this.writer, "StartVeryVeryLongMiddleEnd", stringEscapeOption, charBuffer); - Assert.Equal("\"StartVeryVeryLongMiddleEnd\"", this.StreamToString()); + Assert.Equal("\"StartVeryVeryLongMiddleEnd\"", await this.StreamToStringAsync()); } [Fact] @@ -239,7 +269,7 @@ public async Task WriteByteShouldWork() { var byteArray = new byte[] { 1, 2, 3 }; await JsonValueUtils.WriteValueAsync(this.writer, byteArray, this.buffer); - Assert.Equal("\"AQID\"", this.StreamToString()); + Assert.Equal("\"AQID\"", await this.StreamToStringAsync()); } [Fact] @@ -254,7 +284,7 @@ public async Task WriteLongBytesShouldWork() // Act, Get Base64 string from OData library await JsonValueUtils.WriteValueAsync(this.writer, byteArray, this.buffer); - string convertedBase64String = this.StreamToString(); + string convertedBase64String = await this.StreamToStringAsync(); // Get Base64 string directly calling the converter. string actualBase64String = JsonConstants.QuoteCharacter + Convert.ToBase64String(byteArray) + JsonConstants.QuoteCharacter; @@ -283,7 +313,7 @@ public async Task WriteBytesLengthSameAsBufferSizeShouldWork() // Act, Get Base64 string from OData library await JsonValueUtils.WriteValueAsync(this.writer, byteArray, this.buffer); - string convertedBase64String = this.StreamToString(); + string convertedBase64String = await this.StreamToStringAsync(); // Get Base64 string directly calling the converter. string actualBase64String = JsonConstants.QuoteCharacter + Convert.ToBase64String(byteArray) + JsonConstants.QuoteCharacter; @@ -298,29 +328,30 @@ public async Task WriteEmptyByteShouldWork() { var byteArray = new byte[] { }; await JsonValueUtils.WriteValueAsync(this.writer, byteArray, this.buffer); - Assert.Equal("\"\"", this.StreamToString()); + Assert.Equal("\"\"", await this.StreamToStringAsync()); } [Fact] public async Task WriteNullByteShouldWork() { await JsonValueUtils.WriteValueAsync(this.writer, (byte[])null, this.buffer); - Assert.Equal("null", this.StreamToString()); + Assert.Equal("null", await this.StreamToStringAsync()); } private void Reset() { this.buffer = new Ref(); - this.stream = new MemoryStream(); + this.stream = new AsyncStream(new MemoryStream()); StreamWriter innerWriter = new StreamWriter(this.stream); - innerWriter.AutoFlush = true; this.writer = new NonIndentedTextWriter(innerWriter); } - private string StreamToString() + private async Task StreamToStringAsync() { + await this.writer.FlushAsync(); this.stream.Position = 0; - return (new StreamReader(this.stream)).ReadToEnd(); + string result = await (new StreamReader(this.stream)).ReadToEndAsync(); + return result; } } }