Bug 1181482 - Patch3: Implement |sendResponse| and BluetoothGattAttributeEvent for GATT server read/write requests. r=btian, r=bz
authorJocelyn Liu <joliu@mozilla.com>
Wed, 23 Sep 2015 14:16:27 +0800
changeset 297396 a10d1b694b6b4999e6b7250d0651a997e41ab9af
parent 297395 9ed155901d76297e90ceff74035b041db6c34803
child 297397 c03bd010bbb03437348f89dc8bdddd313c50881d
push id5392
push userraliiev@mozilla.com
push dateMon, 14 Dec 2015 20:08:23 +0000
treeherdermozilla-beta@16ce8562a975 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbtian, bz
bugs1181482
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1181482 - Patch3: Implement |sendResponse| and BluetoothGattAttributeEvent for GATT server read/write requests. r=btian, r=bz
dom/base/nsGkAtomList.h
dom/bindings/Bindings.conf
dom/bluetooth/bluedroid/BluetoothGattManager.cpp
dom/bluetooth/bluedroid/BluetoothGattManager.h
dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
dom/bluetooth/bluez/BluetoothDBusService.cpp
dom/bluetooth/bluez/BluetoothDBusService.h
dom/bluetooth/common/BluetoothCommon.h
dom/bluetooth/common/BluetoothService.h
dom/bluetooth/common/webapi/BluetoothGattAttributeEvent.cpp
dom/bluetooth/common/webapi/BluetoothGattAttributeEvent.h
dom/bluetooth/common/webapi/BluetoothGattCharacteristic.h
dom/bluetooth/common/webapi/BluetoothGattDescriptor.h
dom/bluetooth/common/webapi/BluetoothGattServer.cpp
dom/bluetooth/common/webapi/BluetoothGattServer.h
dom/bluetooth/ipc/BluetoothMessageUtils.h
dom/bluetooth/ipc/BluetoothParent.cpp
dom/bluetooth/ipc/BluetoothParent.h
dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
dom/bluetooth/ipc/BluetoothServiceChildProcess.h
dom/bluetooth/ipc/BluetoothTypes.ipdlh
dom/bluetooth/ipc/PBluetooth.ipdl
dom/bluetooth/moz.build
dom/events/test/test_all_synthetic_events.html
dom/tests/mochitest/general/test_interfaces.html
dom/webidl/BluetoothGattAttributeEvent.webidl
dom/webidl/BluetoothGattServer.webidl
dom/webidl/moz.build
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -679,16 +679,18 @@ GK_ATOM(onafterprint, "onafterprint")
 GK_ATOM(onafterscriptexecute, "onafterscriptexecute")
 GK_ATOM(onalerting, "onalerting")
 GK_ATOM(onanimationend, "onanimationend")
 GK_ATOM(onanimationiteration, "onanimationiteration")
 GK_ATOM(onanimationstart, "onanimationstart")
 GK_ATOM(onantennaavailablechange, "onantennaavailablechange")
 GK_ATOM(onAppCommand, "onAppCommand")
 GK_ATOM(onattributechanged, "onattributechanged")
+GK_ATOM(onattributereadreq, "onattributereadreq")
+GK_ATOM(onattributewritereq, "onattributewritereq")
 GK_ATOM(onaudioprocess, "onaudioprocess")
 GK_ATOM(onbeforecopy, "onbeforecopy")
 GK_ATOM(onbeforecut, "onbeforecut")
 GK_ATOM(onbeforepaste, "onbeforepaste")
 GK_ATOM(onbeforeevicted, "onbeforeevicted")
 GK_ATOM(onbeforeprint, "onbeforeprint")
 GK_ATOM(onbeforescriptexecute, "onbeforescriptexecute")
 GK_ATOM(onbeforeunload, "onbeforeunload")
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -161,16 +161,20 @@ DOMInterfaces = {
 'BluetoothDiscoveryHandle': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothDiscoveryHandle',
 },
 
 'BluetoothGatt': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGatt',
 },
 
+'BluetoothGattAttributeEvent': {
+    'nativeType': 'mozilla::dom::bluetooth::BluetoothGattAttributeEvent',
+},
+
 'BluetoothGattCharacteristic': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGattCharacteristic',
 },
 
 'BluetoothGattDescriptor': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGattDescriptor',
 },
 
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
@@ -286,16 +286,17 @@ public:
 
   BluetoothGattServerAddServiceState mAddServiceState;
   nsRefPtr<BluetoothReplyRunnable> mAddIncludedServiceRunnable;
   nsRefPtr<BluetoothReplyRunnable> mAddCharacteristicRunnable;
   BluetoothGattServerAddDescriptorState mAddDescriptorState;
   nsRefPtr<BluetoothReplyRunnable> mRemoveServiceRunnable;
   nsRefPtr<BluetoothReplyRunnable> mStartServiceRunnable;
   nsRefPtr<BluetoothReplyRunnable> mStopServiceRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mSendResponseRunnable;
 
   // Map connection id from device address
   nsDataHashtable<nsStringHashKey, int> mConnectionMap;
 private:
   ~BluetoothGattServer()
   { }
 };
 
@@ -336,16 +337,28 @@ public:
 class ConnIdComparator
 {
 public:
   bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
               int aConnId) const
   {
     return aClient->mConnId == aConnId;
   }
+
+  bool Equals(const nsRefPtr<BluetoothGattServer>& aServer,
+              int aConnId) const
+  {
+    for (
+      auto iter = aServer->mConnectionMap.Iter(); !iter.Done(); iter.Next()) {
+      if (aConnId == iter.Data()) {
+        return true;
+      }
+    }
+    return false;
+  }
 };
 
 BluetoothGattManager*
 BluetoothGattManager::Get()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // If sBluetoothGattManager already exists, exit early
@@ -2205,16 +2218,91 @@ BluetoothGattManager::ServerStopService(
   server->mStopServiceRunnable = aRunnable;
 
   sBluetoothGattInterface->StopService(
     server->mServerIf,
     aServiceHandle,
     new ServerStopServiceResultHandler(server));
 }
 
