mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-10 11:54:40 +00:00
1603 lines
48 KiB
C++
1603 lines
48 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/dom/BluetoothMapParametersBinding.h"
|
|
#include "mozilla/dom/ipc/BlobParent.h"
|
|
#include "mozilla/Endian.h"
|
|
#include "mozilla/dom/File.h"
|
|
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIObserverService.h"
|
|
|
|
#define FILTER_NO_SMS_GSM 0x01
|
|
#define FILTER_NO_SMS_CDMA 0x02
|
|
#define FILTER_NO_EMAIL 0x04
|
|
#define FILTER_NO_MMS 0x08
|
|
|
|
USING_BLUETOOTH_NAMESPACE
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::ipc;
|
|
|
|
namespace {
|
|
// UUID of Map Mas
|
|
static const BluetoothUuid kMapMas(MAP_MAS);
|
|
|
|
// UUID of Map Mns
|
|
static const BluetoothUuid kMapMns(MAP_MNS);
|
|
|
|
// 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()
|
|
: mBodyRequired(false)
|
|
, mFractionDeliverRequired(false)
|
|
, mMasConnected(false)
|
|
, mMnsConnected(false)
|
|
, mNtfRequired(false)
|
|
{
|
|
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) {
|
|
SendReply(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)) {
|
|
SendReply(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
// "Establishing an OBEX Session"
|
|
// The OBEX header target shall equal to MAS obex target UUID.
|
|
if (!CompareHeaderTarget(pktHeaders)) {
|
|
SendReply(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
mRemoteMaxPacketLength = BigEndian::readUint16(&data[5]);
|
|
|
|
if (mRemoteMaxPacketLength < kObexLeastMaxSize) {
|
|
BT_LOGR("Remote maximum packet length %d", mRemoteMaxPacketLength);
|
|
mRemoteMaxPacketLength = 0;
|
|
SendReply(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)) {
|
|
SendReply(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)) {
|
|
SendReply(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
uint8_t response = SetPath(data[3], pktHeaders);
|
|
if (response != ObexResponseCode::Success) {
|
|
SendReply(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)) {
|
|
SendReply(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
// Multi-packet PUT request (0x02) may not contain Type header
|
|
if (!pktHeaders.Has(ObexHeaderId::Type)) {
|
|
BT_LOGR("Missing OBEX PUT request Type header");
|
|
SendReply(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
pktHeaders.GetContentType(type);
|
|
BT_LOGR("Type: %s", NS_ConvertUTF16toUTF8(type).get());
|
|
|
|
if (type.EqualsLiteral("x-bt/MAP-NotificationRegistration")) {
|
|
HandleNotificationRegistration(pktHeaders);
|
|
ReplyToPut();
|
|
} else if (type.EqualsLiteral("x-bt/messageStatus")) {
|
|
HandleSetMessageStatus(pktHeaders);
|
|
} else if (type.EqualsLiteral("x-bt/message")) {
|
|
HandleSmsMmsPushMessage(pktHeaders);
|
|
} else if (type.EqualsLiteral("x-bt/MAP-messageUpdate")) {
|
|
/* MAP 5.9, There is no concept for Sms/Mms to update inbox. If the
|
|
* MSE does NOT allowed the polling of its mailbox it shall answer
|
|
* with a 'Not implemented' error response.
|
|
*/
|
|
SendReply(ObexResponseCode::NotImplemented);
|
|
} else {
|
|
BT_LOGR("Unknown MAP PUT request type: %s",
|
|
NS_ConvertUTF16toUTF8(type).get());
|
|
SendReply(ObexResponseCode::NotImplemented);
|
|
}
|
|
break;
|
|
case ObexRequestCode::Get:
|
|
case ObexRequestCode::GetFinal: {
|
|
/* When |mDataStream| requires multiple response packets to complete,
|
|
* the client should continue to issue GET requests until the final body
|
|
* information (i.e., End-of-Body header) arrives, along with
|
|
* ObexResponseCode::Success
|
|
*/
|
|
if (mDataStream) {
|
|
auto res = MakeUnique<uint8_t[]>(mRemoteMaxPacketLength);
|
|
if (!ReplyToGetWithHeaderBody(Move(res), kObexRespHeaderSize)) {
|
|
BT_LOGR("Failed to reply to MAP GET request.");
|
|
SendReply(ObexResponseCode::InternalServerError);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// [opcode:1][length:2][Headers:var]
|
|
if (receivedLength < 3 ||
|
|
!ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) {
|
|
SendReply(ObexResponseCode::BadRequest);
|
|
return;
|
|
}
|
|
|
|
if (!pktHeaders.Has(ObexHeaderId::Type)) {
|
|
BT_LOGR("Missing OBEX GET request Type header");
|
|
SendReply(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 GET request type: %s",
|
|
NS_ConvertUTF16toUTF8(type).get());
|
|
SendReply(ObexResponseCode::NotImplemented);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
SendReply(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)
|
|
{
|
|
const ObexHeader* header = aHeader.GetHeader(ObexHeaderId::Target);
|
|
|
|
if (!header) {
|
|
BT_LOGR("No ObexHeaderId::Target in header");
|
|
return false;
|
|
}
|
|
|
|
if (header->mDataLength != sizeof(BluetoothUuid)) {
|
|
BT_LOGR("Length mismatch: %d != 16", header->mDataLength);
|
|
return false;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < sizeof(BluetoothUuid); i++) {
|
|
if (header->mData[i] != kMapMasObexTarget.mUuid[i]) {
|
|
BT_LOGR("UUID mismatch: received target[%d]=0x%x != 0x%x",
|
|
i, header->mData[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;
|
|
mBodyRequired = false;
|
|
mFractionDeliverRequired = false;
|
|
|
|
// To ensure we close MNS connection
|
|
DestroyMnsObexConnection();
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::IsConnected()
|
|
{
|
|
return mMasConnected;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::GetAddress(BluetoothAddress& 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
|
|
BigEndian::writeUint16(&req[5], 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);
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::ReplyToGetWithHeaderBody(UniquePtr<uint8_t[]> aResponse,
|
|
unsigned int aIndex)
|
|
{
|
|
if (!mMasConnected) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* This response consists of following parts:
|
|
* - Part 1: [response code:1][length:2]
|
|
* - Part 2a: [headerId:1][length:2][EndOfBody:0]
|
|
* or
|
|
* - Part 2b: [headerId:1][length:2][Body:var]
|
|
*/
|
|
// ---- Part 1: [response code:1][length:2] ---- //
|
|
// [response code:1][length:2] will be set in |SendObexData|.
|
|
// Reserve index for them here
|
|
uint64_t bytesAvailable = 0;
|
|
nsresult rv = mDataStream->Available(&bytesAvailable);
|
|
if (NS_FAILED(rv)) {
|
|
BT_LOGR("Failed to get available bytes from input stream. rv=0x%x",
|
|
static_cast<uint32_t>(rv));
|
|
return false;
|
|
}
|
|
|
|
/* In practice, some platforms can only handle zero length End-of-Body
|
|
* header separately with Body header.
|
|
* Thus, append End-of-Body only if the data stream had been sent out,
|
|
* otherwise, send 'Continue' to request for next GET request.
|
|
*/
|
|
unsigned int opcode;
|
|
if (!bytesAvailable) {
|
|
// ---- Part 2a: [headerId:1][length:2][EndOfBody:0] ---- //
|
|
aIndex += AppendHeaderEndOfBody(&aResponse[aIndex]);
|
|
|
|
// Close input stream
|
|
mDataStream->Close();
|
|
mDataStream = nullptr;
|
|
|
|
opcode = ObexResponseCode::Success;
|
|
} else {
|
|
// ---- Part 2b: [headerId:1][length:2][Body:var] ---- //
|
|
MOZ_ASSERT(mDataStream);
|
|
|
|
// Compute remaining packet size to append Body, excluding Body's header
|
|
uint32_t remainingPacketSize =
|
|
mRemoteMaxPacketLength - kObexBodyHeaderSize - aIndex;
|
|
|
|
// Read blob data from input stream
|
|
uint32_t numRead = 0;
|
|
nsAutoArrayPtr<char> buf(new char[remainingPacketSize]);
|
|
nsresult rv = mDataStream->Read(buf, remainingPacketSize, &numRead);
|
|
if (NS_FAILED(rv)) {
|
|
BT_LOGR("Failed to read from input stream. rv=0x%x",
|
|
static_cast<uint32_t>(rv));
|
|
return false;
|
|
}
|
|
|
|
// |numRead| must be non-zero
|
|
MOZ_ASSERT(numRead);
|
|
|
|
aIndex += AppendHeaderBody(&aResponse[aIndex],
|
|
remainingPacketSize,
|
|
reinterpret_cast<uint8_t*>(buf.get()),
|
|
numRead);
|
|
|
|
opcode = ObexResponseCode::Continue;
|
|
}
|
|
|
|
SendMasObexData(Move(aResponse), opcode, aIndex);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::ReplyToPut()
|
|
{
|
|
if (!mMasConnected) {
|
|
return;
|
|
}
|
|
|
|
// Section 3.3.3.2 "PutResponse", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t req[kObexRespHeaderSize];
|
|
|
|
SendMasObexData(req, ObexResponseCode::Success, kObexRespHeaderSize);
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::ReplyToFolderListing(long aMasId,
|
|
const nsAString& aFolderlists)
|
|
{
|
|
// TODO: Implement this for future Email support
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::ReplyToMessagesListing(BlobParent* aActor,
|
|
long aMasId,
|
|
bool aNewMessage,
|
|
const nsAString& aTimestamp,
|
|
int aSize)
|
|
{
|
|
RefPtr<BlobImpl> impl = aActor->GetBlobImpl();
|
|
RefPtr<Blob> blob = Blob::Create(nullptr, impl);
|
|
|
|
return ReplyToMessagesListing(blob.get(), aMasId, aNewMessage, aTimestamp,
|
|
aSize);
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::ReplyToMessagesListing(Blob* aBlob, long aMasId,
|
|
bool aNewMessage,
|
|
const nsAString& aTimestamp,
|
|
int aSize)
|
|
{
|
|
/* If the response code is 0x90 or 0xA0, response consists of following parts:
|
|
* - Part 1: [response code:1][length:2]
|
|
* - Part 2: [headerId:1][length:2][appParam:var]
|
|
* where [appParam:var] includes:
|
|
* [NewMessage:3] = [tagId:1][length:1][value:1]
|
|
* [MseTime:var] = [tagId:1][length:1][value:var]
|
|
* [MessageListingSize:4] = [tagId:1][length:1][value:2]
|
|
* If mBodyRequired is true,
|
|
* - Part 3: [headerId:1][length:2][Body:var]
|
|
*/
|
|
// ---- Part 1: [response code:1][length:2] ---- //
|
|
// [response code:1][length:2] will be set in |SendObexData|.
|
|
// Reserve index here
|
|
auto res = MakeUnique<uint8_t[]>(mRemoteMaxPacketLength);
|
|
unsigned int index = kObexRespHeaderSize;
|
|
|
|
// ---- Part 2: headerId:1][length:2][appParam:var] ---- //
|
|
// MSETime - String with the current time basis and UTC-offset of the MSE
|
|
nsCString timestampStr = NS_ConvertUTF16toUTF8(aTimestamp);
|
|
const uint8_t* str = reinterpret_cast<const uint8_t*>(timestampStr.get());
|
|
uint8_t len = timestampStr.Length();
|
|
|
|
// Total length: [NewMessage:3] + [MseTime:var] + [MessageListingSize:4]
|
|
nsAutoArrayPtr<uint8_t> appParameters(new uint8_t[len + 9]);
|
|
uint8_t newMessage = aNewMessage ? 1 : 0;
|
|
|
|
AppendAppParameter(appParameters,
|
|
3,
|
|
(uint8_t) Map::AppParametersTagId::NewMessage,
|
|
&newMessage,
|
|
sizeof(newMessage));
|
|
|
|
AppendAppParameter(appParameters + 3,
|
|
len + 2,
|
|
(uint8_t) Map::AppParametersTagId::MSETime,
|
|
str,
|
|
len);
|
|
|
|
uint8_t msgListingSize[2];
|
|
BigEndian::writeUint16(&msgListingSize[0], aSize);
|
|
|
|
AppendAppParameter(appParameters + 5 + len,
|
|
4,
|
|
(uint8_t) Map::AppParametersTagId::MessagesListingSize,
|
|
msgListingSize,
|
|
sizeof(msgListingSize));
|
|
|
|
index += AppendHeaderAppParameters(&res[index],
|
|
mRemoteMaxPacketLength,
|
|
appParameters,
|
|
len + 9);
|
|
|
|
if (mBodyRequired) {
|
|
// Open input stream only if |mBodyRequired| is true
|
|
if (!GetInputStreamFromBlob(aBlob)) {
|
|
SendReply(ObexResponseCode::InternalServerError);
|
|
return false;
|
|
}
|
|
|
|
// ---- Part 3: [headerId:1][length:2][Body:var] ---- //
|
|
ReplyToGetWithHeaderBody(Move(res), index);
|
|
// Reset flag
|
|
mBodyRequired = false;
|
|
} else {
|
|
SendMasObexData(Move(res), ObexResponseCode::Success, index);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::ReplyToGetMessage(BlobParent* aActor, long aMasId)
|
|
{
|
|
RefPtr<BlobImpl> impl = aActor->GetBlobImpl();
|
|
RefPtr<Blob> blob = Blob::Create(nullptr, impl);
|
|
|
|
return ReplyToGetMessage(blob.get(), aMasId);
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::ReplyToGetMessage(Blob* aBlob, long aMasId)
|
|
{
|
|
if (!GetInputStreamFromBlob(aBlob)) {
|
|
SendReply(ObexResponseCode::InternalServerError);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* If the response code is 0x90 or 0xA0, response consists of following parts:
|
|
* - Part 1: [response code:1][length:2]
|
|
* If mFractionDeliverRequired is true,
|
|
* - Part 2: [headerId:1][length:2][appParameters:3]
|
|
* - Part 3: [headerId:1][length:2][Body:var]
|
|
* where [appParameters] includes:
|
|
* [FractionDeliver:3] = [tagId:1][length:1][value: 1]
|
|
* otherwise,
|
|
* - Part 2: [headerId:1][length:2][appParameters:3]
|
|
*/
|
|
// ---- Part 1: [response code:1][length:2] ---- //
|
|
// [response code:1][length:2] will be set in |SendObexData|.
|
|
// Reserve index here
|
|
auto res = MakeUnique<uint8_t[]>(mRemoteMaxPacketLength);
|
|
unsigned int index = kObexRespHeaderSize;
|
|
|
|
if (mFractionDeliverRequired) {
|
|
// ---- Part 2: [headerId:1][length:2][appParam:3] ---- //
|
|
uint8_t appParameters[3];
|
|
// TODO: Support FractionDeliver, reply "1(last)" now.
|
|
uint8_t fractionDeliver = 1;
|
|
AppendAppParameter(appParameters,
|
|
sizeof(appParameters),
|
|
(uint8_t) Map::AppParametersTagId::FractionDeliver,
|
|
&fractionDeliver,
|
|
sizeof(fractionDeliver));
|
|
|
|
index += AppendHeaderAppParameters(&res[index],
|
|
mRemoteMaxPacketLength,
|
|
appParameters,
|
|
sizeof(appParameters));
|
|
}
|
|
|
|
// TODO: Support bMessage encoding in bug 1166652.
|
|
// ---- Part 3: [headerId:1][length:2][Body:var] ---- //
|
|
ReplyToGetWithHeaderBody(Move(res), index);
|
|
mFractionDeliverRequired = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::ReplyToSendMessage(
|
|
long aMasId, const nsAString& aHandleId, bool aStatus)
|
|
{
|
|
if (!aStatus) {
|
|
SendReply(ObexResponseCode::InternalServerError);
|
|
return true;
|
|
}
|
|
|
|
/* Handle is mandatory if the response code is success (0x90 or 0xA0).
|
|
* The Name header shall be used to contain the handle that was assigned by
|
|
* the MSE device to the message that was pushed by the MCE device.
|
|
* The handle shall be represented by a null-terminated Unicode text strings
|
|
* with 16 hexadecimal digits.
|
|
*/
|
|
int len = aHandleId.Length();
|
|
nsAutoArrayPtr<uint8_t> handleId(new uint8_t[(len + 1) * 2]);
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
BigEndian::writeUint16(&handleId[i * 2], aHandleId[i]);
|
|
}
|
|
BigEndian::writeUint16(&handleId[len * 2], 0);
|
|
|
|
auto res = MakeUnique<uint8_t[]>(mRemoteMaxPacketLength);
|
|
int index = kObexRespHeaderSize;
|
|
index += AppendHeaderName(&res[index], mRemoteMaxPacketLength - index,
|
|
handleId, (len + 1) * 2);
|
|
SendMasObexData(Move(res), ObexResponseCode::Success, index);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::ReplyToSetMessageStatus(long aMasId, bool aStatus)
|
|
{
|
|
SendReply(aStatus ? ObexResponseCode::Success :
|
|
ObexResponseCode::InternalServerError);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::ReplyToMessageUpdate(long aMasId, bool aStatus)
|
|
{
|
|
SendReply(aStatus ? ObexResponseCode::Success :
|
|
ObexResponseCode::InternalServerError);
|
|
return true;
|
|
}
|
|
|
|
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 = BigEndian::readUint16(buf);
|
|
}
|
|
|
|
uint16_t startOffset = 0;
|
|
if (aHeader.GetAppParameter(Map::AppParametersTagId::StartOffset,
|
|
buf, 64)) {
|
|
startOffset = BigEndian::readUint16(buf);
|
|
}
|
|
|
|
// Folder listing size
|
|
int foldersize = mCurrentFolder->GetSubFolderCount();
|
|
|
|
uint8_t folderListingSizeValue[2];
|
|
BigEndian::writeUint16(&folderListingSizeValue[0], foldersize);
|
|
|
|
// 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 = BigEndian::readUint16(buf);
|
|
/* MAP specification 5.4.3.1/5.5.4.1
|
|
* If MaxListCount = 0, the response shall not contain the Body header.
|
|
* The MSE shall ignore the request-parameters "ListStartOffset",
|
|
* "SubjectLength" and "ParameterMask".
|
|
*/
|
|
mBodyRequired = (maxListCount != 0);
|
|
BT_LOGR("max list count: %d", maxListCount);
|
|
AppendNamedValue(aValues, "maxListCount",
|
|
static_cast<uint32_t>(maxListCount));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::StartOffset: {
|
|
uint16_t startOffset = BigEndian::readUint16(buf);
|
|
BT_LOGR("start offset : %d", startOffset);
|
|
AppendNamedValue(aValues, "startOffset",
|
|
static_cast<uint32_t>(startOffset));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::SubjectLength: {
|
|
uint8_t subLength = *((uint8_t *)buf);
|
|
BT_LOGR("msg subLength : %d", subLength);
|
|
AppendNamedValue(aValues, "subLength", static_cast<uint32_t>(subLength));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::ParameterMask: {
|
|
InfallibleTArray<uint32_t> parameterMask = PackParameterMask(buf, 64);
|
|
AppendNamedValue(aValues, "parameterMask", BluetoothValue(parameterMask));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterMessageType: {
|
|
/* Follow MAP 1.2, 6.3.1
|
|
* 0000xxx1 = "SMS_GSM"
|
|
* 0000xx1x = "SMS_CDMA"
|
|
* 0000x1xx = "EMAIL"
|
|
* 00001xxx = "MMS"
|
|
* Where
|
|
* 0 = "no filtering, get this type"
|
|
* 1 = "filter out this type"
|
|
*/
|
|
uint32_t filterMessageType = *((uint8_t *)buf);
|
|
|
|
if (filterMessageType == (FILTER_NO_EMAIL | FILTER_NO_MMS |
|
|
FILTER_NO_SMS_GSM) ||
|
|
filterMessageType == (FILTER_NO_EMAIL | FILTER_NO_MMS |
|
|
FILTER_NO_SMS_CDMA)) {
|
|
filterMessageType = static_cast<uint32_t>(MessageType::Sms);
|
|
} else if (filterMessageType == (FILTER_NO_EMAIL | FILTER_NO_SMS_GSM |
|
|
FILTER_NO_SMS_CDMA)) {
|
|
filterMessageType = static_cast<uint32_t>(MessageType::Mms);
|
|
} else if (filterMessageType == (FILTER_NO_MMS | FILTER_NO_SMS_GSM |
|
|
FILTER_NO_SMS_CDMA)) {
|
|
filterMessageType = static_cast<uint32_t>(MessageType::Email);
|
|
} else {
|
|
BT_LOGR("Unsupportted filter message type");
|
|
filterMessageType = static_cast<uint32_t>(MessageType::Sms);
|
|
}
|
|
|
|
BT_LOGR("msg filterMessageType : %d", filterMessageType);
|
|
AppendNamedValue(aValues, "filterMessageType",
|
|
static_cast<uint32_t>(filterMessageType));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterPeriodBegin: {
|
|
nsCString filterPeriodBegin((char *) buf);
|
|
BT_LOGR("msg FilterPeriodBegin : %s", filterPeriodBegin.get());
|
|
AppendNamedValue(aValues, "filterPeriodBegin",
|
|
NS_ConvertUTF8toUTF16(filterPeriodBegin));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterPeriodEnd: {
|
|
nsCString filterPeriodEnd((char*)buf);
|
|
BT_LOGR("msg filterPeriodEnd : %s", filterPeriodEnd.get());
|
|
AppendNamedValue(aValues, "filterPeriodEnd",
|
|
NS_ConvertUTF8toUTF16(filterPeriodEnd));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterReadStatus: {
|
|
using namespace mozilla::dom::ReadStatusValues;
|
|
uint32_t filterReadStatus =
|
|
buf[0] < ArrayLength(strings) ? static_cast<uint32_t>(buf[0]) : 0;
|
|
BT_LOGR("msg filter read status : %d", filterReadStatus);
|
|
AppendNamedValue(aValues, "filterReadStatus", filterReadStatus);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterRecipient: {
|
|
// FilterRecipient encodes as UTF-8
|
|
nsCString filterRecipient((char*) buf);
|
|
BT_LOGR("msg filterRecipient : %s", filterRecipient.get());
|
|
AppendNamedValue(aValues, "filterRecipient",
|
|
NS_ConvertUTF8toUTF16(filterRecipient));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterOriginator: {
|
|
// FilterOriginator encodes as UTF-8
|
|
nsCString filterOriginator((char*) buf);
|
|
BT_LOGR("msg filter Originator : %s", filterOriginator.get());
|
|
AppendNamedValue(aValues, "filterOriginator",
|
|
NS_ConvertUTF8toUTF16(filterOriginator));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FilterPriority: {
|
|
using namespace mozilla::dom::PriorityValues;
|
|
uint32_t filterPriority =
|
|
buf[0] < ArrayLength(strings) ? static_cast<uint32_t>(buf[0]) : 0;
|
|
|
|
BT_LOGR("msg filter priority: %d", filterPriority);
|
|
AppendNamedValue(aValues, "filterPriority", filterPriority);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::Attachment: {
|
|
uint8_t attachment = *((uint8_t *)buf);
|
|
BT_LOGR("msg filter attachment: %d", attachment);
|
|
AppendNamedValue(aValues, "attachment", static_cast<uint32_t>(attachment));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::Charset: {
|
|
using namespace mozilla::dom::FilterCharsetValues;
|
|
uint32_t filterCharset =
|
|
buf[0] < ArrayLength(strings) ? static_cast<uint32_t>(buf[0]) : 0;
|
|
|
|
BT_LOGR("msg filter charset: %d", filterCharset);
|
|
AppendNamedValue(aValues, "charset", filterCharset);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::FractionRequest: {
|
|
mFractionDeliverRequired = true;
|
|
AppendNamedValue(aValues, "fractionRequest", (buf[0] != 0));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::StatusIndicator: {
|
|
using namespace mozilla::dom::StatusIndicatorsValues;
|
|
uint32_t filterStatusIndicator =
|
|
buf[0] < ArrayLength(strings) ? static_cast<uint32_t>(buf[0]) : 0;
|
|
|
|
BT_LOGR("msg filter statusIndicator: %d", filterStatusIndicator);
|
|
AppendNamedValue(aValues, "statusIndicator", filterStatusIndicator);
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::StatusValue: {
|
|
uint8_t statusValue = *((uint8_t *)buf);
|
|
BT_LOGR("msg filter statusvalue: %d", statusValue);
|
|
AppendNamedValue(aValues, "statusValue",
|
|
static_cast<uint32_t>(statusValue));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::Transparent: {
|
|
uint8_t transparent = *((uint8_t *)buf);
|
|
BT_LOGR("msg filter statusvalue: %d", transparent);
|
|
AppendNamedValue(aValues, "transparent",
|
|
static_cast<uint32_t>(transparent));
|
|
break;
|
|
}
|
|
case Map::AppParametersTagId::Retry: {
|
|
uint8_t retry = *((uint8_t *)buf);
|
|
BT_LOGR("msg filter retry: %d", retry);
|
|
AppendNamedValue(aValues, "retry", static_cast<uint32_t>(retry));
|
|
break;
|
|
}
|
|
default:
|
|
BT_LOGR("Unsupported AppParameterTag: %x", aTagId);
|
|
break;
|
|
}
|
|
}
|
|
|
|
InfallibleTArray<uint32_t>
|
|
BluetoothMapSmsManager::PackParameterMask(uint8_t* aData, int aSize)
|
|
{
|
|
InfallibleTArray<uint32_t> parameterMask;
|
|
|
|
/* Table 6.5, MAP 6.3.1. ParameterMask is Bit 16-31 Reserved for future
|
|
* use. The reserved bits shall be set to 0 by MCE and discarded by MSE.
|
|
* convert big endian to little endian
|
|
*/
|
|
uint32_t x = BigEndian::readUint32(aData);
|
|
|
|
uint32_t count = 0;
|
|
while (x) {
|
|
if (x & 1) {
|
|
parameterMask.AppendElement(count);
|
|
}
|
|
|
|
++count;
|
|
x >>= 1;
|
|
}
|
|
|
|
return parameterMask;
|
|
}
|
|
|
|
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);
|
|
AppendBtNamedValueByTagId(aHeader, data,
|
|
Map::AppParametersTagId::FractionRequest);
|
|
|
|
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::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::HandleSmsMmsPushMessage(const ObexHeaderSet& aHeader)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
NS_ENSURE_TRUE_VOID(bs);
|
|
|
|
if (!aHeader.Has(ObexHeaderId::Body) &&
|
|
!aHeader.Has(ObexHeaderId::EndOfBody)) {
|
|
BT_LOGR("Error! Fail to find Body/EndOfBody. Ignore this push request");
|
|
return;
|
|
}
|
|
|
|
InfallibleTArray<BluetoothNamedValue> data;
|
|
nsString name;
|
|
aHeader.GetName(name);
|
|
AppendNamedValue(data, "folderName", name);
|
|
|
|
AppendBtNamedValueByTagId(aHeader, data,
|
|
Map::AppParametersTagId::Transparent);
|
|
AppendBtNamedValueByTagId(aHeader, data, Map::AppParametersTagId::Retry);
|
|
|
|
/* TODO: Support native format charset (mandatory format).
|
|
*
|
|
* Charset indicates Gaia application how to deal with encoding.
|
|
* - Native: If the message object shall be delivered without trans-coding.
|
|
* - UTF-8: If the message text shall be trans-coded to UTF-8.
|
|
*
|
|
* We only support UTF-8 charset due to current SMS API limitation.
|
|
*/
|
|
AppendBtNamedValueByTagId(aHeader, data, Map::AppParametersTagId::Charset);
|
|
|
|
// Get Body
|
|
uint8_t* bodyPtr = nullptr;
|
|
aHeader.GetBody(&bodyPtr, &mBodySegmentLength);
|
|
mBodySegment = bodyPtr;
|
|
|
|
RefPtr<BluetoothMapBMessage> bmsg =
|
|
new BluetoothMapBMessage(bodyPtr, mBodySegmentLength);
|
|
|
|
/* If FolderName is outbox:
|
|
* 1. Parse body to get SMS
|
|
* 2. Get receipent subject
|
|
* 3. Send it to Gaia
|
|
* Otherwise reply HTTP_NOT_ACCEPTABLE
|
|
*/
|
|
|
|
nsCString subject;
|
|
bmsg->GetBody(subject);
|
|
// It's possible that subject is empty, send it anyway
|
|
AppendNamedValue(data, "messageBody", subject);
|
|
|
|
nsTArray<RefPtr<VCard>> recipients;
|
|
bmsg->GetRecipients(recipients);
|
|
|
|
// Get the topmost level, only one recipient for SMS case
|
|
if (!recipients.IsEmpty()) {
|
|
nsCString recipient;
|
|
recipients[0]->GetTelephone(recipient);
|
|
AppendNamedValue(data, "recipient", recipient);
|
|
}
|
|
|
|
bs->DistributeSignal(NS_LITERAL_STRING(MAP_SEND_MESSAGE_REQ_ID),
|
|
NS_LITERAL_STRING(KEY_ADAPTER), data);
|
|
}
|
|
|
|
bool
|
|
BluetoothMapSmsManager::GetInputStreamFromBlob(Blob* aBlob)
|
|
{
|
|
if (mDataStream) {
|
|
mDataStream->Close();
|
|
mDataStream = nullptr;
|
|
}
|
|
|
|
ErrorResult rv;
|
|
aBlob->GetInternalStream(getter_AddRefs(mDataStream), rv);
|
|
if (rv.Failed()) {
|
|
BT_LOGR("Failed to get internal stream from blob. rv=0x%x",
|
|
rv.ErrorCodeAsInt());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::SendReply(uint8_t aResponseCode)
|
|
{
|
|
BT_LOGR("[0x%x]", aResponseCode);
|
|
|
|
// Section 3.2 "Response Format", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t req[kObexRespHeaderSize];
|
|
|
|
SendMasObexData(req, aResponseCode, kObexRespHeaderSize);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::SendMasObexData(uint8_t* aData, uint8_t aOpcode,
|
|
int aSize)
|
|
{
|
|
SetObexPacketInfo(aData, aOpcode, aSize);
|
|
mMasSocket->SendSocketData(new UnixSocketRawData(aData, aSize));
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::SendMasObexData(UniquePtr<uint8_t[]> aData,
|
|
uint8_t aOpcode, int aSize)
|
|
{
|
|
SetObexPacketInfo(aData.get(), aOpcode, aSize);
|
|
mMasSocket->SendSocketData(new UnixSocketRawData(Move(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);
|
|
|
|
if (mDataStream) {
|
|
mDataStream->Close();
|
|
mDataStream = nullptr;
|
|
}
|
|
|
|
// 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.Clear();
|
|
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 BluetoothAddress& aDeviceAddress,
|
|
BluetoothProfileController* aController)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::OnGetServiceChannel(
|
|
const BluetoothAddress& aDeviceAddress,
|
|
const BluetoothUuid& aServiceUuid,
|
|
int aChannel)
|
|
{
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
void
|
|
BluetoothMapSmsManager::OnUpdateSdpRecords(const BluetoothAddress& 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
|