Bug 1159179 - Patch 3/3: [PBAP] Implement PBAP manger, r=shuang

This commit is contained in:
Ben Tian 2015-05-13 10:52:58 +08:00
parent 5c00861a05
commit 8b891e378e
3 changed files with 518 additions and 0 deletions

View File

@ -0,0 +1,445 @@
/* -*- 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 "base/basictypes.h"
#include "BluetoothPbapManager.h"
#include "BluetoothService.h"
#include "BluetoothSocket.h"
#include "BluetoothUuid.h"
#include "ObexBase.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "nsAutoPtr.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
USING_BLUETOOTH_NAMESPACE
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::ipc;
namespace {
// UUID of PBAP PSE
static const BluetoothUuid kPbapPSE = {
{
0x00, 0x00, 0x11, 0x2F, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
}
};
// UUID used in PBAP OBEX target header
static const BluetoothUuid kPbapObexTarget = {
{
0x79, 0x61, 0x35, 0xF0, 0xF0, 0xC5, 0x11, 0xD8,
0x09, 0x66, 0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66
}
};
StaticRefPtr<BluetoothPbapManager> sPbapManager;
static bool sInShutdown = false;
}
BEGIN_BLUETOOTH_NAMESPACE
NS_IMETHODIMP
BluetoothPbapManager::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(sPbapManager);
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
HandleShutdown();
return NS_OK;
}
MOZ_ASSERT(false, "PbapManager got unexpected topic!");
return NS_ERROR_UNEXPECTED;
}
void
BluetoothPbapManager::HandleShutdown()
{
MOZ_ASSERT(NS_IsMainThread());
sInShutdown = true;
Disconnect(nullptr);
sPbapManager = nullptr;
}
BluetoothPbapManager::BluetoothPbapManager() : mConnected(false)
{
mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
}
BluetoothPbapManager::~BluetoothPbapManager()
{
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (NS_WARN_IF(!obs)) {
return;
}
NS_WARN_IF(NS_FAILED(
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)));
}
bool
BluetoothPbapManager::Init()
{
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (NS_WARN_IF(!obs)) {
return false;
}
if (NS_WARN_IF(NS_FAILED(
obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) {
return false;
}
/**
* We don't start listening here as BluetoothServiceBluedroid calls Listen()
* immediately when BT stops.
*
* If we start listening here, the listening fails when device boots up since
* Listen() is called again and restarts server socket. The restart causes
* absence of read events when device boots up.
*/
return true;
}
//static
BluetoothPbapManager*
BluetoothPbapManager::Get()
{
MOZ_ASSERT(NS_IsMainThread());
// Exit early if sPbapManager already exists
if (sPbapManager) {
return sPbapManager;
}
// Do not create a new instance if we're in shutdown
if (NS_WARN_IF(sInShutdown)) {
return nullptr;
}
// Create a new instance, register, and return
BluetoothPbapManager *manager = new BluetoothPbapManager();
if (NS_WARN_IF(!manager->Init())) {
return nullptr;
}
sPbapManager = manager;
return sPbapManager;
}
bool
BluetoothPbapManager::Listen()
{
MOZ_ASSERT(NS_IsMainThread());
// Fail to listen if |mSocket| already exists
if (NS_WARN_IF(mSocket)) {
return false;
}
/**
* Restart server socket since its underlying fd becomes invalid when
* BT stops; otherwise no more read events would be received even if
* BT restarts.
*/
if (mServerSocket) {
mServerSocket->CloseSocket();
mServerSocket = nullptr;
}
mServerSocket =
new BluetoothSocket(this, BluetoothSocketType::RFCOMM, false, true);
if (NS_WARN_IF(!mServerSocket->ListenSocket(
NS_LITERAL_STRING("OBEX Phonebook Access Server"),
kPbapPSE,
BluetoothReservedChannels::CHANNEL_PBAP_PSE))) {
mServerSocket = nullptr;
return false;
}
BT_LOGR("PBAP socket is listening");
return true;
}
// Virtual function of class SocketConsumer
void
BluetoothPbapManager::ReceiveSocketData(BluetoothSocket* aSocket,
nsAutoPtr<UnixSocketBuffer>& aMessage)
{
MOZ_ASSERT(NS_IsMainThread());
const uint8_t* data = aMessage->GetData();
int receivedLength = aMessage->GetSize();
uint8_t opCode = data[0];
ObexHeaderSet pktHeaders(opCode);
switch (opCode) {
case ObexRequestCode::Connect:
// Section 3.3.1 "Connect", IrOBEX 1.2
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
// [Headers:var]
if (!ParseHeaders(&data[7], receivedLength - 7, &pktHeaders)) {
ReplyError(ObexResponseCode::BadRequest);
return;
}
// Section 6.4 "Establishing an OBEX Session", PBAP 1.2
// The OBEX header target shall equal to kPbapObexTarget.
if (!CompareHeaderTarget(pktHeaders)) {
ReplyError(ObexResponseCode::BadRequest);
return;
}
ReplyToConnect();
AfterPbapConnected();
break;
case ObexRequestCode::Disconnect:
case ObexRequestCode::Abort:
// Section 3.3.2 "Disconnect" and Section 3.3.5 "Abort", IrOBEX 1.2
// The format of request packet of "Disconnect" and "Abort" are the same
// [opcode:1][length:2][Headers:var]
if (!ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) {
ReplyError(ObexResponseCode::BadRequest);
return;
}
ReplyToDisconnectOrAbort();
AfterPbapDisconnected();
break;
case ObexRequestCode::Put:
case ObexRequestCode::PutFinal:
case ObexRequestCode::Get:
case ObexRequestCode::GetFinal:
case ObexRequestCode::SetPath:
ReplyError(ObexResponseCode::BadRequest);
BT_LOGR("Unsupported ObexRequestCode %x", opCode);
break;
default:
ReplyError(ObexResponseCode::NotImplemented);
BT_LOGR("Unrecognized ObexRequestCode %x", opCode);
break;
}
}
bool
BluetoothPbapManager::CompareHeaderTarget(const ObexHeaderSet& aHeader)
{
if (!aHeader.Has(ObexHeaderId::Target)) {
BT_LOGR("No ObexHeaderId::Target in header");
return false;
}
uint8_t* targetPtr;
int targetLength;
aHeader.GetTarget(&targetPtr, &targetLength);
if (targetLength != sizeof(BluetoothUuid)) {
BT_LOGR("Length mismatch: %d != 16", targetLength);
return false;
}
for (uint8_t i = 0; i < sizeof(BluetoothUuid); i++) {
if (targetPtr[i] != kPbapObexTarget.mUuid[i]) {
BT_LOGR("UUID mismatch: received target[%d]=0x%x != 0x%x",
i, targetPtr[i], kPbapObexTarget.mUuid[i]);
return false;
}
}
return true;
}
void
BluetoothPbapManager::AfterPbapConnected()
{
mConnected = true;
}
void
BluetoothPbapManager::AfterPbapDisconnected()
{
mConnected = false;
}
bool
BluetoothPbapManager::IsConnected()
{
return mConnected;
}
void
BluetoothPbapManager::GetAddress(nsAString& aDeviceAddress)
{
return mSocket->GetAddress(aDeviceAddress);
}
void
BluetoothPbapManager::ReplyToConnect()
{
if (mConnected) {
return;
}
// Section 3.3.1 "Connect", IrOBEX 1.2
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
// [Headers:var]
uint8_t req[255];
int index = 7;
req[3] = 0x10; // version=1.0
req[4] = 0x00; // flag=0x00
req[5] = BluetoothPbapManager::MAX_PACKET_LENGTH >> 8;
req[6] = (uint8_t)BluetoothPbapManager::MAX_PACKET_LENGTH;
// Section 6.4 "Establishing an OBEX Session", PBAP 1.2
// Headers: [Who:16][Connection ID]
index += AppendHeaderWho(&req[index], 255, kPbapObexTarget.mUuid,
sizeof(BluetoothUuid));
index += AppendHeaderConnectionId(&req[index], 0x01);
SendObexData(req, ObexResponseCode::Success, index);
}
void
BluetoothPbapManager::ReplyToDisconnectOrAbort()
{
if (!mConnected) {
return;
}
// Section 3.3.2 "Disconnect" and Section 3.3.5 "Abort", IrOBEX 1.2
// The format of response packet of "Disconnect" and "Abort" are the same
// [opcode:1][length:2][Headers:var]
uint8_t req[255];
int index = 3;
SendObexData(req, ObexResponseCode::Success, index);
}
void
BluetoothPbapManager::ReplyError(uint8_t aError)
{
BT_LOGR("[0x%x]", aError);
// Section 3.2 "Response Format", IrOBEX 1.2
// [opcode:1][length:2][Headers:var]
uint8_t req[255];
int index = 3;
SendObexData(req, aError, index);
}
void
BluetoothPbapManager::SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize)
{
SetObexPacketInfo(aData, aOpcode, aSize);
mSocket->SendSocketData(new UnixSocketRawData(aData, aSize));
}
void
BluetoothPbapManager::OnSocketConnectSuccess(BluetoothSocket* aSocket)
{
MOZ_ASSERT(aSocket);
MOZ_ASSERT(aSocket == mServerSocket);
MOZ_ASSERT(!mSocket);
BT_LOGR("PBAP socket is connected");
// Close server socket as only one session is allowed at a time
mServerSocket.swap(mSocket);
// Cache device address since we can't get socket address when a remote
// device disconnect with us.
mSocket->GetAddress(mDeviceAddress);
}
void
BluetoothPbapManager::OnSocketConnectError(BluetoothSocket* aSocket)
{
mServerSocket = nullptr;
mSocket = nullptr;
}
void
BluetoothPbapManager::OnSocketDisconnect(BluetoothSocket* aSocket)
{
MOZ_ASSERT(aSocket);
if (aSocket != mSocket) {
// Do nothing when a listening server socket is closed.
return;
}
AfterPbapDisconnected();
mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
mSocket = nullptr;
Listen();
}
void
BluetoothPbapManager::Disconnect(BluetoothProfileController* aController)
{
if (mSocket) {
mSocket->CloseSocket();
} else {
BT_WARNING("%s: No ongoing connection to disconnect", __FUNCTION__);
}
}
NS_IMPL_ISUPPORTS(BluetoothPbapManager, nsIObserver)
void
BluetoothPbapManager::Connect(const nsAString& aDeviceAddress,
BluetoothProfileController* aController)
{
MOZ_ASSERT(false);
}
void
BluetoothPbapManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
const nsAString& aServiceUuid,
int aChannel)
{
MOZ_ASSERT(false);
}
void
BluetoothPbapManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
{
MOZ_ASSERT(false);
}
void
BluetoothPbapManager::OnConnect(const nsAString& aErrorStr)
{
MOZ_ASSERT(false);
}
void
BluetoothPbapManager::OnDisconnect(const nsAString& aErrorStr)
{
MOZ_ASSERT(false);
}
void
BluetoothPbapManager::Reset()
{
MOZ_ASSERT(false);
}
END_BLUETOOTH_NAMESPACE

