mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-24 02:35:41 +00:00
1128 lines
32 KiB
C++
1128 lines
32 KiB
C++
/* -*- 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 "BluetoothMapSmsManager.h"
|
|
|
|
#include "BluetoothService.h"
|
|
#include "BluetoothSocket.h"
|
|
#include "BluetoothUtils.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 Map Mas
|
|
static const BluetoothUuid kMapMas = {
|
|
{
|
|
0x00, 0x00, 0x11, 0x32, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
}
|
|
};
|
|
// UUID of Map Mns
|
|
static const BluetoothUuid kMapMns = {
|
|
{
|
|
0x00, 0x00, 0x11, 0x33, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
}
|
|
};
|
|
// UUID used in Map OBEX MAS target header
|
|
static const BluetoothUuid kMapMasObexTarget = {
|
|
{
|
|
0xBB, 0x58, 0x2B, 0x40, 0x42, 0x0C, 0x11, 0xDB,
|
|
0xB0, 0xDE, 0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66
|
|
}
|
|
};
|
|
|
|
// UUID used in Map OBEX MNS target header
|
|
static const BluetoothUuid kMapMnsObexTarget = {
|
|
{
|
|
0xBB, 0x58, 0x2B, 0x41, 0x42, 0x0C, 0x11, 0xDB,
|
|
0xB0, 0xDE, 0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66
|
|
}
|
|
};
|
|
|
|
StaticRefPtr<BluetoothMapSmsManager> sMapSmsManager;
|
|
static bool sInShutdown = false;
|
|
}
|
|
|
|
BEGIN_BLUETOOTH_NAMESPACE
|
|
|
|
NS_IMETHODIMP
|
|
BluetoothMapSmsManager::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const char16_t* aData)
|
|
{
|
|
MOZ_ASSERT(sMapSmsManager);
|
|
|
|
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
|
|
HandleShutdown();
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(false, "MapSmsManager got unexpected topic!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::HandleShutdown()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
sInShutdown = true;
|
|
Disconnect(nullptr);
|
|
sMapSmsManager = nullptr;
|
|
}
|
|
|
|
BluetoothMapSmsManager::BluetoothMapSmsManager() : mMasConnected(false),
|
|
mMnsConnected(false),
|
|
mNtfRequired(false)
|
|
{
|
|
mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
|
|
BuildDefaultFolderStructure();
|
|
}
|
|
|
|
BluetoothMapSmsManager::~BluetoothMapSmsManager()
|
|
{
|
|
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
|
|
BluetoothMapSmsManager::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
|
|
BluetoothMapSmsManager*
|
|
BluetoothMapSmsManager::Get()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Exit early if sMapSmsManager already exists
|
|
if (sMapSmsManager) {
|
|
return sMapSmsManager;
|
|
}
|
|
|
|
// 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
|
|
BluetoothMapSmsManager *manager = new BluetoothMapSmsManager();
|
|
if (NS_WARN_IF(!manager->Init())) {
|
|
return nullptr;
|
|
}
|
|
|
|
sMapSmsManager = manager;
|
|
|
|
return sMapSmsManager;
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::Listen()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Fail to listen if |mMasSocket| already exists
|
|
if (NS_WARN_IF(mMasSocket)) {
|
|
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 (mMasServerSocket) {
|
|
mMasServerSocket->Close();
|
|
mMasServerSocket = nullptr;
|
|
}
|
|
|
|
mMasServerSocket = new BluetoothSocket(this);
|
|
|
|
nsString sdpString;
|
|
#if ANDROID_VERSION >= 21
|
|
/**
|
|
* The way bluedroid handles MAP SDP record is very hacky.
|
|
* In Lollipop version, SDP string format would be instanceId + msg type
|
|
* + msg name. See add_maps_sdp in btif/src/btif_sock_sdp.c
|
|
*/
|
|
// MAS instance id
|
|
sdpString.AppendPrintf("%02x", SDP_SMS_MMS_INSTANCE_ID);
|
|
// Supported message type
|
|
sdpString.AppendPrintf("%02x", SDP_MESSAGE_TYPE_SMS_GSM |
|
|
SDP_MESSAGE_TYPE_SMS_CDMA |
|
|
SDP_MESSAGE_TYPE_MMS);
|
|
#endif
|
|
/**
|
|
* SDP service name, we don't assign RFCOMM channel directly
|
|
* bluedroid automatically assign channel number randomly.
|
|
*/
|
|
sdpString.AppendLiteral("SMS/MMS Message Access");
|
|
nsresult rv = mMasServerSocket->Listen(sdpString, kMapMas,
|
|
BluetoothSocketType::RFCOMM, -1, false,
|
|
true);
|
|
if (NS_FAILED(rv)) {
|
|
mMasServerSocket = nullptr;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::MnsDataHandler(UnixSocketBuffer* aMessage)
|
|
{
|
|
// Ensure valid access to data[0], i.e., opCode
|
|
int receivedLength = aMessage->GetSize();
|
|
if (receivedLength < 1) {
|
|
BT_LOGR("Receive empty response packet");
|
|
return;
|
|
}
|
|
|
|
const uint8_t* data = aMessage->GetData();
|
|
uint8_t opCode = data[0];
|
|
if (opCode != ObexResponseCode::Success) {
|
|
BT_LOGR("Unexpected OpCode: %x", opCode);
|
|
if (mLastCommand == ObexRequestCode::Put ||
|
|
mLastCommand == ObexRequestCode::Abort ||
|
|
mLastCommand == ObexRequestCode::PutFinal) {
|
|
SendMnsDisconnectRequest();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::MasDataHandler(UnixSocketBuffer* aMessage)
|
|
{
|
|
/**
|
|
* Ensure
|
|
* - valid access to data[0], i.e., opCode
|
|
* - received packet length smaller than max packet length
|
|
*/
|
|
int receivedLength = aMessage->GetSize();
|
|
if (receivedLength < 1 || receivedLength > MAX_PACKET_LENGTH) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
const uint8_t* data = aMessage->GetData();
|
|
uint8_t opCode = data[0];
|
|
ObexHeaderSet pktHeaders;
|
|
nsString type;
|
|
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 (receivedLength < 7 ||
|
|
!ParseHeaders(&data[7], receivedLength - 7, &pktHeaders)) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
// "Establishing an OBEX Session"
|
|
// The OBEX header target shall equal to MAS obex target UUID.
|
|
if (!CompareHeaderTarget(pktHeaders)) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
mRemoteMaxPacketLength = ((static_cast<int>(data[5]) << 8) | data[6]);
|
|
|
|
if (mRemoteMaxPacketLength < 255) {
|
|
BT_LOGR("Remote maximum packet length %d", mRemoteMaxPacketLength);
|
|
mRemoteMaxPacketLength = 0;
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
ReplyToConnect();
|
|
AfterMapSmsConnected();
|
|
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 (receivedLength < 3 ||
|
|
!ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
ReplyToDisconnectOrAbort();
|
|
AfterMapSmsDisconnected();
|
|
break;
|
|
case ObexRequestCode::SetPath: {
|
|
// Section 3.3.6 "SetPath", IrOBEX 1.2
|
|
// [opcode:1][length:2][flags:1][contants:1][Headers:var]
|
|
if (receivedLength < 5 ||
|
|
!ParseHeaders(&data[5], receivedLength - 5, &pktHeaders)) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
uint8_t response = SetPath(data[3], pktHeaders);
|
|
if (response != ObexResponseCode::Success) {
|
|
ReplyError(response);
|
|
return;
|
|
}
|
|
|
|
ReplyToSetPath();
|
|
}
|
|
break;
|
|
case ObexRequestCode::Put:
|
|
case ObexRequestCode::PutFinal:
|
|
// Section 3.3.3 "Put", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
if (receivedLength < 3 ||
|
|
!ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
if (pktHeaders.Has(ObexHeaderId::Type)) {
|
|
pktHeaders.GetContentType(type);
|
|
BT_LOGR("Type: %s", NS_ConvertUTF16toUTF8(type).get());
|
|
ReplyToPut();
|
|
|
|
if (type.EqualsLiteral("x-bt/MAP-NotificationRegistration")) {
|
|
HandleNotificationRegistration(pktHeaders);
|
|
} else if (type.EqualsLiteral("x-bt/MAP-event-report")) {
|
|
HandleEventReport(pktHeaders);
|
|
} else if (type.EqualsLiteral("x-bt/messageStatus")) {
|
|
HandleSetMessageStatus(pktHeaders);
|
|
}
|
|
}
|
|
break;
|
|
case ObexRequestCode::Get:
|
|
case ObexRequestCode::GetFinal: {
|
|
// [opcode:1][length:2][Headers:var]
|
|
if (receivedLength < 3 ||
|
|
!ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
pktHeaders.GetContentType(type);
|
|
if (type.EqualsLiteral("x-obex/folder-listing")) {
|
|
HandleSmsMmsFolderListing(pktHeaders);
|
|
} else if (type.EqualsLiteral("x-bt/MAP-msg-listing")) {
|
|
HandleSmsMmsMsgListing(pktHeaders);
|
|
} else if (type.EqualsLiteral("x-bt/message")) {
|
|
HandleSmsMmsGetMessage(pktHeaders);
|
|
} else {
|
|
BT_LOGR("Unknown MAP request type: %s",
|
|
NS_ConvertUTF16toUTF8(type).get());
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
ReplyError(ObexResponseCode::NotImplemented);
|
|
BT_LOGR("Unrecognized ObexRequestCode %x", opCode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Virtual function of class SocketConsumer
|
|
void
|
|
BluetoothMapSmsManager::ReceiveSocketData(BluetoothSocket* aSocket,
|
|
nsAutoPtr<UnixSocketBuffer>& aMessage)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (aSocket == mMnsSocket) {
|
|
MnsDataHandler(aMessage);
|
|
} else {
|
|
MasDataHandler(aMessage);
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::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] != kMapMasObexTarget.mUuid[i]) {
|
|
BT_LOGR("UUID mismatch: received target[%d]=0x%x != 0x%x",
|
|
i, targetPtr[i], kMapMasObexTarget.mUuid[i]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint8_t
|
|
BluetoothMapSmsManager::SetPath(uint8_t flags,
|
|
const ObexHeaderSet& aHeader)
|
|
{
|
|
// Section 5.2 "SetPath Function", MapSms 1.2
|
|
// flags bit 1 must be 1 and bit 2~7 be 0
|
|
if ((flags >> 1) != 1) {
|
|
BT_LOGR("Illegal flags [0x%x]: bits 1~7 must be 0x01", flags);
|
|
return ObexResponseCode::BadRequest;
|
|
}
|
|
|
|
/**
|
|
* Three cases:
|
|
* 1) Go up 1 level - flags bit 0 is 1
|
|
* 2) Go back to root - flags bit 0 is 0 AND name header is empty
|
|
* 3) Go down 1 level - flags bit 0 is 0 AND name header is not empty,
|
|
* where name header is the name of child folder
|
|
*/
|
|
if (flags & 1) {
|
|
// Go up 1 level
|
|
BluetoothMapFolder* parent = mCurrentFolder->GetParentFolder();
|
|
if (!parent) {
|
|
mCurrentFolder = parent;
|
|
BT_LOGR("MAS SetPath Go up 1 level");
|
|
}
|
|
} else {
|
|
MOZ_ASSERT(aHeader.Has(ObexHeaderId::Name));
|
|
|
|
nsString childFolderName;
|
|
aHeader.GetName(childFolderName);
|
|
|
|
if (childFolderName.IsEmpty()) {
|
|
// Go back to root
|
|
mCurrentFolder = mRootFolder;
|
|
BT_LOGR("MAS SetPath Go back to root");
|
|
} else {
|
|
// Go down 1 level
|
|
BluetoothMapFolder* child = mCurrentFolder->GetSubFolder(childFolderName);
|
|
if (!child) {
|
|
BT_LOGR("Illegal sub-folder name [%s]",
|
|
NS_ConvertUTF16toUTF8(childFolderName).get());
|
|
return ObexResponseCode::NotFound;
|
|
}
|
|
|
|
mCurrentFolder = child;
|
|
BT_LOGR("MAS SetPath Go down to 1 level");
|
|
}
|
|
}
|
|
|
|
mCurrentFolder->DumpFolderInfo();
|
|
|
|
return ObexResponseCode::Success;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::AfterMapSmsConnected()
|
|
{
|
|
mMasConnected = true;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::AfterMapSmsDisconnected()
|
|
{
|
|
mMasConnected = false;
|
|
// To ensure we close MNS connection
|
|
DestroyMnsObexConnection();
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::IsConnected()
|
|
{
|
|
return mMasConnected;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::GetAddress(nsAString& aDeviceAddress)
|
|
{
|
|
return mMasSocket->GetAddress(aDeviceAddress);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::ReplyToConnect()
|
|
{
|
|
if (mMasConnected) {
|
|
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] = BluetoothMapSmsManager::MAX_PACKET_LENGTH >> 8;
|
|
req[6] = (uint8_t)BluetoothMapSmsManager::MAX_PACKET_LENGTH;
|
|
|
|
// Section 6.4 "Establishing an OBEX Session", MapSms 1.2
|
|
// Headers: [Who:16][Connection ID]
|
|
index += AppendHeaderWho(&req[index], 255, kMapMasObexTarget.mUuid,
|
|
sizeof(BluetoothUuid));
|
|
index += AppendHeaderConnectionId(&req[index], 0x01);
|
|
SendMasObexData(req, ObexResponseCode::Success, index);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::ReplyToDisconnectOrAbort()
|
|
{
|
|
if (!mMasConnected) {
|
|
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;
|
|
|
|
SendMasObexData(req, ObexResponseCode::Success, index);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::ReplyToSetPath()
|
|
{
|
|
if (!mMasConnected) {
|
|
return;
|
|
}
|
|
|
|
// Section 3.3.6 "SetPath", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t req[255];
|
|
int index = 3;
|
|
|
|
SendMasObexData(req, ObexResponseCode::Success, index);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::ReplyToPut()
|
|
{
|
|
if (!mMasConnected) {
|
|
return;
|
|
}
|
|
|
|
// Section 3.3.3.2 "PutResponse", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t req[255];
|
|
int index = 3;
|
|
|
|
SendMasObexData(req, ObexResponseCode::Success, index);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::CreateMnsObexConnection()
|
|
{
|
|
if (mMnsSocket) {
|
|
return;
|
|
}
|
|
|
|
mMnsSocket = new BluetoothSocket(this);
|
|
// Already encrypted in previous session
|
|
mMnsSocket->Connect(mDeviceAddress, kMapMns,
|
|
BluetoothSocketType::RFCOMM, -1, false, false);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::DestroyMnsObexConnection()
|
|
{
|
|
if (!mMnsSocket) {
|
|
return;
|
|
}
|
|
|
|
mMnsSocket->Close();
|
|
mMnsSocket = nullptr;
|
|
mNtfRequired = false;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::SendMnsConnectRequest()
|
|
{
|
|
MOZ_ASSERT(mMnsSocket);
|
|
|
|
// 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] = BluetoothMapSmsManager::MAX_PACKET_LENGTH >> 8;
|
|
req[6] = (uint8_t)BluetoothMapSmsManager::MAX_PACKET_LENGTH;
|
|
|
|
index += AppendHeaderTarget(&req[index], 255, kMapMnsObexTarget.mUuid,
|
|
sizeof(BluetoothUuid));
|
|
SendMnsObexData(req, ObexRequestCode::Connect, index);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::SendMnsDisconnectRequest()
|
|
{
|
|
MOZ_ASSERT(mMnsSocket);
|
|
|
|
if (!mMasConnected) {
|
|
return;
|
|
}
|
|
|
|
// Section 3.3.2 "Disconnect", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t req[255];
|
|
int index = 3;
|
|
|
|
SendMnsObexData(req, ObexRequestCode::Disconnect, index);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::HandleSmsMmsFolderListing(const ObexHeaderSet& aHeader)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
uint8_t buf[64];
|
|
uint16_t maxListCount = 0;
|
|
|
|
if (aHeader.GetAppParameter(Map::AppParametersTagId::MaxListCount,
|
|
buf, 64)) {
|
|
maxListCount = *((uint16_t *)buf);
|
|
// convert big endian to little endian
|
|
maxListCount = (maxListCount >> 8) | (maxListCount << 8);
|
|
}
|
|
|
|
uint16_t startOffset = 0;
|
|
if (aHeader.GetAppParameter(Map::AppParametersTagId::StartOffset,
|
|
buf, 64)) {
|
|
startOffset = *((uint16_t *)buf);
|
|
// convert big endian to little endian
|
|
startOffset = (startOffset >> 8) | (startOffset << 8);
|
|
}
|
|
|
|
// Folder listing size
|
|
int foldersize = mCurrentFolder->GetSubFolderCount();
|
|
|
|
// Convert little endian to big endian
|
|
uint8_t folderListingSizeValue[2];
|
|
folderListingSizeValue[0] = (foldersize & 0xFF00) >> 8;
|
|
folderListingSizeValue[1] = (foldersize & 0x00FF);
|
|
|
|
// Section 3.3.4 "GetResponse", IrOBEX 1.2
|
|
// [opcode:1][length:2][FolderListingSize:4][Headers:var] where
|
|
// Application Parameter [FolderListingSize:4] = [tagId:1][length:1][value: 2]
|
|
uint8_t appParameter[4];
|
|
AppendAppParameter(appParameter, sizeof(appParameter),
|
|
(uint8_t)Map::AppParametersTagId::FolderListingSize,
|
|
folderListingSizeValue, sizeof(folderListingSizeValue));
|
|
|
|
uint8_t resp[255];
|
|
int index = 3;
|
|
index += AppendHeaderAppParameters(&resp[index], 255, appParameter,
|
|
sizeof(appParameter));
|
|
|
|
/*
|
|
* MCE wants to query sub-folder size FolderListingSize AppParameter shall
|
|
* be used in the response if the value of MaxListCount in the request is 0.
|
|
* If MaxListCount = 0, the MSE shall ignore all other applications
|
|
* parameters that may be presented in the request. The response shall
|
|
* contain any Body header.
|
|
*/
|
|
if (maxListCount) {
|
|
nsString output;
|
|
mCurrentFolder->GetFolderListingObjectString(output, maxListCount,
|
|
startOffset);
|
|
index += AppendHeaderBody(&resp[index],
|
|
mRemoteMaxPacketLength - index,
|
|
reinterpret_cast<const uint8_t*>(
|
|
NS_ConvertUTF16toUTF8(output).get()),
|
|
NS_ConvertUTF16toUTF8(output).Length());
|
|
|
|
index += AppendHeaderEndOfBody(&resp[index]);
|
|
}
|
|
|
|
SendMasObexData(resp, ObexResponseCode::Success, index);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::AppendBtNamedValueByTagId(
|
|
const ObexHeaderSet& aHeader,
|
|
InfallibleTArray<BluetoothNamedValue>& aValues,
|
|
const Map::AppParametersTagId aTagId)
|
|
{
|
|
uint8_t buf[64];
|
|
if (!aHeader.GetAppParameter(aTagId, buf, 64)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Follow MAP 6.3.1 Application Parameter Header
|
|
*/
|
|
switch (aTagId) {
|
|
case Map::AppParametersTagId::MaxListCount: {
|
|
uint16_t maxListCount = *((uint16_t *)buf);
|
|
// convert big endian to little endian
|
|
maxListCount = (maxListCount >> 8) | (maxListCount << 8);
|
|
BT_LOGR("max list count: %d", maxListCount);
|
|
AppendNamedValue(aValues, "maxListCount", maxListCount);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::StartOffset: {
|
|
uint16_t startOffset = *((uint16_t *)buf);
|
|
// convert big endian to little endian
|
|
startOffset = (startOffset >> 8) | (startOffset << 8);
|
|
BT_LOGR("start offset : %d", startOffset);
|
|
AppendNamedValue(aValues, "startOffset", startOffset);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::SubjectLength: {
|
|
uint8_t subLength = *((uint8_t *)buf);
|
|
// convert big endian to little endian
|
|
subLength = (subLength >> 8) | (subLength << 8);
|
|
BT_LOGR("msg subLength : %d", subLength);
|
|
AppendNamedValue(aValues, "subLength", subLength);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::ParameterMask: {
|
|
// 4 bytes
|
|
uint32_t parameterMask = *((uint32_t *)buf);
|
|
// convert big endian to little endian
|
|
parameterMask = (parameterMask >> 8) | (parameterMask << 8);
|
|
BT_LOGR("msg parameterMask : %d", parameterMask);
|
|
AppendNamedValue(aValues, "parameterMask", parameterMask);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterMessageType: {
|
|
uint8_t filterMessageType = *((uint8_t *)buf);
|
|
// convert big endian to little endian
|
|
filterMessageType = (filterMessageType >> 8) | (filterMessageType << 8);
|
|
BT_LOGR("msg filterMessageType : %d", filterMessageType);
|
|
AppendNamedValue(aValues, "filterMessageType", filterMessageType);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterPeriodBegin: {
|
|
nsCString filterPeriodBegin((char *) buf);
|
|
BT_LOGR("msg FilterPeriodBegin : %s", filterPeriodBegin.get());
|
|
AppendNamedValue(aValues, "filterPeriodBegin", filterPeriodBegin);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterPeriodEnd: {
|
|
nsCString filterPeriodEnd((char*)buf);
|
|
BT_LOGR("msg filterPeriodEnd : %s", filterPeriodEnd.get());
|
|
AppendNamedValue(aValues, "filterPeriodEnd", filterPeriodEnd);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterReadStatus: {
|
|
uint8_t filterReadStatus = *((uint8_t *)buf);
|
|
// convert big endian to little endian
|
|
filterReadStatus = (filterReadStatus >> 8) | (filterReadStatus << 8);
|
|
BT_LOGR("msg filter read status : %d", filterReadStatus);
|
|
AppendNamedValue(aValues, "filterReadStatus", filterReadStatus);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterRecipient: {
|
|
nsCString filterRecipient((char*) buf);
|
|
BT_LOGR("msg filterRecipient : %s", filterRecipient.get());
|
|
AppendNamedValue(aValues, "filterRecipient", filterRecipient);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterOriginator: {
|
|
nsCString filterOriginator((char*) buf);
|
|
BT_LOGR("msg filter Originator : %s", filterOriginator.get());
|
|
AppendNamedValue(aValues, "filterOriginator", filterOriginator);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterPriority: {
|
|
uint8_t filterPriority = *((uint8_t *)buf);
|
|
// convert big endian to little endian
|
|
filterPriority = (filterPriority >> 8) | (filterPriority << 8);
|
|
BT_LOGR("msg filter priority: %d", filterPriority);
|
|
AppendNamedValue(aValues, "filterPriority", filterPriority);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::Attachment: {
|
|
uint8_t attachment = *((uint8_t *)buf);
|
|
// convert big endian to little endian
|
|
attachment = (attachment >> 8) | (attachment << 8);
|
|
BT_LOGR("msg filter attachment: %d", attachment);
|
|
AppendNamedValue(aValues, "attachment", attachment);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::Charset: {
|
|
uint8_t charset = *((uint8_t *)buf);
|
|
// convert big endian to little endian
|
|
charset = (charset >> 8) | (charset << 8);
|
|
BT_LOGR("msg filter charset: %d", charset);
|
|
AppendNamedValue(aValues, "charset", charset);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::StatusIndicator: {
|
|
uint8_t statusIndicator = *((uint8_t *)buf);
|
|
// convert big endian to little endian
|
|
statusIndicator = (statusIndicator >> 8) | (statusIndicator << 8);
|
|
BT_LOGR("msg filter statusIndicator: %d", statusIndicator);
|
|
AppendNamedValue(aValues, "statusIndicator",
|
|
static_cast<uint32_t>(statusIndicator));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::StatusValue: {
|
|
uint8_t statusValue = *((uint8_t *)buf);
|
|
// convert big endian to little endian
|
|
statusValue = (statusValue >> 8) | (statusValue << 8);
|
|
BT_LOGR("msg filter statusvalue: %d", statusValue);
|
|
AppendNamedValue(aValues, "statusValue",
|
|
static_cast<uint32_t>(statusValue));
|
|
break;
|
|
}
|
|
default:
|
|
BT_LOGR("Unsupported AppParameterTag: %x", aTagId);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::HandleSmsMmsMsgListing(const ObexHeaderSet& aHeader)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
|
|
InfallibleTArray<BluetoothNamedValue> data;
|
|
|
|
static Map::AppParametersTagId sMsgListingParameters[] = {
|
|
[0] = Map::AppParametersTagId::MaxListCount,
|
|
[1] = Map::AppParametersTagId::StartOffset,
|
|
[2] = Map::AppParametersTagId::SubjectLength,
|
|
[3] = Map::AppParametersTagId::ParameterMask,
|
|
[4] = Map::AppParametersTagId::FilterMessageType,
|
|
[5] = Map::AppParametersTagId::FilterPeriodBegin,
|
|
[6] = Map::AppParametersTagId::FilterPeriodEnd,
|
|
[7] = Map::AppParametersTagId::FilterReadStatus,
|
|
[8] = Map::AppParametersTagId::FilterRecipient,
|
|
[9] = Map::AppParametersTagId::FilterOriginator,
|
|
[10] = Map::AppParametersTagId::FilterPriority
|
|
};
|
|
|
|
for (uint8_t i = 0; i < MOZ_ARRAY_LENGTH(sMsgListingParameters); i++) {
|
|
AppendBtNamedValueByTagId(aHeader, data, sMsgListingParameters[i]);
|
|
}
|
|
|
|
bs->DistributeSignal(NS_LITERAL_STRING(MAP_MESSAGES_LISTING_REQ_ID),
|
|
NS_LITERAL_STRING(KEY_ADAPTER),
|
|
data);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::HandleSmsMmsGetMessage(const ObexHeaderSet& aHeader)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
NS_ENSURE_TRUE_VOID(bs);
|
|
|
|
InfallibleTArray<BluetoothNamedValue> data;
|
|
nsString name;
|
|
aHeader.GetName(name);
|
|
AppendNamedValue(data, "handle", name);
|
|
|
|
AppendBtNamedValueByTagId(aHeader, data,
|
|
Map::AppParametersTagId::Attachment);
|
|
AppendBtNamedValueByTagId(aHeader, data,
|
|
Map::AppParametersTagId::Charset);
|
|
|
|
bs->DistributeSignal(NS_LITERAL_STRING(MAP_GET_MESSAGE_REQ_ID),
|
|
NS_LITERAL_STRING(KEY_ADAPTER),
|
|
data);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::BuildDefaultFolderStructure()
|
|
{
|
|
/* MAP specification defines virtual folders structure
|
|
* /
|
|
* /telecom
|
|
* /telecom/msg
|
|
* /telecom/msg/inbox
|
|
* /telecom/msg/draft
|
|
* /telecom/msg/outbox
|
|
* /telecom/msg/sent
|
|
* /telecom/msg/deleted
|
|
*/
|
|
mRootFolder = new BluetoothMapFolder(NS_LITERAL_STRING("root"), nullptr);
|
|
BluetoothMapFolder* folder =
|
|
mRootFolder->AddSubFolder(NS_LITERAL_STRING("telecom"));
|
|
folder = folder->AddSubFolder(NS_LITERAL_STRING("msg"));
|
|
|
|
// Add mandatory folders
|
|
folder->AddSubFolder(NS_LITERAL_STRING("inbox"));
|
|
folder->AddSubFolder(NS_LITERAL_STRING("sent"));
|
|
folder->AddSubFolder(NS_LITERAL_STRING("deleted"));
|
|
folder->AddSubFolder(NS_LITERAL_STRING("outbox"));
|
|
folder->AddSubFolder(NS_LITERAL_STRING("draft"));
|
|
mCurrentFolder = mRootFolder;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::HandleNotificationRegistration(
|
|
const ObexHeaderSet& aHeader)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
uint8_t buf[64];
|
|
if (!aHeader.GetAppParameter(Map::AppParametersTagId::NotificationStatus,
|
|
buf, 64)) {
|
|
return;
|
|
}
|
|
|
|
bool ntfRequired = static_cast<bool>(buf[0]);
|
|
if (mNtfRequired == ntfRequired) {
|
|
// Ignore request
|
|
return;
|
|
}
|
|
|
|
mNtfRequired = ntfRequired;
|
|
/*
|
|
* Initialization sequence for a MAP session that uses both the Messsage
|
|
* Access service and the Message Notification service. The MNS connection
|
|
* shall be established by the first SetNotificationRegistration set to ON
|
|
* during MAP session. Only one MNS connection per device pair.
|
|
* Section 6.4.2, MAP
|
|
* If the Message Access connection is disconnected after Message Notification
|
|
* connection establishment, this will automatically indicate a MAS
|
|
* Notification-Deregistration for this MAS instance.
|
|
*/
|
|
if (mNtfRequired) {
|
|
CreateMnsObexConnection();
|
|
} else {
|
|
/*
|
|
* TODO: we shall check multiple MAS instances unregister notification to
|
|
* drop MNS connection, but now we only support SMS/MMS, so drop connection
|
|
* directly.
|
|
*/
|
|
DestroyMnsObexConnection();
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::HandleEventReport(const ObexHeaderSet& aHeader)
|
|
{
|
|
// TODO: Handle event report in Bug 1166666
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::HandleSetMessageStatus(const ObexHeaderSet& aHeader)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
NS_ENSURE_TRUE_VOID(bs);
|
|
|
|
InfallibleTArray<BluetoothNamedValue> data;
|
|
nsString name;
|
|
aHeader.GetName(name);
|
|
/* The Name header shall contain the handle of the message the status of which
|
|
* shall be modified. The handle shall be represented by a null-terminated
|
|
* Unicode text string with 16 hexadecimal digits.
|
|
*/
|
|
AppendNamedValue(data, "handle", name);
|
|
|
|
AppendBtNamedValueByTagId(aHeader, data,
|
|
Map::AppParametersTagId::StatusIndicator);
|
|
AppendBtNamedValueByTagId(aHeader, data,
|
|
Map::AppParametersTagId::StatusValue);
|
|
|
|
bs->DistributeSignal(NS_LITERAL_STRING(MAP_SET_MESSAGE_STATUS_REQ_ID),
|
|
NS_LITERAL_STRING(KEY_ADAPTER), data);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::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;
|
|
|
|
SendMasObexData(req, aError, index);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::SendMasObexData(uint8_t* aData, uint8_t aOpcode,
|
|
int aSize)
|
|
{
|
|
SetObexPacketInfo(aData, aOpcode, aSize);
|
|
mMasSocket->SendSocketData(new UnixSocketRawData(aData, aSize));
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::SendMnsObexData(uint8_t* aData, uint8_t aOpcode,
|
|
int aSize)
|
|
{
|
|
mLastCommand = aOpcode;
|
|
SetObexPacketInfo(aData, aOpcode, aSize);
|
|
mMnsSocket->SendSocketData(new UnixSocketRawData(aData, aSize));
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::OnSocketConnectSuccess(BluetoothSocket* aSocket)
|
|
{
|
|
MOZ_ASSERT(aSocket);
|
|
|
|
// MNS socket is connected
|
|
if (aSocket == mMnsSocket) {
|
|
mMnsConnected = true;
|
|
SendMnsConnectRequest();
|
|
return;
|
|
}
|
|
// MAS socket is connected
|
|
// Close server socket as only one session is allowed at a time
|
|
mMasServerSocket.swap(mMasSocket);
|
|
|
|
// Cache device address since we can't get socket address when a remote
|
|
// device disconnect with us.
|
|
mMasSocket->GetAddress(mDeviceAddress);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::OnSocketConnectError(BluetoothSocket* aSocket)
|
|
{
|
|
// MNS socket connection error
|
|
if (aSocket == mMnsSocket) {
|
|
mMnsConnected = false;
|
|
mMnsSocket = nullptr;
|
|
return;
|
|
}
|
|
|
|
// MAS socket connection error
|
|
mMasServerSocket = nullptr;
|
|
mMasSocket = nullptr;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::OnSocketDisconnect(BluetoothSocket* aSocket)
|
|
{
|
|
MOZ_ASSERT(aSocket);
|
|
|
|
// MNS socket is disconnected
|
|
if (aSocket == mMnsSocket) {
|
|
mMnsConnected = false;
|
|
mMnsSocket = nullptr;
|
|
BT_LOGR("MNS socket disconnected");
|
|
return;
|
|
}
|
|
|
|
// MAS server socket is closed
|
|
if (aSocket != mMasSocket) {
|
|
// Do nothing when a listening server socket is closed.
|
|
return;
|
|
}
|
|
|
|
// MAS socket is disconnected
|
|
AfterMapSmsDisconnected();
|
|
mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
|
|
mMasSocket = nullptr;
|
|
|
|
Listen();
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::Disconnect(BluetoothProfileController* aController)
|
|
{
|
|
if (!mMasSocket) {
|
|
BT_WARNING("%s: No ongoing connection to disconnect", __FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
mMasSocket->Close();
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(BluetoothMapSmsManager, nsIObserver)
|
|
|
|
void
|
|
BluetoothMapSmsManager::Connect(const nsAString& aDeviceAddress,
|
|
BluetoothProfileController* aController)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
|
|
const nsAString& aServiceUuid,
|
|
int aChannel)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::OnConnect(const nsAString& aErrorStr)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::OnDisconnect(const nsAString& aErrorStr)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::Reset()
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
END_BLUETOOTH_NAMESPACE
|