Bug 1036977: Handle socket setup for |Connect| in BluetoothInterface (under bluetooth2/), r=btian

Bluedroid's internal socket setup transfers 2 messages and possibly
a file descriptor as the first data of a socket connection.

This patch moves the socket-setup code for the |Connect| call to
the implementation of BluetoothInterface. BluetoothSocket only
handles the socket setup of |Listen|, and general socket state.
This commit is contained in:
Thomas Zimmermann 2014-07-16 10:24:33 +02:00
parent aafa226fd6
commit 218eee1aee
4 changed files with 454 additions and 61 deletions

View File

@ -4,9 +4,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include "base/message_loop.h"
#include "BluetoothInterface.h"
#include "nsAutoPtr.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
BEGIN_BLUETOOTH_NAMESPACE
@ -69,6 +74,40 @@ private:
Arg1 mArg1;
};
template <typename Obj, typename Res,
typename Arg1, typename Arg2, typename Arg3>
class BluetoothInterfaceRunnable3 : public nsRunnable
{
public:
BluetoothInterfaceRunnable3(Obj* aObj,
Res (Obj::*aMethod)(Arg1, Arg2, Arg3),
const Arg1& aArg1, const Arg2& aArg2,
const Arg3& aArg3)
: mObj(aObj)
, mMethod(aMethod)
, mArg1(aArg1)
, mArg2(aArg2)
, mArg3(aArg3)
{
MOZ_ASSERT(mObj);
MOZ_ASSERT(mMethod);
}
NS_METHOD
Run() MOZ_OVERRIDE
{
((*mObj).*mMethod)(mArg1, mArg2, mArg3);
return NS_OK;
}
private:
nsRefPtr<Obj> mObj;
void (Obj::*mMethod)(Arg1, Arg2, Arg3);
Arg1 mArg1;
Arg2 mArg2;
Arg3 mArg3;
};
//
// Socket Interface
//
@ -88,6 +127,11 @@ typedef
BluetoothInterfaceRunnable1<BluetoothSocketResultHandler, void, int>
BluetoothSocketIntResultRunnable;
typedef
BluetoothInterfaceRunnable3<BluetoothSocketResultHandler,
void, int, const nsAString_internal&, int>
BluetoothSocketIntStringIntResultRunnable;
typedef
BluetoothInterfaceRunnable1<BluetoothSocketResultHandler, void, bt_status_t>
BluetoothSocketErrorRunnable;
@ -114,6 +158,31 @@ DispatchBluetoothSocketResult(BluetoothSocketResultHandler* aRes,
return rv;
}
static nsresult
DispatchBluetoothSocketResult(
BluetoothSocketResultHandler* aRes,
void (BluetoothSocketResultHandler::*aMethod)(int, const nsAString&, int),
int aArg1, const nsAString& aArg2, int aArg3, bt_status_t aStatus)
{
MOZ_ASSERT(aRes);
nsRunnable* runnable;
if (aStatus == BT_STATUS_SUCCESS) {
runnable = new BluetoothSocketIntStringIntResultRunnable(aRes, aMethod,
aArg1, aArg2,
aArg3);
} else {
runnable = new BluetoothSocketErrorRunnable(aRes,
&BluetoothSocketResultHandler::OnError, aStatus);
}
nsresult rv = NS_DispatchToMainThread(runnable);
if (NS_FAILED(rv)) {
BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
}
return rv;
}
void
BluetoothSocketInterface::Listen(btsock_type_t aType,
const char* aServiceName,
@ -131,6 +200,293 @@ BluetoothSocketInterface::Listen(btsock_type_t aType,
}
}
#define CMSGHDR_CONTAINS_FD(_cmsghdr) \
( ((_cmsghdr)->cmsg_level == SOL_SOCKET) && \
((_cmsghdr)->cmsg_type == SCM_RIGHTS) )
/* |SocketMessageWatcher| receives Bluedroid's socket setup
* messages on the I/O thread. You need to inherit from this
* class to make use of it.
*
* Bluedroid sends two socket info messages (20 bytes) at
* the beginning of a connection to both peers.
*
* - 1st message: [channel:4]
* - 2nd message: [size:2][bd address:6][channel:4][connection status:4]
*
* On the server side, the second message will contain a
* socket file descriptor for the connection. The client
* uses the original file descriptor.
*/
class SocketMessageWatcher : public MessageLoopForIO::Watcher
{
public:
static const unsigned char MSG1_SIZE = 4;
static const unsigned char MSG2_SIZE = 16;
static const unsigned char OFF_CHANNEL1 = 0;
static const unsigned char OFF_SIZE = 4;
static const unsigned char OFF_BDADDRESS = 6;
static const unsigned char OFF_CHANNEL2 = 12;
static const unsigned char OFF_STATUS = 16;
SocketMessageWatcher(int aFd)
: mFd(aFd)
, mClientFd(-1)
, mLen(0)
{ }
virtual ~SocketMessageWatcher()
{ }
virtual void Proceed(bt_status_t aStatus) = 0;
void OnFileCanReadWithoutBlocking(int aFd) MOZ_OVERRIDE
{
bt_status_t status;
switch (mLen) {
case 0:
status = RecvMsg1();
break;
case MSG1_SIZE:
status = RecvMsg2();
break;
default:
/* message-size error */
status = BT_STATUS_FAIL;
break;
}
if (IsComplete() || status != BT_STATUS_SUCCESS) {
mWatcher.StopWatchingFileDescriptor();
Proceed(status);
}
}
void OnFileCanWriteWithoutBlocking(int aFd) MOZ_OVERRIDE
{ }
void Watch()
{
MessageLoopForIO::current()->WatchFileDescriptor(
mFd,
true,
MessageLoopForIO::WATCH_READ,
&mWatcher,
this);
}
bool IsComplete() const
{
return mLen == (MSG1_SIZE + MSG2_SIZE);
}
int GetFd() const
{
return mFd;
}
int32_t GetChannel1() const
{
return ReadInt32(OFF_CHANNEL1);
}
int32_t GetSize() const
{
return ReadInt16(OFF_SIZE);
}
nsString GetBdAddress() const
{
nsString bdAddress;
ReadBdAddress(OFF_BDADDRESS, bdAddress);
return bdAddress;
}
int32_t GetChannel2() const
{
return ReadInt32(OFF_CHANNEL2);
}
int32_t GetConnectionStatus() const
{
return ReadInt32(OFF_STATUS);
}
int GetClientFd() const
{
return mClientFd;
}
private:
bt_status_t RecvMsg1()
{
struct iovec iv;
memset(&iv, 0, sizeof(iv));
iv.iov_base = mBuf;
iv.iov_len = MSG1_SIZE;
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iv;
msg.msg_iovlen = 1;
ssize_t res = TEMP_FAILURE_RETRY(recvmsg(mFd, &msg, MSG_NOSIGNAL));
if (res < 0) {
return BT_STATUS_FAIL;
}
mLen += res;
return BT_STATUS_SUCCESS;
}
bt_status_t RecvMsg2()
{
struct iovec iv;
memset(&iv, 0, sizeof(iv));
iv.iov_base = mBuf + MSG1_SIZE;
iv.iov_len = MSG2_SIZE;
struct msghdr msg;
struct cmsghdr cmsgbuf[2 * sizeof(cmsghdr) + 0x100];
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iv;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
ssize_t res = TEMP_FAILURE_RETRY(recvmsg(mFd, &msg, MSG_NOSIGNAL));
if (res < 0) {
return BT_STATUS_FAIL;
}
mLen += res;
if (msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE)) {
return BT_STATUS_FAIL;
}
struct cmsghdr *cmsgptr = CMSG_FIRSTHDR(&msg);
// Extract client fd from message header
for (; cmsgptr; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
if (CMSGHDR_CONTAINS_FD(cmsgptr)) {
// if multiple file descriptors have been sent, we close
// all but the final one.
if (mClientFd != -1) {
TEMP_FAILURE_RETRY(close(mClientFd));
}
// retrieve sent client fd
mClientFd = *(static_cast<int*>(CMSG_DATA(cmsgptr)));
}
}
return BT_STATUS_SUCCESS;
}
int16_t ReadInt16(unsigned long aOffset) const
{
/* little-endian buffer */
return (static_cast<int16_t>(mBuf[aOffset + 1]) << 8) |
static_cast<int16_t>(mBuf[aOffset]);
}
int32_t ReadInt32(unsigned long aOffset) const
{
/* little-endian buffer */
return (static_cast<int32_t>(mBuf[aOffset + 3]) << 24) |
(static_cast<int32_t>(mBuf[aOffset + 2]) << 16) |
(static_cast<int32_t>(mBuf[aOffset + 1]) << 8) |
static_cast<int32_t>(mBuf[aOffset]);
}
void ReadBdAddress(unsigned long aOffset, nsAString& aBdAddress) const
{
char str[18];
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
mBuf[aOffset + 0], mBuf[aOffset + 1], mBuf[aOffset + 2],
mBuf[aOffset + 3], mBuf[aOffset + 4], mBuf[aOffset + 5]);
aBdAddress.AssignLiteral(str);
}
MessageLoopForIO::FileDescriptorWatcher mWatcher;
int mFd;
int mClientFd;
unsigned char mLen;
uint8_t mBuf[MSG1_SIZE + MSG2_SIZE];
};
/* |SocketMessageWatcherTask| starts a SocketMessageWatcher
* on the I/O task
*/
class SocketMessageWatcherTask MOZ_FINAL : public Task
{
public:
SocketMessageWatcherTask(SocketMessageWatcher* aWatcher)
: mWatcher(aWatcher)
{
MOZ_ASSERT(mWatcher);
}
void Run() MOZ_OVERRIDE
{
mWatcher->Watch();
}
private:
SocketMessageWatcher* mWatcher;
};
/* |DeleteTask| deletes a class instance on the I/O thread
*/
template <typename T>
class DeleteTask MOZ_FINAL : public Task
{
public:
DeleteTask(T* aPtr)
: mPtr(aPtr)
{ }
void Run() MOZ_OVERRIDE
{
mPtr = nullptr;
}
private:
nsAutoPtr<T> mPtr;
};
/* |ConnectWatcher| specializes SocketMessageWatcher for
* connect operations by reading the socket messages from
* Bluedroid and forwarding the connected socket to the
* resource handler.
*/
class ConnectWatcher MOZ_FINAL : public SocketMessageWatcher
{
public:
ConnectWatcher(int aFd, BluetoothSocketResultHandler* aRes)
: SocketMessageWatcher(aFd)
, mRes(aRes)
{ }
void Proceed(bt_status_t aStatus) MOZ_OVERRIDE
{
if (mRes) {
DispatchBluetoothSocketResult(mRes,
&BluetoothSocketResultHandler::Connect,
GetFd(), GetBdAddress(),
GetConnectionStatus(), aStatus);
}
MessageLoopForIO::current()->PostTask(
FROM_HERE, new DeleteTask<ConnectWatcher>(this));
}
private:
nsRefPtr<BluetoothSocketResultHandler> mRes;
};
void
BluetoothSocketInterface::Connect(const bt_bdaddr_t* aBdAddr,
btsock_type_t aType, const uint8_t* aUuid,
@ -141,10 +497,14 @@ BluetoothSocketInterface::Connect(const bt_bdaddr_t* aBdAddr,
bt_status_t status = mInterface->connect(aBdAddr, aType, aUuid, aChannel,
&fd, aFlags);
if (aRes) {
DispatchBluetoothSocketResult(aRes,
&BluetoothSocketResultHandler::Connect,
fd, status);
if (status == BT_STATUS_SUCCESS) {
/* receive Bluedroid's socket-setup messages */
Task* t = new SocketMessageWatcherTask(new ConnectWatcher(fd, aRes));
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t);
} else if (aRes) {
DispatchBluetoothSocketResult(aRes,
&BluetoothSocketResultHandler::Connect,
-1, EmptyString(), 0, status);
}
}

View File

@ -37,7 +37,8 @@ public:
}
virtual void Listen(int aSockFd) { }
virtual void Connect(int aSockFd) { }
virtual void Connect(int aSockFd, const nsAString& aBdAddress,
int aConnectionState) { }
};
class BluetoothSocketInterface