View File

@ -0,0 +1,72 @@
/* -*- 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_dom_bluetooth_bluetoothpbapmanager_h__
#define mozilla_dom_bluetooth_bluetoothpbapmanager_h__
#include "BluetoothCommon.h"
#include "BluetoothProfileManagerBase.h"
#include "BluetoothSocketObserver.h"
#include "mozilla/ipc/SocketBase.h"
BEGIN_BLUETOOTH_NAMESPACE
class BluetoothSocket;
class ObexHeaderSet;
class BluetoothPbapManager : public BluetoothSocketObserver
, public BluetoothProfileManagerBase
{
public:
BT_DECL_PROFILE_MGR_BASE
BT_DECL_SOCKET_OBSERVER
virtual void GetName(nsACString& aName)
{
aName.AssignLiteral("PBAP");
}
static const int MAX_PACKET_LENGTH = 0xFFFE;
static BluetoothPbapManager* Get();
bool Listen();
protected:
virtual ~BluetoothPbapManager();
private:
BluetoothPbapManager();
bool Init();
void HandleShutdown();
void ReplyToConnect();
void ReplyToDisconnectOrAbort();
void ReplyError(uint8_t aError);
void SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize);
bool CompareHeaderTarget(const ObexHeaderSet& aHeader);
void AfterPbapConnected();
void AfterPbapDisconnected();
/**
* OBEX session status. Set when OBEX session is established.
*/
bool mConnected;
nsString mDeviceAddress;
// If a connection has been established, mSocket will be the socket
// communicating with the remote socket. We maintain the invariant that if
// mSocket is non-null, mServerSocket must be null (and vice versa).
nsRefPtr<BluetoothSocket> mSocket;
// Server socket. Once an inbound connection is established, it will hand
// over the ownership to mSocket, and get a new server socket while Listen()
// is called.
nsRefPtr<BluetoothSocket> mServerSocket;
};
END_BLUETOOTH_NAMESPACE
#endif

View File

@ -104,6 +104,7 @@ if CONFIG['MOZ_B2G_BT']:
'bluedroid/BluetoothHALInterface.cpp',
'bluedroid/BluetoothHandsfreeHALInterface.cpp',
'bluedroid/BluetoothOppManager.cpp',
'bluedroid/BluetoothPbapManager.cpp',
'bluedroid/BluetoothServiceBluedroid.cpp',
'bluedroid/BluetoothSocket.cpp',
'bluedroid/BluetoothSocketHALInterface.cpp',