/// <summary> /// This method will write SASL string to given byte array using given <see cref="IEncodingInfo"/>, while escaping and transforming string as needed. /// See <see cref="CheckString"/> for more information about escaping and transforming. /// </summary> /// <param name="str">The string to write.</param> /// <param name="encodingInfo">The <see cref="IEncodingInfo"/> to use.</param> /// <param name="array">The byte array where to write the string.</param> /// <param name="offset">The offset in <paramref name="array"/> where to start writing.</param> /// <param name="normalizer">The optional custom normalizer callback to use.</param> /// <param name="checkResult">The result of <see cref="CheckString"/>, if it was used before calling this method.</param> /// <seealso cref="CheckString"/> /// <remarks> /// This method will not allocate any strings. /// </remarks> public static void WriteString( String str, IEncodingInfo encodingInfo, ResizableArray <Byte> array, ref Int32 offset, CustomStringNormalizer normalizer = null, Int32?checkResult = null ) { var checkResultValue = checkResult ?? CheckString(str, normalizer); var encoding = encodingInfo.Encoding; if (checkResultValue == -1) { // String may be written as-is array.CurrentMaxCapacity = offset + encoding.GetByteCount(str); encodingInfo.WriteString(array.Array, ref offset, str); } else { var maxCharByteCountDiff = encodingInfo.MaxCharByteCount - 1; String tmpStr; // Calculate byte size (actually, we are calculating the upper bound for byte size, but that's ok) var byteSize = encoding.GetByteCount(str); for (var i = checkResultValue; i < str.Length; ++i) { var c = str[i]; if (c >= MIN_SPECIAL_CHAR) { // It is possible that we need to replace character if (IsNonAsciiSpace(c)) { // Mapping special space into "normal" space (0x20) // Byte size doesn't grow at least. } else if (IsCommonlyMappedToNothing(c)) { --byteSize; } else if ((tmpStr = normalizer?.Invoke(str, i)) != null) { byteSize += encoding.GetByteCount(tmpStr) - 1; } } } // Now we know that the string will take max 'byteSize' amount of bytes array.CurrentMaxCapacity = offset + byteSize; // We can get the actual array now, since we won't be resizing the ResizableArray anymore var buffer = array.Array; // Helper method to write chunks of "normal" characters var prev = 0; void WritePreviousChunk(Int32 charIdx, ref Int32 writeIdx) { writeIdx += encoding.GetBytes(str, prev, charIdx - prev, buffer, writeIdx); prev = charIdx + 1; } for (var i = checkResultValue; i < str.Length; ++i) { var c = str[i]; if (c >= MIN_SPECIAL_CHAR) { // It is possible that we need to replace character if (IsNonAsciiSpace(c)) { // Mapping special space into "normal" space (0x20) WritePreviousChunk(i, ref offset); encodingInfo.WriteASCIIByte(buffer, ref offset, 0x20); } else if (IsCommonlyMappedToNothing(c)) { // Nothing additional to write WritePreviousChunk(i, ref offset); } else if ((tmpStr = normalizer?.Invoke(str, i)) != null) { WritePreviousChunk(i, ref offset); if (tmpStr.Length > 0) { encodingInfo.WriteString(buffer, ref offset, tmpStr); } } } } if (prev < str.Length) { // Write final chunk WritePreviousChunk(str.Length - 1, ref offset); } } }
private static async ValueTask <Int32> PerformWriteJSONTTokenAsync( StreamWriterWithResizableBuffer stream, IEncodingInfo encoding, JToken jsonValue ) { ArgumentValidator.ValidateNotNull(nameof(jsonValue), jsonValue); Int32 bytesWritten; var asciiSize = encoding.BytesPerASCIICharacter; Int32 max; (Int32 Offset, Int32 Count)range; switch (jsonValue) { case JArray array: range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)ARRAY_START); bytesWritten = range.Count; max = array.Count; if (max > 0) { for (var i = 0; i < max; ++i) { bytesWritten += await PerformWriteJSONTTokenAsync(stream, encoding, array[i]); if (i < max - 1) { range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)ARRAY_VALUE_DELIM); bytesWritten += range.Count; } } } range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)ARRAY_END); bytesWritten += await stream.FlushAsync(); break; case JObject obj: range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_START); bytesWritten = range.Count; max = obj.Count; if (max > 0) { var j = 0; foreach (var kvp in obj) { bytesWritten += WriteJSONString(stream, encoding, kvp.Key); range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_KEY_VALUE_DELIM); bytesWritten += range.Count; await stream.FlushAsync(); bytesWritten += await PerformWriteJSONTTokenAsync(stream, encoding, kvp.Value); if (++j < max) { range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_VALUE_DELIM); bytesWritten += range.Count; } } } range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_END); bytesWritten += await stream.FlushAsync(); break; case JValue value: var val = value.Value; switch (val) { case String str: bytesWritten = WriteJSONString(stream, encoding, str); break; case Boolean boolean: // Write 'true' or 'false' range = stream.ReserveBufferSegment(boolean ? 4 : 5); encoding.WriteString(stream.Buffer, ref range.Offset, boolean ? "true" : "false"); bytesWritten = range.Count; break; case Int64 i64: range = stream.ReserveBufferSegment(encoding.GetTextualIntegerRepresentationSize(i64)); encoding.WriteIntegerTextual(stream.Buffer, ref range.Offset, i64); bytesWritten = range.Count; break; case Double dbl: // Have to allocate string :/ var dblStr = dbl.ToString(System.Globalization.CultureInfo.InvariantCulture); range = stream.ReserveBufferSegment(encoding.Encoding.GetByteCount(dblStr)); encoding.WriteString(stream.Buffer, ref range.Offset, dblStr); bytesWritten = range.Count; break; case null: // Write 'null' range = stream.ReserveBufferSegment(asciiSize * 4); encoding.WriteString(stream.Buffer, ref range.Offset, "null"); bytesWritten = range.Count; break; default: throw new NotSupportedException($"Unsupported primitive value {val}."); } // Remember to flush stream await stream.FlushAsync(); break; default: throw new NotSupportedException($"Unrecognized JToken type: {jsonValue?.GetType()}."); } return(bytesWritten); }