| /* |
| * Copyright 2010 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. |
| */ |
| // |
| // MacAsyncSocket is a kind of AsyncSocket. It does not support the SOCK_DGRAM |
| // type (yet). It works asynchronously, which means that users of this socket |
| // should connect to the various events declared in asyncsocket.h to receive |
| // notifications about this socket. It uses CFSockets for signals, but prefers |
| // the basic bsd socket operations rather than their CFSocket wrappers when |
| // possible. |
| |
| #include <CoreFoundation/CoreFoundation.h> |
| #include <fcntl.h> |
| |
| #include "webrtc/base/macasyncsocket.h" |
| |
| #include "webrtc/base/logging.h" |
| #include "webrtc/base/macsocketserver.h" |
| |
| namespace rtc { |
| |
| static const int kCallbackFlags = kCFSocketReadCallBack | |
| kCFSocketConnectCallBack | |
| kCFSocketWriteCallBack; |
| |
| MacAsyncSocket::MacAsyncSocket(MacBaseSocketServer* ss, int family) |
| : ss_(ss), |
| socket_(NULL), |
| native_socket_(INVALID_SOCKET), |
| source_(NULL), |
| current_callbacks_(0), |
| disabled_(false), |
| error_(0), |
| state_(CS_CLOSED), |
| resolver_(NULL) { |
| Initialize(family); |
| } |
| |
| MacAsyncSocket::~MacAsyncSocket() { |
| Close(); |
| } |
| |
| // Returns the address to which the socket is bound. If the socket is not |
| // bound, then the any-address is returned. |
| SocketAddress MacAsyncSocket::GetLocalAddress() const { |
| SocketAddress address; |
| |
| // The CFSocket doesn't pick up on implicit binds from the connect call. |
| // Calling bind in before connect explicitly causes errors, so just query |
| // the underlying bsd socket. |
| sockaddr_storage addr; |
| socklen_t addrlen = sizeof(addr); |
| int result = ::getsockname(native_socket_, |
| reinterpret_cast<sockaddr*>(&addr), &addrlen); |
| if (result >= 0) { |
| SocketAddressFromSockAddrStorage(addr, &address); |
| } |
| return address; |
| } |
| |
| // Returns the address to which the socket is connected. If the socket is not |
| // connected, then the any-address is returned. |
| SocketAddress MacAsyncSocket::GetRemoteAddress() const { |
| SocketAddress address; |
| |
| // Use native_socket for consistency with GetLocalAddress. |
| sockaddr_storage addr; |
| socklen_t addrlen = sizeof(addr); |
| int result = ::getpeername(native_socket_, |
| reinterpret_cast<sockaddr*>(&addr), &addrlen); |
| if (result >= 0) { |
| SocketAddressFromSockAddrStorage(addr, &address); |
| } |
| return address; |
| } |
| |
| // Bind the socket to a local address. |
| int MacAsyncSocket::Bind(const SocketAddress& address) { |
| sockaddr_storage saddr = {0}; |
| size_t len = address.ToSockAddrStorage(&saddr); |
| int err = ::bind(native_socket_, reinterpret_cast<sockaddr*>(&saddr), len); |
| if (err == SOCKET_ERROR) error_ = errno; |
| return err; |
| } |
| |
| void MacAsyncSocket::OnResolveResult(SignalThread* thread) { |
| if (thread != resolver_) { |
| return; |
| } |
| int error = resolver_->GetError(); |
| if (error == 0) { |
| error = DoConnect(resolver_->address()); |
| } else { |
| Close(); |
| } |
| if (error) { |
| error_ = error; |
| SignalCloseEvent(this, error_); |
| } |
| } |
| |
| // Connect to a remote address. |
| int MacAsyncSocket::Connect(const SocketAddress& addr) { |
| // TODO(djw): Consolidate all the connect->resolve->doconnect implementations. |
| if (state_ != CS_CLOSED) { |
| SetError(EALREADY); |
| return SOCKET_ERROR; |
| } |
| if (addr.IsUnresolvedIP()) { |
| LOG(LS_VERBOSE) << "Resolving addr in MacAsyncSocket::Connect"; |
| resolver_ = new AsyncResolver(); |
| resolver_->SignalWorkDone.connect(this, |
| &MacAsyncSocket::OnResolveResult); |
| resolver_->Start(addr); |
| state_ = CS_CONNECTING; |
| return 0; |
| } |
| return DoConnect(addr); |
| } |
| |
| int MacAsyncSocket::DoConnect(const SocketAddress& addr) { |
| if (!valid()) { |
| Initialize(addr.family()); |
| if (!valid()) |
| return SOCKET_ERROR; |
| } |
| |
| sockaddr_storage saddr; |
| size_t len = addr.ToSockAddrStorage(&saddr); |
| int result = ::connect(native_socket_, reinterpret_cast<sockaddr*>(&saddr), |
| len); |
| |
| if (result != SOCKET_ERROR) { |
| state_ = CS_CONNECTED; |
| } else { |
| error_ = errno; |
| if (error_ == EINPROGRESS) { |
| state_ = CS_CONNECTING; |
| result = 0; |
| } |
| } |
| return result; |
| } |
| |
| // Send to the remote end we're connected to. |
| int MacAsyncSocket::Send(const void* buffer, size_t length) { |
| if (!valid()) { |
| return SOCKET_ERROR; |
| } |
| |
| int sent = ::send(native_socket_, buffer, length, 0); |
| |
| if (sent == SOCKET_ERROR) { |
| error_ = errno; |
| |
| if (IsBlocking()) { |
| // Reenable the writable callback (once), since we are flow controlled. |
| CFSocketEnableCallBacks(socket_, kCallbackFlags); |
| current_callbacks_ = kCallbackFlags; |
| } |
| } |
| return sent; |
| } |
| |
| // Send to the given address. We may or may not be connected to anyone. |
| int MacAsyncSocket::SendTo(const void* buffer, size_t length, |
| const SocketAddress& address) { |
| if (!valid()) { |
| return SOCKET_ERROR; |
| } |
| |
| sockaddr_storage saddr; |
| size_t len = address.ToSockAddrStorage(&saddr); |
| int sent = ::sendto(native_socket_, buffer, length, 0, |
| reinterpret_cast<sockaddr*>(&saddr), len); |
| |
| if (sent == SOCKET_ERROR) { |
| error_ = errno; |
| } |
| |
| return sent; |
| } |
| |
| // Read data received from the remote end we're connected to. |
| int MacAsyncSocket::Recv(void* buffer, size_t length) { |
| int received = ::recv(native_socket_, reinterpret_cast<char*>(buffer), |
| length, 0); |
| if (received == SOCKET_ERROR) error_ = errno; |
| |
| // Recv should only be called when there is data to read |
| ASSERT((received != 0) || (length == 0)); |
| return received; |
| } |
| |
| // Read data received from any remote party |
| int MacAsyncSocket::RecvFrom(void* buffer, size_t length, |
| SocketAddress* out_addr) { |
| sockaddr_storage saddr; |
| socklen_t addr_len = sizeof(saddr); |
| int received = ::recvfrom(native_socket_, reinterpret_cast<char*>(buffer), |
| length, 0, reinterpret_cast<sockaddr*>(&saddr), |
| &addr_len); |
| if (received >= 0 && out_addr != NULL) { |
| SocketAddressFromSockAddrStorage(saddr, out_addr); |
| } else if (received == SOCKET_ERROR) { |
| error_ = errno; |
| } |
| return received; |
| } |
| |
| int MacAsyncSocket::Listen(int backlog) { |
| if (!valid()) { |
| return SOCKET_ERROR; |
| } |
| |
| int res = ::listen(native_socket_, backlog); |
| if (res != SOCKET_ERROR) |
| state_ = CS_CONNECTING; |
| else |
| error_ = errno; |
| |
| return res; |
| } |
| |
| MacAsyncSocket* MacAsyncSocket::Accept(SocketAddress* out_addr) { |
| sockaddr_storage saddr; |
| socklen_t addr_len = sizeof(saddr); |
| |
| int socket_fd = ::accept(native_socket_, reinterpret_cast<sockaddr*>(&saddr), |
| &addr_len); |
| if (socket_fd == INVALID_SOCKET) { |
| error_ = errno; |
| return NULL; |
| } |
| |
| MacAsyncSocket* s = new MacAsyncSocket(ss_, saddr.ss_family, socket_fd); |
| if (s && s->valid()) { |
| s->state_ = CS_CONNECTED; |
| if (out_addr) |
| SocketAddressFromSockAddrStorage(saddr, out_addr); |
| } else { |
| delete s; |
| s = NULL; |
| } |
| return s; |
| } |
| |
| int MacAsyncSocket::Close() { |
| if (source_ != NULL) { |
| CFRunLoopSourceInvalidate(source_); |
| CFRelease(source_); |
| if (ss_) ss_->UnregisterSocket(this); |
| source_ = NULL; |
| } |
| |
| if (socket_ != NULL) { |
| CFSocketInvalidate(socket_); |
| CFRelease(socket_); |
| socket_ = NULL; |
| } |
| |
| if (resolver_) { |
| resolver_->Destroy(false); |
| resolver_ = NULL; |
| } |
| |
| native_socket_ = INVALID_SOCKET; // invalidates the socket |
| error_ = 0; |
| state_ = CS_CLOSED; |
| return 0; |
| } |
| |
| int MacAsyncSocket::EstimateMTU(uint16_t* mtu) { |
| ASSERT(false && "NYI"); |
| return -1; |
| } |
| |
| int MacAsyncSocket::GetError() const { |
| return error_; |
| } |
| |
| void MacAsyncSocket::SetError(int error) { |
| error_ = error; |
| } |
| |
| Socket::ConnState MacAsyncSocket::GetState() const { |
| return state_; |
| } |
| |
| int MacAsyncSocket::GetOption(Option opt, int* value) { |
| ASSERT(false && "NYI"); |
| return -1; |
| } |
| |
| int MacAsyncSocket::SetOption(Option opt, int value) { |
| ASSERT(false && "NYI"); |
| return -1; |
| } |
| |
| void MacAsyncSocket::EnableCallbacks() { |
| if (valid()) { |
| disabled_ = false; |
| CFSocketEnableCallBacks(socket_, current_callbacks_); |
| } |
| } |
| |
| void MacAsyncSocket::DisableCallbacks() { |
| if (valid()) { |
| disabled_ = true; |
| CFSocketDisableCallBacks(socket_, kCallbackFlags); |
| } |
| } |
| |
| MacAsyncSocket::MacAsyncSocket(MacBaseSocketServer* ss, int family, |
| int native_socket) |
| : ss_(ss), |
| socket_(NULL), |
| native_socket_(native_socket), |
| source_(NULL), |
| current_callbacks_(0), |
| disabled_(false), |
| error_(0), |
| state_(CS_CLOSED), |
| resolver_(NULL) { |
| Initialize(family); |
| } |
| |
| // Create a new socket, wrapping the native socket if provided or creating one |
| // otherwise. In case of any failure, consume the native socket. We assume the |
| // wrapped socket is in the closed state. If this is not the case you must |
| // update the state_ field for this socket yourself. |
| void MacAsyncSocket::Initialize(int family) { |
| CFSocketContext ctx = { 0 }; |
| ctx.info = this; |
| |
| // First create the CFSocket |
| CFSocketRef cf_socket = NULL; |
| bool res = false; |
| if (native_socket_ == INVALID_SOCKET) { |
| cf_socket = CFSocketCreate(kCFAllocatorDefault, |
| family, SOCK_STREAM, IPPROTO_TCP, |
| kCallbackFlags, MacAsyncSocketCallBack, &ctx); |
| } else { |
| cf_socket = CFSocketCreateWithNative(kCFAllocatorDefault, |
| native_socket_, kCallbackFlags, |
| MacAsyncSocketCallBack, &ctx); |
| } |
| |
| if (cf_socket) { |
| res = true; |
| socket_ = cf_socket; |
| native_socket_ = CFSocketGetNative(cf_socket); |
| current_callbacks_ = kCallbackFlags; |
| } |
| |
| if (res) { |
| // Make the underlying socket asynchronous |
| res = (-1 != ::fcntl(native_socket_, F_SETFL, |
| ::fcntl(native_socket_, F_GETFL, 0) | O_NONBLOCK)); |
| } |
| |
| if (res) { |
| // Add this socket to the run loop, at priority 1 so that it will be |
| // queued behind any pending signals. |
| source_ = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 1); |
| res = (source_ != NULL); |
| if (!res) errno = EINVAL; |
| } |
| |
| if (res) { |
| if (ss_) ss_->RegisterSocket(this); |
| CFRunLoopAddSource(CFRunLoopGetCurrent(), source_, kCFRunLoopCommonModes); |
| } |
| |
| if (!res) { |
| int error = errno; |
| Close(); // Clears error_. |
| error_ = error; |
| } |
| } |
| |
| // Call CFRelease on the result when done using it |
| CFDataRef MacAsyncSocket::CopyCFAddress(const SocketAddress& address) { |
| sockaddr_storage saddr; |
| size_t len = address.ToSockAddrStorage(&saddr); |
| |
| const UInt8* bytes = reinterpret_cast<UInt8*>(&saddr); |
| |
| CFDataRef cf_address = CFDataCreate(kCFAllocatorDefault, |
| bytes, len); |
| |
| ASSERT(cf_address != NULL); |
| return cf_address; |
| } |
| |
| void MacAsyncSocket::MacAsyncSocketCallBack(CFSocketRef s, |
| CFSocketCallBackType callbackType, |
| CFDataRef address, |
| const void* data, |
| void* info) { |
| MacAsyncSocket* this_socket = |
| reinterpret_cast<MacAsyncSocket*>(info); |
| ASSERT(this_socket != NULL && this_socket->socket_ == s); |
| |
| // Don't signal any socket messages if the socketserver is not listening on |
| // them. When we are reenabled they will be requeued and will fire again. |
| if (this_socket->disabled_) |
| return; |
| |
| switch (callbackType) { |
| case kCFSocketReadCallBack: |
| // This callback is invoked in one of 3 situations: |
| // 1. A new connection is waiting to be accepted. |
| // 2. The remote end closed the connection (a recv will return 0). |
| // 3. Data is available to read. |
| // 4. The connection closed unhappily (recv will return -1). |
| if (this_socket->state_ == CS_CONNECTING) { |
| // Case 1. |
| this_socket->SignalReadEvent(this_socket); |
| } else { |
| char ch, amt; |
| amt = ::recv(this_socket->native_socket_, &ch, 1, MSG_PEEK); |
| if (amt == 0) { |
| // Case 2. |
| this_socket->state_ = CS_CLOSED; |
| |
| // Disable additional callbacks or we will signal close twice. |
| CFSocketDisableCallBacks(this_socket->socket_, kCFSocketReadCallBack); |
| this_socket->current_callbacks_ &= ~kCFSocketReadCallBack; |
| this_socket->SignalCloseEvent(this_socket, 0); |
| } else if (amt > 0) { |
| // Case 3. |
| this_socket->SignalReadEvent(this_socket); |
| } else { |
| // Case 4. |
| int error = errno; |
| if (error == EAGAIN) { |
| // Observed in practice. Let's hope it's a spurious or out of date |
| // signal, since we just eat it. |
| } else { |
| this_socket->error_ = error; |
| this_socket->SignalCloseEvent(this_socket, error); |
| } |
| } |
| } |
| break; |
| |
| case kCFSocketConnectCallBack: |
| if (data != NULL) { |
| // An error occured in the background while connecting |
| this_socket->error_ = errno; |
| this_socket->state_ = CS_CLOSED; |
| this_socket->SignalCloseEvent(this_socket, this_socket->error_); |
| } else { |
| this_socket->state_ = CS_CONNECTED; |
| this_socket->SignalConnectEvent(this_socket); |
| } |
| break; |
| |
| case kCFSocketWriteCallBack: |
| // Update our callback tracking. Write doesn't reenable, so it's off now. |
| this_socket->current_callbacks_ &= ~kCFSocketWriteCallBack; |
| this_socket->SignalWriteEvent(this_socket); |
| break; |
| |
| default: |
| ASSERT(false && "Invalid callback type for socket"); |
| } |
| } |
| |
| } // namespace rtc |