+class BluetoothGattManager::ServerSendResponseResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerSendResponseResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void SendResponse() override
+  {
+    if (mServer->mSendResponseRunnable) {
+      DispatchReplySuccess(mServer->mSendResponseRunnable);
+      mServer->mSendResponseRunnable = nullptr;
+    }
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::SendResponse failed: %d",
+               (int)aStatus);
+
+    // Reject the send response request
+    if (mServer->mSendResponseRunnable) {
+      DispatchReplyError(mServer->mSendResponseRunnable,
+                         NS_LITERAL_STRING("Send response failed"));
+      mServer->mSendResponseRunnable = nullptr;
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerSendResponse(const nsAString& aAppUuid,
+                                         const nsAString& aAddress,
+                                         uint16_t aStatus,
+                                         int aRequestId,
+                                         const BluetoothGattResponse& aRsp,
+                                         BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (index == sServers->NoIndex) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  if (server->mSendResponseRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  int connId = 0;
+  server->mConnectionMap.Get(aAddress, &connId);
+  if (!connId) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+
+  sBluetoothGattInterface->SendResponse(
+    connId,
+    aRequestId,
+    aStatus,
+    aRsp,
+    new ServerSendResponseResultHandler(server));
+}
+
 //
 // Notification Handlers
 //
 void
 BluetoothGattManager::RegisterClientNotification(BluetoothGattStatus aStatus,
                                                  int aClientIf,
                                                  const BluetoothUuid& aAppUuid)
 {
@@ -3331,16 +3419,120 @@ BluetoothGattManager::ServiceDeletedNoti
   }
 
   if (server->mRemoveServiceRunnable) {
     DispatchReplySuccess(server->mRemoveServiceRunnable);
     server->mRemoveServiceRunnable = nullptr;
   }
 }
 
+void
+BluetoothGattManager::RequestReadNotification(
+  int aConnId,
+  int aTransId,
+  const nsAString& aBdAddr,
+  const BluetoothAttributeHandle& aAttributeHandle,
+  int aOffset,
+  bool aIsLong)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  NS_ENSURE_TRUE_VOID(aConnId);
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  size_t index = sServers->IndexOf(aConnId, 0 /* Start */, ConnIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = (*sServers)[index];
+
+  // Send an error response for unsupported requests
+  if (aIsLong || aOffset > 0) {
+    BT_LOGR("Unsupported long attribute read requests");
+    BluetoothGattResponse response;
+    memset(&response, 0, sizeof(BluetoothGattResponse));
+    sBluetoothGattInterface->SendResponse(
+      aConnId,
+      aTransId,
+      GATT_STATUS_REQUEST_NOT_SUPPORTED,
+      response,
+      new ServerSendResponseResultHandler(server));
+    return;
+  }
+
+  // Distribute a signal to gattServer
+  InfallibleTArray<BluetoothNamedValue> properties;
+
+  AppendNamedValue(properties, "TransId", aTransId);
+  AppendNamedValue(properties, "AttrHandle", aAttributeHandle);
+  AppendNamedValue(properties, "Address", nsString(aBdAddr));
+  AppendNamedValue(properties, "NeedResponse", true);
+  AppendNamedValue(properties, "Value", new nsTArray<uint8_t>());
+
+  bs->DistributeSignal(NS_LITERAL_STRING("ReadRequested"),
+                       server->mAppUuid,
+                       properties);
+}
+
+void
+BluetoothGattManager::RequestWriteNotification(
+  int aConnId,
+  int aTransId,
+  const nsAString& aBdAddr,
+  const BluetoothAttributeHandle& aAttributeHandle,
+  int aOffset,
+  int aLength,
+  const uint8_t* aValue,
+  bool aNeedResponse,
+  bool aIsPrepareWrite)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  NS_ENSURE_TRUE_VOID(aConnId);
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  size_t index = sServers->IndexOf(aConnId, 0 /* Start */, ConnIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = (*sServers)[index];
+
+  // Send an error response for unsupported requests
+  if (aIsPrepareWrite || aOffset > 0) {
+    BT_LOGR("Unsupported prepare write or long attribute write requests");
+    if (aNeedResponse) {
+      BluetoothGattResponse response;
+      memset(&response, 0, sizeof(BluetoothGattResponse));
+      sBluetoothGattInterface->SendResponse(
+        aConnId,
+        aTransId,
+        GATT_STATUS_REQUEST_NOT_SUPPORTED,
+        response,
+        new ServerSendResponseResultHandler(server));
+    }
+    return;
+  }
+
+  // Distribute a signal to gattServer
+  InfallibleTArray<BluetoothNamedValue> properties;
+
+  AppendNamedValue(properties, "TransId", aTransId);
+  AppendNamedValue(properties, "AttrHandle", aAttributeHandle);
+  AppendNamedValue(properties, "Address", nsString(aBdAddr));
+  AppendNamedValue(properties, "NeedResponse", aNeedResponse);
+
+  nsTArray<uint8_t> value;
+  value.AppendElements(aValue, aLength);
+  AppendNamedValue(properties, "Value", value);
+
+  bs->DistributeSignal(NS_LITERAL_STRING("WrtieRequested"),
+                       server->mAppUuid,
+                       properties);
+}
+
 BluetoothGattManager::BluetoothGattManager()
 { }
 
 BluetoothGattManager::~BluetoothGattManager()
 {
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   NS_ENSURE_TRUE_VOID(obs);
   if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.h
@@ -141,16 +141,24 @@ public:
     const BluetoothAttributeHandle& aServiceHandle,
     BluetoothReplyRunnable* aRunnable);
 
   void ServerStopService(
     const nsAString& aAppUuid,
     const BluetoothAttributeHandle& aServiceHandle,
     BluetoothReplyRunnable* aRunnable);
 
+  void ServerSendResponse(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable);
+
 private:
   ~BluetoothGattManager();
 
   class CleanupResultHandler;
   class CleanupResultHandlerRunnable;
   class InitGattResultHandler;
   class RegisterClientResultHandler;
   class UnregisterClientResultHandler;
@@ -174,16 +182,17 @@ private:
   class UnregisterServerResultHandler;
   class ServerAddServiceResultHandler;
   class ServerAddIncludedServiceResultHandler;
   class ServerAddCharacteristicResultHandler;
   class ServerAddDescriptorResultHandler;
   class ServerRemoveDescriptorResultHandler;
   class ServerStartServiceResultHandler;
   class ServerStopServiceResultHandler;
+  class ServerSendResponseResultHandler;
 
   BluetoothGattManager();
 
   void HandleShutdown();
 
   void RegisterClientNotification(BluetoothGattStatus aStatus,
                                   int aClientIf,
                                   const BluetoothUuid& aAppUuid) override;
@@ -320,14 +329,33 @@ private:
     const BluetoothAttributeHandle& aServiceHandle) override;
 
   void
   ServiceDeletedNotification(
     BluetoothGattStatus aStatus,
     int aServerIf,
     const BluetoothAttributeHandle& aServiceHandle) override;
 
+  void
+  RequestReadNotification(int aConnId,
+                          int aTransId,
+                          const nsAString& aBdAddr,
+                          const BluetoothAttributeHandle& aAttributeHandle,
+                          int aOffset,
+                          bool aIsLong) override;
+
+  void
+  RequestWriteNotification(int aConnId,
+                           int aTransId,
+                           const nsAString& aBdAddr,
+                           const BluetoothAttributeHandle& aAttributeHandle,
+                           int aOffset,
+                           int aLength,
+                           const uint8_t* aValue,
+                           bool aNeedResponse,
+                           bool aIsPrepareWrite) override;
+
   static bool mInShutdown;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_bluedroid_BluetoothGattManager_h
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -731,16 +731,36 @@ BluetoothServiceBluedroid::GattServerSto
   ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
   BluetoothGattManager* gatt = BluetoothGattManager::Get();
   ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
 
   gatt->ServerStopService(aAppUuid, aServiceHandle, aRunnable);
 }
 
+void
+BluetoothServiceBluedroid::GattServerSendResponseInternal(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  uint16_t aStatus,
+  int32_t aRequestId,
+  const BluetoothGattResponse& aRsp,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerSendResponse(
+    aAppUuid, aAddress, aStatus, aRequestId, aRsp, aRunnable);
+}
+
 nsresult
 BluetoothServiceBluedroid::GetAdaptersInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   /**
    * Wrap BluetoothValue =
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
@@ -355,16 +355,25 @@ public:
     BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattServerStopServiceInternal(
     const nsAString& aAppUuid,
     const BluetoothAttributeHandle& aServiceHandle,
     BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  GattServerSendResponseInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable) override;
+
   //
   // Bluetooth notifications
   //
 
   virtual void AdapterStateChangedNotification(bool aState) override;
   virtual void AdapterPropertiesNotification(
     BluetoothStatus aStatus, int aNumProperties,
     const BluetoothProperty* aProperties) override;
--- a/dom/bluetooth/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp
@@ -4497,8 +4497,19 @@ BluetoothDBusService::GattServerStartSer
 
 void
 BluetoothDBusService::GattServerStopServiceInternal(
   const nsAString& aAppUuid,
   const BluetoothAttributeHandle& aServiceHandle,
   BluetoothReplyRunnable* aRunnable)
 {
 }
+
+void
+BluetoothDBusService::GattServerSendResponseInternal(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  uint16_t aStatus,
+  int32_t aRequestId,
+  const BluetoothGattResponse& aRsp,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
--- a/dom/bluetooth/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth/bluez/BluetoothDBusService.h
@@ -366,16 +366,25 @@ public:
     BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattServerStopServiceInternal(
     const nsAString& aAppUuid,
     const BluetoothAttributeHandle& aServiceHandle,
     BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  GattServerSendResponseInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable) override;
+
 private:
   nsresult SendGetPropertyMessage(const nsAString& aPath,
                                   const char* aInterface,
                                   void (*aCB)(DBusMessage *, void *),
                                   BluetoothReplyRunnable* aRunnable);
 
   nsresult SendDiscoveryMessage(const char* aMessageName,
                                 BluetoothReplyRunnable* aRunnable);
--- a/dom/bluetooth/common/BluetoothCommon.h
+++ b/dom/bluetooth/common/BluetoothCommon.h
@@ -269,16 +269,23 @@ extern bool gBluetoothDebugFlag;
 #define GATT_CONNECTION_STATE_CHANGED_ID     "connectionstatechanged"
 
 /**
  * When attributes of BluetoothManager, BluetoothAdapter, or BluetoothDevice
  * are changed, we'll dispatch an event.
  */
 #define ATTRIBUTE_CHANGED_ID                 "attributechanged"
 
+/**
+ * When the local GATT server received attribute read/write requests, we'll
+ * dispatch an event.
+ */
+#define ATTRIBUTE_READ_REQUEST               "attributereadreq"
+#define ATTRIBUTE_WRITE_REQUEST              "attributewritereq"
+
 // Bluetooth address format: xx:xx:xx:xx:xx:xx (or xx_xx_xx_xx_xx_xx)
 #define BLUETOOTH_ADDRESS_LENGTH 17
 #define BLUETOOTH_ADDRESS_NONE   "00:00:00:00:00:00"
 #define BLUETOOTH_ADDRESS_BYTES  6
 
 // Bluetooth stack internal error, such as I/O error
 #define ERR_INTERNAL_ERROR "InternalError"
 
@@ -690,17 +697,18 @@ enum BluetoothGattStatus {
   GATT_STATUS_UNKNOWN_ERROR
 };
 
 enum BluetoothGattAuthReq {
   GATT_AUTH_REQ_NONE,
   GATT_AUTH_REQ_NO_MITM,
   GATT_AUTH_REQ_MITM,
   GATT_AUTH_REQ_SIGNED_NO_MITM,
-  GATT_AUTH_REQ_SIGNED_MITM
+  GATT_AUTH_REQ_SIGNED_MITM,
+  GATT_AUTH_REQ_END_GUARD
 };
 
 enum BluetoothGattWriteType {
   GATT_WRITE_TYPE_NO_RESPONSE,
   GATT_WRITE_TYPE_NORMAL,
   GATT_WRITE_TYPE_PREPARE,
   GATT_WRITE_TYPE_SIGNED,
   GATT_WRITE_TYPE_END_GUARD
@@ -816,48 +824,57 @@ struct BluetoothGattTestParam {
   BluetoothUuid mUuid;
   uint16_t mU1;
   uint16_t mU2;
   uint16_t mU3;
   uint16_t mU4;
   uint16_t mU5;
 };
 
-struct BluetoothGattResponse {
-  uint16_t mHandle;
-  uint16_t mOffset;
-  uint16_t mLength;
-  BluetoothGattAuthReq mAuthReq;
-  uint8_t mValue[BLUETOOTH_GATT_MAX_ATTR_LEN];
-};
-
-/**
- * EIR Data Type, Advertising Data Type (AD Type) and OOB Data Type Definitions
- * Please refer to https://www.bluetooth.org/en-us/specification/\
- * assigned-numbers/generic-access-profile
- */
-enum BluetoothGapDataType {
-  GAP_INCOMPLETE_UUID16  = 0X02, // Incomplete List of 16-bit Service Class UUIDs
-  GAP_COMPLETE_UUID16    = 0X03, // Complete List of 16-bit Service Class UUIDs
-  GAP_INCOMPLETE_UUID32  = 0X04, // Incomplete List of 32-bit Service Class UUIDs
-  GAP_COMPLETE_UUID32    = 0X05, // Complete List of 32-bit Service Class UUIDs┬╗
-  GAP_INCOMPLETE_UUID128 = 0X06, // Incomplete List of 128-bit Service Class UUIDs
-  GAP_COMPLETE_UUID128   = 0X07, // Complete List of 128-bit Service Class UUIDs
-  GAP_SHORTENED_NAME     = 0X08, // Shortened Local Name
-  GAP_COMPLETE_NAME      = 0X09, // Complete Local Name
-};
-
 struct BluetoothAttributeHandle {
   uint16_t mHandle;
 
   BluetoothAttributeHandle()
     : mHandle(0x0000)
   { }
 
   bool operator==(const BluetoothAttributeHandle& aOther) const
   {
     return mHandle == aOther.mHandle;
   }
 };
 
+struct BluetoothGattResponse {
+  BluetoothAttributeHandle mHandle;
+  uint16_t mOffset;
+  uint16_t mLength;
+  BluetoothGattAuthReq mAuthReq;
+  uint8_t mValue[BLUETOOTH_GATT_MAX_ATTR_LEN];
+
+  bool operator==(const BluetoothGattResponse& aOther) const
+  {
+    return mHandle == aOther.mHandle &&
+           mOffset == aOther.mOffset &&
+           mLength == aOther.mLength &&
+           mAuthReq == aOther.mAuthReq &&
+           !memcmp(mValue, aOther.mValue, mLength);
+  }
+};
+
+/**
+ * EIR Data Type, Advertising Data Type (AD Type) and OOB Data Type Definitions
+ * Please refer to https://www.bluetooth.org/en-us/specification/\
+ * assigned-numbers/generic-access-profile
+ */
+enum BluetoothGapDataType {
+  GAP_INCOMPLETE_UUID16  = 0X02, // Incomplete List of 16-bit Service Class UUIDs
+  GAP_COMPLETE_UUID16    = 0X03, // Complete List of 16-bit Service Class UUIDs
+  GAP_INCOMPLETE_UUID32  = 0X04, // Incomplete List of 32-bit Service Class UUIDs
+  GAP_COMPLETE_UUID32    = 0X05, // Complete List of 32-bit Service Class UUIDs
+  GAP_INCOMPLETE_UUID128 = 0X06, // Incomplete List of 128-bit Service Class UUIDs
+  GAP_COMPLETE_UUID128   = 0X07, // Complete List of 128-bit Service Class UUIDs
+  GAP_SHORTENED_NAME     = 0X08, // Shortened Local Name
+  GAP_COMPLETE_NAME      = 0X09, // Complete Local Name
+};
+
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_BluetoothCommon_h
--- a/dom/bluetooth/common/BluetoothService.h
+++ b/dom/bluetooth/common/BluetoothService.h
@@ -553,16 +553,25 @@ public:
     BluetoothReplyRunnable* aRunnable) = 0;
 
   virtual void
   GattServerStopServiceInternal(
     const nsAString& aAppUuid,
     const BluetoothAttributeHandle& aServiceHandle,
     BluetoothReplyRunnable* aRunnable) = 0;
 
