1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 // Original authors: ekr@rtfm.com; ryan@tokbox.com
8
9 #include <vector>
10 #include <numeric>
11
12 #include "mozilla/net/NeckoChannelParams.h"
13 #include "nr_socket_tcp.h"
14 #include "WebrtcTCPSocketWrapper.h"
15
16 #define GTEST_HAS_RTTI 0
17 #include "gtest/gtest.h"
18 #include "gtest_utils.h"
19
20 using namespace mozilla;
21
22 // update TestReadMultipleSizes if you change this
23 const std::string kHelloMessage = "HELLO IS IT ME YOU'RE LOOKING FOR?";
24
25 class NrTcpSocketTest : public MtransportTest {
26 public:
NrTcpSocketTest()27 NrTcpSocketTest()
28 : mSProxy(nullptr),
29 nr_socket_(nullptr),
30 mEmptyArray(0),
31 mReadChunkSize(0),
32 mReadChunkSizeIncrement(1),
33 mReadAllowance(-1),
34 mConnected(false) {}
35
SetUp()36 void SetUp() override {
37 mSProxy = new NrTcpSocket(nullptr);
38 int r = nr_socket_create_int((void*)mSProxy.get(), mSProxy->vtbl(),
39 &nr_socket_);
40 ASSERT_EQ(0, r);
41
42 // fake calling AsyncOpen() due to IPC calls. must be non-null
43 mSProxy->AssignChannel_DoNotUse(new WebrtcTCPSocketWrapper(nullptr));
44 }
45
TearDown()46 void TearDown() override { mSProxy->close(); }
47
readable_cb(NR_SOCKET s,int how,void * cb_arg)48 static void readable_cb(NR_SOCKET s, int how, void* cb_arg) {
49 NrTcpSocketTest* test = (NrTcpSocketTest*)cb_arg;
50 size_t capacity = std::min(test->mReadChunkSize, test->mReadAllowance);
51 nsTArray<uint8_t> array(capacity);
52 size_t read;
53
54 nr_socket_read(test->nr_socket_, (char*)array.Elements(), array.Capacity(),
55 &read, 0);
56
57 ASSERT_TRUE(read <= array.Capacity());
58 ASSERT_TRUE(test->mReadAllowance >= read);
59
60 array.SetLength(read);
61 test->mData.AppendElements(array);
62 test->mReadAllowance -= read;
63
64 // We may read more bytes each time we're called. This way we can ensure we
65 // consume buffers partially and across multiple buffers.
66 test->mReadChunkSize += test->mReadChunkSizeIncrement;
67
68 if (test->mReadAllowance > 0) {
69 NR_ASYNC_WAIT(s, how, &NrTcpSocketTest::readable_cb, cb_arg);
70 }
71 }
72
writable_cb(NR_SOCKET s,int how,void * cb_arg)73 static void writable_cb(NR_SOCKET s, int how, void* cb_arg) {
74 NrTcpSocketTest* test = (NrTcpSocketTest*)cb_arg;
75 test->mConnected = true;
76 }
77
DataString()78 const std::string DataString() {
79 return std::string((char*)mData.Elements(), mData.Length());
80 }
81
82 protected:
83 RefPtr<NrTcpSocket> mSProxy;
84 nr_socket* nr_socket_;
85
86 nsTArray<uint8_t> mData;
87 nsTArray<uint8_t> mEmptyArray;
88
89 uint32_t mReadChunkSize;
90 uint32_t mReadChunkSizeIncrement;
91 uint32_t mReadAllowance;
92
93 bool mConnected;
94 };
95
TEST_F(NrTcpSocketTest,TestCreate)96 TEST_F(NrTcpSocketTest, TestCreate) {}
97
TEST_F(NrTcpSocketTest,TestConnected)98 TEST_F(NrTcpSocketTest, TestConnected) {
99 ASSERT_TRUE(!mConnected);
100
101 NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_WRITE, &NrTcpSocketTest::writable_cb,
102 this);
103
104 // still not connected just registered for writes...
105 ASSERT_TRUE(!mConnected);
106
107 mSProxy->OnConnected(NS_LITERAL_CSTRING("http"));
108
109 ASSERT_TRUE(mConnected);
110 }
111
TEST_F(NrTcpSocketTest,TestRead)112 TEST_F(NrTcpSocketTest, TestRead) {
113 nsTArray<uint8_t> array;
114 array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
115
116 NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
117 this);
118 // this will read 0 bytes here
119 mSProxy->OnRead(std::move(array));
120
121 ASSERT_EQ(kHelloMessage.length(), mSProxy->CountUnreadBytes());
122
123 // callback is still set but terminated due to 0 byte read
124 // start callbacks again (first read is 0 then 1,2,3,...)
125 mSProxy->OnRead(std::move(mEmptyArray));
126
127 ASSERT_EQ(kHelloMessage.length(), mData.Length());
128 ASSERT_EQ(kHelloMessage, DataString());
129 }
130
TEST_F(NrTcpSocketTest,TestReadConstantConsumeSize)131 TEST_F(NrTcpSocketTest, TestReadConstantConsumeSize) {
132 std::string data;
133
134 // triangle number
135 const int kCount = 32;
136
137 // ~17kb
138 // triangle number formula n*(n+1)/2
139 for (int i = 0; i < kCount * (kCount + 1) / 2; ++i) {
140 data += kHelloMessage;
141 }
142
143 // decreasing buffer sizes
144 for (int i = 0, start = 0; i < kCount; ++i) {
145 int length = (kCount - i) * kHelloMessage.length();
146
147 nsTArray<uint8_t> array;
148 array.AppendElements(data.c_str() + start, length);
149 start += length;
150
151 mSProxy->OnRead(std::move(array));
152 }
153
154 ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
155
156 // read same amount each callback
157 mReadChunkSize = 128;
158 mReadChunkSizeIncrement = 0;
159 NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
160 this);
161
162 ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
163
164 // start callbacks
165 mSProxy->OnRead(std::move(mEmptyArray));
166
167 ASSERT_EQ(data.length(), mData.Length());
168 ASSERT_EQ(data, DataString());
169 }
170
TEST_F(NrTcpSocketTest,TestReadNone)171 TEST_F(NrTcpSocketTest, TestReadNone) {
172 char buf[4096];
173 size_t read = 0;
174 int r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
175
176 ASSERT_EQ(R_WOULDBLOCK, r);
177
178 nsTArray<uint8_t> array;
179 array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
180 mSProxy->OnRead(std::move(array));
181
182 ASSERT_EQ(kHelloMessage.length(), mSProxy->CountUnreadBytes());
183
184 r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
185
186 ASSERT_EQ(0, r);
187 ASSERT_EQ(kHelloMessage.length(), read);
188 ASSERT_EQ(kHelloMessage, std::string(buf, read));
189 }
190
TEST_F(NrTcpSocketTest,TestReadMultipleSizes)191 TEST_F(NrTcpSocketTest, TestReadMultipleSizes) {
192 using namespace std;
193
194 string data;
195 // 515 * kHelloMessage.length() == 17510
196 const size_t kCount = 515;
197 // randomly generated numbers, sums to 17510, 20 numbers
198 vector<int> varyingSizes = {404, 622, 1463, 1597, 1676, 389, 389,
199 1272, 781, 81, 1030, 1450, 256, 812,
200 1571, 29, 1045, 911, 643, 1089};
201
202 // changing varyingSizes or the test message breaks this so check here
203 ASSERT_EQ(kCount, 17510 / kHelloMessage.length());
204 ASSERT_EQ(17510, accumulate(varyingSizes.begin(), varyingSizes.end(), 0));
205
206 // ~17kb
207 for (size_t i = 0; i < kCount; ++i) {
208 data += kHelloMessage;
209 }
210
211 nsTArray<uint8_t> array;
212 array.AppendElements(data.c_str(), data.length());
213
214 for (int amountToRead : varyingSizes) {
215 nsTArray<uint8_t> buffer;
216 buffer.AppendElements(array.Elements(), amountToRead);
217 array.RemoveElementsAt(0, amountToRead);
218 mSProxy->OnRead(std::move(buffer));
219 }
220
221 ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
222
223 // don't need to read 0 on the first read, so start at 1 and keep going
224 mReadChunkSize = 1;
225 NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
226 this);
227 // start callbacks
228 mSProxy->OnRead(std::move(mEmptyArray));
229
230 ASSERT_EQ(data.length(), mData.Length());
231 ASSERT_EQ(data, DataString());
232 }
233
TEST_F(NrTcpSocketTest,TestReadConsumeReadDrain)234 TEST_F(NrTcpSocketTest, TestReadConsumeReadDrain) {
235 std::string data;
236 // ~26kb total; should be even
237 const int kCount = 512;
238
239 // there's some division by 2 here so check that kCount is even
240 ASSERT_EQ(0, kCount % 2);
241
242 for (int i = 0; i < kCount; ++i) {
243 data += kHelloMessage;
244 nsTArray<uint8_t> array;
245 array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
246 mSProxy->OnRead(std::move(array));
247 }
248
249 // read half at first
250 mReadAllowance = kCount / 2 * kHelloMessage.length();
251 // start by reading 1 byte
252 mReadChunkSize = 1;
253 NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
254 this);
255 mSProxy->OnRead(std::move(mEmptyArray));
256
257 ASSERT_EQ(data.length() / 2, mSProxy->CountUnreadBytes());
258 ASSERT_EQ(data.length() / 2, mData.Length());
259
260 // fill read buffer back up
261 for (int i = 0; i < kCount / 2; ++i) {
262 data += kHelloMessage;
263 nsTArray<uint8_t> array;
264 array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
265 mSProxy->OnRead(std::move(array));
266 }
267
268 // remove read limit
269 mReadAllowance = -1;
270 // used entire read allowance so we need to setup a new await
271 NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
272 this);
273 // start callbacks
274 mSProxy->OnRead(std::move(mEmptyArray));
275
276 ASSERT_EQ(data.length(), mData.Length());
277 ASSERT_EQ(data, DataString());
278 }
279