dom/bluetooth/bluedroid/BluetoothMapSmsManager.cpp
author Nicholas Nethercote <nnethercote@mozilla.com>
Fri, 02 Sep 2016 17:12:24 +1000
changeset 312898 177f41cecedb422d7a11156091ee718ec919c643
parent 299409 86cda9d3eaa2c6ca8c88801f44dcfaff22591ed8
child 313022 da216b2fc7dc009f228f0cf078b74b3b59008da0
permissions -rw-r--r--
Bug 1299384 - Use MOZ_MUST_USE with NS_warn_if_impl(). r=erahm. This change avoids lots of false positives for Coverity's CHECKED_RETURN warning, caused by NS_WARN_IF's current use in both statement-style and expression-style. In the case where the code within the NS_WARN_IF has side-effects, I made the following change. > NS_WARN_IF(NS_FAILED(FunctionWithSideEffects())); > --> > Unused << NS_WARN_IF(NS_FAILED(FunctionWithSideEffects())); In the case where the code within the NS_WARN_IF lacks side-effects, I made the following change. > NS_WARN_IF(!condWithoutSideEffects); > --> > NS_WARNING_ASSERTION(condWithoutSideEffects, "msg"); This has two improvements. - The condition is not evaluated in non-debug builds. - The sense of the condition is inverted to the familiar "this condition should be true" sense used in assertions. A common variation on the side-effect-free case is the following. > nsresult rv = Fn(); > NS_WARN_IF_(NS_FAILED(rv)); > --> > DebugOnly<nsresult rv> = Fn(); > NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Fn failed");

/* -*- 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 "BluetoothUuidHelper.h"
#include "ObexBase.h"

#include "mozilla/dom/BluetoothMapParametersBinding.h"
#include "mozilla/dom/ipc/BlobParent.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/dom/File.h"

#include "mozilla/RefPtr.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.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);
  Uninit();

  sMapSmsManager = nullptr;
}

BluetoothMapSmsManager::BluetoothMapSmsManager()
  : mBodyRequired(false)
  , mFractionDeliverRequired(false)
  , mMasConnected(false)
  , mMnsConnected(false)
  , mNtfRequired(false)
{
  BuildDefaultFolderStructure();
}

BluetoothMapSmsManager::~BluetoothMapSmsManager()
{ }

nsresult
BluetoothMapSmsManager::Init()
{
  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (NS_WARN_IF(!obs)) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  auto rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  /**
   * 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 NS_OK;
}

void
BluetoothMapSmsManager::Uninit()
{
  if (mMasServerSocket) {
    mMasServerSocket->SetObserver(nullptr);

    if (mMasServerSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
      mMasServerSocket->Close();
    }
    mMasServerSocket = nullptr;
  }

  if (mMasSocket) {
    mMasSocket->SetObserver(nullptr);

    if (mMasSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
      mMasSocket->Close();
    }
    mMasSocket = nullptr;
  }

  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (NS_WARN_IF(!obs)) {
    return;
  }

  Unused << NS_WARN_IF(NS_FAILED(
    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)));
}

// static
void
BluetoothMapSmsManager::InitMapSmsInterface(BluetoothProfileResultHandler* aRes)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (aRes) {
    aRes->Init();
  }
}

// static
void
BluetoothMapSmsManager::DeinitMapSmsInterface(BluetoothProfileResultHandler* aRes)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (sMapSmsManager) {
    sMapSmsManager->Uninit();
    sMapSmsManager = nullptr;
  }

  if (aRes) {
    aRes->Deinit();
  }
}

//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
  RefPtr<BluetoothMapSmsManager> manager = new BluetoothMapSmsManager();
  if (NS_WARN_IF(NS_FAILED(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->GetConnectionStatus() != SOCKET_DISCONNECTED) {
    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,
                                          UniquePtr<UnixSocketBuffer>& aMessage)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (aSocket == mMnsSocket) {
    MnsDataHandler(aMessage.get());
  } else {
    MasDataHandler(aMessage.get());
  }
}

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;
    auto buf = MakeUnique<char[]>(remainingPacketSize);
    nsresult rv = mDataStream->Read(buf.get(), 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]
  auto appParameters = MakeUnique<uint8_t[]>(len + 9);
  uint8_t newMessage = aNewMessage ? 1 : 0;

  AppendAppParameter(&appParameters[0],
                     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.get(),
                                     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();
  auto handleId = MakeUnique<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.get(), (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.reset(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

  if (mMasServerSocket &&
      mMasServerSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
    mMasServerSocket->Close();
  }
  mMasServerSocket = nullptr;

  if (mMasSocket &&
      mMasSocket->GetConnectionStatus() != SOCKET_DISCONNECTED) {
    mMasSocket->Close();
  }
  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