• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }