| /* |
| * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "test/network/fake_network_socket_server.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/algorithm/container.h" |
| #include "api/scoped_refptr.h" |
| #include "api/task_queue/pending_task_safety_flag.h" |
| #include "api/task_queue/to_queued_task.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/thread.h" |
| |
| namespace webrtc { |
| namespace test { |
| namespace { |
| std::string ToString(const rtc::SocketAddress& addr) { |
| return addr.HostAsURIString() + ":" + std::to_string(addr.port()); |
| } |
| |
| } // namespace |
| |
| // Represents a socket, which will operate with emulated network. |
| class FakeNetworkSocket : public rtc::Socket, |
| public EmulatedNetworkReceiverInterface { |
| public: |
| explicit FakeNetworkSocket(FakeNetworkSocketServer* scoket_manager, |
| rtc::Thread* thread); |
| ~FakeNetworkSocket() override; |
| |
| // Will be invoked by EmulatedEndpoint to deliver packets into this socket. |
| void OnPacketReceived(EmulatedIpPacket packet) override; |
| |
| // rtc::Socket methods: |
| rtc::SocketAddress GetLocalAddress() const override; |
| rtc::SocketAddress GetRemoteAddress() const override; |
| int Bind(const rtc::SocketAddress& addr) override; |
| int Connect(const rtc::SocketAddress& addr) override; |
| int Close() override; |
| int Send(const void* pv, size_t cb) override; |
| int SendTo(const void* pv, |
| size_t cb, |
| const rtc::SocketAddress& addr) override; |
| int Recv(void* pv, size_t cb, int64_t* timestamp) override; |
| int RecvFrom(void* pv, |
| size_t cb, |
| rtc::SocketAddress* paddr, |
| int64_t* timestamp) override; |
| int Listen(int backlog) override; |
| rtc::Socket* Accept(rtc::SocketAddress* paddr) override; |
| int GetError() const override; |
| void SetError(int error) override; |
| ConnState GetState() const override; |
| int GetOption(Option opt, int* value) override; |
| int SetOption(Option opt, int value) override; |
| |
| private: |
| FakeNetworkSocketServer* const socket_server_; |
| rtc::Thread* const thread_; |
| EmulatedEndpointImpl* endpoint_ RTC_GUARDED_BY(&thread_); |
| rtc::SocketAddress local_addr_ RTC_GUARDED_BY(&thread_); |
| rtc::SocketAddress remote_addr_ RTC_GUARDED_BY(&thread_); |
| ConnState state_ RTC_GUARDED_BY(&thread_); |
| int error_ RTC_GUARDED_BY(&thread_); |
| std::map<Option, int> options_map_ RTC_GUARDED_BY(&thread_); |
| |
| absl::optional<EmulatedIpPacket> pending_ RTC_GUARDED_BY(thread_); |
| rtc::scoped_refptr<PendingTaskSafetyFlag> alive_; |
| }; |
| |
| FakeNetworkSocket::FakeNetworkSocket(FakeNetworkSocketServer* socket_server, |
| rtc::Thread* thread) |
| : socket_server_(socket_server), |
| thread_(thread), |
| state_(CS_CLOSED), |
| error_(0), |
| alive_(PendingTaskSafetyFlag::Create()) {} |
| |
| FakeNetworkSocket::~FakeNetworkSocket() { |
| // Abandon all pending packets. |
| alive_->SetNotAlive(); |
| |
| Close(); |
| socket_server_->Unregister(this); |
| } |
| |
| void FakeNetworkSocket::OnPacketReceived(EmulatedIpPacket packet) { |
| auto task = [this, packet = std::move(packet)]() mutable { |
| RTC_DCHECK_RUN_ON(thread_); |
| if (!endpoint_->Enabled()) |
| return; |
| RTC_DCHECK(!pending_); |
| pending_ = std::move(packet); |
| // Note that we expect that this will trigger exactly one call to RecvFrom() |
| // where pending_packet will be read and reset. This call is done without |
| // any thread switch (see AsyncUDPSocket::OnReadEvent) so it's safe to |
| // assume that SignalReadEvent() will block until the packet has been read. |
| SignalReadEvent(this); |
| RTC_DCHECK(!pending_); |
| }; |
| thread_->PostTask(ToQueuedTask(alive_, std::move(task))); |
| socket_server_->WakeUp(); |
| } |
| |
| |
| rtc::SocketAddress FakeNetworkSocket::GetLocalAddress() const { |
| RTC_DCHECK_RUN_ON(thread_); |
| return local_addr_; |
| } |
| |
| rtc::SocketAddress FakeNetworkSocket::GetRemoteAddress() const { |
| RTC_DCHECK_RUN_ON(thread_); |
| return remote_addr_; |
| } |
| |
| int FakeNetworkSocket::Bind(const rtc::SocketAddress& addr) { |
| RTC_DCHECK_RUN_ON(thread_); |
| RTC_CHECK(local_addr_.IsNil()) |
| << "Socket already bound to address: " << ToString(local_addr_); |
| local_addr_ = addr; |
| endpoint_ = socket_server_->GetEndpointNode(local_addr_.ipaddr()); |
| if (!endpoint_) { |
| local_addr_.Clear(); |
| RTC_LOG(LS_INFO) << "No endpoint for address: " << ToString(addr); |
| error_ = EADDRNOTAVAIL; |
| return 2; |
| } |
| absl::optional<uint16_t> port = |
| endpoint_->BindReceiver(local_addr_.port(), this); |
| if (!port) { |
| local_addr_.Clear(); |
| RTC_LOG(LS_INFO) << "Cannot bind to in-use address: " << ToString(addr); |
| error_ = EADDRINUSE; |
| return 1; |
| } |
| local_addr_.SetPort(port.value()); |
| return 0; |
| } |
| |
| int FakeNetworkSocket::Connect(const rtc::SocketAddress& addr) { |
| RTC_DCHECK_RUN_ON(thread_); |
| RTC_CHECK(remote_addr_.IsNil()) |
| << "Socket already connected to address: " << ToString(remote_addr_); |
| RTC_CHECK(!local_addr_.IsNil()) |
| << "Socket have to be bind to some local address"; |
| remote_addr_ = addr; |
| state_ = CS_CONNECTED; |
| return 0; |
| } |
| |
| int FakeNetworkSocket::Send(const void* pv, size_t cb) { |
| RTC_DCHECK_RUN_ON(thread_); |
| RTC_CHECK(state_ == CS_CONNECTED) << "Socket cannot send: not connected"; |
| return SendTo(pv, cb, remote_addr_); |
| } |
| |
| int FakeNetworkSocket::SendTo(const void* pv, |
| size_t cb, |
| const rtc::SocketAddress& addr) { |
| RTC_DCHECK_RUN_ON(thread_); |
| RTC_CHECK(!local_addr_.IsNil()) |
| << "Socket have to be bind to some local address"; |
| if (!endpoint_->Enabled()) { |
| error_ = ENETDOWN; |
| return -1; |
| } |
| rtc::CopyOnWriteBuffer packet(static_cast<const uint8_t*>(pv), cb); |
| endpoint_->SendPacket(local_addr_, addr, packet); |
| return cb; |
| } |
| |
| int FakeNetworkSocket::Recv(void* pv, size_t cb, int64_t* timestamp) { |
| rtc::SocketAddress paddr; |
| return RecvFrom(pv, cb, &paddr, timestamp); |
| } |
| |
| // Reads 1 packet from internal queue. Reads up to `cb` bytes into `pv` |
| // and returns the length of received packet. |
| int FakeNetworkSocket::RecvFrom(void* pv, |
| size_t cb, |
| rtc::SocketAddress* paddr, |
| int64_t* timestamp) { |
| RTC_DCHECK_RUN_ON(thread_); |
| |
| if (timestamp) { |
| *timestamp = -1; |
| } |
| RTC_CHECK(pending_); |
| |
| *paddr = pending_->from; |
| size_t data_read = std::min(cb, pending_->size()); |
| memcpy(pv, pending_->cdata(), data_read); |
| *timestamp = pending_->arrival_time.us(); |
| |
| // According to RECV(2) Linux Man page |
| // real socket will discard data, that won't fit into provided buffer, |
| // but we won't to skip such error, so we will assert here. |
| RTC_CHECK(data_read == pending_->size()) |
| << "Too small buffer is provided for socket read. " |
| "Received data size: " |
| << pending_->size() << "; Provided buffer size: " << cb; |
| |
| pending_.reset(); |
| |
| // According to RECV(2) Linux Man page |
| // real socket will return message length, not data read. In our case it is |
| // actually the same value. |
| return static_cast<int>(data_read); |
| } |
| |
| int FakeNetworkSocket::Listen(int backlog) { |
| RTC_CHECK(false) << "Listen() isn't valid for SOCK_DGRAM"; |
| } |
| |
| rtc::Socket* FakeNetworkSocket::Accept(rtc::SocketAddress* /*paddr*/) { |
| RTC_CHECK(false) << "Accept() isn't valid for SOCK_DGRAM"; |
| } |
| |
| int FakeNetworkSocket::Close() { |
| RTC_DCHECK_RUN_ON(thread_); |
| state_ = CS_CLOSED; |
| if (!local_addr_.IsNil()) { |
| endpoint_->UnbindReceiver(local_addr_.port()); |
| } |
| local_addr_.Clear(); |
| remote_addr_.Clear(); |
| return 0; |
| } |
| |
| int FakeNetworkSocket::GetError() const { |
| RTC_DCHECK_RUN_ON(thread_); |
| return error_; |
| } |
| |
| void FakeNetworkSocket::SetError(int error) { |
| RTC_DCHECK_RUN_ON(thread_); |
| RTC_CHECK(error == 0); |
| error_ = error; |
| } |
| |
| rtc::Socket::ConnState FakeNetworkSocket::GetState() const { |
| RTC_DCHECK_RUN_ON(thread_); |
| return state_; |
| } |
| |
| int FakeNetworkSocket::GetOption(Option opt, int* value) { |
| RTC_DCHECK_RUN_ON(thread_); |
| auto it = options_map_.find(opt); |
| if (it == options_map_.end()) { |
| return -1; |
| } |
| *value = it->second; |
| return 0; |
| } |
| |
| int FakeNetworkSocket::SetOption(Option opt, int value) { |
| RTC_DCHECK_RUN_ON(thread_); |
| options_map_[opt] = value; |
| return 0; |
| } |
| |
| FakeNetworkSocketServer::FakeNetworkSocketServer( |
| EndpointsContainer* endpoints_container) |
| : endpoints_container_(endpoints_container), |
| wakeup_(/*manual_reset=*/false, /*initially_signaled=*/false) {} |
| FakeNetworkSocketServer::~FakeNetworkSocketServer() = default; |
| |
| EmulatedEndpointImpl* FakeNetworkSocketServer::GetEndpointNode( |
| const rtc::IPAddress& ip) { |
| return endpoints_container_->LookupByLocalAddress(ip); |
| } |
| |
| void FakeNetworkSocketServer::Unregister(FakeNetworkSocket* socket) { |
| MutexLock lock(&lock_); |
| sockets_.erase(absl::c_find(sockets_, socket)); |
| } |
| |
| rtc::Socket* FakeNetworkSocketServer::CreateSocket(int family, int type) { |
| RTC_DCHECK(family == AF_INET || family == AF_INET6); |
| // We support only UDP sockets for now. |
| RTC_DCHECK(type == SOCK_DGRAM) << "Only UDP sockets are supported"; |
| RTC_DCHECK(thread_) << "must be attached to thread before creating sockets"; |
| FakeNetworkSocket* out = new FakeNetworkSocket(this, thread_); |
| { |
| MutexLock lock(&lock_); |
| sockets_.push_back(out); |
| } |
| return out; |
| } |
| |
| void FakeNetworkSocketServer::SetMessageQueue(rtc::Thread* thread) { |
| thread_ = thread; |
| } |
| |
| // Always returns true (if return false, it won't be invoked again...) |
| bool FakeNetworkSocketServer::Wait(int cms, bool process_io) { |
| RTC_DCHECK(thread_ == rtc::Thread::Current()); |
| if (cms != 0) |
| wakeup_.Wait(cms); |
| return true; |
| } |
| |
| void FakeNetworkSocketServer::WakeUp() { |
| wakeup_.Set(); |
| } |
| |
| |
| } // namespace test |
| } // namespace webrtc |