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