Bug 1073548: Add |mozilla::ipc::BluetoothDaemonConnection|, r=shawnjohnjr

This patch adds |BluetoothDaemonConnection|, which transfers PDUs from and
to the Bluetooth daemon. The class is build around the existing socket I/O
infrastructure.
This commit is contained in:
Thomas Zimmermann 2014-11-03 13:03:49 +01:00
parent 2a5770563c
commit eb92836ea3
4 changed files with 699 additions and 0 deletions

View File

@ -0,0 +1,555 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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 "BluetoothDaemonConnection.h"
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include "mozilla/ipc/UnixSocketWatcher.h"
#include "nsTArray.h"
#include "nsXULAppAPI.h"
#ifdef CHROMIUM_LOG
#undef CHROMIUM_LOG
#endif
#if defined(MOZ_WIDGET_GONK)
#include <android/log.h>
#define CHROMIUM_LOG(args...) __android_log_print(ANDROID_LOG_INFO, "I/O", args);
#else
#include <stdio.h>
#define IODEBUG true
#define CHROMIUM_LOG(args...) if (IODEBUG) printf(args);
#endif
namespace mozilla {
namespace ipc {
// The connection to the Bluetooth daemon is established
// using an abstract socket name. The \0 prefix will be added
// by the |Connect| method.
static const char sBluetoothdSocketName[] = "bluez_hal_socket";
//
// BluetoothDaemonPDU
//
BluetoothDaemonPDU::BluetoothDaemonPDU(uint8_t aService, uint8_t aOpcode,
uint16_t aPayloadSize)
: UnixSocketIOBuffer(HEADER_SIZE + aPayloadSize)
, mUserData(nullptr)
{
uint8_t* data = Append(HEADER_SIZE);
MOZ_ASSERT(data);
// Setup PDU header
data[OFF_SERVICE] = aService;
data[OFF_OPCODE] = aOpcode;
memcpy(data + OFF_LENGTH, &aPayloadSize, sizeof(aPayloadSize));
}
BluetoothDaemonPDU::BluetoothDaemonPDU(size_t aPayloadSize)
: UnixSocketIOBuffer(HEADER_SIZE + aPayloadSize)
, mUserData(nullptr)
{ }
ssize_t
BluetoothDaemonPDU::Send(int aFd)
{
struct iovec iv;
memset(&iv, 0, sizeof(iv));
iv.iov_base = GetData(GetLeadingSpace());
iv.iov_len = GetSize();
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iv;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
ssize_t res = TEMP_FAILURE_RETRY(sendmsg(aFd, &msg, 0));
if (res < 0) {
MOZ_ASSERT(errno != EBADF); /* internal error */
OnError("sendmsg", errno);
return -1;
}
Consume(res);
return res;
}
#define CMSGHDR_CONTAINS_FD(_cmsghdr) \
( ((_cmsghdr)->cmsg_level == SOL_SOCKET) && \
((_cmsghdr)->cmsg_type == SCM_RIGHTS) )
ssize_t
BluetoothDaemonPDU::Receive(int aFd)
{
struct iovec iv;
memset(&iv, 0, sizeof(iv));
iv.iov_base = GetData(0);
iv.iov_len = GetAvailableSpace();
uint8_t cmsgbuf[CMSG_SPACE(sizeof(int))];
struct msghdr msg;
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(aFd, &msg, MSG_NOSIGNAL));
if (res < 0) {
MOZ_ASSERT(errno != EBADF); /* internal error */
OnError("recvmsg", errno);
return -1;
}
if (msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE)) {
return -1;
}
SetRange(0, res);
struct cmsghdr *chdr = CMSG_FIRSTHDR(&msg);
for (; chdr; chdr = CMSG_NXTHDR(&msg, chdr)) {
if (NS_WARN_IF(!CMSGHDR_CONTAINS_FD(chdr))) {
continue;
}
// Retrieve sent file descriptor. If multiple file descriptors
// have been sent, we close all but the final one.
mReceivedFd = *(static_cast<int*>(CMSG_DATA(chdr)));
}
return res;
}
int
BluetoothDaemonPDU::AcquireFd()
{
return mReceivedFd.forget();
}
nsresult
BluetoothDaemonPDU::UpdateHeader()
{
size_t len = GetPayloadSize();
if (len >= MAX_PAYLOAD_LENGTH) {
return NS_ERROR_ILLEGAL_VALUE;
}
uint16_t len16 = static_cast<uint16_t>(len);
memcpy(GetData(OFF_LENGTH), &len16, sizeof(len16));
return NS_OK;
}
size_t
BluetoothDaemonPDU::GetPayloadSize() const
{
MOZ_ASSERT(GetSize() >= HEADER_SIZE);
return GetSize() - HEADER_SIZE;
}
void
BluetoothDaemonPDU::OnError(const char* aFunction, int aErrno)
{
CHROMIUM_LOG("%s failed with error %d (%s)",
aFunction, aErrno, strerror(aErrno));
}
//
// BluetoothDaemonPDUConsumer
//
BluetoothDaemonPDUConsumer::BluetoothDaemonPDUConsumer()
{ }
BluetoothDaemonPDUConsumer::~BluetoothDaemonPDUConsumer()
{ }
//
// BluetoothDaemonConnectionIO
//
class BluetoothDaemonConnectionIO MOZ_FINAL : public UnixSocketWatcher
{
public:
BluetoothDaemonConnectionIO(MessageLoop* aIOLoop,
BluetoothDaemonConnection* aConnection,
BluetoothDaemonPDUConsumer* aConsumer);
SocketBase* GetSocketBase();
// Shutdown state
//
bool IsShutdownOnMainThread() const;
void ShutdownOnMainThread();
bool IsShutdownOnIOThread() const;
void ShutdownOnIOThread();
// Task callback methods
//
void Connect(const char* aSocketName);
void Send(BluetoothDaemonPDU* aPDU);
void EnqueueData(BluetoothDaemonPDU* aPDU);
bool HasPendingData() const;
nsresult Receive(struct msghdr& msg);
void OnSocketCanReceiveWithoutBlocking() MOZ_OVERRIDE;
void OnSocketCanSendWithoutBlocking() MOZ_OVERRIDE;
void OnConnected() MOZ_OVERRIDE;
void OnError(const char* aFunction, int aErrno) MOZ_OVERRIDE;
private:
ssize_t ReceiveData(int aFd);
nsresult SendPendingData(int aFd);
BluetoothDaemonConnection* mConnection;
BluetoothDaemonPDUConsumer* mConsumer;
nsAutoPtr<BluetoothDaemonPDU> mPDU;
nsTArray<BluetoothDaemonPDU*> mOutgoingQ;
bool mShuttingDownOnIOThread;
};
BluetoothDaemonConnectionIO::BluetoothDaemonConnectionIO(
MessageLoop* aIOLoop,
BluetoothDaemonConnection* aConnection,
BluetoothDaemonPDUConsumer* aConsumer)
: UnixSocketWatcher(aIOLoop)
, mConnection(aConnection)
, mConsumer(aConsumer)
, mShuttingDownOnIOThread(false)
{
MOZ_ASSERT(mConnection);
MOZ_ASSERT(mConsumer);
/* There's only one PDU for receiving, which we reuse everytime */
mPDU = new BluetoothDaemonPDU(BluetoothDaemonPDU::MAX_PAYLOAD_LENGTH);
}
SocketBase*
BluetoothDaemonConnectionIO::GetSocketBase()
{
return mConnection;
}
bool
BluetoothDaemonConnectionIO::IsShutdownOnMainThread() const
{
MOZ_ASSERT(NS_IsMainThread());
return mConnection == nullptr;
}
void
BluetoothDaemonConnectionIO::ShutdownOnMainThread()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!IsShutdownOnMainThread());
mConnection = nullptr;
}
bool
BluetoothDaemonConnectionIO::IsShutdownOnIOThread() const
{
return mShuttingDownOnIOThread;
}
void
BluetoothDaemonConnectionIO::ShutdownOnIOThread()
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!mShuttingDownOnIOThread);
Close(); // will also remove fd from I/O loop
mShuttingDownOnIOThread = true;
}
void
BluetoothDaemonConnectionIO::Connect(const char* aSocketName)
{
static const size_t sNameOffset = 1;
MOZ_ASSERT(aSocketName);
// Create socket address
struct sockaddr_un addr;
size_t namesiz = strlen(aSocketName) + 1;
if((sNameOffset + namesiz) > sizeof(addr.sun_path)) {
CHROMIUM_LOG("Address too long for socket struct!");
return;
}
memset(addr.sun_path, '\0', sNameOffset); // abstract socket
memcpy(addr.sun_path + sNameOffset, aSocketName, namesiz);
addr.sun_family = AF_UNIX;
socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + 1 + namesiz;
// Create socket
int fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (fd < 0) {
OnError("socket", errno);
return;
}
if (TEMP_FAILURE_RETRY(fcntl(fd, F_SETFL, O_NONBLOCK)) < 0) {
OnError("fcntl", errno);
ScopedClose cleanupFd(fd);
return;
}
SetFd(fd);
// Connect socket to address; calls OnConnected()
// on success, or OnError() otherwise
nsresult rv = UnixSocketWatcher::Connect(
reinterpret_cast<struct sockaddr*>(&addr), socklen);
NS_WARN_IF(NS_FAILED(rv));
}
void
BluetoothDaemonConnectionIO::Send(BluetoothDaemonPDU* aPDU)
{
MOZ_ASSERT(mConsumer);
MOZ_ASSERT(aPDU);
mConsumer->StoreUserData(*aPDU); // Store user data for reply
EnqueueData(aPDU);
AddWatchers(WRITE_WATCHER, false);
}
void
BluetoothDaemonConnectionIO::EnqueueData(BluetoothDaemonPDU* aPDU)
{
MOZ_ASSERT(aPDU);
mOutgoingQ.AppendElement(aPDU);
}
bool
BluetoothDaemonConnectionIO::HasPendingData() const
{
return !mOutgoingQ.IsEmpty();
}
ssize_t
BluetoothDaemonConnectionIO::ReceiveData(int aFd)
{
MOZ_ASSERT(aFd >= 0);
ssize_t res = mPDU->Receive(aFd);
if (res < 0) {
/* an I/O error occured */
nsRefPtr<nsRunnable> r =
new SocketIORequestClosingRunnable<BluetoothDaemonConnectionIO>(this);
NS_DispatchToMainThread(r);
return -1;
} else if (!res) {
/* EOF or peer shut down sending */
nsRefPtr<nsRunnable> r =
new SocketIORequestClosingRunnable<BluetoothDaemonConnectionIO>(this);
NS_DispatchToMainThread(r);
return 0;
}
mConsumer->Handle(*mPDU);
return res;
}
void
BluetoothDaemonConnectionIO::OnSocketCanReceiveWithoutBlocking()
{
MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED);
MOZ_ASSERT(!IsShutdownOnIOThread());
ssize_t res = ReceiveData(GetFd());
if (res < 0) {
/* I/O error */
RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
} else if (!res) {
/* EOF or peer shutdown */
RemoveWatchers(READ_WATCHER);
}
}
nsresult
BluetoothDaemonConnectionIO::SendPendingData(int aFd)
{
while (HasPendingData()) {
BluetoothDaemonPDU* outgoing = mOutgoingQ.ElementAt(0);
MOZ_ASSERT(outgoing);
ssize_t res = outgoing->Send(aFd);
if (res < 0) {
/* an I/O error occured */
return NS_ERROR_FAILURE;
} else if (!res) {
/* I/O is currently blocked; try again later */
return NS_OK;
}
MOZ_ASSERT(!outgoing->GetSize());
mOutgoingQ.RemoveElementAt(0);
delete outgoing;
}
return NS_OK;
}
void
BluetoothDaemonConnectionIO::OnSocketCanSendWithoutBlocking()
{
MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED);
MOZ_ASSERT(!IsShutdownOnIOThread());
if (NS_WARN_IF(NS_FAILED(SendPendingData(GetFd())))) {
RemoveWatchers(WRITE_WATCHER);
}
}
void
BluetoothDaemonConnectionIO::OnConnected()
{
MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED);
nsRefPtr<nsRunnable> r =
new SocketIOEventRunnable<BluetoothDaemonConnectionIO>(
this,
SocketIOEventRunnable<BluetoothDaemonConnectionIO>::CONNECT_SUCCESS);
NS_DispatchToMainThread(r);
AddWatchers(READ_WATCHER, true);
if (HasPendingData()) {
AddWatchers(WRITE_WATCHER, false);
}
}
void
BluetoothDaemonConnectionIO::OnError(const char* aFunction, int aErrno)
{
MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
UnixFdWatcher::OnError(aFunction, aErrno);
// Clean up watchers, status, fd
Close();
// Tell the main thread we've errored
nsRefPtr<nsRunnable> r =
new SocketIOEventRunnable<BluetoothDaemonConnectionIO>(
this,
SocketIOEventRunnable<BluetoothDaemonConnectionIO>::CONNECT_ERROR);
NS_DispatchToMainThread(r);
}
//
// I/O helper tasks
//
class BluetoothDaemonConnectTask MOZ_FINAL
: public SocketIOTask<BluetoothDaemonConnectionIO>
{
public:
BluetoothDaemonConnectTask(BluetoothDaemonConnectionIO* aIO)
: SocketIOTask<BluetoothDaemonConnectionIO>(aIO)
{ }
void Run() MOZ_OVERRIDE
{
if (IsCanceled()) {
return;
}
GetIO()->Connect(sBluetoothdSocketName);
}
};
//
// BluetoothDaemonConnection
//
BluetoothDaemonConnection::BluetoothDaemonConnection()
: mIO(nullptr)
{ }
BluetoothDaemonConnection::~BluetoothDaemonConnection()
{ }
nsresult
BluetoothDaemonConnection::ConnectSocket(BluetoothDaemonPDUConsumer* aConsumer)
{
MOZ_ASSERT(NS_IsMainThread());
if (mIO) {
CHROMIUM_LOG("Bluetooth daemon already connecting/connected!");
return NS_ERROR_FAILURE;
}
SetConnectionStatus(SOCKET_CONNECTING);
MessageLoop* ioLoop = XRE_GetIOMessageLoop();
mIO = new BluetoothDaemonConnectionIO(ioLoop, this, aConsumer);
ioLoop->PostTask(FROM_HERE, new BluetoothDaemonConnectTask(mIO));
return NS_OK;
}
void
BluetoothDaemonConnection::CloseSocket()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mIO) {
CHROMIUM_LOG("Bluetooth daemon already disconnected!");
return;
}
XRE_GetIOMessageLoop()->PostTask(
FROM_HERE, new SocketIOShutdownTask<BluetoothDaemonConnectionIO>(mIO));
mIO = nullptr;
NotifyDisconnect();
}
nsresult
BluetoothDaemonConnection::Send(BluetoothDaemonPDU* aPDU)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mIO) {
CHROMIUM_LOG("Bluetooth daemon already connecting/connected!");
return NS_ERROR_FAILURE;
}
XRE_GetIOMessageLoop()->PostTask(
FROM_HERE,
new SocketIOSendTask<BluetoothDaemonConnectionIO,
BluetoothDaemonPDU>(mIO, aPDU));
return NS_OK;
}
}
}

