// Copyright 2021 Cloud Native Foundation. // Licensed under the Apache 2.0 license. // See LICENSE file in the project root for full license information. using System; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace CloudNative.CloudEvents.Core { /// /// Utilities methods for dealing with binary data, converting between /// streams, arrays, Memory{T} etc. /// public static class BinaryDataUtilities { /// /// Asynchronously consumes the remaining content of the given stream, returning /// it as a read-only memory segment. /// /// The stream to read from. Must not be null. /// The content of the stream (from its original position), as a read-only memory segment. public async static Task> ToReadOnlyMemoryAsync(Stream stream) { Validation.CheckNotNull(stream, nameof(stream)); // TODO: Optimize if it's already a MemoryStream? Will only work in some cases, // and is most likely to occur in tests, where the efficiency doesn't matter as much. var memory = new MemoryStream(); await stream.CopyToAsync(memory).ConfigureAwait(false); // It's safe to use memory.GetBuffer() and memory.Position here, as this is a stream // we've created using the parameterless constructor. var buffer = memory.GetBuffer(); return new ReadOnlyMemory(buffer, 0, (int)memory.Position); } /// /// Consumes the remaining content of the given stream, returning /// it as a read-only memory segment. /// /// The stream to read from. Must not be null. /// The content of the stream (from its original position), as a read-only memory segment. public static ReadOnlyMemory ToReadOnlyMemory(Stream stream) { Validation.CheckNotNull(stream, nameof(stream)); // TODO: Optimize if it's already a MemoryStream? Will only work in some cases, // and is most likely to occur in tests, where the efficiency doesn't matter as much. var memory = new MemoryStream(); stream.CopyTo(memory); // It's safe to use memory.GetBuffer() and memory.Position here, as this is a stream // we've created using the parameterless constructor. var buffer = memory.GetBuffer(); return new ReadOnlyMemory(buffer, 0, (int)memory.Position); } /// /// Returns a read-only view over the given memory where /// possible, or over a copy of the data if the memory cannot be read as an array segment. /// This method should be used with care, due to the "sometimes shared, sometimes not" /// nature of the result. /// /// The memory to create a stream view over. /// A read-only stream view over . public static MemoryStream AsStream(ReadOnlyMemory memory) { var segment = GetArraySegment(memory); return new MemoryStream(segment.Array!, segment.Offset, segment.Count, false); } /// /// Decodes the given memory as a string, using the specified encoding. /// /// The memory to decode. /// The encoding to use. Must not be null. public static string GetString(ReadOnlyMemory memory, Encoding encoding) { Validation.CheckNotNull(encoding, nameof(encoding)); // TODO: If we introduce an additional netstandard2.1 target, we can use encoding.GetString(memory.Span) var segment = GetArraySegment(memory); return encoding.GetString(segment.Array!, segment.Offset, segment.Count); } /// /// Copies the given memory to a stream, asynchronously. /// /// The source memory to copy from. /// The stream to copy to. Must not be null. /// A task representing the asynchronous operation. public static async Task CopyToStreamAsync(ReadOnlyMemory source, Stream destination) { Validation.CheckNotNull(destination, nameof(destination)); var segment = GetArraySegment(source); await destination.WriteAsync(segment.Array!, segment.Offset, segment.Count).ConfigureAwait(false); } /// /// Returns the data from as a byte array, return the underlying array /// if there is one, or creating a copy otherwise. This method should be used with care, due to the /// "sometimes shared, sometimes not" nature of the result. (It is generally safe to use this with the result /// of encoding a CloudEvent, assuming the same memory is not used elsewhere.) /// /// The memory to obtain the data from. /// The data in as an array. public static byte[] AsArray(ReadOnlyMemory memory) { var segment = GetArraySegment(memory); // We probably don't actually need to check the offset: if the count is the same as the length, // I can't see how the offset can be non-zero. But it doesn't *hurt* as a check. return segment.Array is not null && segment.Offset == 0 && segment.Count == segment.Array.Length ? segment.Array : memory.ToArray(); } private static ArraySegment GetArraySegment(ReadOnlyMemory memory) => MemoryMarshal.TryGetArray(memory, out var segment) ? segment : new ArraySegment(memory.ToArray()); } }