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