+  virtual void
+  GattServerSendResponseInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
   bool
   IsEnabled() const
   {
     return mEnabled;
   }
 
   bool
   IsToggling() const;
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/common/webapi/BluetoothGattAttributeEvent.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/dom/bluetooth/BluetoothGattAttributeEvent.h"
+
+#include "js/GCAPI.h"
+#include "jsfriendapi.h"
+#include "mozilla/dom/BluetoothGattAttributeEventBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothGattCharacteristic.h"
+#include "mozilla/dom/bluetooth/BluetoothGattDescriptor.h"
+#include "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/dom/TypedArray.h"
+
+USING_BLUETOOTH_NAMESPACE
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGattAttributeEvent)
+
+NS_IMPL_ADDREF_INHERITED(BluetoothGattAttributeEvent, Event)
+NS_IMPL_RELEASE_INHERITED(BluetoothGattAttributeEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothGattAttributeEvent,
+                                                  Event)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCharacteristic)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDescriptor)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(BluetoothGattAttributeEvent,
+                                               Event)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mValue)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothGattAttributeEvent,
+                                                Event)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCharacteristic)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDescriptor)
+  tmp->mValue = nullptr;
+  mozilla::DropJSObjects(this);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothGattAttributeEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+BluetoothGattAttributeEvent::BluetoothGattAttributeEvent(EventTarget* aOwner)
+  : Event(aOwner, nullptr, nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
+
+BluetoothGattAttributeEvent::~BluetoothGattAttributeEvent()
+{
+  mozilla::DropJSObjects(this);
+}
+
+JSObject*
+BluetoothGattAttributeEvent::WrapObjectInternal(
+  JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return BluetoothGattAttributeEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<BluetoothGattAttributeEvent>
+BluetoothGattAttributeEvent::Constructor(
+  EventTarget* aOwner,
+  const nsAString& aType,
+  const nsAString& aAddress,
+  int32_t aRequestId,
+  BluetoothGattCharacteristic* aCharacteristic,
+  BluetoothGattDescriptor* aDescriptor,
+  const nsTArray<uint8_t>* aValue,
+  bool aNeedResponse,
+  bool aBubbles,
+  bool aCancelable)
+{
+  nsRefPtr<BluetoothGattAttributeEvent> e =
+    new BluetoothGattAttributeEvent(aOwner);
+  bool trusted = e->Init(aOwner);
+
+  e->InitEvent(aType, aBubbles, aCancelable);
+  e->mAddress = aAddress;
+  e->mRequestId = aRequestId;
+  e->mCharacteristic = aCharacteristic;
+  e->mDescriptor = aDescriptor;
+  e->mNeedResponse = aNeedResponse;
+
+  if (aValue) {
+    e->mRawValue = *aValue;
+  }
+
+  e->SetTrusted(trusted);
+
+  return e.forget();
+}
+
+already_AddRefed<BluetoothGattAttributeEvent>
+BluetoothGattAttributeEvent::Constructor(
+  const GlobalObject& aGlobal,
+  const nsAString& aType,
+  const BluetoothGattAttributeEventInit& aEventInitDict,
+  ErrorResult& aRv)
+{
+  nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+
+  nsRefPtr<BluetoothGattAttributeEvent> e =
+    Constructor(owner, aType, aEventInitDict.mAddress,
+                aEventInitDict.mRequestId, aEventInitDict.mCharacteristic,
+                aEventInitDict.mDescriptor, nullptr,
+                aEventInitDict.mNeedResponse, aEventInitDict.mBubbles,
+                aEventInitDict.mCancelable);
+
+  if (!aEventInitDict.mValue.IsNull()) {
+    const auto& value = aEventInitDict.mValue.Value();
+    value.ComputeLengthAndData();
+    e->mValue = ArrayBuffer::Create(aGlobal.Context(),
+                                    value.Length(),
+                                    value.Data());
+
+    if (!e->mValue) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return nullptr;
+    }
+  }
+
+  return e.forget();
+}
+
+void
+BluetoothGattAttributeEvent::GetAddress(nsString& aRetVal) const
+{
+  aRetVal = mAddress;
+}
+
+int32_t
+BluetoothGattAttributeEvent::RequestId() const
+{
+  return mRequestId;
+}
+
+BluetoothGattCharacteristic*
+BluetoothGattAttributeEvent::GetCharacteristic() const
+{
+  return mCharacteristic;
+}
+
+BluetoothGattDescriptor*
+BluetoothGattAttributeEvent::GetDescriptor() const
+{
+  return mDescriptor;
+}
+
+void
+BluetoothGattAttributeEvent::GetValue(
+  JSContext* cx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv)
+{
+  if (!mValue) {
+    mValue = ArrayBuffer::Create(
+      cx, this, mRawValue.Length(), mRawValue.Elements());
+
+    if (!mValue) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+
+    mRawValue.Clear();
+  }
+
+  JS::ExposeObjectToActiveJS(mValue);
+  aValue.set(mValue);
+
+  return;
+}
+
+bool
+BluetoothGattAttributeEvent::NeedResponse() const
+{
+  return mNeedResponse;
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/common/webapi/BluetoothGattAttributeEvent.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_bluetooth_BluetoothGattAttributeEvent_h
+#define mozilla_dom_bluetooth_BluetoothGattAttributeEvent_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BluetoothGattAttributeEventBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "mozilla/dom/Event.h"
+
+struct JSContext;
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothGattAttributeEvent final : public Event
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+    BluetoothGattAttributeEvent, Event)
+protected:
+  virtual ~BluetoothGattAttributeEvent();
+  explicit BluetoothGattAttributeEvent(EventTarget* aOwner);
+
+  nsString mAddress;
+  int32_t mRequestId;
+  nsRefPtr<BluetoothGattCharacteristic> mCharacteristic;
+  nsRefPtr<BluetoothGattDescriptor> mDescriptor;
+  JS::Heap<JSObject*> mValue;
+  bool mNeedResponse;
+
+public:
+  virtual
+  JSObject* WrapObjectInternal(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  static already_AddRefed<BluetoothGattAttributeEvent>
+  Constructor(EventTarget* aOwner,
+              const nsAString& aType,
+              const nsAString& aAddress,
+              int32_t aRequestId,
+              BluetoothGattCharacteristic* aCharacteristic,
+              BluetoothGattDescriptor* aDescriptor,
+              const nsTArray<uint8_t>* aValue,
+              bool aNeedResponse,
+              bool aBubbles,
+              bool aCancelable);
+
+  static already_AddRefed<BluetoothGattAttributeEvent>
+  Constructor(const GlobalObject& aGlobal,
+              const nsAString& aType,
+              const BluetoothGattAttributeEventInit& aEventInitDict,
+              ErrorResult& aRv);
+
+  void GetAddress(nsString& aRetVal) const;
+
+  int32_t RequestId() const;
+
+  BluetoothGattCharacteristic* GetCharacteristic() const;
+
+  BluetoothGattDescriptor* GetDescriptor() const;
+
+  void
+  GetValue(JSContext* cx,
+           JS::MutableHandle<JSObject*> aValue,
+           ErrorResult& aRv);
+
+  bool NeedResponse() const;
+
+private:
+  nsTArray<uint8_t> mRawValue;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif // mozilla_dom_BluetoothGattAttributeEvent_h
--- a/dom/bluetooth/common/webapi/BluetoothGattCharacteristic.h
+++ b/dom/bluetooth/common/webapi/BluetoothGattCharacteristic.h
@@ -27,16 +27,17 @@ BEGIN_BLUETOOTH_NAMESPACE
 class BluetoothGattService;
 class BluetoothSignal;
 class BluetoothValue;
 
 class BluetoothGattCharacteristic final : public nsISupports
                                         , public nsWrapperCache
                                         , public BluetoothSignalObserver
 {
+  friend class BluetoothGattServer;
   friend class BluetoothGattService;
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattCharacteristic)
 
   /****************************************************************************
    * Attribute Getters
    ***************************************************************************/
@@ -109,16 +110,21 @@ public:
 
   BluetoothGattCharProp GetProperties() const
   {
     return mProperties;
   }
 
   uint16_t GetHandleCount() const;
 
+  const nsTArray<uint8_t>& GetValue() const
+  {
+    return mValue;
+  }
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // Constructor of BluetoothGattCharacteristic in ATT client role
   BluetoothGattCharacteristic(nsPIDOMWindow* aOwner,
                               BluetoothGattService* aService,
                               const BluetoothGattCharAttribute& aChar);
 
--- a/dom/bluetooth/common/webapi/BluetoothGattDescriptor.h
+++ b/dom/bluetooth/common/webapi/BluetoothGattDescriptor.h
@@ -27,16 +27,17 @@ BEGIN_BLUETOOTH_NAMESPACE
 class BluetoothGattCharacteristic;
 class BluetoothSignal;
 class BluetoothValue;
 
 class BluetoothGattDescriptor final : public nsISupports
                                     , public nsWrapperCache
                                     , public BluetoothSignalObserver
 {
+  friend class BluetoothGattServer;
   friend class BluetoothGattCharacteristic;
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattDescriptor)
 
   /****************************************************************************
    * Attribute Getters
    ***************************************************************************/
@@ -83,16 +84,21 @@ public:
     return mPermissions;
   }
 
   uint16_t GetHandleCount() const
   {
     return sHandleCount;
   }
 
+  const nsTArray<uint8_t>& GetValue() const
+  {
+    return mValue;
+  }
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // Constructor of BluetoothGattDescriptor in ATT client role
   BluetoothGattDescriptor(nsPIDOMWindow* aOwner,
                           BluetoothGattCharacteristic* aCharacteristic,
                           const BluetoothGattId& aDescriptorId);
 
@@ -223,9 +229,30 @@ public:
     const mozilla::dom::bluetooth::BluetoothUuid& aUuid) const
   {
     mozilla::dom::bluetooth::BluetoothUuid uuid;
     aDesc->GetUuid(uuid);
     return uuid == aUuid;
   }
 };
 
