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
33using System;
34using System.Text;
35using NUnit.Framework;
36using System.IO;
37#if !NET35
38using System.Threading.Tasks;
39#endif
40
41namespace Google.Protobuf
42{
43    public class ByteStringTest
44    {
45        [Test]
46        public void Equality()
47        {
48            ByteString b1 = ByteString.CopyFrom(1, 2, 3);
49            ByteString b2 = ByteString.CopyFrom(1, 2, 3);
50            ByteString b3 = ByteString.CopyFrom(1, 2, 4);
51            ByteString b4 = ByteString.CopyFrom(1, 2, 3, 4);
52            EqualityTester.AssertEquality(b1, b1);
53            EqualityTester.AssertEquality(b1, b2);
54            EqualityTester.AssertInequality(b1, b3);
55            EqualityTester.AssertInequality(b1, b4);
56            EqualityTester.AssertInequality(b1, null);
57#pragma warning disable 1718 // Deliberately calling ==(b1, b1) and !=(b1, b1)
58            Assert.IsTrue(b1 == b1);
59            Assert.IsTrue(b1 == b2);
60            Assert.IsFalse(b1 == b3);
61            Assert.IsFalse(b1 == b4);
62            Assert.IsFalse(b1 == null);
63            Assert.IsTrue((ByteString) null == null);
64            Assert.IsFalse(b1 != b1);
65            Assert.IsFalse(b1 != b2);
66#pragma warning disable 1718
67            Assert.IsTrue(b1 != b3);
68            Assert.IsTrue(b1 != b4);
69            Assert.IsTrue(b1 != null);
70            Assert.IsFalse((ByteString) null != null);
71        }
72
73        [Test]
74        public void EmptyByteStringHasZeroSize()
75        {
76            Assert.AreEqual(0, ByteString.Empty.Length);
77        }
78
79        [Test]
80        public void CopyFromStringWithExplicitEncoding()
81        {
82            ByteString bs = ByteString.CopyFrom("AB", Encoding.Unicode);
83            Assert.AreEqual(4, bs.Length);
84            Assert.AreEqual(65, bs[0]);
85            Assert.AreEqual(0, bs[1]);
86            Assert.AreEqual(66, bs[2]);
87            Assert.AreEqual(0, bs[3]);
88        }
89
90        [Test]
91        public void IsEmptyWhenEmpty()
92        {
93            Assert.IsTrue(ByteString.CopyFromUtf8("").IsEmpty);
94        }
95
96        [Test]
97        public void IsEmptyWhenNotEmpty()
98        {
99            Assert.IsFalse(ByteString.CopyFromUtf8("X").IsEmpty);
100        }
101
102        [Test]
103        public void CopyFromByteArrayCopiesContents()
104        {
105            byte[] data = new byte[1];
106            data[0] = 10;
107            ByteString bs = ByteString.CopyFrom(data);
108            Assert.AreEqual(10, bs[0]);
109            data[0] = 5;
110            Assert.AreEqual(10, bs[0]);
111        }
112
113        [Test]
114        public void ToByteArrayCopiesContents()
115        {
116            ByteString bs = ByteString.CopyFromUtf8("Hello");
117            byte[] data = bs.ToByteArray();
118            Assert.AreEqual((byte)'H', data[0]);
119            Assert.AreEqual((byte)'H', bs[0]);
120            data[0] = 0;
121            Assert.AreEqual(0, data[0]);
122            Assert.AreEqual((byte)'H', bs[0]);
123        }
124
125        [Test]
126        public void CopyFromUtf8UsesUtf8()
127        {
128            ByteString bs = ByteString.CopyFromUtf8("\u20ac");
129            Assert.AreEqual(3, bs.Length);
130            Assert.AreEqual(0xe2, bs[0]);
131            Assert.AreEqual(0x82, bs[1]);
132            Assert.AreEqual(0xac, bs[2]);
133        }
134
135        [Test]
136        public void CopyFromPortion()
137        {
138            byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6};
139            ByteString bs = ByteString.CopyFrom(data, 2, 3);
140            Assert.AreEqual(3, bs.Length);
141            Assert.AreEqual(2, bs[0]);
142            Assert.AreEqual(3, bs[1]);
143        }
144
145        [Test]
146        public void ToStringUtf8()
147        {
148            ByteString bs = ByteString.CopyFromUtf8("\u20ac");
149            Assert.AreEqual("\u20ac", bs.ToStringUtf8());
150        }
151
152        [Test]
153        public void ToStringWithExplicitEncoding()
154        {
155            ByteString bs = ByteString.CopyFrom("\u20ac", Encoding.Unicode);
156            Assert.AreEqual("\u20ac", bs.ToString(Encoding.Unicode));
157        }
158
159        [Test]
160        public void FromBase64_WithText()
161        {
162            byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6};
163            string base64 = Convert.ToBase64String(data);
164            ByteString bs = ByteString.FromBase64(base64);
165            Assert.AreEqual(data, bs.ToByteArray());
166        }
167
168        [Test]
169        public void FromBase64_Empty()
170        {
171            // Optimization which also fixes issue 61.
172            Assert.AreSame(ByteString.Empty, ByteString.FromBase64(""));
173        }
174
175        [Test]
176        public void FromStream_Seekable()
177        {
178            var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
179            // Consume the first byte, just to test that it's "from current position"
180            stream.ReadByte();
181            var actual = ByteString.FromStream(stream);
182            ByteString expected = ByteString.CopyFrom(2, 3, 4, 5);
183            Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}");
184        }
185
186        [Test]
187        public void FromStream_NotSeekable()
188        {
189            var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
190            // Consume the first byte, just to test that it's "from current position"
191            stream.ReadByte();
192            // Wrap the original stream in LimitedInputStream, which has CanSeek=false
193            var limitedStream = new LimitedInputStream(stream, 3);
194            var actual = ByteString.FromStream(limitedStream);
195            ByteString expected = ByteString.CopyFrom(2, 3, 4);
196            Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}");
197        }
198
199#if !NET35
200        [Test]
201        public async Task FromStreamAsync_Seekable()
202        {
203            var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
204            // Consume the first byte, just to test that it's "from current position"
205            stream.ReadByte();
206            var actual = await ByteString.FromStreamAsync(stream);
207            ByteString expected = ByteString.CopyFrom(2, 3, 4, 5);
208            Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}");
209        }
210
211        [Test]
212        public async Task FromStreamAsync_NotSeekable()
213        {
214            var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
215            // Consume the first byte, just to test that it's "from current position"
216            stream.ReadByte();
217            // Wrap the original stream in LimitedInputStream, which has CanSeek=false
218            var limitedStream = new LimitedInputStream(stream, 3);
219            var actual = await ByteString.FromStreamAsync(limitedStream);
220            ByteString expected = ByteString.CopyFrom(2, 3, 4);
221            Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}");
222        }
223#endif
224
225        [Test]
226        public void GetHashCode_Regression()
227        {
228            // We used to have an awful hash algorithm where only the last four
229            // bytes were relevant. This is a regression test for
230            // https://github.com/protocolbuffers/protobuf/issues/2511
231
232            ByteString b1 = ByteString.CopyFrom(100, 1, 2, 3, 4);
233            ByteString b2 = ByteString.CopyFrom(200, 1, 2, 3, 4);
234            Assert.AreNotEqual(b1.GetHashCode(), b2.GetHashCode());
235        }
236
237        [Test]
238        public void GetContentsAsReadOnlySpan()
239        {
240            var byteString = ByteString.CopyFrom(1, 2, 3, 4, 5);
241            var copied = byteString.Span.ToArray();
242            CollectionAssert.AreEqual(byteString, copied);
243        }
244
245        [Test]
246        public void GetContentsAsReadOnlyMemory()
247        {
248            var byteString = ByteString.CopyFrom(1, 2, 3, 4, 5);
249            var copied = byteString.Memory.ToArray();
250            CollectionAssert.AreEqual(byteString, copied);
251        }
252    }
253}