1 /*
2 * Copyright (c) 2019 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 #include "media/sctp/sctp_transport.h"
11
12 #include <memory>
13 #include <queue>
14 #include <string>
15
16 #include "media/sctp/sctp_transport_internal.h"
17 #include "rtc_base/copy_on_write_buffer.h"
18 #include "rtc_base/gunit.h"
19 #include "rtc_base/logging.h"
20 #include "rtc_base/random.h"
21 #include "rtc_base/thread.h"
22 #include "test/gtest.h"
23
24 namespace {
25
26 static constexpr int kDefaultTimeout = 10000; // 10 seconds.
27 static constexpr int kTransport1Port = 15001;
28 static constexpr int kTransport2Port = 25002;
29 static constexpr int kLogPerMessagesCount = 100;
30
31 /**
32 * An simple packet transport implementation which can be
33 * configured to simulate uniform random packet loss and
34 * configurable random packet delay and reordering.
35 */
36 class SimulatedPacketTransport final : public rtc::PacketTransportInternal {
37 public:
SimulatedPacketTransport(std::string name,rtc::Thread * transport_thread,uint8_t packet_loss_percents,uint16_t avg_send_delay_millis)38 SimulatedPacketTransport(std::string name,
39 rtc::Thread* transport_thread,
40 uint8_t packet_loss_percents,
41 uint16_t avg_send_delay_millis)
42 : transport_name_(name),
43 transport_thread_(transport_thread),
44 packet_loss_percents_(packet_loss_percents),
45 avg_send_delay_millis_(avg_send_delay_millis),
46 random_(42) {
47 RTC_DCHECK(transport_thread_);
48 RTC_DCHECK_LE(packet_loss_percents_, 100);
49 RTC_DCHECK_RUN_ON(transport_thread_);
50 }
51
~SimulatedPacketTransport()52 ~SimulatedPacketTransport() override {
53 RTC_DCHECK_RUN_ON(transport_thread_);
54 auto destination = destination_.load();
55 if (destination != nullptr) {
56 invoker_.Flush(destination->transport_thread_);
57 }
58 invoker_.Flush(transport_thread_);
59 destination_ = nullptr;
60 SignalWritableState(this);
61 }
62
transport_name() const63 const std::string& transport_name() const override { return transport_name_; }
64
writable() const65 bool writable() const override { return destination_ != nullptr; }
66
receiving() const67 bool receiving() const override { return true; }
68
SendPacket(const char * data,size_t len,const rtc::PacketOptions & options,int flags=0)69 int SendPacket(const char* data,
70 size_t len,
71 const rtc::PacketOptions& options,
72 int flags = 0) {
73 RTC_DCHECK_RUN_ON(transport_thread_);
74 auto destination = destination_.load();
75 if (destination == nullptr) {
76 return -1;
77 }
78 if (random_.Rand(100) < packet_loss_percents_) {
79 // silent packet loss
80 return 0;
81 }
82 rtc::CopyOnWriteBuffer buffer(data, len);
83 auto send_job = [this, flags, buffer = std::move(buffer)] {
84 auto destination = destination_.load();
85 if (destination == nullptr) {
86 return;
87 }
88 destination->SignalReadPacket(
89 destination, reinterpret_cast<const char*>(buffer.data()),
90 buffer.size(), rtc::Time(), flags);
91 };
92 // Introduce random send delay in range [0 .. 2 * avg_send_delay_millis_]
93 // millis, which will also work as random packet reordering mechanism.
94 uint16_t actual_send_delay = avg_send_delay_millis_;
95 int16_t reorder_delay =
96 avg_send_delay_millis_ *
97 std::min(1.0, std::max(-1.0, random_.Gaussian(0, 0.5)));
98 actual_send_delay += reorder_delay;
99
100 if (actual_send_delay > 0) {
101 invoker_.AsyncInvokeDelayed<void>(RTC_FROM_HERE,
102 destination->transport_thread_,
103 std::move(send_job), actual_send_delay);
104 } else {
105 invoker_.AsyncInvoke<void>(RTC_FROM_HERE, destination->transport_thread_,
106 std::move(send_job));
107 }
108 return 0;
109 }
110
SetOption(rtc::Socket::Option opt,int value)111 int SetOption(rtc::Socket::Option opt, int value) override { return 0; }
112
GetOption(rtc::Socket::Option opt,int * value)113 bool GetOption(rtc::Socket::Option opt, int* value) override { return false; }
114
GetError()115 int GetError() override { return 0; }
116
network_route() const117 absl::optional<rtc::NetworkRoute> network_route() const override {
118 return absl::nullopt;
119 }
120
SetDestination(SimulatedPacketTransport * destination)121 void SetDestination(SimulatedPacketTransport* destination) {
122 RTC_DCHECK_RUN_ON(transport_thread_);
123 if (destination == this) {
124 return;
125 }
126 destination_ = destination;
127 SignalWritableState(this);
128 }
129
130 private:
131 const std::string transport_name_;
132 rtc::Thread* const transport_thread_;
133 const uint8_t packet_loss_percents_;
134 const uint16_t avg_send_delay_millis_;
135 std::atomic<SimulatedPacketTransport*> destination_ ATOMIC_VAR_INIT(nullptr);
136 rtc::AsyncInvoker invoker_;
137 webrtc::Random random_;
138 RTC_DISALLOW_COPY_AND_ASSIGN(SimulatedPacketTransport);
139 };
140
141 /**
142 * A helper class to send specified number of messages
143 * over SctpTransport with SCTP reliability settings
144 * provided by user. The reliability settings are specified
145 * by passing a template instance of SendDataParams.
146 * When .sid field inside SendDataParams is specified to
147 * negative value it means that actual .sid will be
148 * assigned by sender itself, .sid will be assigned from
149 * range [cricket::kMinSctpSid; cricket::kMaxSctpSid].
150 * The wide range of sids are used to possibly trigger
151 * more execution paths inside usrsctp.
152 */
153 class SctpDataSender final {
154 public:
SctpDataSender(rtc::Thread * thread,cricket::SctpTransport * transport,uint64_t target_messages_count,cricket::SendDataParams send_params,uint32_t sender_id)155 SctpDataSender(rtc::Thread* thread,
156 cricket::SctpTransport* transport,
157 uint64_t target_messages_count,
158 cricket::SendDataParams send_params,
159 uint32_t sender_id)
160 : thread_(thread),
161 transport_(transport),
162 target_messages_count_(target_messages_count),
163 send_params_(send_params),
164 sender_id_(sender_id) {
165 RTC_DCHECK(thread_);
166 RTC_DCHECK(transport_);
167 }
168
Start()169 void Start() {
170 invoker_.AsyncInvoke<void>(RTC_FROM_HERE, thread_, [this] {
171 if (started_) {
172 RTC_LOG(LS_INFO) << sender_id_ << " sender is already started";
173 return;
174 }
175 started_ = true;
176 SendNextMessage();
177 });
178 }
179
BytesSentCount() const180 uint64_t BytesSentCount() const { return num_bytes_sent_; }
181
MessagesSentCount() const182 uint64_t MessagesSentCount() const { return num_messages_sent_; }
183
GetLastError()184 absl::optional<std::string> GetLastError() {
185 absl::optional<std::string> result = absl::nullopt;
186 thread_->Invoke<void>(RTC_FROM_HERE,
187 [this, &result] { result = last_error_; });
188 return result;
189 }
190
WaitForCompletion(int give_up_after_ms)191 bool WaitForCompletion(int give_up_after_ms) {
192 return sent_target_messages_count_.Wait(give_up_after_ms, kDefaultTimeout);
193 }
194
195 private:
SendNextMessage()196 void SendNextMessage() {
197 RTC_DCHECK_RUN_ON(thread_);
198 if (!started_ || num_messages_sent_ >= target_messages_count_) {
199 sent_target_messages_count_.Set();
200 return;
201 }
202
203 if (num_messages_sent_ % kLogPerMessagesCount == 0) {
204 RTC_LOG(LS_INFO) << sender_id_ << " sender will try send message "
205 << (num_messages_sent_ + 1) << " out of "
206 << target_messages_count_;
207 }
208
209 cricket::SendDataParams params(send_params_);
210 if (params.sid < 0) {
211 params.sid = cricket::kMinSctpSid +
212 (num_messages_sent_ % cricket::kMaxSctpStreams);
213 }
214
215 cricket::SendDataResult result;
216 transport_->SendData(params, payload_, &result);
217 switch (result) {
218 case cricket::SDR_BLOCK:
219 // retry after timeout
220 invoker_.AsyncInvokeDelayed<void>(
221 RTC_FROM_HERE, thread_,
222 rtc::Bind(&SctpDataSender::SendNextMessage, this), 500);
223 break;
224 case cricket::SDR_SUCCESS:
225 // send next
226 num_bytes_sent_ += payload_.size();
227 ++num_messages_sent_;
228 invoker_.AsyncInvoke<void>(
229 RTC_FROM_HERE, thread_,
230 rtc::Bind(&SctpDataSender::SendNextMessage, this));
231 break;
232 case cricket::SDR_ERROR:
233 // give up
234 last_error_ = "SctpTransport::SendData error returned";
235 sent_target_messages_count_.Set();
236 break;
237 }
238 }
239
240 rtc::Thread* const thread_;
241 cricket::SctpTransport* const transport_;
242 const uint64_t target_messages_count_;
243 const cricket::SendDataParams send_params_;
244 const uint32_t sender_id_;
245 rtc::CopyOnWriteBuffer payload_{std::string(1400, '.').c_str(), 1400};
246 std::atomic<bool> started_ ATOMIC_VAR_INIT(false);
247 rtc::AsyncInvoker invoker_;
248 std::atomic<uint64_t> num_messages_sent_ ATOMIC_VAR_INIT(0);
249 rtc::Event sent_target_messages_count_{true, false};
250 std::atomic<uint64_t> num_bytes_sent_ ATOMIC_VAR_INIT(0);
251 absl::optional<std::string> last_error_;
252 RTC_DISALLOW_COPY_AND_ASSIGN(SctpDataSender);
253 };
254
255 /**
256 * A helper class which counts number of received messages
257 * and bytes over SctpTransport. Also allow waiting until
258 * specified number of messages received.
259 */
260 class SctpDataReceiver final : public sigslot::has_slots<> {
261 public:
SctpDataReceiver(uint32_t receiver_id,uint64_t target_messages_count)262 explicit SctpDataReceiver(uint32_t receiver_id,
263 uint64_t target_messages_count)
264 : receiver_id_(receiver_id),
265 target_messages_count_(target_messages_count) {}
266
OnDataReceived(const cricket::ReceiveDataParams & params,const rtc::CopyOnWriteBuffer & data)267 void OnDataReceived(const cricket::ReceiveDataParams& params,
268 const rtc::CopyOnWriteBuffer& data) {
269 num_bytes_received_ += data.size();
270 if (++num_messages_received_ == target_messages_count_) {
271 received_target_messages_count_.Set();
272 }
273
274 if (num_messages_received_ % kLogPerMessagesCount == 0) {
275 RTC_LOG(INFO) << receiver_id_ << " receiver got "
276 << num_messages_received_ << " messages";
277 }
278 }
279
MessagesReceivedCount() const280 uint64_t MessagesReceivedCount() const { return num_messages_received_; }
281
BytesReceivedCount() const282 uint64_t BytesReceivedCount() const { return num_bytes_received_; }
283
WaitForMessagesReceived(int timeout_millis)284 bool WaitForMessagesReceived(int timeout_millis) {
285 return received_target_messages_count_.Wait(timeout_millis);
286 }
287
288 private:
289 std::atomic<uint64_t> num_messages_received_ ATOMIC_VAR_INIT(0);
290 std::atomic<uint64_t> num_bytes_received_ ATOMIC_VAR_INIT(0);
291 rtc::Event received_target_messages_count_{true, false};
292 const uint32_t receiver_id_;
293 const uint64_t target_messages_count_;
294 RTC_DISALLOW_COPY_AND_ASSIGN(SctpDataReceiver);
295 };
296
297 /**
298 * Simple class to manage set of threads.
299 */
300 class ThreadPool final {
301 public:
ThreadPool(size_t threads_count)302 explicit ThreadPool(size_t threads_count) : random_(42) {
303 RTC_DCHECK(threads_count > 0);
304 threads_.reserve(threads_count);
305 for (size_t i = 0; i < threads_count; i++) {
306 auto thread = rtc::Thread::Create();
307 thread->SetName("Thread #" + rtc::ToString(i + 1) + " from Pool", this);
308 thread->Start();
309 threads_.emplace_back(std::move(thread));
310 }
311 }
312
GetRandomThread()313 rtc::Thread* GetRandomThread() {
314 return threads_[random_.Rand(0U, threads_.size() - 1)].get();
315 }
316
317 private:
318 webrtc::Random random_;
319 std::vector<std::unique_ptr<rtc::Thread>> threads_;
320 RTC_DISALLOW_COPY_AND_ASSIGN(ThreadPool);
321 };
322
323 /**
324 * Represents single ping-pong test over SctpTransport.
325 * User can specify target number of message for bidirectional
326 * send, underlying transport packets loss and average packet delay
327 * and SCTP delivery settings.
328 */
329 class SctpPingPong final {
330 public:
SctpPingPong(uint32_t id,uint16_t port1,uint16_t port2,rtc::Thread * transport_thread1,rtc::Thread * transport_thread2,uint32_t messages_count,uint8_t packet_loss_percents,uint16_t avg_send_delay_millis,cricket::SendDataParams send_params)331 SctpPingPong(uint32_t id,
332 uint16_t port1,
333 uint16_t port2,
334 rtc::Thread* transport_thread1,
335 rtc::Thread* transport_thread2,
336 uint32_t messages_count,
337 uint8_t packet_loss_percents,
338 uint16_t avg_send_delay_millis,
339 cricket::SendDataParams send_params)
340 : id_(id),
341 port1_(port1),
342 port2_(port2),
343 transport_thread1_(transport_thread1),
344 transport_thread2_(transport_thread2),
345 messages_count_(messages_count),
346 packet_loss_percents_(packet_loss_percents),
347 avg_send_delay_millis_(avg_send_delay_millis),
348 send_params_(send_params) {
349 RTC_DCHECK(transport_thread1_ != nullptr);
350 RTC_DCHECK(transport_thread2_ != nullptr);
351 }
352
~SctpPingPong()353 virtual ~SctpPingPong() {
354 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
355 data_sender1_.reset();
356 sctp_transport1_->SetDtlsTransport(nullptr);
357 packet_transport1_->SetDestination(nullptr);
358 });
359 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
360 data_sender2_.reset();
361 sctp_transport2_->SetDtlsTransport(nullptr);
362 packet_transport2_->SetDestination(nullptr);
363 });
364 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
365 sctp_transport1_.reset();
366 data_receiver1_.reset();
367 packet_transport1_.reset();
368 });
369 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
370 sctp_transport2_.reset();
371 data_receiver2_.reset();
372 packet_transport2_.reset();
373 });
374 }
375
Start()376 bool Start() {
377 CreateTwoConnectedSctpTransportsWithAllStreams();
378
379 {
380 rtc::CritScope cs(&lock_);
381 if (!errors_list_.empty()) {
382 return false;
383 }
384 }
385
386 data_sender1_.reset(new SctpDataSender(transport_thread1_,
387 sctp_transport1_.get(),
388 messages_count_, send_params_, id_));
389 data_sender2_.reset(new SctpDataSender(transport_thread2_,
390 sctp_transport2_.get(),
391 messages_count_, send_params_, id_));
392 data_sender1_->Start();
393 data_sender2_->Start();
394 return true;
395 }
396
GetErrorsList() const397 std::vector<std::string> GetErrorsList() const {
398 std::vector<std::string> result;
399 {
400 rtc::CritScope cs(&lock_);
401 result = errors_list_;
402 }
403 return result;
404 }
405
WaitForCompletion(int32_t timeout_millis)406 void WaitForCompletion(int32_t timeout_millis) {
407 if (data_sender1_ == nullptr) {
408 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
409 ", sender 1 is not created");
410 return;
411 }
412 if (data_sender2_ == nullptr) {
413 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
414 ", sender 2 is not created");
415 return;
416 }
417
418 if (!data_sender1_->WaitForCompletion(timeout_millis)) {
419 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
420 ", sender 1 failed to complete within " +
421 rtc::ToString(timeout_millis) + " millis");
422 return;
423 }
424
425 auto sender1_error = data_sender1_->GetLastError();
426 if (sender1_error.has_value()) {
427 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
428 ", sender 1 error: " + sender1_error.value());
429 return;
430 }
431
432 if (!data_sender2_->WaitForCompletion(timeout_millis)) {
433 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
434 ", sender 2 failed to complete within " +
435 rtc::ToString(timeout_millis) + " millis");
436 return;
437 }
438
439 auto sender2_error = data_sender2_->GetLastError();
440 if (sender2_error.has_value()) {
441 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
442 ", sender 2 error: " + sender1_error.value());
443 return;
444 }
445
446 if ((data_sender1_->MessagesSentCount() != messages_count_)) {
447 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
448 ", sender 1 sent only " +
449 rtc::ToString(data_sender1_->MessagesSentCount()) +
450 " out of " + rtc::ToString(messages_count_));
451 return;
452 }
453
454 if ((data_sender2_->MessagesSentCount() != messages_count_)) {
455 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
456 ", sender 2 sent only " +
457 rtc::ToString(data_sender2_->MessagesSentCount()) +
458 " out of " + rtc::ToString(messages_count_));
459 return;
460 }
461
462 if (!data_receiver1_->WaitForMessagesReceived(timeout_millis)) {
463 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
464 ", receiver 1 did not complete within " +
465 rtc::ToString(messages_count_));
466 return;
467 }
468
469 if (!data_receiver2_->WaitForMessagesReceived(timeout_millis)) {
470 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
471 ", receiver 2 did not complete within " +
472 rtc::ToString(messages_count_));
473 return;
474 }
475
476 if (data_receiver1_->BytesReceivedCount() !=
477 data_sender2_->BytesSentCount()) {
478 ReportError(
479 "SctpPingPong id = " + rtc::ToString(id_) + ", receiver 1 received " +
480 rtc::ToString(data_receiver1_->BytesReceivedCount()) +
481 " bytes, but sender 2 send " +
482 rtc::ToString(rtc::ToString(data_sender2_->BytesSentCount())));
483 return;
484 }
485
486 if (data_receiver2_->BytesReceivedCount() !=
487 data_sender1_->BytesSentCount()) {
488 ReportError(
489 "SctpPingPong id = " + rtc::ToString(id_) + ", receiver 2 received " +
490 rtc::ToString(data_receiver2_->BytesReceivedCount()) +
491 " bytes, but sender 1 send " +
492 rtc::ToString(rtc::ToString(data_sender1_->BytesSentCount())));
493 return;
494 }
495
496 RTC_LOG(LS_INFO) << "SctpPingPong id = " << id_ << " is done";
497 }
498
499 private:
CreateTwoConnectedSctpTransportsWithAllStreams()500 void CreateTwoConnectedSctpTransportsWithAllStreams() {
501 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
502 packet_transport1_.reset(new SimulatedPacketTransport(
503 "SctpPingPong id = " + rtc::ToString(id_) + ", packet transport 1",
504 transport_thread1_, packet_loss_percents_, avg_send_delay_millis_));
505 data_receiver1_.reset(new SctpDataReceiver(id_, messages_count_));
506 sctp_transport1_.reset(new cricket::SctpTransport(
507 transport_thread1_, packet_transport1_.get()));
508 sctp_transport1_->set_debug_name_for_testing("sctp transport 1");
509
510 sctp_transport1_->SignalDataReceived.connect(
511 data_receiver1_.get(), &SctpDataReceiver::OnDataReceived);
512
513 for (uint32_t i = cricket::kMinSctpSid; i <= cricket::kMaxSctpSid; i++) {
514 if (!sctp_transport1_->OpenStream(i)) {
515 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
516 ", sctp transport 1 stream " + rtc::ToString(i) +
517 " failed to open");
518 break;
519 }
520 }
521 });
522
523 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
524 packet_transport2_.reset(new SimulatedPacketTransport(
525 "SctpPingPong id = " + rtc::ToString(id_) + "packet transport 2",
526 transport_thread2_, packet_loss_percents_, avg_send_delay_millis_));
527 data_receiver2_.reset(new SctpDataReceiver(id_, messages_count_));
528 sctp_transport2_.reset(new cricket::SctpTransport(
529 transport_thread2_, packet_transport2_.get()));
530 sctp_transport2_->set_debug_name_for_testing("sctp transport 2");
531 sctp_transport2_->SignalDataReceived.connect(
532 data_receiver2_.get(), &SctpDataReceiver::OnDataReceived);
533
534 for (uint32_t i = cricket::kMinSctpSid; i <= cricket::kMaxSctpSid; i++) {
535 if (!sctp_transport2_->OpenStream(i)) {
536 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
537 ", sctp transport 2 stream " + rtc::ToString(i) +
538 " failed to open");
539 break;
540 }
541 }
542 });
543
544 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
545 packet_transport1_->SetDestination(packet_transport2_.get());
546 });
547 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
548 packet_transport2_->SetDestination(packet_transport1_.get());
549 });
550
551 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
552 if (!sctp_transport1_->Start(port1_, port2_,
553 cricket::kSctpSendBufferSize)) {
554 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
555 ", failed to start sctp transport 1");
556 }
557 });
558
559 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
560 if (!sctp_transport2_->Start(port2_, port1_,
561 cricket::kSctpSendBufferSize)) {
562 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
563 ", failed to start sctp transport 2");
564 }
565 });
566 }
567
ReportError(std::string error)568 void ReportError(std::string error) {
569 rtc::CritScope cs(&lock_);
570 errors_list_.push_back(std::move(error));
571 }
572
573 std::unique_ptr<SimulatedPacketTransport> packet_transport1_;
574 std::unique_ptr<SimulatedPacketTransport> packet_transport2_;
575 std::unique_ptr<SctpDataReceiver> data_receiver1_;
576 std::unique_ptr<SctpDataReceiver> data_receiver2_;
577 std::unique_ptr<cricket::SctpTransport> sctp_transport1_;
578 std::unique_ptr<cricket::SctpTransport> sctp_transport2_;
579 std::unique_ptr<SctpDataSender> data_sender1_;
580 std::unique_ptr<SctpDataSender> data_sender2_;
581 rtc::CriticalSection lock_;
582 std::vector<std::string> errors_list_ RTC_GUARDED_BY(lock_);
583
584 const uint32_t id_;
585 const uint16_t port1_;
586 const uint16_t port2_;
587 rtc::Thread* const transport_thread1_;
588 rtc::Thread* const transport_thread2_;
589 const uint32_t messages_count_;
590 const uint8_t packet_loss_percents_;
591 const uint16_t avg_send_delay_millis_;
592 const cricket::SendDataParams send_params_;
593 RTC_DISALLOW_COPY_AND_ASSIGN(SctpPingPong);
594 };
595
596 /**
597 * Helper function to calculate max number of milliseconds
598 * allowed for test to run based on test configuration.
599 */
GetExecutionTimeLimitInMillis(uint32_t total_messages,uint8_t packet_loss_percents)600 constexpr int32_t GetExecutionTimeLimitInMillis(uint32_t total_messages,
601 uint8_t packet_loss_percents) {
602 return std::min<int64_t>(
603 std::numeric_limits<int32_t>::max(),
604 std::max<int64_t>(
605 1LL * total_messages * 100 *
606 std::max(1, packet_loss_percents * packet_loss_percents),
607 kDefaultTimeout));
608 }
609
610 } // namespace
611
612 namespace cricket {
613
614 /**
615 * The set of tests intended to check usrsctp reliability on
616 * stress conditions: multiple sockets, concurrent access,
617 * lossy network link. It was observed in the past that
618 * usrsctp might misbehave in concurrent environment
619 * under load on lossy networks: deadlocks and memory corruption
620 * issues might happen in non-basic usage scenarios.
621 * It's recommended to run this test whenever usrsctp version
622 * used is updated to verify it properly works in stress
623 * conditions under higher than usual load.
624 * It is also recommended to enable ASAN when these tests
625 * are executed, so whenever memory bug is happen inside usrsctp,
626 * it will be easier to understand what went wrong with ASAN
627 * provided diagnostics information.
628 * The tests cases currently disabled by default due to
629 * long execution time and due to unresolved issue inside
630 * `usrsctp` library detected by try-bots with ThreadSanitizer.
631 */
632 class UsrSctpReliabilityTest : public ::testing::Test {};
633
634 /**
635 * A simple test which send multiple messages over reliable
636 * connection, usefull to verify test infrastructure works.
637 * Execution time is less than 1 second.
638 */
TEST_F(UsrSctpReliabilityTest,DISABLED_AllMessagesAreDeliveredOverReliableConnection)639 TEST_F(UsrSctpReliabilityTest,
640 DISABLED_AllMessagesAreDeliveredOverReliableConnection) {
641 auto thread1 = rtc::Thread::Create();
642 auto thread2 = rtc::Thread::Create();
643 thread1->Start();
644 thread2->Start();
645 constexpr uint8_t packet_loss_percents = 0;
646 constexpr uint16_t avg_send_delay_millis = 10;
647 constexpr uint32_t messages_count = 100;
648 constexpr int32_t wait_timeout =
649 GetExecutionTimeLimitInMillis(messages_count, packet_loss_percents);
650 static_assert(wait_timeout > 0,
651 "Timeout computation must produce positive value");
652
653 cricket::SendDataParams send_params;
654 send_params.sid = -1;
655 send_params.ordered = true;
656 send_params.reliable = true;
657 send_params.max_rtx_count = 0;
658 send_params.max_rtx_ms = 0;
659
660 SctpPingPong test(1, kTransport1Port, kTransport2Port, thread1.get(),
661 thread2.get(), messages_count, packet_loss_percents,
662 avg_send_delay_millis, send_params);
663 EXPECT_TRUE(test.Start()) << rtc::join(test.GetErrorsList(), ';');
664 test.WaitForCompletion(wait_timeout);
665 auto errors_list = test.GetErrorsList();
666 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
667 }
668
669 /**
670 * A test to verify that multiple messages can be reliably delivered
671 * over lossy network when usrsctp configured to guarantee reliably
672 * and in order delivery.
673 * The test case is disabled by default because it takes
674 * long time to run.
675 * Execution time is about 2.5 minutes.
676 */
TEST_F(UsrSctpReliabilityTest,DISABLED_AllMessagesAreDeliveredOverLossyConnectionReliableAndInOrder)677 TEST_F(UsrSctpReliabilityTest,
678 DISABLED_AllMessagesAreDeliveredOverLossyConnectionReliableAndInOrder) {
679 auto thread1 = rtc::Thread::Create();
680 auto thread2 = rtc::Thread::Create();
681 thread1->Start();
682 thread2->Start();
683 constexpr uint8_t packet_loss_percents = 5;
684 constexpr uint16_t avg_send_delay_millis = 16;
685 constexpr uint32_t messages_count = 10000;
686 constexpr int32_t wait_timeout =
687 GetExecutionTimeLimitInMillis(messages_count, packet_loss_percents);
688 static_assert(wait_timeout > 0,
689 "Timeout computation must produce positive value");
690
691 cricket::SendDataParams send_params;
692 send_params.sid = -1;
693 send_params.ordered = true;
694 send_params.reliable = true;
695 send_params.max_rtx_count = 0;
696 send_params.max_rtx_ms = 0;
697
698 SctpPingPong test(1, kTransport1Port, kTransport2Port, thread1.get(),
699 thread2.get(), messages_count, packet_loss_percents,
700 avg_send_delay_millis, send_params);
701
702 EXPECT_TRUE(test.Start()) << rtc::join(test.GetErrorsList(), ';');
703 test.WaitForCompletion(wait_timeout);
704 auto errors_list = test.GetErrorsList();
705 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
706 }
707
708 /**
709 * A test to verify that multiple messages can be reliably delivered
710 * over lossy network when usrsctp configured to retransmit lost
711 * packets.
712 * The test case is disabled by default because it takes
713 * long time to run.
714 * Execution time is about 2.5 minutes.
715 */
TEST_F(UsrSctpReliabilityTest,DISABLED_AllMessagesAreDeliveredOverLossyConnectionWithRetries)716 TEST_F(UsrSctpReliabilityTest,
717 DISABLED_AllMessagesAreDeliveredOverLossyConnectionWithRetries) {
718 auto thread1 = rtc::Thread::Create();
719 auto thread2 = rtc::Thread::Create();
720 thread1->Start();
721 thread2->Start();
722 constexpr uint8_t packet_loss_percents = 5;
723 constexpr uint16_t avg_send_delay_millis = 16;
724 constexpr uint32_t messages_count = 10000;
725 constexpr int32_t wait_timeout =
726 GetExecutionTimeLimitInMillis(messages_count, packet_loss_percents);
727 static_assert(wait_timeout > 0,
728 "Timeout computation must produce positive value");
729
730 cricket::SendDataParams send_params;
731 send_params.sid = -1;
732 send_params.ordered = false;
733 send_params.reliable = false;
734 send_params.max_rtx_count = INT_MAX;
735 send_params.max_rtx_ms = INT_MAX;
736
737 SctpPingPong test(1, kTransport1Port, kTransport2Port, thread1.get(),
738 thread2.get(), messages_count, packet_loss_percents,
739 avg_send_delay_millis, send_params);
740
741 EXPECT_TRUE(test.Start()) << rtc::join(test.GetErrorsList(), ';');
742 test.WaitForCompletion(wait_timeout);
743 auto errors_list = test.GetErrorsList();
744 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
745 }
746
747 /**
748 * This is kind of reliability stress-test of usrsctp to verify
749 * that all messages are delivered when multiple usrsctp
750 * sockets used concurrently and underlying transport is lossy.
751 *
752 * It was observed in the past that in stress condtions usrsctp
753 * might encounter deadlock and memory corruption bugs:
754 * https://github.com/sctplab/usrsctp/issues/325
755 *
756 * It is recoomended to run this test whenever usrsctp version
757 * used by WebRTC is updated.
758 *
759 * The test case is disabled by default because it takes
760 * long time to run.
761 * Execution time of this test is about 1-2 hours.
762 */
TEST_F(UsrSctpReliabilityTest,DISABLED_AllMessagesAreDeliveredOverLossyConnectionConcurrentTests)763 TEST_F(UsrSctpReliabilityTest,
764 DISABLED_AllMessagesAreDeliveredOverLossyConnectionConcurrentTests) {
765 ThreadPool pool(16);
766
767 cricket::SendDataParams send_params;
768 send_params.sid = -1;
769 send_params.ordered = true;
770 send_params.reliable = true;
771 send_params.max_rtx_count = 0;
772 send_params.max_rtx_ms = 0;
773 constexpr uint32_t base_sctp_port = 5000;
774
775 // The constants value below were experimentally chosen
776 // to have reasonable execution time and to reproduce
777 // particular deadlock issue inside usrsctp:
778 // https://github.com/sctplab/usrsctp/issues/325
779 // The constants values may be adjusted next time
780 // some other issue inside usrsctp need to be debugged.
781 constexpr uint32_t messages_count = 200;
782 constexpr uint8_t packet_loss_percents = 5;
783 constexpr uint16_t avg_send_delay_millis = 0;
784 constexpr uint32_t parallel_ping_pongs = 16 * 1024;
785 constexpr uint32_t total_ping_pong_tests = 16 * parallel_ping_pongs;
786
787 constexpr int32_t wait_timeout = GetExecutionTimeLimitInMillis(
788 total_ping_pong_tests * messages_count, packet_loss_percents);
789 static_assert(wait_timeout > 0,
790 "Timeout computation must produce positive value");
791
792 std::queue<std::unique_ptr<SctpPingPong>> tests;
793
794 for (uint32_t i = 0; i < total_ping_pong_tests; i++) {
795 uint32_t port1 =
796 base_sctp_port + (2 * i) % (UINT16_MAX - base_sctp_port - 1);
797
798 auto test = std::make_unique<SctpPingPong>(
799 i, port1, port1 + 1, pool.GetRandomThread(), pool.GetRandomThread(),
800 messages_count, packet_loss_percents, avg_send_delay_millis,
801 send_params);
802
803 EXPECT_TRUE(test->Start()) << rtc::join(test->GetErrorsList(), ';');
804 tests.emplace(std::move(test));
805
806 while (tests.size() >= parallel_ping_pongs) {
807 auto& oldest_test = tests.front();
808 oldest_test->WaitForCompletion(wait_timeout);
809
810 auto errors_list = oldest_test->GetErrorsList();
811 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
812 tests.pop();
813 }
814 }
815
816 while (!tests.empty()) {
817 auto& oldest_test = tests.front();
818 oldest_test->WaitForCompletion(wait_timeout);
819
820 auto errors_list = oldest_test->GetErrorsList();
821 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
822 tests.pop();
823 }
824 }
825
826 } // namespace cricket
827