1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using System; 11 using System.Collections; 12 using System.Collections.Generic; 13 using System.Diagnostics; 14 using System.IO; 15 using System.Runtime.InteropServices; 16 using System.Security; 17 using System.Text; 18 using System.Threading; 19 using System.Threading.Tasks; 20 21 namespace Google.Protobuf 22 { 23 /// <summary> 24 /// Immutable array of bytes. 25 /// </summary> 26 [SecuritySafeCritical] 27 [DebuggerDisplay("Length = {Length}")] 28 [DebuggerTypeProxy(typeof(ByteStringDebugView))] 29 public sealed class ByteString : IEnumerable<byte>, IEquatable<ByteString> 30 { 31 private static readonly ByteString empty = new ByteString(new byte[0]); 32 33 private readonly ReadOnlyMemory<byte> bytes; 34 35 /// <summary> 36 /// Internal use only. Ensure that the provided memory is not mutated and belongs to this instance. 37 /// </summary> AttachBytes(ReadOnlyMemory<byte> bytes)38 internal static ByteString AttachBytes(ReadOnlyMemory<byte> bytes) 39 { 40 return new ByteString(bytes); 41 } 42 43 /// <summary> 44 /// Internal use only. Ensure that the provided memory is not mutated and belongs to this instance. 45 /// This method encapsulates converting array to memory. Reduces need for SecuritySafeCritical 46 /// in .NET Framework. 47 /// </summary> AttachBytes(byte[] bytes)48 internal static ByteString AttachBytes(byte[] bytes) 49 { 50 return AttachBytes(bytes.AsMemory()); 51 } 52 53 /// <summary> 54 /// Constructs a new ByteString from the given memory. The memory is 55 /// *not* copied, and must not be modified after this constructor is called. 56 /// </summary> ByteString(ReadOnlyMemory<byte> bytes)57 private ByteString(ReadOnlyMemory<byte> bytes) 58 { 59 this.bytes = bytes; 60 } 61 62 /// <summary> 63 /// Returns an empty ByteString. 64 /// </summary> 65 public static ByteString Empty 66 { 67 get { return empty; } 68 } 69 70 /// <summary> 71 /// Returns the length of this ByteString in bytes. 72 /// </summary> 73 public int Length 74 { 75 get { return bytes.Length; } 76 } 77 78 /// <summary> 79 /// Returns <c>true</c> if this byte string is empty, <c>false</c> otherwise. 80 /// </summary> 81 public bool IsEmpty 82 { 83 get { return Length == 0; } 84 } 85 86 /// <summary> 87 /// Provides read-only access to the data of this <see cref="ByteString"/>. 88 /// No data is copied so this is the most efficient way of accessing. 89 /// </summary> 90 public ReadOnlySpan<byte> Span 91 { 92 get { return bytes.Span; } 93 } 94 95 /// <summary> 96 /// Provides read-only access to the data of this <see cref="ByteString"/>. 97 /// No data is copied so this is the most efficient way of accessing. 98 /// </summary> 99 public ReadOnlyMemory<byte> Memory 100 { 101 get { return bytes; } 102 } 103 104 /// <summary> 105 /// Converts this <see cref="ByteString"/> into a byte array. 106 /// </summary> 107 /// <remarks>The data is copied - changes to the returned array will not be reflected in this <c>ByteString</c>.</remarks> 108 /// <returns>A byte array with the same data as this <c>ByteString</c>.</returns> ToByteArray()109 public byte[] ToByteArray() 110 { 111 return bytes.ToArray(); 112 } 113 114 /// <summary> 115 /// Converts this <see cref="ByteString"/> into a standard base64 representation. 116 /// </summary> 117 /// <returns>A base64 representation of this <c>ByteString</c>.</returns> ToBase64()118 public string ToBase64() 119 { 120 #if NET5_0_OR_GREATER 121 return Convert.ToBase64String(bytes.Span); 122 #else 123 if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment)) 124 { 125 // Fast path. ByteString was created with an array, so pass the underlying array. 126 return Convert.ToBase64String(segment.Array, segment.Offset, segment.Count); 127 } 128 else 129 { 130 // Slow path. BytesString is not an array. Convert memory and pass result to ToBase64String. 131 return Convert.ToBase64String(bytes.ToArray()); 132 } 133 #endif 134 } 135 136 /// <summary> 137 /// Constructs a <see cref="ByteString" /> from the Base64 Encoded String. 138 /// </summary> FromBase64(string bytes)139 public static ByteString FromBase64(string bytes) 140 { 141 // By handling the empty string explicitly, we not only optimize but we fix a 142 // problem on CF 2.0. See issue 61 for details. 143 return bytes == "" ? Empty : new ByteString(Convert.FromBase64String(bytes)); 144 } 145 146 /// <summary> 147 /// Constructs a <see cref="ByteString"/> from data in the given stream, synchronously. 148 /// </summary> 149 /// <remarks>If successful, <paramref name="stream"/> will be read completely, from the position 150 /// at the start of the call.</remarks> 151 /// <param name="stream">The stream to copy into a ByteString.</param> 152 /// <returns>A ByteString with content read from the given stream.</returns> FromStream(Stream stream)153 public static ByteString FromStream(Stream stream) 154 { 155 ProtoPreconditions.CheckNotNull(stream, nameof(stream)); 156 int capacity = stream.CanSeek ? checked((int) (stream.Length - stream.Position)) : 0; 157 var memoryStream = new MemoryStream(capacity); 158 stream.CopyTo(memoryStream); 159 #if NETSTANDARD1_1 || NETSTANDARD2_0 160 byte[] bytes = memoryStream.ToArray(); 161 #else 162 // Avoid an extra copy if we can. 163 byte[] bytes = memoryStream.Length == memoryStream.Capacity ? memoryStream.GetBuffer() : memoryStream.ToArray(); 164 #endif 165 return AttachBytes(bytes); 166 } 167 168 /// <summary> 169 /// Constructs a <see cref="ByteString"/> from data in the given stream, asynchronously. 170 /// </summary> 171 /// <remarks>If successful, <paramref name="stream"/> will be read completely, from the position 172 /// at the start of the call.</remarks> 173 /// <param name="stream">The stream to copy into a ByteString.</param> 174 /// <param name="cancellationToken">The cancellation token to use when reading from the stream, if any.</param> 175 /// <returns>A ByteString with content read from the given stream.</returns> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default)176 public static Task<ByteString> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default) 177 { 178 ProtoPreconditions.CheckNotNull(stream, nameof(stream)); 179 return ByteStringAsync.FromStreamAsyncCore(stream, cancellationToken); 180 } 181 182 /// <summary> 183 /// Constructs a <see cref="ByteString" /> from the given array. The contents 184 /// are copied, so further modifications to the array will not 185 /// be reflected in the returned ByteString. 186 /// This method can also be invoked in <c>ByteString.CopyFrom(0xaa, 0xbb, ...)</c> form 187 /// which is primarily useful for testing. 188 /// </summary> CopyFrom(params byte[] bytes)189 public static ByteString CopyFrom(params byte[] bytes) 190 { 191 return new ByteString((byte[]) bytes.Clone()); 192 } 193 194 /// <summary> 195 /// Constructs a <see cref="ByteString" /> from a portion of a byte array. 196 /// </summary> CopyFrom(byte[] bytes, int offset, int count)197 public static ByteString CopyFrom(byte[] bytes, int offset, int count) 198 { 199 byte[] portion = new byte[count]; 200 ByteArray.Copy(bytes, offset, portion, 0, count); 201 return new ByteString(portion); 202 } 203 204 /// <summary> 205 /// Constructs a <see cref="ByteString" /> from a read only span. The contents 206 /// are copied, so further modifications to the span will not 207 /// be reflected in the returned <see cref="ByteString" />. 208 /// </summary> CopyFrom(ReadOnlySpan<byte> bytes)209 public static ByteString CopyFrom(ReadOnlySpan<byte> bytes) 210 { 211 return new ByteString(bytes.ToArray()); 212 } 213 214 /// <summary> 215 /// Creates a new <see cref="ByteString" /> by encoding the specified text with 216 /// the given encoding. 217 /// </summary> CopyFrom(string text, Encoding encoding)218 public static ByteString CopyFrom(string text, Encoding encoding) 219 { 220 return new ByteString(encoding.GetBytes(text)); 221 } 222 223 /// <summary> 224 /// Creates a new <see cref="ByteString" /> by encoding the specified text in UTF-8. 225 /// </summary> CopyFromUtf8(string text)226 public static ByteString CopyFromUtf8(string text) 227 { 228 return CopyFrom(text, Encoding.UTF8); 229 } 230 231 /// <summary> 232 /// Returns the byte at the given index. 233 /// </summary> 234 public byte this[int index] 235 { 236 get { return bytes.Span[index]; } 237 } 238 239 /// <summary> 240 /// Converts this <see cref="ByteString"/> into a string by applying the given encoding. 241 /// </summary> 242 /// <remarks> 243 /// This method should only be used to convert binary data which was the result of encoding 244 /// text with the given encoding. 245 /// </remarks> 246 /// <param name="encoding">The encoding to use to decode the binary data into text.</param> 247 /// <returns>The result of decoding the binary data with the given decoding.</returns> ToString(Encoding encoding)248 public string ToString(Encoding encoding) 249 { 250 if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment)) 251 { 252 // Fast path. ByteString was created with an array. 253 return encoding.GetString(segment.Array, segment.Offset, segment.Count); 254 } 255 else 256 { 257 // Slow path. BytesString is not an array. Convert memory and pass result to GetString. 258 // TODO: Consider using GetString overload that takes a pointer. 259 byte[] array = bytes.ToArray(); 260 return encoding.GetString(array, 0, array.Length); 261 } 262 } 263 264 /// <summary> 265 /// Converts this <see cref="ByteString"/> into a string by applying the UTF-8 encoding. 266 /// </summary> 267 /// <remarks> 268 /// This method should only be used to convert binary data which was the result of encoding 269 /// text with UTF-8. 270 /// </remarks> 271 /// <returns>The result of decoding the binary data with the given decoding.</returns> ToStringUtf8()272 public string ToStringUtf8() 273 { 274 return ToString(Encoding.UTF8); 275 } 276 277 /// <summary> 278 /// Returns an iterator over the bytes in this <see cref="ByteString"/>. 279 /// </summary> 280 /// <returns>An iterator over the bytes in this object.</returns> 281 [SecuritySafeCritical] GetEnumerator()282 public IEnumerator<byte> GetEnumerator() 283 { 284 return MemoryMarshal.ToEnumerable(bytes).GetEnumerator(); 285 } 286 287 /// <summary> 288 /// Returns an iterator over the bytes in this <see cref="ByteString"/>. 289 /// </summary> 290 /// <returns>An iterator over the bytes in this object.</returns> IEnumerable.GetEnumerator()291 IEnumerator IEnumerable.GetEnumerator() 292 { 293 return GetEnumerator(); 294 } 295 296 /// <summary> 297 /// Creates a CodedInputStream from this ByteString's data. 298 /// </summary> CreateCodedInput()299 public CodedInputStream CreateCodedInput() 300 { 301 // We trust CodedInputStream not to reveal the provided byte array or modify it 302 if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment) && segment.Count == bytes.Length) 303 { 304 // Fast path. ByteString was created with a complete array. 305 return new CodedInputStream(segment.Array, segment.Offset, segment.Count); 306 } 307 else 308 { 309 // Slow path. BytesString is not an array, or is a slice of an array. 310 // Convert memory and pass result to WriteRawBytes. 311 return new CodedInputStream(bytes.ToArray()); 312 } 313 } 314 315 /// <summary> 316 /// Compares two byte strings for equality. 317 /// </summary> 318 /// <param name="lhs">The first byte string to compare.</param> 319 /// <param name="rhs">The second byte string to compare.</param> 320 /// <returns><c>true</c> if the byte strings are equal; false otherwise.</returns> operator ==(ByteString lhs, ByteString rhs)321 public static bool operator ==(ByteString lhs, ByteString rhs) 322 { 323 if (ReferenceEquals(lhs, rhs)) 324 { 325 return true; 326 } 327 if (lhs is null || rhs is null) 328 { 329 return false; 330 } 331 332 return lhs.bytes.Span.SequenceEqual(rhs.bytes.Span); 333 } 334 335 /// <summary> 336 /// Compares two byte strings for inequality. 337 /// </summary> 338 /// <param name="lhs">The first byte string to compare.</param> 339 /// <param name="rhs">The second byte string to compare.</param> 340 /// <returns><c>false</c> if the byte strings are equal; true otherwise.</returns> operator !=(ByteString lhs, ByteString rhs)341 public static bool operator !=(ByteString lhs, ByteString rhs) 342 { 343 return !(lhs == rhs); 344 } 345 346 /// <summary> 347 /// Compares this byte string with another object. 348 /// </summary> 349 /// <param name="obj">The object to compare this with.</param> 350 /// <returns><c>true</c> if <paramref name="obj"/> refers to an equal <see cref="ByteString"/>; <c>false</c> otherwise.</returns> 351 [SecuritySafeCritical] Equals(object obj)352 public override bool Equals(object obj) 353 { 354 return this == (obj as ByteString); 355 } 356 357 /// <summary> 358 /// Returns a hash code for this object. Two equal byte strings 359 /// will return the same hash code. 360 /// </summary> 361 /// <returns>A hash code for this object.</returns> 362 [SecuritySafeCritical] GetHashCode()363 public override int GetHashCode() 364 { 365 ReadOnlySpan<byte> b = bytes.Span; 366 367 int ret = 23; 368 for (int i = 0; i < b.Length; i++) 369 { 370 ret = (ret * 31) + b[i]; 371 } 372 return ret; 373 } 374 375 /// <summary> 376 /// Compares this byte string with another. 377 /// </summary> 378 /// <param name="other">The <see cref="ByteString"/> to compare this with.</param> 379 /// <returns><c>true</c> if <paramref name="other"/> refers to an equal byte string; <c>false</c> otherwise.</returns> Equals(ByteString other)380 public bool Equals(ByteString other) 381 { 382 return this == other; 383 } 384 385 /// <summary> 386 /// Copies the entire byte array to the destination array provided at the offset specified. 387 /// </summary> CopyTo(byte[] array, int position)388 public void CopyTo(byte[] array, int position) 389 { 390 bytes.CopyTo(array.AsMemory(position)); 391 } 392 393 /// <summary> 394 /// Writes the entire byte array to the provided stream 395 /// </summary> WriteTo(Stream outputStream)396 public void WriteTo(Stream outputStream) 397 { 398 if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment)) 399 { 400 // Fast path. ByteString was created with an array, so pass the underlying array. 401 outputStream.Write(segment.Array, segment.Offset, segment.Count); 402 } 403 else 404 { 405 // Slow path. BytesString is not an array. Convert memory and pass result to WriteRawBytes. 406 var array = bytes.ToArray(); 407 outputStream.Write(array, 0, array.Length); 408 } 409 } 410 411 private sealed class ByteStringDebugView 412 { 413 private readonly ByteString data; 414 ByteStringDebugView(ByteString data)415 public ByteStringDebugView(ByteString data) 416 { 417 this.data = data; 418 } 419 420 [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 421 public byte[] Items => data.bytes.ToArray(); 422 } 423 } 424 }