|
@@ -34,6 +34,7 @@ using System;
|
|
using System.Collections;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO;
|
|
|
|
+using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using System.Security;
|
|
using System.Text;
|
|
using System.Text;
|
|
#if !NET35
|
|
#if !NET35
|
|
@@ -49,40 +50,26 @@ namespace Google.Protobuf
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Immutable array of bytes.
|
|
/// Immutable array of bytes.
|
|
/// </summary>
|
|
/// </summary>
|
|
|
|
+ [SecuritySafeCritical]
|
|
public sealed class ByteString : IEnumerable<byte>, IEquatable<ByteString>
|
|
public sealed class ByteString : IEnumerable<byte>, IEquatable<ByteString>
|
|
{
|
|
{
|
|
private static readonly ByteString empty = new ByteString(new byte[0]);
|
|
private static readonly ByteString empty = new ByteString(new byte[0]);
|
|
|
|
|
|
- private readonly byte[] bytes;
|
|
|
|
|
|
+ private readonly ReadOnlyMemory<byte> bytes;
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
- /// Unsafe operations that can cause IO Failure and/or other catastrophic side-effects.
|
|
|
|
|
|
+ /// Internal use only. Ensure that the provided memory is not mutated and belongs to this instance.
|
|
/// </summary>
|
|
/// </summary>
|
|
- internal static class Unsafe
|
|
|
|
- {
|
|
|
|
- /// <summary>
|
|
|
|
- /// Constructs a new ByteString from the given byte array. The array is
|
|
|
|
- /// *not* copied, and must not be modified after this constructor is called.
|
|
|
|
- /// </summary>
|
|
|
|
- internal static ByteString FromBytes(byte[] bytes)
|
|
|
|
- {
|
|
|
|
- return new ByteString(bytes);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// <summary>
|
|
|
|
- /// Internal use only. Ensure that the provided array is not mutated and belongs to this instance.
|
|
|
|
- /// </summary>
|
|
|
|
- internal static ByteString AttachBytes(byte[] bytes)
|
|
|
|
|
|
+ internal static ByteString AttachBytes(ReadOnlyMemory<byte> bytes)
|
|
{
|
|
{
|
|
return new ByteString(bytes);
|
|
return new ByteString(bytes);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
- /// Constructs a new ByteString from the given byte array. The array is
|
|
|
|
|
|
+ /// Constructs a new ByteString from the given memory. The memory is
|
|
/// *not* copied, and must not be modified after this constructor is called.
|
|
/// *not* copied, and must not be modified after this constructor is called.
|
|
/// </summary>
|
|
/// </summary>
|
|
- private ByteString(byte[] bytes)
|
|
|
|
|
|
+ private ByteString(ReadOnlyMemory<byte> bytes)
|
|
{
|
|
{
|
|
this.bytes = bytes;
|
|
this.bytes = bytes;
|
|
}
|
|
}
|
|
@@ -117,11 +104,7 @@ namespace Google.Protobuf
|
|
/// </summary>
|
|
/// </summary>
|
|
public ReadOnlySpan<byte> Span
|
|
public ReadOnlySpan<byte> Span
|
|
{
|
|
{
|
|
- [SecuritySafeCritical]
|
|
|
|
- get
|
|
|
|
- {
|
|
|
|
- return new ReadOnlySpan<byte>(bytes);
|
|
|
|
- }
|
|
|
|
|
|
+ get { return bytes.Span; }
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -130,11 +113,7 @@ namespace Google.Protobuf
|
|
/// </summary>
|
|
/// </summary>
|
|
public ReadOnlyMemory<byte> Memory
|
|
public ReadOnlyMemory<byte> Memory
|
|
{
|
|
{
|
|
- [SecuritySafeCritical]
|
|
|
|
- get
|
|
|
|
- {
|
|
|
|
- return new ReadOnlyMemory<byte>(bytes);
|
|
|
|
- }
|
|
|
|
|
|
+ get { return bytes; }
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -144,7 +123,7 @@ namespace Google.Protobuf
|
|
/// <returns>A byte array with the same data as this <c>ByteString</c>.</returns>
|
|
/// <returns>A byte array with the same data as this <c>ByteString</c>.</returns>
|
|
public byte[] ToByteArray()
|
|
public byte[] ToByteArray()
|
|
{
|
|
{
|
|
- return (byte[]) bytes.Clone();
|
|
|
|
|
|
+ return bytes.ToArray();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -153,7 +132,16 @@ namespace Google.Protobuf
|
|
/// <returns>A base64 representation of this <c>ByteString</c>.</returns>
|
|
/// <returns>A base64 representation of this <c>ByteString</c>.</returns>
|
|
public string ToBase64()
|
|
public string ToBase64()
|
|
{
|
|
{
|
|
- return Convert.ToBase64String(bytes);
|
|
|
|
|
|
+ if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment))
|
|
|
|
+ {
|
|
|
|
+ // Fast path. ByteString was created with an array, so pass the underlying array.
|
|
|
|
+ return Convert.ToBase64String(segment.Array, segment.Offset, segment.Count);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Slow path. BytesString is not an array. Convert memory and pass result to ToBase64String.
|
|
|
|
+ return Convert.ToBase64String(bytes.ToArray());
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -197,21 +185,10 @@ namespace Google.Protobuf
|
|
/// <param name="stream">The stream to copy into a ByteString.</param>
|
|
/// <param name="stream">The stream to copy into a ByteString.</param>
|
|
/// <param name="cancellationToken">The cancellation token to use when reading from the stream, if any.</param>
|
|
/// <param name="cancellationToken">The cancellation token to use when reading from the stream, if any.</param>
|
|
/// <returns>A ByteString with content read from the given stream.</returns>
|
|
/// <returns>A ByteString with content read from the given stream.</returns>
|
|
- public async static Task<ByteString> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken))
|
|
|
|
|
|
+ public static Task<ByteString> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
{
|
|
ProtoPreconditions.CheckNotNull(stream, nameof(stream));
|
|
ProtoPreconditions.CheckNotNull(stream, nameof(stream));
|
|
- int capacity = stream.CanSeek ? checked((int) (stream.Length - stream.Position)) : 0;
|
|
|
|
- var memoryStream = new MemoryStream(capacity);
|
|
|
|
- // We have to specify the buffer size here, as there's no overload accepting the cancellation token
|
|
|
|
- // alone. But it's documented to use 81920 by default if not specified.
|
|
|
|
- await stream.CopyToAsync(memoryStream, 81920, cancellationToken);
|
|
|
|
-#if NETSTANDARD1_1 || NETSTANDARD2_0
|
|
|
|
- byte[] bytes = memoryStream.ToArray();
|
|
|
|
-#else
|
|
|
|
- // Avoid an extra copy if we can.
|
|
|
|
- byte[] bytes = memoryStream.Length == memoryStream.Capacity ? memoryStream.GetBuffer() : memoryStream.ToArray();
|
|
|
|
-#endif
|
|
|
|
- return AttachBytes(bytes);
|
|
|
|
|
|
+ return ByteStringAsync.FromStreamAsyncCore(stream, cancellationToken);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
@@ -242,7 +219,6 @@ namespace Google.Protobuf
|
|
/// are copied, so further modifications to the span will not
|
|
/// are copied, so further modifications to the span will not
|
|
/// be reflected in the returned <see cref="ByteString" />.
|
|
/// be reflected in the returned <see cref="ByteString" />.
|
|
/// </summary>
|
|
/// </summary>
|
|
- [SecuritySafeCritical]
|
|
|
|
public static ByteString CopyFrom(ReadOnlySpan<byte> bytes)
|
|
public static ByteString CopyFrom(ReadOnlySpan<byte> bytes)
|
|
{
|
|
{
|
|
return new ByteString(bytes.ToArray());
|
|
return new ByteString(bytes.ToArray());
|
|
@@ -270,7 +246,7 @@ namespace Google.Protobuf
|
|
/// </summary>
|
|
/// </summary>
|
|
public byte this[int index]
|
|
public byte this[int index]
|
|
{
|
|
{
|
|
- get { return bytes[index]; }
|
|
|
|
|
|
+ get { return bytes.Span[index]; }
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -284,7 +260,18 @@ namespace Google.Protobuf
|
|
/// <returns>The result of decoding the binary data with the given decoding.</returns>
|
|
/// <returns>The result of decoding the binary data with the given decoding.</returns>
|
|
public string ToString(Encoding encoding)
|
|
public string ToString(Encoding encoding)
|
|
{
|
|
{
|
|
- return encoding.GetString(bytes, 0, bytes.Length);
|
|
|
|
|
|
+ if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment))
|
|
|
|
+ {
|
|
|
|
+ // Fast path. ByteString was created with an array.
|
|
|
|
+ return encoding.GetString(segment.Array, segment.Offset, segment.Count);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Slow path. BytesString is not an array. Convert memory and pass result to GetString.
|
|
|
|
+ // TODO: Consider using GetString overload that takes a pointer.
|
|
|
|
+ byte[] array = bytes.ToArray();
|
|
|
|
+ return encoding.GetString(array, 0, array.Length);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -304,9 +291,10 @@ namespace Google.Protobuf
|
|
/// Returns an iterator over the bytes in this <see cref="ByteString"/>.
|
|
/// Returns an iterator over the bytes in this <see cref="ByteString"/>.
|
|
/// </summary>
|
|
/// </summary>
|
|
/// <returns>An iterator over the bytes in this object.</returns>
|
|
/// <returns>An iterator over the bytes in this object.</returns>
|
|
|
|
+ [SecuritySafeCritical]
|
|
public IEnumerator<byte> GetEnumerator()
|
|
public IEnumerator<byte> GetEnumerator()
|
|
{
|
|
{
|
|
- return ((IEnumerable<byte>) bytes).GetEnumerator();
|
|
|
|
|
|
+ return MemoryMarshal.ToEnumerable(bytes).GetEnumerator();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -324,7 +312,17 @@ namespace Google.Protobuf
|
|
public CodedInputStream CreateCodedInput()
|
|
public CodedInputStream CreateCodedInput()
|
|
{
|
|
{
|
|
// We trust CodedInputStream not to reveal the provided byte array or modify it
|
|
// We trust CodedInputStream not to reveal the provided byte array or modify it
|
|
- return new CodedInputStream(bytes);
|
|
|
|
|
|
+ if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment) && segment.Count == bytes.Length)
|
|
|
|
+ {
|
|
|
|
+ // Fast path. ByteString was created with a complete array.
|
|
|
|
+ return new CodedInputStream(segment.Array);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Slow path. BytesString is not an array, or is a slice of an array.
|
|
|
|
+ // Convert memory and pass result to WriteRawBytes.
|
|
|
|
+ return new CodedInputStream(bytes.ToArray());
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -343,18 +341,8 @@ namespace Google.Protobuf
|
|
{
|
|
{
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
- if (lhs.bytes.Length != rhs.bytes.Length)
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- for (int i = 0; i < lhs.Length; i++)
|
|
|
|
- {
|
|
|
|
- if (rhs.bytes[i] != lhs.bytes[i])
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return true;
|
|
|
|
|
|
+
|
|
|
|
+ return lhs.bytes.Span.SequenceEqual(rhs.bytes.Span);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -373,6 +361,7 @@ namespace Google.Protobuf
|
|
/// </summary>
|
|
/// </summary>
|
|
/// <param name="obj">The object to compare this with.</param>
|
|
/// <param name="obj">The object to compare this with.</param>
|
|
/// <returns><c>true</c> if <paramref name="obj"/> refers to an equal <see cref="ByteString"/>; <c>false</c> otherwise.</returns>
|
|
/// <returns><c>true</c> if <paramref name="obj"/> refers to an equal <see cref="ByteString"/>; <c>false</c> otherwise.</returns>
|
|
|
|
+ [SecuritySafeCritical]
|
|
public override bool Equals(object obj)
|
|
public override bool Equals(object obj)
|
|
{
|
|
{
|
|
return this == (obj as ByteString);
|
|
return this == (obj as ByteString);
|
|
@@ -383,12 +372,15 @@ namespace Google.Protobuf
|
|
/// will return the same hash code.
|
|
/// will return the same hash code.
|
|
/// </summary>
|
|
/// </summary>
|
|
/// <returns>A hash code for this object.</returns>
|
|
/// <returns>A hash code for this object.</returns>
|
|
|
|
+ [SecuritySafeCritical]
|
|
public override int GetHashCode()
|
|
public override int GetHashCode()
|
|
{
|
|
{
|
|
|
|
+ ReadOnlySpan<byte> b = bytes.Span;
|
|
|
|
+
|
|
int ret = 23;
|
|
int ret = 23;
|
|
- foreach (byte b in bytes)
|
|
|
|
|
|
+ for (int i = 0; i < b.Length; i++)
|
|
{
|
|
{
|
|
- ret = (ret * 31) + b;
|
|
|
|
|
|
+ ret = (ret * 31) + b[i];
|
|
}
|
|
}
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
@@ -403,20 +395,12 @@ namespace Google.Protobuf
|
|
return this == other;
|
|
return this == other;
|
|
}
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
|
- /// Used internally by CodedOutputStream to avoid creating a copy for the write
|
|
|
|
- /// </summary>
|
|
|
|
- internal void WriteRawBytesTo(CodedOutputStream outputStream)
|
|
|
|
- {
|
|
|
|
- outputStream.WriteRawBytes(bytes, 0, bytes.Length);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Copies the entire byte array to the destination array provided at the offset specified.
|
|
/// Copies the entire byte array to the destination array provided at the offset specified.
|
|
/// </summary>
|
|
/// </summary>
|
|
public void CopyTo(byte[] array, int position)
|
|
public void CopyTo(byte[] array, int position)
|
|
{
|
|
{
|
|
- ByteArray.Copy(bytes, 0, array, position, bytes.Length);
|
|
|
|
|
|
+ bytes.CopyTo(array.AsMemory(position));
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -424,7 +408,17 @@ namespace Google.Protobuf
|
|
/// </summary>
|
|
/// </summary>
|
|
public void WriteTo(Stream outputStream)
|
|
public void WriteTo(Stream outputStream)
|
|
{
|
|
{
|
|
- outputStream.Write(bytes, 0, bytes.Length);
|
|
|
|
|
|
+ if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment))
|
|
|
|
+ {
|
|
|
|
+ // Fast path. ByteString was created with an array, so pass the underlying array.
|
|
|
|
+ outputStream.Write(segment.Array, segment.Offset, segment.Count);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Slow path. BytesString is not an array. Convert memory and pass result to WriteRawBytes.
|
|
|
|
+ var array = bytes.ToArray();
|
|
|
|
+ outputStream.Write(array, 0, array.Length);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|