+/**
+ * Explicit Specialization of Function Templates
+ *
+ * Allows customizing the template code for a given set of template arguments.
+ * With this function template, nsTArray can handle comparison between
+ * 'nsRefPtr<BluetoothGattDescriptor>' and 'BluetoothAttributeHandle'
+ * properly, including IndexOf() and Contains();
+ */
+template <>
+class nsDefaultComparator <
+  nsRefPtr<mozilla::dom::bluetooth::BluetoothGattDescriptor>,
+  mozilla::dom::bluetooth::BluetoothAttributeHandle> {
+public:
+  bool Equals(
+    const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattDescriptor>& aDesc,
+    const mozilla::dom::bluetooth::BluetoothAttributeHandle& aHandle) const
+  {
+    return aDesc->GetDescriptorHandle() == aHandle;
+  }
+};
+
 #endif // mozilla_dom_bluetooth_BluetoothGattDescriptor_h
--- a/dom/bluetooth/common/webapi/BluetoothGattServer.cpp
+++ b/dom/bluetooth/common/webapi/BluetoothGattServer.cpp
@@ -5,16 +5,19 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "BluetoothGattServer.h"
 
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 #include "mozilla/dom/BluetoothStatusChangedEvent.h"
+#include "mozilla/dom/bluetooth/BluetoothGattAttributeEvent.h"
+#include "mozilla/dom/bluetooth/BluetoothGattCharacteristic.h"
+#include "mozilla/dom/bluetooth/BluetoothGattService.h"
 #include "mozilla/dom/Promise.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGattServer)
@@ -172,16 +175,79 @@ BluetoothGattServer::HandleDescriptorHan
   NS_ENSURE_TRUE_VOID(mPendingService);
   NS_ENSURE_TRUE_VOID(mPendingService->GetServiceHandle() == serviceHandle);
   mPendingService->AssignDescriptorHandle(descriptorUuid,
                                           characteristicHandle,
                                           descriptorHandle);
 }
 
 void
