xref: /aosp_15_r20/external/protobuf/csharp/src/Google.Protobuf.Test/ByteStringTest.cs (revision 1b3f573f81763fcece89efc2b6a5209149e44ab8)
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.Text;
35 using NUnit.Framework;
36 using System.IO;
37 using System.Collections.Generic;
38 using System.Collections;
39 using System.Linq;
40 using System.Buffers;
41 using System.Runtime.InteropServices;
42 using System.Threading;
43 using System.Runtime.CompilerServices;
44 #if !NET35
45 using System.Threading.Tasks;
46 #endif
47 
48 namespace Google.Protobuf
49 {
50     public class ByteStringTest
51     {
52         [Test]
Equality()53         public void Equality()
54         {
55             ByteString b1 = ByteString.CopyFrom(1, 2, 3);
56             ByteString b2 = ByteString.CopyFrom(1, 2, 3);
57             ByteString b3 = ByteString.CopyFrom(1, 2, 4);
58             ByteString b4 = ByteString.CopyFrom(1, 2, 3, 4);
59             EqualityTester.AssertEquality(b1, b1);
60             EqualityTester.AssertEquality(b1, b2);
61             EqualityTester.AssertInequality(b1, b3);
62             EqualityTester.AssertInequality(b1, b4);
63             EqualityTester.AssertInequality(b1, null);
64             EqualityTester.AssertEquality(ByteString.Empty, ByteString.Empty);
65 #pragma warning disable 1718 // Deliberately calling ==(b1, b1) and !=(b1, b1)
66             Assert.IsTrue(b1 == b1);
67             Assert.IsTrue(b1 == b2);
68             Assert.IsFalse(b1 == b3);
69             Assert.IsFalse(b1 == b4);
70             Assert.IsFalse(b1 == null);
71             Assert.IsTrue((ByteString) null == null);
72             Assert.IsFalse(b1 != b1);
73             Assert.IsFalse(b1 != b2);
74             Assert.IsTrue(ByteString.Empty == ByteString.Empty);
75 #pragma warning disable 1718
76             Assert.IsTrue(b1 != b3);
77             Assert.IsTrue(b1 != b4);
78             Assert.IsTrue(b1 != null);
79             Assert.IsFalse((ByteString) null != null);
80         }
81 
82         [Test]
EmptyByteStringHasZeroSize()83         public void EmptyByteStringHasZeroSize()
84         {
85             Assert.AreEqual(0, ByteString.Empty.Length);
86         }
87 
88         [Test]
CopyFromStringWithExplicitEncoding()89         public void CopyFromStringWithExplicitEncoding()
90         {
91             ByteString bs = ByteString.CopyFrom("AB", Encoding.Unicode);
92             Assert.AreEqual(4, bs.Length);
93             Assert.AreEqual(65, bs[0]);
94             Assert.AreEqual(0, bs[1]);
95             Assert.AreEqual(66, bs[2]);
96             Assert.AreEqual(0, bs[3]);
97         }
98 
99         [Test]
IsEmptyWhenEmpty()100         public void IsEmptyWhenEmpty()
101         {
102             Assert.IsTrue(ByteString.CopyFromUtf8("").IsEmpty);
103         }
104 
105         [Test]
IsEmptyWhenNotEmpty()106         public void IsEmptyWhenNotEmpty()
107         {
108             Assert.IsFalse(ByteString.CopyFromUtf8("X").IsEmpty);
109         }
110 
111         [Test]
CopyFromByteArrayCopiesContents()112         public void CopyFromByteArrayCopiesContents()
113         {
114             byte[] data = new byte[1];
115             data[0] = 10;
116             ByteString bs = ByteString.CopyFrom(data);
117             Assert.AreEqual(10, bs[0]);
118             data[0] = 5;
119             Assert.AreEqual(10, bs[0]);
120         }
121 
122         [Test]
CopyFromReadOnlySpanCopiesContents()123         public void CopyFromReadOnlySpanCopiesContents()
124         {
125             byte[] data = new byte[1];
126             data[0] = 10;
127             ReadOnlySpan<byte> byteSpan = data;
128             var bs = ByteString.CopyFrom(byteSpan);
129             Assert.AreEqual(10, bs[0]);
130             data[0] = 5;
131             Assert.AreEqual(10, bs[0]);
132         }
133 
134         [Test]
ToByteArrayCopiesContents()135         public void ToByteArrayCopiesContents()
136         {
137             ByteString bs = ByteString.CopyFromUtf8("Hello");
138             byte[] data = bs.ToByteArray();
139             Assert.AreEqual((byte)'H', data[0]);
140             Assert.AreEqual((byte)'H', bs[0]);
141             data[0] = 0;
142             Assert.AreEqual(0, data[0]);
143             Assert.AreEqual((byte)'H', bs[0]);
144         }
145 
146         [Test]
CopyFromUtf8UsesUtf8()147         public void CopyFromUtf8UsesUtf8()
148         {
149             ByteString bs = ByteString.CopyFromUtf8("\u20ac");
150             Assert.AreEqual(3, bs.Length);
151             Assert.AreEqual(0xe2, bs[0]);
152             Assert.AreEqual(0x82, bs[1]);
153             Assert.AreEqual(0xac, bs[2]);
154         }
155 
156         [Test]
CopyFromPortion()157         public void CopyFromPortion()
158         {
159             byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6};
160             ByteString bs = ByteString.CopyFrom(data, 2, 3);
161             Assert.AreEqual(3, bs.Length);
162             Assert.AreEqual(2, bs[0]);
163             Assert.AreEqual(3, bs[1]);
164         }
165 
166         [Test]
CopyTo()167         public void CopyTo()
168         {
169             byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
170             ByteString bs = ByteString.CopyFrom(data);
171 
172             byte[] dest = new byte[data.Length];
173             bs.CopyTo(dest, 0);
174 
175             CollectionAssert.AreEqual(data, dest);
176         }
177 
178         [Test]
GetEnumerator()179         public void GetEnumerator()
180         {
181             byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
182             ByteString bs = ByteString.CopyFrom(data);
183 
184             IEnumerator<byte> genericEnumerator = bs.GetEnumerator();
185             Assert.IsTrue(genericEnumerator.MoveNext());
186             Assert.AreEqual(0, genericEnumerator.Current);
187 
188             IEnumerator enumerator = ((IEnumerable)bs).GetEnumerator();
189             Assert.IsTrue(enumerator.MoveNext());
190             Assert.AreEqual(0, enumerator.Current);
191 
192             // Call via LINQ
193             CollectionAssert.AreEqual(bs.Span.ToArray(), bs.ToArray());
194         }
195 
196         [Test]
UnsafeWrap()197         public void UnsafeWrap()
198         {
199             byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
200             ByteString bs = UnsafeByteOperations.UnsafeWrap(data.AsMemory(2, 3));
201             ReadOnlySpan<byte> s = bs.Span;
202 
203             Assert.AreEqual(3, s.Length);
204             Assert.AreEqual(2, s[0]);
205             Assert.AreEqual(3, s[1]);
206             Assert.AreEqual(4, s[2]);
207 
208             // Check that the value is not a copy
209             data[2] = byte.MaxValue;
210             Assert.AreEqual(byte.MaxValue, s[0]);
211         }
212 
213         [Test]
CreateCodedInput_FromArraySegment()214         public void CreateCodedInput_FromArraySegment()
215         {
216             byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
217             ByteString bs = UnsafeByteOperations.UnsafeWrap(data.AsMemory(2, 3));
218             CodedInputStream codedInputStream = bs.CreateCodedInput();
219 
220             byte[] bytes = codedInputStream.ReadRawBytes(3);
221 
222             Assert.AreEqual(3, bytes.Length);
223             Assert.AreEqual(2, bytes[0]);
224             Assert.AreEqual(3, bytes[1]);
225             Assert.AreEqual(4, bytes[2]);
226             Assert.IsTrue(codedInputStream.IsAtEnd);
227         }
228 
229         [Test]
WriteToStream()230         public void WriteToStream()
231         {
232             byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
233             ByteString bs = ByteString.CopyFrom(data);
234 
235             MemoryStream ms = new MemoryStream();
236             bs.WriteTo(ms);
237 
238             CollectionAssert.AreEqual(data, ms.ToArray());
239         }
240 
241         [Test]
WriteToStream_Stackalloc()242         public void WriteToStream_Stackalloc()
243         {
244             byte[] data = Encoding.UTF8.GetBytes("Hello world");
245             Span<byte> s = stackalloc byte[data.Length];
246             data.CopyTo(s);
247 
248             MemoryStream ms = new MemoryStream();
249 
250             using (UnmanagedMemoryManager<byte> manager = new UnmanagedMemoryManager<byte>(s))
251             {
252                 ByteString bs = ByteString.AttachBytes(manager.Memory);
253 
254                 bs.WriteTo(ms);
255             }
256 
257             CollectionAssert.AreEqual(data, ms.ToArray());
258         }
259 
260         [Test]
ToStringUtf8()261         public void ToStringUtf8()
262         {
263             ByteString bs = ByteString.CopyFromUtf8("\u20ac");
264             Assert.AreEqual("\u20ac", bs.ToStringUtf8());
265         }
266 
267         [Test]
ToStringWithExplicitEncoding()268         public void ToStringWithExplicitEncoding()
269         {
270             ByteString bs = ByteString.CopyFrom("\u20ac", Encoding.Unicode);
271             Assert.AreEqual("\u20ac", bs.ToString(Encoding.Unicode));
272         }
273 
274         [Test]
ToString_Stackalloc()275         public void ToString_Stackalloc()
276         {
277             byte[] data = Encoding.UTF8.GetBytes("Hello world");
278             Span<byte> s = stackalloc byte[data.Length];
279             data.CopyTo(s);
280 
281             using (UnmanagedMemoryManager<byte> manager = new UnmanagedMemoryManager<byte>(s))
282             {
283                 ByteString bs = ByteString.AttachBytes(manager.Memory);
284 
285                 Assert.AreEqual("Hello world", bs.ToString(Encoding.UTF8));
286             }
287         }
288 
289         [Test]
FromBase64_WithText()290         public void FromBase64_WithText()
291         {
292             byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6};
293             string base64 = Convert.ToBase64String(data);
294             ByteString bs = ByteString.FromBase64(base64);
295             Assert.AreEqual(data, bs.ToByteArray());
296         }
297 
298         [Test]
FromBase64_Empty()299         public void FromBase64_Empty()
300         {
301             // Optimization which also fixes issue 61.
302             Assert.AreSame(ByteString.Empty, ByteString.FromBase64(""));
303         }
304 
305         [Test]
ToBase64_Array()306         public void ToBase64_Array()
307         {
308             ByteString bs = ByteString.CopyFrom(Encoding.UTF8.GetBytes("Hello world"));
309 
310             Assert.AreEqual("SGVsbG8gd29ybGQ=", bs.ToBase64());
311         }
312 
313         [Test]
ToBase64_Stackalloc()314         public void ToBase64_Stackalloc()
315         {
316             byte[] data = Encoding.UTF8.GetBytes("Hello world");
317             Span<byte> s = stackalloc byte[data.Length];
318             data.CopyTo(s);
319 
320             using (UnmanagedMemoryManager<byte> manager = new UnmanagedMemoryManager<byte>(s))
321             {
322                 ByteString bs = ByteString.AttachBytes(manager.Memory);
323 
324                 Assert.AreEqual("SGVsbG8gd29ybGQ=", bs.ToBase64());
325             }
326         }
327 
328         [Test]
FromStream_Seekable()329         public void FromStream_Seekable()
330         {
331             var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
332             // Consume the first byte, just to test that it's "from current position"
333             stream.ReadByte();
334             var actual = ByteString.FromStream(stream);
335             ByteString expected = ByteString.CopyFrom(2, 3, 4, 5);
336             Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}");
337         }
338 
339         [Test]
FromStream_NotSeekable()340         public void FromStream_NotSeekable()
341         {
342             var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
343             // Consume the first byte, just to test that it's "from current position"
344             stream.ReadByte();
345             // Wrap the original stream in LimitedInputStream, which has CanSeek=false
346             var limitedStream = new LimitedInputStream(stream, 3);
347             var actual = ByteString.FromStream(limitedStream);
348             ByteString expected = ByteString.CopyFrom(2, 3, 4);
349             Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}");
350         }
351 
352 #if !NET35
353         [Test]
FromStreamAsync_Seekable()354         public async Task FromStreamAsync_Seekable()
355         {
356             var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
357             // Consume the first byte, just to test that it's "from current position"
358             stream.ReadByte();
359             var actual = await ByteString.FromStreamAsync(stream);
360             ByteString expected = ByteString.CopyFrom(2, 3, 4, 5);
361             Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}");
362         }
363 
364         [Test]
FromStreamAsync_NotSeekable()365         public async Task FromStreamAsync_NotSeekable()
366         {
367             var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
368             // Consume the first byte, just to test that it's "from current position"
369             stream.ReadByte();
370             // Wrap the original stream in LimitedInputStream, which has CanSeek=false
371             var limitedStream = new LimitedInputStream(stream, 3);
372             var actual = await ByteString.FromStreamAsync(limitedStream);
373             ByteString expected = ByteString.CopyFrom(2, 3, 4);
374             Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}");
375         }
376 #endif
377 
378         [Test]
GetHashCode_Regression()379         public void GetHashCode_Regression()
380         {
381             // We used to have an awful hash algorithm where only the last four
382             // bytes were relevant. This is a regression test for
383             // https://github.com/protocolbuffers/protobuf/issues/2511
384 
385             ByteString b1 = ByteString.CopyFrom(100, 1, 2, 3, 4);
386             ByteString b2 = ByteString.CopyFrom(200, 1, 2, 3, 4);
387             Assert.AreNotEqual(b1.GetHashCode(), b2.GetHashCode());
388         }
389 
390         [Test]
GetContentsAsReadOnlySpan()391         public void GetContentsAsReadOnlySpan()
392         {
393             var byteString = ByteString.CopyFrom(1, 2, 3, 4, 5);
394             var copied = byteString.Span.ToArray();
395             CollectionAssert.AreEqual(byteString, copied);
396         }
397 
398         [Test]
GetContentsAsReadOnlyMemory()399         public void GetContentsAsReadOnlyMemory()
400         {
401             var byteString = ByteString.CopyFrom(1, 2, 3, 4, 5);
402             var copied = byteString.Memory.ToArray();
403             CollectionAssert.AreEqual(byteString, copied);
404         }
405 
406         // Create Memory<byte> from non-array source.
407         // Use by ByteString tests that have optimized path for array backed Memory<byte>.
408         private sealed unsafe class UnmanagedMemoryManager<T> : MemoryManager<T> where T : unmanaged
409         {
410             private readonly T* _pointer;
411             private readonly int _length;
412 
UnmanagedMemoryManager(Span<T> span)413             public UnmanagedMemoryManager(Span<T> span)
414             {
415                 fixed (T* ptr = &MemoryMarshal.GetReference(span))
416                 {
417                     _pointer = ptr;
418                     _length = span.Length;
419                 }
420             }
421 
GetSpan()422             public override Span<T> GetSpan() => new Span<T>(_pointer, _length);
423 
Pin(int elementIndex = 0)424             public override MemoryHandle Pin(int elementIndex = 0)
425             {
426                 if (elementIndex < 0 || elementIndex >= _length)
427                 {
428                     throw new ArgumentOutOfRangeException(nameof(elementIndex));
429                 }
430 
431                 return new MemoryHandle(_pointer + elementIndex);
432             }
433 
Unpin()434             public override void Unpin() { }
435 
Dispose(bool disposing)436             protected override void Dispose(bool disposing) { }
437         }
438     }
439 }