michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Original author: ekr@rtfm.com michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "sigslot.h" michael@0: michael@0: #include "logging.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsITimer.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXPCOM.h" michael@0: michael@0: #include "transportflow.h" michael@0: #include "transportlayer.h" michael@0: #include "transportlayerloopback.h" michael@0: michael@0: #include "mtransport_test_utils.h" michael@0: #include "runnable_utils.h" michael@0: #include "usrsctp.h" michael@0: michael@0: #define GTEST_HAS_RTTI 0 michael@0: #include "gtest/gtest.h" michael@0: #include "gtest_utils.h" michael@0: michael@0: michael@0: using namespace mozilla; michael@0: michael@0: MtransportTestUtils *test_utils; michael@0: static bool sctp_logging = false; michael@0: static int port_number = 5000; michael@0: michael@0: namespace { michael@0: michael@0: class TransportTestPeer; michael@0: michael@0: class SendPeriodic : public nsITimerCallback { michael@0: public: michael@0: SendPeriodic(TransportTestPeer *peer, int to_send) : michael@0: peer_(peer), michael@0: to_send_(to_send) {} michael@0: virtual ~SendPeriodic() {} michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSITIMERCALLBACK michael@0: michael@0: protected: michael@0: TransportTestPeer *peer_; michael@0: int to_send_; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(SendPeriodic, nsITimerCallback) michael@0: michael@0: michael@0: class TransportTestPeer : public sigslot::has_slots<> { michael@0: public: michael@0: TransportTestPeer(std::string name, int local_port, int remote_port) michael@0: : name_(name), connected_(false), michael@0: sent_(0), received_(0), michael@0: flow_(new TransportFlow()), michael@0: loopback_(new TransportLayerLoopback()), michael@0: sctp_(usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, nullptr, 0, nullptr)), michael@0: timer_(do_CreateInstance(NS_TIMER_CONTRACTID)), michael@0: periodic_(nullptr) { michael@0: std::cerr << "Creating TransportTestPeer; flow=" << michael@0: static_cast(flow_.get()) << michael@0: " local=" << local_port << michael@0: " remote=" << remote_port << std::endl; michael@0: michael@0: usrsctp_register_address(static_cast(this)); michael@0: int r = usrsctp_set_non_blocking(sctp_, 1); michael@0: EXPECT_GE(r, 0); michael@0: michael@0: struct linger l; michael@0: l.l_onoff = 1; michael@0: l.l_linger = 0; michael@0: r = usrsctp_setsockopt(sctp_, SOL_SOCKET, SO_LINGER, &l, michael@0: (socklen_t)sizeof(l)); michael@0: EXPECT_GE(r, 0); michael@0: michael@0: struct sctp_event subscription; michael@0: memset(&subscription, 0, sizeof(subscription)); michael@0: subscription.se_assoc_id = SCTP_ALL_ASSOC; michael@0: subscription.se_on = 1; michael@0: subscription.se_type = SCTP_ASSOC_CHANGE; michael@0: r = usrsctp_setsockopt(sctp_, IPPROTO_SCTP, SCTP_EVENT, &subscription, michael@0: sizeof(subscription)); michael@0: EXPECT_GE(r, 0); michael@0: michael@0: memset(&local_addr_, 0, sizeof(local_addr_)); michael@0: local_addr_.sconn_family = AF_CONN; michael@0: #if !defined(__Userspace_os_Linux) && !defined(__Userspace_os_Windows) && !defined(__Userspace_os_Android) michael@0: local_addr_.sconn_len = sizeof(struct sockaddr_conn); michael@0: #endif michael@0: local_addr_.sconn_port = htons(local_port); michael@0: local_addr_.sconn_addr = static_cast(this); michael@0: michael@0: michael@0: memset(&remote_addr_, 0, sizeof(remote_addr_)); michael@0: remote_addr_.sconn_family = AF_CONN; michael@0: #if !defined(__Userspace_os_Linux) && !defined(__Userspace_os_Windows) && !defined(__Userspace_os_Android) michael@0: remote_addr_.sconn_len = sizeof(struct sockaddr_conn); michael@0: #endif michael@0: remote_addr_.sconn_port = htons(remote_port); michael@0: remote_addr_.sconn_addr = static_cast(this); michael@0: michael@0: nsresult res; michael@0: res = loopback_->Init(); michael@0: EXPECT_EQ((nsresult)NS_OK, res); michael@0: } michael@0: michael@0: ~TransportTestPeer() { michael@0: std::cerr << "Destroying sctp connection flow=" << michael@0: static_cast(flow_.get()) << std::endl; michael@0: usrsctp_close(sctp_); michael@0: usrsctp_deregister_address(static_cast(this)); michael@0: michael@0: test_utils->sts_target()->Dispatch(WrapRunnable(this, michael@0: &TransportTestPeer::Disconnect_s), michael@0: NS_DISPATCH_SYNC); michael@0: michael@0: std::cerr << "~TransportTestPeer() completed" << std::endl; michael@0: } michael@0: michael@0: void ConnectSocket(TransportTestPeer *peer) { michael@0: test_utils->sts_target()->Dispatch(WrapRunnable( michael@0: this, &TransportTestPeer::ConnectSocket_s, peer), michael@0: NS_DISPATCH_SYNC); michael@0: } michael@0: michael@0: void ConnectSocket_s(TransportTestPeer *peer) { michael@0: loopback_->Connect(peer->loopback_); michael@0: michael@0: ASSERT_EQ((nsresult)NS_OK, flow_->PushLayer(loopback_)); michael@0: michael@0: flow_->SignalPacketReceived.connect(this, &TransportTestPeer::PacketReceived); michael@0: michael@0: // SCTP here! michael@0: ASSERT_TRUE(sctp_); michael@0: std::cerr << "Calling usrsctp_bind()" << std::endl; michael@0: int r = usrsctp_bind(sctp_, reinterpret_cast( michael@0: &local_addr_), sizeof(local_addr_)); michael@0: ASSERT_GE(0, r); michael@0: michael@0: std::cerr << "Calling usrsctp_connect()" << std::endl; michael@0: r = usrsctp_connect(sctp_, reinterpret_cast( michael@0: &remote_addr_), sizeof(remote_addr_)); michael@0: ASSERT_GE(0, r); michael@0: } michael@0: michael@0: void Disconnect_s() { michael@0: if (flow_) { michael@0: flow_ = nullptr; michael@0: } michael@0: } michael@0: michael@0: void Disconnect() { michael@0: loopback_->Disconnect(); michael@0: } michael@0: michael@0: michael@0: void StartTransfer(size_t to_send) { michael@0: periodic_ = new SendPeriodic(this, to_send); michael@0: timer_->SetTarget(test_utils->sts_target()); michael@0: timer_->InitWithCallback(periodic_, 10, nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: michael@0: void SendOne() { michael@0: unsigned char buf[100]; michael@0: memset(buf, sent_ & 0xff, sizeof(buf)); michael@0: michael@0: struct sctp_sndinfo info; michael@0: info.snd_sid = 1; michael@0: info.snd_flags = 0; michael@0: info.snd_ppid = 50; // What the heck is this? michael@0: info.snd_context = 0; michael@0: info.snd_assoc_id = 0; michael@0: michael@0: int r = usrsctp_sendv(sctp_, buf, sizeof(buf), nullptr, 0, michael@0: static_cast(&info), michael@0: sizeof(info), SCTP_SENDV_SNDINFO, 0); michael@0: ASSERT_TRUE(r >= 0); michael@0: ASSERT_EQ(sizeof(buf), (size_t)r); michael@0: michael@0: ++sent_; michael@0: } michael@0: michael@0: int sent() const { return sent_; } michael@0: int received() const { return received_; } michael@0: bool connected() const { return connected_; } michael@0: michael@0: static TransportResult SendPacket_s(const unsigned char* data, size_t len, michael@0: const mozilla::RefPtr& flow) { michael@0: TransportResult res = flow->SendPacket(data, len); michael@0: delete data; // we always allocate michael@0: return res; michael@0: } michael@0: michael@0: TransportResult SendPacket(const unsigned char* data, size_t len) { michael@0: unsigned char *buffer = new unsigned char[len]; michael@0: memcpy(buffer, data, len); michael@0: michael@0: // Uses DISPATCH_NORMAL to avoid possible deadlocks when we're called michael@0: // from MainThread especially during shutdown (same as DataChannels). michael@0: // RUN_ON_THREAD short-circuits if already on the STS thread, which is michael@0: // normal for most transfers outside of connect() and close(). Passes michael@0: // a refptr to flow_ to avoid any async deletion issues (since we can't michael@0: // make 'this' into a refptr as it isn't refcounted) michael@0: RUN_ON_THREAD(test_utils->sts_target(), WrapRunnableNM( michael@0: &TransportTestPeer::SendPacket_s, buffer, len, flow_), michael@0: NS_DISPATCH_NORMAL); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: void PacketReceived(TransportFlow * flow, const unsigned char* data, michael@0: size_t len) { michael@0: std::cerr << "Received " << len << " bytes" << std::endl; michael@0: michael@0: // Pass the data to SCTP michael@0: michael@0: usrsctp_conninput(static_cast(this), data, len, 0); michael@0: } michael@0: michael@0: // Process SCTP notification michael@0: void Notification(union sctp_notification *msg, size_t len) { michael@0: ASSERT_EQ(msg->sn_header.sn_length, len); michael@0: michael@0: if (msg->sn_header.sn_type == SCTP_ASSOC_CHANGE) { michael@0: struct sctp_assoc_change *change = &msg->sn_assoc_change; michael@0: michael@0: if (change->sac_state == SCTP_COMM_UP) { michael@0: std::cerr << "Connection up" << std::endl; michael@0: SetConnected(true); michael@0: } else { michael@0: std::cerr << "Connection down" << std::endl; michael@0: SetConnected(false); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void SetConnected(bool state) { michael@0: connected_ = state; michael@0: } michael@0: michael@0: static int conn_output(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df) { michael@0: TransportTestPeer *peer = static_cast(addr); michael@0: michael@0: peer->SendPacket(static_cast(buffer), length); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int receive_cb(struct socket* sock, union sctp_sockstore addr, michael@0: void *data, size_t datalen, michael@0: struct sctp_rcvinfo rcv, int flags, void *ulp_info) { michael@0: TransportTestPeer *me = static_cast( michael@0: addr.sconn.sconn_addr); michael@0: MOZ_ASSERT(me); michael@0: michael@0: if (flags & MSG_NOTIFICATION) { michael@0: union sctp_notification *notif = michael@0: static_cast(data); michael@0: michael@0: me->Notification(notif, datalen); michael@0: return 0; michael@0: } michael@0: michael@0: me->received_ += datalen; michael@0: michael@0: std::cerr << "receive_cb: sock " << sock << " data " << data << "(" << datalen << ") total received bytes = " << me->received_ << std::endl; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: michael@0: private: michael@0: std::string name_; michael@0: bool connected_; michael@0: size_t sent_; michael@0: size_t received_; michael@0: mozilla::RefPtr flow_; michael@0: TransportLayerLoopback *loopback_; michael@0: michael@0: struct sockaddr_conn local_addr_; michael@0: struct sockaddr_conn remote_addr_; michael@0: struct socket *sctp_; michael@0: nsCOMPtr timer_; michael@0: nsRefPtr periodic_; michael@0: }; michael@0: michael@0: michael@0: // Implemented here because it calls a method of TransportTestPeer michael@0: NS_IMETHODIMP SendPeriodic::Notify(nsITimer *timer) { michael@0: peer_->SendOne(); michael@0: --to_send_; michael@0: if (!to_send_) { michael@0: timer->Cancel(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: class TransportTest : public ::testing::Test { michael@0: public: michael@0: TransportTest() { michael@0: } michael@0: michael@0: ~TransportTest() { michael@0: if (p1_) michael@0: p1_->Disconnect(); michael@0: if (p2_) michael@0: p2_->Disconnect(); michael@0: delete p1_; michael@0: delete p2_; michael@0: } michael@0: michael@0: static void debug_printf(const char *format, ...) { michael@0: va_list ap; michael@0: michael@0: va_start(ap, format); michael@0: vprintf(format, ap); michael@0: va_end(ap); michael@0: } michael@0: michael@0: michael@0: static void SetUpTestCase() { michael@0: if (sctp_logging) { michael@0: usrsctp_init(0, &TransportTestPeer::conn_output, debug_printf); michael@0: usrsctp_sysctl_set_sctp_debug_on(0xffffffff); michael@0: } else { michael@0: usrsctp_init(0, &TransportTestPeer::conn_output, nullptr); michael@0: } michael@0: } michael@0: michael@0: void SetUp() { michael@0: } michael@0: michael@0: void ConnectSocket(int p1port = 0, int p2port = 0) { michael@0: if (!p1port) michael@0: p1port = port_number++; michael@0: if (!p2port) michael@0: p2port = port_number++; michael@0: michael@0: p1_ = new TransportTestPeer("P1", p1port, p2port); michael@0: p2_ = new TransportTestPeer("P2", p2port, p1port); michael@0: michael@0: p1_->ConnectSocket(p2_); michael@0: p2_->ConnectSocket(p1_); michael@0: ASSERT_TRUE_WAIT(p1_->connected(), 2000); michael@0: ASSERT_TRUE_WAIT(p2_->connected(), 2000); michael@0: } michael@0: michael@0: void TestTransfer(int expected = 1) { michael@0: std::cerr << "Starting trasnsfer test" << std::endl; michael@0: p1_->StartTransfer(expected); michael@0: ASSERT_TRUE_WAIT(p1_->sent() == expected, 10000); michael@0: ASSERT_TRUE_WAIT(p2_->received() == (expected * 100), 10000); michael@0: std::cerr << "P2 received " << p2_->received() << std::endl; michael@0: } michael@0: michael@0: protected: michael@0: TransportTestPeer *p1_; michael@0: TransportTestPeer *p2_; michael@0: }; michael@0: michael@0: TEST_F(TransportTest, TestConnect) { michael@0: ConnectSocket(); michael@0: } michael@0: michael@0: TEST_F(TransportTest, TestConnectSymmetricalPorts) { michael@0: ConnectSocket(5002,5002); michael@0: } michael@0: michael@0: TEST_F(TransportTest, TestTransfer) { michael@0: ConnectSocket(); michael@0: TestTransfer(50); michael@0: } michael@0: michael@0: michael@0: } // end namespace michael@0: michael@0: int main(int argc, char **argv) michael@0: { michael@0: test_utils = new MtransportTestUtils(); michael@0: // Start the tests michael@0: ::testing::InitGoogleTest(&argc, argv); michael@0: michael@0: for(int i=0; i