+BluetoothGattServer::HandleReadWriteRequest(const BluetoothValue& aValue,
+                                            const nsAString& aEventName)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aValue.get_ArrayOfBluetoothNamedValue();
+
+  MOZ_ASSERT(arr.Length() == 5 &&
+    arr[0].value().type() == BluetoothValue::Tint32_t &&
+    arr[1].value().type() == BluetoothValue::TBluetoothAttributeHandle &&
+    arr[2].value().type() == BluetoothValue::TnsString &&
+    arr[3].value().type() == BluetoothValue::Tbool &&
+    arr[4].value().type() == BluetoothValue::TArrayOfuint8_t);
+
+  int32_t requestId = arr[0].value().get_int32_t();
+  BluetoothAttributeHandle handle =
+    arr[1].value().get_BluetoothAttributeHandle();
+  nsString address = arr[2].value().get_nsString();
+  bool needResponse = arr[3].value().get_bool();
+  nsTArray<uint8_t> value;
+  value = arr[4].value().get_ArrayOfuint8_t();
+
+  // Find the target characteristic or descriptor from the given handle
+  nsRefPtr<BluetoothGattCharacteristic> characteristic = nullptr;
+  nsRefPtr<BluetoothGattDescriptor> descriptor = nullptr;
+  for (uint32_t i = 0; i < mServices.Length(); i++) {
+    for (uint32_t j = 0; j < mServices[i]->mCharacteristics.Length(); j++) {
+      nsRefPtr<BluetoothGattCharacteristic> currentChar =
+        mServices[i]->mCharacteristics[j];
+
+      if (handle == currentChar->GetCharacteristicHandle()) {
+        characteristic = currentChar;
+        break;
+      }
+
+      size_t index = currentChar->mDescriptors.IndexOf(handle);
+      if (index != currentChar->mDescriptors.NoIndex) {
+        descriptor = currentChar->mDescriptors[index];
+        break;
+      }
+    }
+  }
+
+  if (!(characteristic || descriptor)) {
+    BT_WARNING("Wrong handle: no matched characteristic or descriptor");
+    return;
+  }
+
+  // Save the request information for sending the response later
+  RequestData data(handle,
+                   characteristic,
+                   descriptor);
+  mRequestMap.Put(requestId, &data);
+
+  nsRefPtr<BluetoothGattAttributeEvent> event =
+    BluetoothGattAttributeEvent::Constructor(
+      this, aEventName, address, requestId, characteristic, descriptor,
+      &value, needResponse, false /* Bubble */, false /* Cancelable*/);
+
+  DispatchTrustedEvent(event);
+}
+
+void
 BluetoothGattServer::Notify(const BluetoothSignal& aData)
 {
   BT_LOGD("[GattServer] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
   NS_ENSURE_TRUE_VOID(mSignalRegistered);
 
   BluetoothValue v = aData.value();
   if (aData.name().EqualsLiteral("ServerRegistered")) {
     HandleServerRegistered(v);
@@ -190,16 +256,20 @@ BluetoothGattServer::Notify(const Blueto
   } else if (aData.name().EqualsLiteral(GATT_CONNECTION_STATE_CHANGED_ID)) {
     HandleConnectionStateChanged(v);
   } else if (aData.name().EqualsLiteral("ServiceHandleUpdated")) {
     HandleServiceHandleUpdated(v);
   } else if (aData.name().EqualsLiteral("CharacteristicHandleUpdated")) {
     HandleCharacteristicHandleUpdated(v);
   } else if (aData.name().EqualsLiteral("DescriptorHandleUpdated")) {
     HandleDescriptorHandleUpdated(v);
+  } else if (aData.name().EqualsLiteral("ReadRequested")) {
+    HandleReadWriteRequest(v, NS_LITERAL_STRING(ATTRIBUTE_READ_REQUEST));
+  } else if (aData.name().EqualsLiteral("WriteRequested")) {
+    HandleReadWriteRequest(v, NS_LITERAL_STRING(ATTRIBUTE_WRITE_REQUEST));
   } else {
     BT_WARNING("Not handling GATT signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
   }
 }
 
 JSObject*
 BluetoothGattServer::WrapObject(JSContext* aContext,
@@ -214,18 +284,19 @@ BluetoothGattServer::DisconnectFromOwner
   DOMEventTargetHelper::DisconnectFromOwner();
   Invalidate();
 }
 
 void
 BluetoothGattServer::Invalidate()
 {
   mValid = false;
+  mPendingService = nullptr;
   mServices.Clear();
-  mPendingService = nullptr;
+  mRequestMap.Clear();
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   if (mServerIf > 0) {
     bs->UnregisterGattServerInternal(mServerIf,
                                      new BluetoothVoidReplyRunnable(nullptr,
                                                                     nullptr));
@@ -703,8 +774,64 @@ BluetoothGattServer::RemoveService(Bluet
 
   bs->GattServerRemoveServiceInternal(
     mAppUuid, aService.GetServiceHandle(), new RemoveServiceTask(this,
                                                                  &aService,
                                                                  promise));
 
   return promise.forget();
 }
+
+already_AddRefed<Promise>
+BluetoothGattServer::SendResponse(const nsAString& aAddress,
+                                  uint16_t aStatus,
+                                  int32_t aRequestId,
+                                  ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mValid, promise, NS_ERROR_NOT_AVAILABLE);
+
+  RequestData* requestData;
+  mRequestMap.Get(aRequestId, &requestData);
+  BT_ENSURE_TRUE_REJECT(requestData, promise, NS_ERROR_UNEXPECTED);
+
+  BluetoothGattResponse response;
+  memset(&response, 0, sizeof(response));
+  response.mHandle = requestData->mHandle;
+
+  if (requestData->mCharacteristic) {
+    const nsTArray<uint8_t>& value = requestData->mCharacteristic->GetValue();
+    response.mLength = value.Length();
+    memcpy(&response.mValue, value.Elements(), response.mLength);
+  } else if (requestData->mDescriptor) {
+    const nsTArray<uint8_t>& value = requestData->mDescriptor->GetValue();
+    response.mLength = value.Length();
+    memcpy(&response.mValue, value.Elements(), response.mLength);
+  } else {
+    MOZ_ASSERT_UNREACHABLE(
+      "There should be at least one characteristic or descriptor in the "
+      "request data.");
+
+    promise->MaybeReject(NS_ERROR_INVALID_ARG);
+    return promise.forget();
+  }
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
+
+  bs->GattServerSendResponseInternal(
+    mAppUuid,
+    aAddress,
+    aStatus,
+    aRequestId,
+    response,
+    new BluetoothVoidReplyRunnable(nullptr, promise));
+
+  return promise.forget();
+}
--- a/dom/bluetooth/common/webapi/BluetoothGattServer.h
+++ b/dom/bluetooth/common/webapi/BluetoothGattServer.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_bluetooth_BluetoothGattServer_h
 #define mozilla_dom_bluetooth_BluetoothGattServer_h
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/BluetoothGattServerBinding.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothGattService.h"
 #include "mozilla/dom/Promise.h"
+#include "nsClassHashtable.h"
 #include "nsCOMPtr.h"
 #include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace dom {
 class Promise;
 }
 }
@@ -41,29 +42,36 @@ public:
   {
     aServices = mServices;
   }
 
   /****************************************************************************
    * Event Handlers
    ***************************************************************************/
   IMPL_EVENT_HANDLER(connectionstatechanged);
+  IMPL_EVENT_HANDLER(attributereadreq);
+  IMPL_EVENT_HANDLER(attributewritereq);
 
   /****************************************************************************
    * Methods (Web API Implementation)
    ***************************************************************************/
   already_AddRefed<Promise> Connect(
     const nsAString& aAddress, ErrorResult& aRv);
   already_AddRefed<Promise> Disconnect(
     const nsAString& aAddress, ErrorResult& aRv);
   already_AddRefed<Promise> AddService(BluetoothGattService& aService,
                                        ErrorResult& aRv);
   already_AddRefed<Promise> RemoveService(BluetoothGattService& aService,
                                           ErrorResult& aRv);
 
+  already_AddRefed<Promise> SendResponse(const nsAString& aAddress,
+                                         uint16_t aStatus,
+                                         int32_t aRequestId,
+                                         ErrorResult& aRv);
+
   /****************************************************************************
    * Others
    ***************************************************************************/
   void Notify(const BluetoothSignal& aData); // BluetoothSignalObserver
 
   nsPIDOMWindow* GetParentObject() const
   {
      return mOwner;
@@ -97,22 +105,39 @@ private:
   friend class AddCharacteristicTask;
   friend class AddDescriptorTask;
   friend class StartServiceTask;
   friend class CancelAddServiceTask;
   friend class AddServiceTaskQueue;
   friend class AddServiceTask;
   friend class RemoveServiceTask;
 
+  struct RequestData
+  {
+    RequestData(const BluetoothAttributeHandle& aHandle,
+                BluetoothGattCharacteristic* aCharacteristic,
+                BluetoothGattDescriptor* aDescriptor)
+    : mHandle(aHandle)
+    , mCharacteristic(aCharacteristic)
+    , mDescriptor(aDescriptor)
+    { }
+
+    BluetoothAttributeHandle mHandle;
+    nsRefPtr<BluetoothGattCharacteristic> mCharacteristic;
+    nsRefPtr<BluetoothGattDescriptor> mDescriptor;
+  };
+
   void HandleServerRegistered(const BluetoothValue& aValue);
   void HandleServerUnregistered(const BluetoothValue& aValue);
   void HandleConnectionStateChanged(const BluetoothValue& aValue);
   void HandleServiceHandleUpdated(const BluetoothValue& aValue);
   void HandleCharacteristicHandleUpdated(const BluetoothValue& aValue);
   void HandleDescriptorHandleUpdated(const BluetoothValue& aValue);
+  void HandleReadWriteRequest(const BluetoothValue& aValue,
+                              const nsAString& aString);
 
   /****************************************************************************
    * Variables
    ***************************************************************************/
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   /**
    * Random generated UUID of this GATT client.
@@ -131,13 +156,18 @@ private:
    * Array of services for this server.
    */
   nsTArray<nsRefPtr<BluetoothGattService>> mServices;
 
   /**
    * The service that is being added to this server.
    */
   nsRefPtr<BluetoothGattService> mPendingService;
+
+  /**
+   * Map request information from the request ID.
+   */
+  nsClassHashtable<nsUint32HashKey, RequestData> mRequestMap;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_BluetoothGattServer_h
--- a/dom/bluetooth/ipc/BluetoothMessageUtils.h
+++ b/dom/bluetooth/ipc/BluetoothMessageUtils.h
@@ -40,16 +40,24 @@ template <>
 struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattWriteType>
   : public ContiguousEnumSerializer<
              mozilla::dom::bluetooth::BluetoothGattWriteType,
              mozilla::dom::bluetooth::GATT_WRITE_TYPE_NO_RESPONSE,
              mozilla::dom::bluetooth::GATT_WRITE_TYPE_END_GUARD>
 { };
 
 template <>
+struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattAuthReq>
+  : public ContiguousEnumSerializer<
+             mozilla::dom::bluetooth::BluetoothGattAuthReq,
+             mozilla::dom::bluetooth::GATT_AUTH_REQ_NONE,
+             mozilla::dom::bluetooth::GATT_AUTH_REQ_END_GUARD>
+{ };
+
+template <>
 struct ParamTraits<mozilla::dom::bluetooth::BluetoothUuid>
 {
   typedef mozilla::dom::bluetooth::BluetoothUuid paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     for (uint8_t i = 0; i < 16; i++) {
       WriteParam(aMsg, aParam.mUuid[i]);
@@ -151,11 +159,45 @@ struct ParamTraits<mozilla::dom::bluetoo
     if (!ReadParam(aMsg, aIter, &(aResult->mHandle))) {
       return false;
     }
 
     return true;
   }
 };
 
+template <>
+struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattResponse>
+{
+  typedef mozilla::dom::bluetooth::BluetoothGattResponse paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mHandle);
+    WriteParam(aMsg, aParam.mOffset);
+    WriteParam(aMsg, aParam.mLength);
+    WriteParam(aMsg, aParam.mAuthReq);
+    for (uint16_t i = 0; i < aParam.mLength; i++) {
+      WriteParam(aMsg, aParam.mValue[i]);
+    }
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mHandle)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mOffset)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mLength)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mAuthReq))) {
+      return false;
+    }
+
+    for (uint16_t i = 0; i < aResult->mLength; i++) {
+      if (!ReadParam(aMsg, aIter, &(aResult->mValue[i]))) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
 } // namespace IPC
 
 #endif // mozilla_dom_bluetooth_ipc_BluetoothMessageUtils_h
