1 /*
2 * Copyright 2004 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "p2p/base/stun_request.h"
12
13 #include <vector>
14
15 #include "rtc_base/fake_clock.h"
16 #include "rtc_base/gunit.h"
17 #include "rtc_base/logging.h"
18 #include "rtc_base/time_utils.h"
19 #include "test/gtest.h"
20
21 namespace cricket {
22
23 class StunRequestTest : public ::testing::Test, public sigslot::has_slots<> {
24 public:
StunRequestTest()25 StunRequestTest()
26 : manager_(rtc::Thread::Current()),
27 request_count_(0),
28 response_(NULL),
29 success_(false),
30 failure_(false),
31 timeout_(false) {
32 manager_.SignalSendPacket.connect(this, &StunRequestTest::OnSendPacket);
33 }
34
OnSendPacket(const void * data,size_t size,StunRequest * req)35 void OnSendPacket(const void* data, size_t size, StunRequest* req) {
36 request_count_++;
37 }
38
OnResponse(StunMessage * res)39 void OnResponse(StunMessage* res) {
40 response_ = res;
41 success_ = true;
42 }
OnErrorResponse(StunMessage * res)43 void OnErrorResponse(StunMessage* res) {
44 response_ = res;
45 failure_ = true;
46 }
OnTimeout()47 void OnTimeout() { timeout_ = true; }
48
49 protected:
CreateStunMessage(StunMessageType type,StunMessage * req)50 static StunMessage* CreateStunMessage(StunMessageType type,
51 StunMessage* req) {
52 StunMessage* msg = new StunMessage();
53 msg->SetType(type);
54 if (req) {
55 msg->SetTransactionID(req->transaction_id());
56 }
57 return msg;
58 }
TotalDelay(int sends)59 static int TotalDelay(int sends) {
60 std::vector<int> delays = {0, 250, 750, 1750, 3750,
61 7750, 15750, 23750, 31750, 39750};
62 return delays[sends];
63 }
64
65 StunRequestManager manager_;
66 int request_count_;
67 StunMessage* response_;
68 bool success_;
69 bool failure_;
70 bool timeout_;
71 };
72
73 // Forwards results to the test class.
74 class StunRequestThunker : public StunRequest {
75 public:
StunRequestThunker(StunMessage * msg,StunRequestTest * test)76 StunRequestThunker(StunMessage* msg, StunRequestTest* test)
77 : StunRequest(msg), test_(test) {}
StunRequestThunker(StunRequestTest * test)78 explicit StunRequestThunker(StunRequestTest* test) : test_(test) {}
79
80 private:
OnResponse(StunMessage * res)81 virtual void OnResponse(StunMessage* res) { test_->OnResponse(res); }
OnErrorResponse(StunMessage * res)82 virtual void OnErrorResponse(StunMessage* res) {
83 test_->OnErrorResponse(res);
84 }
OnTimeout()85 virtual void OnTimeout() { test_->OnTimeout(); }
86
Prepare(StunMessage * request)87 virtual void Prepare(StunMessage* request) {
88 request->SetType(STUN_BINDING_REQUEST);
89 }
90
91 StunRequestTest* test_;
92 };
93
94 // Test handling of a normal binding response.
TEST_F(StunRequestTest,TestSuccess)95 TEST_F(StunRequestTest, TestSuccess) {
96 StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
97
98 manager_.Send(new StunRequestThunker(req, this));
99 StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
100 EXPECT_TRUE(manager_.CheckResponse(res));
101
102 EXPECT_TRUE(response_ == res);
103 EXPECT_TRUE(success_);
104 EXPECT_FALSE(failure_);
105 EXPECT_FALSE(timeout_);
106 delete res;
107 }
108
109 // Test handling of an error binding response.
TEST_F(StunRequestTest,TestError)110 TEST_F(StunRequestTest, TestError) {
111 StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
112
113 manager_.Send(new StunRequestThunker(req, this));
114 StunMessage* res = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE, req);
115 EXPECT_TRUE(manager_.CheckResponse(res));
116
117 EXPECT_TRUE(response_ == res);
118 EXPECT_FALSE(success_);
119 EXPECT_TRUE(failure_);
120 EXPECT_FALSE(timeout_);
121 delete res;
122 }
123
124 // Test handling of a binding response with the wrong transaction id.
TEST_F(StunRequestTest,TestUnexpected)125 TEST_F(StunRequestTest, TestUnexpected) {
126 StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
127
128 manager_.Send(new StunRequestThunker(req, this));
129 StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, NULL);
130 EXPECT_FALSE(manager_.CheckResponse(res));
131
132 EXPECT_TRUE(response_ == NULL);
133 EXPECT_FALSE(success_);
134 EXPECT_FALSE(failure_);
135 EXPECT_FALSE(timeout_);
136 delete res;
137 }
138
139 // Test that requests are sent at the right times.
TEST_F(StunRequestTest,TestBackoff)140 TEST_F(StunRequestTest, TestBackoff) {
141 rtc::ScopedFakeClock fake_clock;
142 StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
143
144 int64_t start = rtc::TimeMillis();
145 manager_.Send(new StunRequestThunker(req, this));
146 StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
147 for (int i = 0; i < 9; ++i) {
148 EXPECT_TRUE_SIMULATED_WAIT(request_count_ != i, STUN_TOTAL_TIMEOUT,
149 fake_clock);
150 int64_t elapsed = rtc::TimeMillis() - start;
151 RTC_LOG(LS_INFO) << "STUN request #" << (i + 1) << " sent at " << elapsed
152 << " ms";
153 EXPECT_EQ(TotalDelay(i), elapsed);
154 }
155 EXPECT_TRUE(manager_.CheckResponse(res));
156
157 EXPECT_TRUE(response_ == res);
158 EXPECT_TRUE(success_);
159 EXPECT_FALSE(failure_);
160 EXPECT_FALSE(timeout_);
161 delete res;
162 }
163
164 // Test that we timeout properly if no response is received.
TEST_F(StunRequestTest,TestTimeout)165 TEST_F(StunRequestTest, TestTimeout) {
166 rtc::ScopedFakeClock fake_clock;
167 StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
168 StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
169
170 manager_.Send(new StunRequestThunker(req, this));
171 SIMULATED_WAIT(false, cricket::STUN_TOTAL_TIMEOUT, fake_clock);
172
173 EXPECT_FALSE(manager_.CheckResponse(res));
174 EXPECT_TRUE(response_ == NULL);
175 EXPECT_FALSE(success_);
176 EXPECT_FALSE(failure_);
177 EXPECT_TRUE(timeout_);
178 delete res;
179 }
180
181 // Regression test for specific crash where we receive a response with the
182 // same id as a request that doesn't have an underlying StunMessage yet.
TEST_F(StunRequestTest,TestNoEmptyRequest)183 TEST_F(StunRequestTest, TestNoEmptyRequest) {
184 StunRequestThunker* request = new StunRequestThunker(this);
185
186 manager_.SendDelayed(request, 100);
187
188 StunMessage dummy_req;
189 dummy_req.SetTransactionID(request->id());
190 StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, &dummy_req);
191
192 EXPECT_TRUE(manager_.CheckResponse(res));
193
194 EXPECT_TRUE(response_ == res);
195 EXPECT_TRUE(success_);
196 EXPECT_FALSE(failure_);
197 EXPECT_FALSE(timeout_);
198 delete res;
199 }
200
201 // If the response contains an attribute in the "comprehension required" range
202 // which is not recognized, the transaction should be considered a failure and
203 // the response should be ignored.
TEST_F(StunRequestTest,TestUnrecognizedComprehensionRequiredAttribute)204 TEST_F(StunRequestTest, TestUnrecognizedComprehensionRequiredAttribute) {
205 StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
206
207 manager_.Send(new StunRequestThunker(req, this));
208 StunMessage* res = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE, req);
209 res->AddAttribute(StunAttribute::CreateUInt32(0x7777));
210 EXPECT_FALSE(manager_.CheckResponse(res));
211
212 EXPECT_EQ(nullptr, response_);
213 EXPECT_FALSE(success_);
214 EXPECT_FALSE(failure_);
215 EXPECT_FALSE(timeout_);
216 delete res;
217 }
218
219 } // namespace cricket
220