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: michael@0: #include "nspr.h" michael@0: #include "nss.h" michael@0: #include "ssl.h" michael@0: michael@0: #include "mozilla/Scoped.h" michael@0: michael@0: extern "C" { michael@0: #include "nr_api.h" michael@0: #include "nr_socket.h" michael@0: #include "nr_socket_buffered_stun.h" michael@0: #include "transport_addr.h" michael@0: #include "stun.h" michael@0: } michael@0: michael@0: #include "databuffer.h" michael@0: #include "mtransport_test_utils.h" michael@0: michael@0: #include "nr_socket_prsock.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: using namespace mozilla; michael@0: MtransportTestUtils *test_utils; michael@0: michael@0: static uint8_t kStunMessage[] = { michael@0: 0x00, 0x01, 0x00, 0x08, 0x21, 0x12, 0xa4, 0x42, michael@0: 0x9b, 0x90, 0xbe, 0x2c, 0xae, 0x1a, 0x0c, 0xa8, michael@0: 0xa0, 0xd6, 0x8b, 0x08, 0x80, 0x28, 0x00, 0x04, michael@0: 0xdb, 0x35, 0x5f, 0xaa michael@0: }; michael@0: static size_t kStunMessageLen = sizeof(kStunMessage); michael@0: michael@0: class DummySocket : public NrSocketBase { michael@0: public: michael@0: DummySocket() michael@0: : writable_(UINT_MAX), michael@0: write_buffer_(nullptr), michael@0: readable_(UINT_MAX), michael@0: read_buffer_(nullptr), michael@0: cb_(nullptr), michael@0: cb_arg_(nullptr), michael@0: self_(nullptr) {} michael@0: michael@0: // the nr_socket APIs michael@0: virtual int create(nr_transport_addr *addr) { michael@0: return 0; michael@0: } michael@0: michael@0: virtual int sendto(const void *msg, size_t len, michael@0: int flags, nr_transport_addr *to) { michael@0: MOZ_CRASH(); michael@0: return 0; michael@0: } michael@0: michael@0: virtual int recvfrom(void * buf, size_t maxlen, michael@0: size_t *len, int flags, michael@0: nr_transport_addr *from) { michael@0: MOZ_CRASH(); michael@0: return 0; michael@0: } michael@0: michael@0: virtual int getaddr(nr_transport_addr *addrp) { michael@0: MOZ_CRASH(); michael@0: return 0; michael@0: } michael@0: michael@0: virtual void close() { michael@0: } michael@0: michael@0: virtual int connect(nr_transport_addr *addr) { michael@0: return 0; michael@0: } michael@0: michael@0: virtual int write(const void *msg, size_t len, size_t *written) { michael@0: // Shouldn't be anything here. michael@0: EXPECT_EQ(nullptr, write_buffer_.get()); michael@0: michael@0: size_t to_write = std::min(len, writable_); michael@0: michael@0: if (to_write) { michael@0: write_buffer_ = new DataBuffer( michael@0: static_cast(msg), to_write); michael@0: *written = to_write; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: virtual int read(void* buf, size_t maxlen, size_t *len) { michael@0: if (!read_buffer_.get()) { michael@0: return R_WOULDBLOCK; michael@0: } michael@0: michael@0: size_t to_read = std::min(read_buffer_->len(), michael@0: std::min(maxlen, readable_)); michael@0: michael@0: memcpy(buf, read_buffer_->data(), to_read); michael@0: *len = to_read; michael@0: michael@0: if (to_read < read_buffer_->len()) { michael@0: read_buffer_ = new DataBuffer(read_buffer_->data() + to_read, michael@0: read_buffer_->len() - to_read); michael@0: } else { michael@0: read_buffer_ = nullptr; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: // Implementations of the async_event APIs. michael@0: // These are no-ops because we handle scheduling manually michael@0: // for test purposes. michael@0: virtual int async_wait(int how, NR_async_cb cb, void *cb_arg, michael@0: char *function, int line) { michael@0: EXPECT_EQ(nullptr, cb_); michael@0: cb_ = cb; michael@0: cb_arg_ = cb_arg; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: virtual int cancel(int how) { michael@0: cb_ = nullptr; michael@0: cb_arg_ = nullptr; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: michael@0: // Read/Manipulate the current state. michael@0: void CheckWriteBuffer(uint8_t *data, size_t len) { michael@0: if (!len) { michael@0: EXPECT_EQ(nullptr, write_buffer_.get()); michael@0: } else { michael@0: EXPECT_NE(nullptr, write_buffer_.get()); michael@0: ASSERT_EQ(len, write_buffer_->len()); michael@0: ASSERT_EQ(0, memcmp(data, write_buffer_->data(), len)); michael@0: } michael@0: } michael@0: michael@0: void ClearWriteBuffer() { michael@0: write_buffer_ = nullptr; michael@0: } michael@0: michael@0: void SetWritable(size_t val) { michael@0: writable_ = val; michael@0: } michael@0: michael@0: void FireWritableCb() { michael@0: NR_async_cb cb = cb_; michael@0: void *cb_arg = cb_arg_; michael@0: michael@0: cb_ = nullptr; michael@0: cb_arg_ = nullptr; michael@0: michael@0: cb(this, NR_ASYNC_WAIT_WRITE, cb_arg); michael@0: } michael@0: michael@0: void SetReadBuffer(uint8_t *data, size_t len) { michael@0: EXPECT_EQ(nullptr, write_buffer_.get()); michael@0: read_buffer_ = new DataBuffer(data, len); michael@0: } michael@0: michael@0: void ClearReadBuffer() { michael@0: read_buffer_ = nullptr; michael@0: } michael@0: michael@0: void SetReadable(size_t val) { michael@0: readable_ = val; michael@0: } michael@0: michael@0: nr_socket *get_nr_socket() { michael@0: if (!self_) { michael@0: int r = nr_socket_create_int(this, vtbl(), &self_); michael@0: AddRef(); michael@0: if (r) michael@0: return nullptr; michael@0: } michael@0: michael@0: return self_; michael@0: } michael@0: michael@0: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DummySocket); michael@0: michael@0: private: michael@0: DISALLOW_COPY_ASSIGN(DummySocket); michael@0: michael@0: size_t writable_; // Amount we allow someone to write. michael@0: ScopedDeletePtr write_buffer_; michael@0: size_t readable_; // Amount we allow someone to read. michael@0: ScopedDeletePtr read_buffer_; michael@0: michael@0: NR_async_cb cb_; michael@0: void *cb_arg_; michael@0: nr_socket *self_; michael@0: }; michael@0: michael@0: class BufferedStunSocketTest : public ::testing::Test { michael@0: public: michael@0: BufferedStunSocketTest() michael@0: : dummy_(nullptr), michael@0: test_socket_(nullptr) {} michael@0: michael@0: ~BufferedStunSocketTest() { michael@0: nr_socket_destroy(&test_socket_); michael@0: } michael@0: michael@0: void SetUp() { michael@0: ScopedDeletePtr dummy(new DummySocket()); michael@0: michael@0: int r = nr_socket_buffered_stun_create( michael@0: dummy->get_nr_socket(), michael@0: kStunMessageLen, michael@0: &test_socket_); michael@0: ASSERT_EQ(0, r); michael@0: dummy_ = dummy.forget(); // Now owned by test_socket_. michael@0: michael@0: r = nr_ip4_str_port_to_transport_addr( michael@0: (char *)"192.0.2.133", 3333, IPPROTO_TCP, &remote_addr_); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: r = nr_socket_connect(test_socket_, michael@0: &remote_addr_); michael@0: ASSERT_EQ(0, r); michael@0: } michael@0: michael@0: nr_socket *socket() { return test_socket_; } michael@0: michael@0: protected: michael@0: DummySocket *dummy_; michael@0: nr_socket *test_socket_; michael@0: nr_transport_addr remote_addr_; michael@0: }; michael@0: michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestCreate) { michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestSendTo) { michael@0: int r = nr_socket_sendto(test_socket_, michael@0: kStunMessage, michael@0: kStunMessageLen, michael@0: 0, &remote_addr_); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: dummy_->CheckWriteBuffer(kStunMessage, kStunMessageLen); michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestSendToBuffered) { michael@0: dummy_->SetWritable(0); michael@0: michael@0: int r = nr_socket_sendto(test_socket_, michael@0: kStunMessage, michael@0: kStunMessageLen, michael@0: 0, &remote_addr_); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: dummy_->CheckWriteBuffer(nullptr, 0); michael@0: michael@0: dummy_->SetWritable(kStunMessageLen); michael@0: dummy_->FireWritableCb(); michael@0: dummy_->CheckWriteBuffer(kStunMessage, kStunMessageLen); michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestSendFullThenDrain) { michael@0: dummy_->SetWritable(0); michael@0: michael@0: for (;;) { michael@0: int r = nr_socket_sendto(test_socket_, michael@0: kStunMessage, michael@0: kStunMessageLen, michael@0: 0, &remote_addr_); michael@0: if (r == R_WOULDBLOCK) michael@0: break; michael@0: michael@0: ASSERT_EQ(0, r); michael@0: } michael@0: michael@0: // Nothing was written. michael@0: dummy_->CheckWriteBuffer(nullptr, 0); michael@0: michael@0: // Now flush. michael@0: dummy_->SetWritable(kStunMessageLen); michael@0: dummy_->FireWritableCb(); michael@0: dummy_->ClearWriteBuffer(); michael@0: michael@0: // Verify we can write something. michael@0: int r = nr_socket_sendto(test_socket_, michael@0: kStunMessage, michael@0: kStunMessageLen, michael@0: 0, &remote_addr_); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: // And that it appears. michael@0: dummy_->CheckWriteBuffer(kStunMessage, kStunMessageLen); michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestSendToPartialBuffered) { michael@0: dummy_->SetWritable(10); michael@0: michael@0: int r = nr_socket_sendto(test_socket_, michael@0: kStunMessage, michael@0: kStunMessageLen, michael@0: 0, &remote_addr_); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: dummy_->CheckWriteBuffer(kStunMessage, 10); michael@0: dummy_->ClearWriteBuffer(); michael@0: michael@0: dummy_->SetWritable(kStunMessageLen); michael@0: dummy_->FireWritableCb(); michael@0: dummy_->CheckWriteBuffer(kStunMessage + 10, kStunMessageLen - 10); michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestSendToReject) { michael@0: dummy_->SetWritable(0); michael@0: michael@0: int r = nr_socket_sendto(test_socket_, michael@0: kStunMessage, michael@0: kStunMessageLen, michael@0: 0, &remote_addr_); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: dummy_->CheckWriteBuffer(nullptr, 0); michael@0: michael@0: r = nr_socket_sendto(test_socket_, michael@0: kStunMessage, michael@0: kStunMessageLen, michael@0: 0, &remote_addr_); michael@0: ASSERT_EQ(R_WOULDBLOCK, r); michael@0: michael@0: dummy_->CheckWriteBuffer(nullptr, 0); michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestSendToWrongAddr) { michael@0: nr_transport_addr addr; michael@0: michael@0: int r = nr_ip4_str_port_to_transport_addr( michael@0: (char *)"192.0.2.134", 3333, IPPROTO_TCP, &addr); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: r = nr_socket_sendto(test_socket_, michael@0: kStunMessage, michael@0: kStunMessageLen, michael@0: 0, &addr); michael@0: ASSERT_EQ(R_BAD_DATA, r); michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestReceiveRecvFrom) { michael@0: dummy_->SetReadBuffer(kStunMessage, kStunMessageLen); michael@0: michael@0: unsigned char tmp[2048]; michael@0: size_t len; michael@0: nr_transport_addr addr; michael@0: michael@0: int r = nr_socket_recvfrom(test_socket_, michael@0: tmp, sizeof(tmp), &len, 0, michael@0: &addr); michael@0: ASSERT_EQ(0, r); michael@0: ASSERT_EQ(kStunMessageLen, len); michael@0: ASSERT_EQ(0, memcmp(kStunMessage, tmp, kStunMessageLen)); michael@0: ASSERT_EQ(0, nr_transport_addr_cmp(&addr, &remote_addr_, michael@0: NR_TRANSPORT_ADDR_CMP_MODE_ALL)); michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestReceiveRecvFromPartial) { michael@0: dummy_->SetReadBuffer(kStunMessage, 15); michael@0: michael@0: unsigned char tmp[2048]; michael@0: size_t len; michael@0: nr_transport_addr addr; michael@0: michael@0: int r = nr_socket_recvfrom(test_socket_, michael@0: tmp, sizeof(tmp), &len, 0, michael@0: &addr); michael@0: ASSERT_EQ(R_WOULDBLOCK, r); michael@0: michael@0: michael@0: dummy_->SetReadBuffer(kStunMessage + 15, kStunMessageLen - 15); michael@0: michael@0: r = nr_socket_recvfrom(test_socket_, michael@0: tmp, sizeof(tmp), &len, 0, michael@0: &addr); michael@0: ASSERT_EQ(0, r); michael@0: ASSERT_EQ(kStunMessageLen, len); michael@0: ASSERT_EQ(0, memcmp(kStunMessage, tmp, kStunMessageLen)); michael@0: ASSERT_EQ(0, nr_transport_addr_cmp(&addr, &remote_addr_, michael@0: NR_TRANSPORT_ADDR_CMP_MODE_ALL)); michael@0: michael@0: r = nr_socket_recvfrom(test_socket_, michael@0: tmp, sizeof(tmp), &len, 0, michael@0: &addr); michael@0: ASSERT_EQ(R_WOULDBLOCK, r); michael@0: } michael@0: michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestReceiveRecvFromGarbage) { michael@0: uint8_t garbage[50]; michael@0: memset(garbage, 0xff, sizeof(garbage)); michael@0: michael@0: dummy_->SetReadBuffer(garbage, sizeof(garbage)); michael@0: michael@0: unsigned char tmp[2048]; michael@0: size_t len; michael@0: nr_transport_addr addr; michael@0: int r = nr_socket_recvfrom(test_socket_, michael@0: tmp, sizeof(tmp), &len, 0, michael@0: &addr); michael@0: ASSERT_EQ(R_BAD_DATA, r); michael@0: michael@0: r = nr_socket_recvfrom(test_socket_, michael@0: tmp, sizeof(tmp), &len, 0, michael@0: &addr); michael@0: ASSERT_EQ(R_FAILED, r); michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestReceiveRecvFromTooShort) { michael@0: dummy_->SetReadBuffer(kStunMessage, kStunMessageLen); michael@0: michael@0: unsigned char tmp[2048]; michael@0: size_t len; michael@0: nr_transport_addr addr; michael@0: michael@0: int r = nr_socket_recvfrom(test_socket_, michael@0: tmp, kStunMessageLen - 1, &len, 0, michael@0: &addr); michael@0: ASSERT_EQ(R_BAD_ARGS, r); michael@0: } michael@0: michael@0: TEST_F(BufferedStunSocketTest, TestReceiveRecvFromReallyLong) { michael@0: uint8_t garbage[4096]; michael@0: memset(garbage, 0xff, sizeof(garbage)); michael@0: memcpy(garbage, kStunMessage, kStunMessageLen); michael@0: nr_stun_message_header *hdr = reinterpret_cast michael@0: (garbage); michael@0: hdr->length = htons(3000); michael@0: michael@0: dummy_->SetReadBuffer(garbage, sizeof(garbage)); michael@0: michael@0: unsigned char tmp[4096]; michael@0: size_t len; michael@0: nr_transport_addr addr; michael@0: michael@0: int r = nr_socket_recvfrom(test_socket_, michael@0: tmp, kStunMessageLen - 1, &len, 0, michael@0: &addr); michael@0: ASSERT_EQ(R_BAD_DATA, r); michael@0: } michael@0: michael@0: michael@0: michael@0: int main(int argc, char **argv) michael@0: { michael@0: test_utils = new MtransportTestUtils(); michael@0: NSS_NoDB_Init(nullptr); michael@0: NSS_SetDomesticPolicy(); michael@0: michael@0: // Start the tests michael@0: ::testing::InitGoogleTest(&argc, argv); michael@0: michael@0: int rv = RUN_ALL_TESTS(); michael@0: michael@0: delete test_utils; michael@0: return rv; michael@0: }