--- a/dom/bluetooth/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/ipc/BluetoothParent.cpp
@@ -311,16 +311,18 @@ BluetoothParent::RecvPBluetoothRequestCo
     case Request::TGattServerAddDescriptorRequest:
       return actor->DoRequest(aRequest.get_GattServerAddDescriptorRequest());
     case Request::TGattServerRemoveServiceRequest:
       return actor->DoRequest(aRequest.get_GattServerRemoveServiceRequest());
     case Request::TGattServerStartServiceRequest:
       return actor->DoRequest(aRequest.get_GattServerStartServiceRequest());
     case Request::TGattServerStopServiceRequest:
       return actor->DoRequest(aRequest.get_GattServerStopServiceRequest());
+    case Request::TGattServerSendResponseRequest:
+      return actor->DoRequest(aRequest.get_GattServerSendResponseRequest());
     default:
       MOZ_CRASH("Unknown type!");
   }
 
   MOZ_CRASH("Should never get here!");
 }
 
 PBluetoothRequestParent*
@@ -1152,8 +1154,27 @@ BluetoothRequestParent::DoRequest(
 
   mService->GattServerStopServiceInternal(
     aRequest.appUuid(),
     aRequest.serviceHandle(),
     mReplyRunnable.get());
 
   return true;
 }
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerSendResponseRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattServerSendResponseRequest);
+
+  mService->GattServerSendResponseInternal(
+    aRequest.appUuid(),
+    aRequest.address(),
+    aRequest.status(),
+    aRequest.requestId(),
+    aRequest.response(),
+    mReplyRunnable.get());
+
+  return true;
+}
--- a/dom/bluetooth/ipc/BluetoothParent.h
+++ b/dom/bluetooth/ipc/BluetoothParent.h
@@ -295,13 +295,16 @@ protected:
   bool
   DoRequest(const GattServerRemoveServiceRequest& aRequest);
 
   bool
   DoRequest(const GattServerStartServiceRequest& aRequest);
 
   bool
   DoRequest(const GattServerStopServiceRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerSendResponseRequest& aRequest);
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_ipc_BluetoothParent_h
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
@@ -710,16 +710,30 @@ BluetoothServiceChildProcess::GattServer
   const nsAString& aAppUuid,
   const BluetoothAttributeHandle& aServiceHandle,
   BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable,
     GattServerStopServiceRequest(nsString(aAppUuid), aServiceHandle));
 }
 
+void
+BluetoothServiceChildProcess::GattServerSendResponseInternal(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  uint16_t aStatus,
+  int32_t aRequestId,
+  const BluetoothGattResponse& aRsp,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerSendResponseRequest(
+      nsString(aAppUuid), nsString(aAddress), aStatus, aRequestId, aRsp));
+}
+
 nsresult
 BluetoothServiceChildProcess::HandleStartup()
 {
   // Don't need to do anything here for startup since our Create function takes
   // care of the actor machinery.
   return NS_OK;
 }
 
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
@@ -359,16 +359,25 @@ public:
     BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattServerStopServiceInternal(
     const nsAString& aAppUuid,
     const BluetoothAttributeHandle& aServiceHandle,
     BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  GattServerSendResponseInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable) override;
+
 protected:
   BluetoothServiceChildProcess();
   virtual ~BluetoothServiceChildProcess();
 
   void
   NoteDeadActor();
 
   void
--- a/dom/bluetooth/ipc/BluetoothTypes.ipdlh
+++ b/dom/bluetooth/ipc/BluetoothTypes.ipdlh
@@ -9,16 +9,18 @@ using mozilla::dom::bluetooth::Bluetooth
 using mozilla::dom::bluetooth::BluetoothGattAttrPerm
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattCharAttribute
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattCharProp
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattId
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
+using mozilla::dom::bluetooth::BluetoothGattResponse
+  from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattServiceId
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattWriteType
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothSspVariant
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothStatus
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
--- a/dom/bluetooth/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth/ipc/PBluetooth.ipdl
@@ -354,16 +354,25 @@ struct GattServerStartServiceRequest
 };
 
 struct GattServerStopServiceRequest
 {
   nsString appUuid;
   BluetoothAttributeHandle serviceHandle;
 };
 
