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 }