View File

@ -263,6 +263,70 @@ private:
T* mInstance;
};
class DroidSocketImplRunnable : public nsRunnable
{
public:
DroidSocketImpl* GetImpl() const
{
return mImpl;
}
protected:
DroidSocketImplRunnable(DroidSocketImpl* aImpl)
: mImpl(aImpl)
{
MOZ_ASSERT(aImpl);
}
virtual ~DroidSocketImplRunnable()
{ }
private:
DroidSocketImpl* mImpl;
};
class OnSocketEventRunnable : public DroidSocketImplRunnable
{
public:
enum SocketEvent {
CONNECT_SUCCESS,
CONNECT_ERROR,
DISCONNECT
};
OnSocketEventRunnable(DroidSocketImpl* aImpl, SocketEvent e)
: DroidSocketImplRunnable(aImpl)
, mEvent(e)
{
MOZ_ASSERT(!NS_IsMainThread());
}
NS_IMETHOD Run() MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
DroidSocketImpl* impl = GetImpl();
if (impl->IsShutdownOnMainThread()) {
NS_WARNING("CloseSocket has already been called!");
// Since we've already explicitly closed and the close happened before
// this, this isn't really an error. Since we've warned, return OK.
return NS_OK;
}
if (mEvent == CONNECT_SUCCESS) {
impl->mConsumer->NotifySuccess();
} else if (mEvent == CONNECT_ERROR) {
impl->mConsumer->NotifyError();
} else if (mEvent == DISCONNECT) {
impl->mConsumer->NotifyDisconnect();
}
return NS_OK;
}
private:
SocketEvent mEvent;
};
class RequestClosingSocketTask : public nsRunnable
{
public:
@ -467,7 +531,7 @@ DroidSocketImpl::Connect(int aFd)
SetFd(aFd);
mConnectionStatus = SOCKET_IS_CONNECTING;
AddWatchers(READ_WATCHER, true);
AddWatchers(WRITE_WATCHER, false);
}
void
@ -550,9 +614,6 @@ DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
OnSocketCanReceiveWithoutBlocking(aFd);
} else if (mConnectionStatus == SOCKET_IS_LISTENING) {
OnSocketCanAcceptWithoutBlocking(aFd);
} else if (mConnectionStatus == SOCKET_IS_CONNECTING) {
/* TODO: remove this branch when connect is handled by BluetoothInterface */
OnSocketCanConnectWithoutBlocking(aFd);
} else {
NS_NOTREACHED("invalid connection state for reading");
}
@ -720,51 +781,20 @@ DroidSocketImpl::OnSocketCanConnectWithoutBlocking(int aFd)
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!mShuttingDownOnIOThread);
// Read all of the incoming data.
while (true) {
nsAutoPtr<UnixSocketRawData> incoming(new UnixSocketRawData(MAX_READ_SIZE));
/* We follow Posix behaviour here: Connect operations are
* complete once we can write to the connecting socket.
*/
ssize_t ret;
if (!mReadMsgForClientFd) {
ret = read(aFd, incoming->mData, incoming->mSize);
} else {
ret = ReadMsg(aFd, incoming->mData, incoming->mSize);
}
mConnectionStatus = SOCKET_IS_CONNECTED;
if (ret <= 0) {
if (ret == -1) {
if (errno == EINTR) {
continue; // retry system call when interrupted
}
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return; // no data available: return and re-poll
}
nsRefPtr<OnSocketEventRunnable> r =
new OnSocketEventRunnable(this, OnSocketEventRunnable::CONNECT_SUCCESS);
NS_DispatchToMainThread(r);
BT_WARNING("Cannot read from network");
// else fall through to error handling on other errno's
}
// We're done with our descriptors. Ensure that spurious events don't
// cause us to end up back here.
RemoveWatchers(READ_WATCHER | WRITE_WATCHER);
nsRefPtr<RequestClosingSocketTask> t = new RequestClosingSocketTask(this);
NS_DispatchToMainThread(t);
return;
}
incoming->mSize = ret;
nsRefPtr<SocketReceiveTask> t =
new SocketReceiveTask(this, incoming.forget());
NS_DispatchToMainThread(t);
// If ret is less than MAX_READ_SIZE, there's no
// more data in the socket for us to read now.
if (ret < ssize_t(MAX_READ_SIZE)) {
return;
}
AddWatchers(READ_WATCHER, true);
if (!mOutgoingQ.IsEmpty()) {
AddWatchers(WRITE_WATCHER, false);
}
MOZ_CRASH("We returned early");
}
BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
@ -811,9 +841,14 @@ public:
MOZ_ASSERT(mImpl);
}
void Connect(int aFd) MOZ_OVERRIDE
void Connect(int aFd, const nsAString& aBdAddress,
int aConnectionStatus) MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
if (!mImpl->IsShutdownOnMainThread()) {
mImpl->mConsumer->SetAddress(aBdAddress);
}
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
new SocketConnectTask(mImpl, aFd));
}
@ -964,7 +999,8 @@ BluetoothSocket::ReceiveSocketInfo(nsAutoPtr<UnixSocketRawData>& aMessage)
void
BluetoothSocket::ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage)
{
if (ReceiveSocketInfo(aMessage)) {
/* clients handle socket setup in BluetoothInterface::Connect */
if (mIsServer && ReceiveSocketInfo(aMessage)) {
return;
}

View File

@ -23,15 +23,6 @@ public:
bool aAuth,
bool aEncrypt);
/**
* Connect to remote server as a client.
*
* The steps are as following:
* 1) BluetoothSocket acquires fd from bluedroid, and creates
* a DroidSocketImpl to watch read/write of the fd.
* 2) DroidSocketImpl receives first 2 messages to get socket info.
* 3) Obex client session starts.
*/
bool Connect(const nsAString& aDeviceAddress, int aChannel);
/**
@ -65,6 +56,11 @@ public:
aDeviceAddress = mDeviceAddress;
}
inline void SetAddress(const nsAString& aDeviceAddress)
{
mDeviceAddress = aDeviceAddress;
}
void CloseDroidSocket();
bool SendDroidSocketData(mozilla::ipc::UnixSocketRawData* aData);