+struct GattServerSendResponseRequest
+{
+  nsString appUuid;
+  nsString address;
+  uint16_t status;
+  int32_t requestId;
+  BluetoothGattResponse response;
+};
+
 union Request
 {
   GetAdaptersRequest;
   StartBluetoothRequest;
   StopBluetoothRequest;
   SetPropertyRequest;
   GetPropertyRequest;
   StartDiscoveryRequest;
@@ -414,16 +423,17 @@ union Request
   UnregisterGattServerRequest;
   GattServerAddServiceRequest;
   GattServerAddIncludedServiceRequest;
   GattServerAddCharacteristicRequest;
   GattServerAddDescriptorRequest;
   GattServerRemoveServiceRequest;
   GattServerStartServiceRequest;
   GattServerStopServiceRequest;
+  GattServerSendResponseRequest;
 };
 
 protocol PBluetooth
 {
   manager PContent;
   manages PBluetoothRequest;
 
   /**
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -24,16 +24,17 @@ if CONFIG['MOZ_B2G_BT']:
         'common/BluetoothUtils.cpp',
         'common/BluetoothUuid.cpp',
         'common/ObexBase.cpp',
         'common/webapi/BluetoothAdapter.cpp',
         'common/webapi/BluetoothClassOfDevice.cpp',
         'common/webapi/BluetoothDevice.cpp',
         'common/webapi/BluetoothDiscoveryHandle.cpp',
         'common/webapi/BluetoothGatt.cpp',
+        'common/webapi/BluetoothGattAttributeEvent.cpp',
         'common/webapi/BluetoothGattCharacteristic.cpp',
         'common/webapi/BluetoothGattDescriptor.cpp',
         'common/webapi/BluetoothGattServer.cpp',
         'common/webapi/BluetoothGattService.cpp',
         'common/webapi/BluetoothLeDeviceEvent.cpp',
         'common/webapi/BluetoothManager.cpp',
         'common/webapi/BluetoothPairingHandle.cpp',
         'common/webapi/BluetoothPairingListener.cpp',
@@ -136,16 +137,17 @@ EXPORTS.mozilla.dom.bluetooth.ipc += [
 ]
 EXPORTS.mozilla.dom.bluetooth += [
     'common/BluetoothCommon.h',
     'common/webapi/BluetoothAdapter.h',
     'common/webapi/BluetoothClassOfDevice.h',
     'common/webapi/BluetoothDevice.h',
     'common/webapi/BluetoothDiscoveryHandle.h',
     'common/webapi/BluetoothGatt.h',
+    'common/webapi/BluetoothGattAttributeEvent.h',
     'common/webapi/BluetoothGattCharacteristic.h',
     'common/webapi/BluetoothGattDescriptor.h',
     'common/webapi/BluetoothGattServer.h',
     'common/webapi/BluetoothGattService.h',
     'common/webapi/BluetoothLeDeviceEvent.h',
     'common/webapi/BluetoothManager.h',
     'common/webapi/BluetoothPairingHandle.h',
     'common/webapi/BluetoothPairingListener.h',
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -59,16 +59,20 @@ const kEventConstructors = {
   BluetoothAttributeEvent:                   { create: function (aName, aProps) {
                                                           return new BluetoothAttributeEvent(aName, aProps);
                                                        },
                                              },
   BluetoothDeviceEvent:                      { create: function (aName, aProps) {
                                                           return new BluetoothDeviceEvent(aName, aProps);
                                                        },
                                              },
+  BluetoothGattAttributeEvent:               { create: function (aName, aProps) {
+                                                          return new BluetoothGattAttributeEvent(aName, aProps);
+                                                       },
+                                             },
   BluetoothGattCharacteristicEvent:          { create: function (aName, aProps) {
                                                           return new BluetoothGattCharacteristicEvent(aName, aProps);
                                                        },
                                              },
   BluetoothLeDeviceEvent:                    { create: function (aName, aProps) {
                                                           return new BluetoothLeDeviceEvent(aName, aProps);
                                                        },
                                              },
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -196,16 +196,19 @@ var interfaceNamesInGlobalScope =
     {name: "BluetoothDevice", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothDeviceEvent", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothDiscoveryHandle", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothGatt", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothGattAttributeEvent", b2g: true,
+     permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothGattCharacteristic", b2g: true,
      permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothGattCharacteristicEvent", b2g: true,
      permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothGattDescriptor", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BluetoothGattAttributeEvent.webidl
@@ -0,0 +1,33 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+[Constructor(DOMString type,
+             optional BluetoothGattAttributeEventInit eventInitDict),
+ CheckAnyPermissions="bluetooth"]
+interface BluetoothGattAttributeEvent : Event
+{
+  readonly attribute DOMString                    address;
+  readonly attribute long                         requestId;
+  readonly attribute BluetoothGattCharacteristic? characteristic;
+  readonly attribute BluetoothGattDescriptor?     descriptor;
+  [Throws]
+  readonly attribute ArrayBuffer?                 value;
+  readonly attribute boolean                      needResponse;
+};
+
+dictionary BluetoothGattAttributeEventInit : EventInit
+{
+  DOMString address = "";
+  long requestId = 0;
+  BluetoothGattCharacteristic? characteristic = null;
+  BluetoothGattDescriptor? descriptor = null;
+  /**
+   * Note that the passed-in value will be copied by the event constructor
+   * here instead of storing the reference.
+   */
+  ArrayBuffer? value = null;
+  boolean needResponse = true;
+};
--- a/dom/webidl/BluetoothGattServer.webidl
+++ b/dom/webidl/BluetoothGattServer.webidl
@@ -8,16 +8,20 @@
 interface BluetoothGattServer : EventTarget
 {
   [Cached, Pure]
   readonly attribute sequence<BluetoothGattService> services;
 
   // Fired when a remote device has been connected/disconnected
   attribute EventHandler  onconnectionstatechanged;
 
+  // Fired when a remote BLE client send a read/write request
+  attribute EventHandler  onattributereadreq;
+  attribute EventHandler  onattributewritereq;
+
   /**
    * Connect/Disconnect to the remote BLE device with the target address.
    *
    * Promise will be rejected if the local GATT server is busy connecting or
    * disconnecting to other devices.
    */
   [NewObject]
   Promise<void> connect(DOMString address);
@@ -36,9 +40,16 @@ interface BluetoothGattServer : EventTar
   /**
    * Remove a BLE service to the local GATT server.
    *
    * This API will be rejected if this service does not exist in the GATT
    * server.
    */
   [NewObject]
   Promise<void> removeService(BluetoothGattService service);
+
+  /**
+   * Send a read/write response to a remote BLE client
+   */
+  [NewObject]
+  Promise<void> sendResponse(
+    DOMString address, unsigned short status, long requestId);
 };
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -678,16 +678,17 @@ if CONFIG['MOZ_DEBUG']:
 
 if CONFIG['MOZ_B2G_BT']:
     WEBIDL_FILES += [
         'BluetoothAdapter.webidl',
         'BluetoothClassOfDevice.webidl',
         'BluetoothDevice.webidl',
         'BluetoothDiscoveryHandle.webidl',
         'BluetoothGatt.webidl',
+        'BluetoothGattAttributeEvent.webidl',
         'BluetoothGattCharacteristic.webidl',
         'BluetoothGattDescriptor.webidl',
         'BluetoothGattServer.webidl',
         'BluetoothGattService.webidl',
         'BluetoothLeDeviceEvent.webidl',
         'BluetoothManager.webidl',
         'BluetoothPairingHandle.webidl',
         'BluetoothPairingListener.webidl',