Adds a modified copy of talk/base to webrtc/base. It is the first step in
migrating talk/base to webrtc/base.
BUG=N/A
R=niklas.enbom@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/17479005
git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@6129 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/base/macasyncsocket.cc b/base/macasyncsocket.cc
new file mode 100644
index 0000000..ee982ff
--- /dev/null
+++ b/base/macasyncsocket.cc
@@ -0,0 +1,477 @@
+/*
+ * 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.IsUnresolved()) {
+ 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* 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