// 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());
}
}