View File

@ -0,0 +1,122 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
#ifndef mozilla_ipc_bluetooth_BluetoothDaemonConnection_h
#define mozilla_ipc_bluetooth_BluetoothDaemonConnection_h
#include "mozilla/Attributes.h"
#include "mozilla/FileUtils.h"
#include "mozilla/ipc/SocketBase.h"
#include "nsError.h"
#include "nsAutoPtr.h"
namespace mozilla {
namespace ipc {
class BluetoothDaemonConnectionIO;
/*
* |BlutoothDaemonPDU| represents a single PDU that is transfered from or to
* the Bluetooth daemon. Each PDU contains exactly one command.
*
* A PDU as the following format
*
* | 1 | 1 | 2 | n |
* | service | opcode | payload length | payload |
*
* Service and Opcode each require 1 byte, the payload length requires 2
* bytes, and the payload requires the number of bytes as stored in the
* payload-length field.
*
* Each service and opcode can have a different payload with individual
* length. For the exact details of the Bluetooth protocol, please refer
* to
*
* https://git.kernel.org/cgit/bluetooth/bluez.git/tree/android/hal-ipc-api.txt?id=5.24
*
*/
class BluetoothDaemonPDU MOZ_FINAL : public UnixSocketIOBuffer
{
public:
enum {
OFF_SERVICE = 0,
OFF_OPCODE = 1,
OFF_LENGTH = 2,
OFF_PAYLOAD = 4,
HEADER_SIZE = OFF_PAYLOAD,
MAX_PAYLOAD_LENGTH = 1 << 16
};
BluetoothDaemonPDU(uint8_t aService, uint8_t aOpcode,
uint16_t aPayloadSize);
BluetoothDaemonPDU(size_t aPayloadSize);
void SetUserData(void* aUserData)
{
mUserData = aUserData;
}
void* GetUserData() const
{
return mUserData;
}
ssize_t Send(int aFd);
ssize_t Receive(int aFd);
int AcquireFd();
nsresult UpdateHeader();
private:
size_t GetPayloadSize() const;
void OnError(const char* aFunction, int aErrno);
void* mUserData;
ScopedClose mReceivedFd;
};
/*
* |BluetoothDaemonPDUConsumer| processes incoming PDUs from the Bluetooth
* daemon. Please note that its method |Handle| runs on a different than the
* main thread.
*/
class BluetoothDaemonPDUConsumer
{
public:
virtual ~BluetoothDaemonPDUConsumer();
virtual void Handle(BluetoothDaemonPDU& aPDU) = 0;
virtual void StoreUserData(const BluetoothDaemonPDU& aPDU) = 0;
protected:
BluetoothDaemonPDUConsumer();
};
/*
* |BluetoothDaemonConnection| represents the socket to connect to the
* Bluetooth daemon. It offers connection establishment and sending
* PDUs. PDU receiving is performed by |BluetoothDaemonPDUConsumer|.
*/
class BluetoothDaemonConnection : public SocketBase
{
public:
BluetoothDaemonConnection();
virtual ~BluetoothDaemonConnection();
nsresult ConnectSocket(BluetoothDaemonPDUConsumer* aConsumer);
void CloseSocket();
nsresult Send(BluetoothDaemonPDU* aPDU);
private:
BluetoothDaemonConnectionIO* mIO;
};
}
}
#endif

19
ipc/bluetooth/moz.build Normal file
View File

@ -0,0 +1,19 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# 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/.
EXPORTS.mozilla.ipc += [
'BluetoothDaemonConnection.h'
]
SOURCES += [
'BluetoothDaemonConnection.cpp'
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
FAIL_ON_WARNINGS = True

View File

@ -14,6 +14,9 @@ DIRS += [
if CONFIG['MOZ_B2G_RIL']:
DIRS += ['ril']
if CONFIG['MOZ_B2G_BT_BLUEDROID']:
DIRS += ['bluetooth']
if CONFIG['MOZ_B2G_BT_BLUEZ']:
DIRS += ['dbus']