Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=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
#include "mozilla/dom/MLS.h"
#include "MLSGroupView.h"
#include "MLSLogging.h"
#include "MLSTypeUtils.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/Logging.h"
#include "mozilla/dom/MLSGroupView.h"
#include "mozilla/dom/MLSTransactionChild.h"
#include "mozilla/dom/MLSTransactionMessage.h"
#include "mozilla/dom/PMLSTransaction.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsIGlobalObject.h"
#include "nsTArray.h"
namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MLS, mGlobalObject)
NS_IMPL_CYCLE_COLLECTING_ADDREF(MLS)
NS_IMPL_CYCLE_COLLECTING_RELEASE(MLS)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MLS)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
// Setup logging
mozilla::LazyLogModule gMlsLog("MLS");
/* static */ already_AddRefed<MLS> MLS::Constructor(GlobalObject& aGlobalObject,
                                                    ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLS::Constructor()"));
  nsCOMPtr<nsIGlobalObject> global(
      do_QueryInterface(aGlobalObject.GetAsSupports()));
  if (NS_WARN_IF(!global)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }
  // Get the principal and perform some validation on it.
  // We do not allow MLS in Private Browsing Mode for now.
  nsIPrincipal* principal = global->PrincipalOrNull();
  if (!principal || !principal->GetIsContentPrincipal() ||
      principal->GetIsInPrivateBrowsing()) {
    aRv.ThrowSecurityError("Cannot create MLS store for origin");
    return nullptr;
  }
  // Create the endpoints for the MLS actor
  mozilla::ipc::Endpoint<PMLSTransactionParent> parentEndpoint;
  mozilla::ipc::Endpoint<PMLSTransactionChild> childEndpoint;
  MOZ_ALWAYS_SUCCEEDS(
      PMLSTransaction::CreateEndpoints(&parentEndpoint, &childEndpoint));
  mozilla::ipc::PBackgroundChild* backgroundChild =
      mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
  if (!backgroundChild) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }
  // Bind the child actor, and send the parent endpoint.
  RefPtr<MLSTransactionChild> actor = new MLSTransactionChild();
  MOZ_ALWAYS_TRUE(childEndpoint.Bind(actor));
  MOZ_ALWAYS_TRUE(backgroundChild->SendCreateMLSTransaction(
      std::move(parentEndpoint), WrapNotNull(principal)));
  return MakeAndAddRef<MLS>(global, actor);
}
MLS::MLS(nsIGlobalObject* aGlobalObject, MLSTransactionChild* aActor)
    : mGlobalObject(aGlobalObject), mTransactionChild(aActor) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLS::MLS()"));
}
MLS::~MLS() {
  if (mTransactionChild) {
    mTransactionChild->Close();
  }
}
JSObject* MLS::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
  return MLS_Binding::Wrap(aCx, this, aGivenProto);
}
//
// API
//
already_AddRefed<Promise> MLS::DeleteState(ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLS::DeleteState()"));
  // Create a new Promise object for the result
  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  mTransactionChild->SendRequestStateDelete(
      [promise](bool result) {
        if (result) {
          promise->MaybeResolveWithUndefined();
        } else {
          promise->MaybeReject(NS_ERROR_FAILURE);
        }
      },
      [promise](::mozilla::ipc::ResponseRejectReason) {
        promise->MaybeRejectWithUnknownError("deleteState failed");
      });
  return promise.forget();
}
already_AddRefed<Promise> MLS::GenerateIdentity(ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLS::GenerateIdentity()"));
  // Create a new Promise object for the result
  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  mTransactionChild->SendRequestGenerateIdentityKeypair()->Then(
      GetCurrentSerialEventTarget(), __func__,
      [promise, self = RefPtr{this}](Maybe<RawBytes>&& result) {
        // Check if the value is Nothing
        if (result.isNothing()) {
          promise->MaybeRejectWithUnknownError(
              "generateIdentityKeypair failed");
          return;
        }
        // Get the context from the GlobalObject
        AutoJSAPI jsapi;
        if (NS_WARN_IF(!jsapi.Init(self->mGlobalObject))) {
          promise->MaybeRejectWithUnknownError(
              "generateIdentityKeypair failed");
          return;
        }
        JSContext* cx = jsapi.cx();
        // Construct the Uint8Array object
        ErrorResult error;
        JS::Rooted<JSObject*> content(
            cx, Uint8Array::Create(cx, result->data(), error));
        error.WouldReportJSException();
        if (error.Failed()) {
          promise->MaybeReject(std::move(error));
          return;
        }
        // Construct MLSBytes with the client identifer as content
        RootedDictionary<MLSBytes> rvalue(cx);
        rvalue.mType = MLSObjectType::Client_identifier;
        rvalue.mContent.Init(content);
        // Resolve the promise with the MLSBytes object
        promise->MaybeResolve(rvalue);
      },
      [promise](::mozilla::ipc::ResponseRejectReason aReason) {
        promise->MaybeRejectWithUnknownError("generateIdentity failed");
      });
  return promise.forget();
}
already_AddRefed<Promise> MLS::GenerateCredential(
    const MLSBytesOrUint8ArrayOrUTF8String& aJsCredContent, ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
          ("MLS::GenerateCredentialBasic()"));
  // Handle the credential content parameter
  nsTArray<uint8_t> credContent = ExtractMLSBytesOrUint8ArrayOrUTF8String(
      MLSObjectType::Credential_basic, aJsCredContent, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the credContent is empty
  if (NS_WARN_IF(credContent.IsEmpty())) {
    aRv.ThrowTypeError("The credential content must not be empty");
    return nullptr;
  }
  // Create a new Promise object for the result
  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  mTransactionChild->SendRequestGenerateCredentialBasic(credContent)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise, self = RefPtr{this}](Maybe<RawBytes>&& result) {
            // Check if the value is Nothing
            if (result.isNothing()) {
              promise->MaybeRejectWithUnknownError(
                  "generateCredentialBasic failed");
              return;
            }
            // Get the context from the GlobalObject
            AutoJSAPI jsapi;
            if (NS_WARN_IF(!jsapi.Init(self->mGlobalObject))) {
              promise->MaybeRejectWithUnknownError(
                  "generateCredentialBasic failed");
              return;
            }
            JSContext* cx = jsapi.cx();
            // Construct the Uint8Array object
            ErrorResult error;
            JS::Rooted<JSObject*> content(
                cx, Uint8Array::Create(cx, result->data(), error));
            error.WouldReportJSException();
            if (error.Failed()) {
              promise->MaybeReject(std::move(error));
              return;
            }
            // Construct MLSBytes with the client identifer as content
            RootedDictionary<MLSBytes> rvalue(cx);
            rvalue.mType = MLSObjectType::Credential_basic;
            rvalue.mContent.Init(content);
            // Resolve the promise
            promise->MaybeResolve(rvalue);
          },
          [promise](::mozilla::ipc::ResponseRejectReason aReason) {
            promise->MaybeRejectWithUnknownError(
                "generateCredentialBasic failed");
          });
  return promise.forget();
}
already_AddRefed<Promise> MLS::GenerateKeyPackage(
    const MLSBytesOrUint8Array& aJsClientIdentifier,
    const MLSBytesOrUint8Array& aJsCredential, ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLS::GenerateKeyPackage()"));
  // Handle the client identifier parameter
  nsTArray<uint8_t> clientIdentifier = ExtractMLSBytesOrUint8Array(
      MLSObjectType::Client_identifier, aJsClientIdentifier, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the client identifier is empty
  if (NS_WARN_IF(clientIdentifier.IsEmpty())) {
    aRv.ThrowTypeError("The client identifier must not be empty");
    return nullptr;
  }
  // Handle the credential parameter
  nsTArray<uint8_t> credential = ExtractMLSBytesOrUint8Array(
      MLSObjectType::Credential_basic, aJsCredential, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the credential is empty
  if (NS_WARN_IF(credential.IsEmpty())) {
    aRv.ThrowTypeError("The credential must not be empty");
    return nullptr;
  }
  // Create a new Promise object for the result
  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Use the static method or instance to send the IPC message
  mTransactionChild->SendRequestGenerateKeyPackage(clientIdentifier, credential)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise, self = RefPtr{this}](Maybe<RawBytes>&& keyPackage) {
            // Check if the value is Nothing
            if (keyPackage.isNothing()) {
              promise->MaybeReject(NS_ERROR_FAILURE);
              return;
            }
            // Get the context from the GlobalObject
            AutoJSAPI jsapi;
            if (NS_WARN_IF(!jsapi.Init(self->mGlobalObject))) {
              promise->MaybeReject(NS_ERROR_FAILURE);
              return;
            }
            JSContext* cx = jsapi.cx();
            // Construct the Uint8Array object
            ErrorResult error;
            JS::Rooted<JSObject*> content(
                cx, Uint8Array::Create(cx, keyPackage->data(), error));
            error.WouldReportJSException();
            if (error.Failed()) {
              promise->MaybeReject(std::move(error));
              return;
            }
            // Construct MLSBytes with the client identifer as content
            RootedDictionary<MLSBytes> rvalue(cx);
            rvalue.mType = MLSObjectType::Key_package;
            rvalue.mContent.Init(content);
            // Resolve the promise
            promise->MaybeResolve(rvalue);
          },
          [promise](::mozilla::ipc::ResponseRejectReason aReason) {
            promise->MaybeRejectWithUnknownError("generateKeyPackage failed");
          });
  return promise.forget();
}
already_AddRefed<Promise> MLS::GroupCreate(
    const MLSBytesOrUint8Array& aJsClientIdentifier,
    const MLSBytesOrUint8Array& aJsCredential, ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLS::GroupCreate()"));
  // Handle the client identifier parameter
  nsTArray<uint8_t> clientIdentifier = ExtractMLSBytesOrUint8Array(
      MLSObjectType::Client_identifier, aJsClientIdentifier, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the client identifier is empty
  if (NS_WARN_IF(clientIdentifier.IsEmpty())) {
    aRv.ThrowTypeError("The client identifier must not be empty");
    return nullptr;
  }
  // Handle the credential parameter
  nsTArray<uint8_t> credential = ExtractMLSBytesOrUint8Array(
      MLSObjectType::Credential_basic, aJsCredential, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the credential is empty
  if (NS_WARN_IF(credential.IsEmpty())) {
    aRv.ThrowTypeError("The credential must not be empty");
    return nullptr;
  }
  // Log the hex of clientIdentifier
  if (MOZ_LOG_TEST(gMlsLog, LogLevel::Debug)) {
    nsAutoCString clientIdHex;
    for (uint8_t byte : clientIdentifier) {
      clientIdHex.AppendPrintf("%02X", byte);
    }
    MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
            ("clientIdentifier in hex: %s\n", clientIdHex.get()));
  }
  // Initialize jsGroupIdentifier to one byte of value 0xFF.
  // We do not want to allow choosing the GID at this point.
  // This value not being of the correct length will be discarded
  // internally and a fresh GID will be generated.
  //
  // In the future, the caller will allow choosing the GID.
  AutoTArray<uint8_t, 1> groupIdentifier{0xFF};
  // Create a new Promise object for the result
  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Use the static method or instance to send the IPC message
  mTransactionChild
      ->SendRequestGroupCreate(clientIdentifier, credential, groupIdentifier)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise, self = RefPtr{this},
           clientIdentifier(std::move(clientIdentifier))](
              Maybe<mozilla::security::mls::GkGroupIdEpoch>&&
                  groupIdEpoch) mutable {
            // Check if the value is Nothing
            if (groupIdEpoch.isNothing()) {
              promise->MaybeReject(NS_ERROR_FAILURE);
              return;
            }
            RefPtr<MLSGroupView> group =
                new MLSGroupView(self, std::move(groupIdEpoch->group_id),
                                 std::move(clientIdentifier));
            promise->MaybeResolve(group);
          },
          [promise](::mozilla::ipc::ResponseRejectReason aReason) {
            MOZ_LOG(gMlsLog, mozilla::LogLevel::Error,
                    ("IPC message rejected with reason: %d",
                     static_cast<int>(aReason)));
            promise->MaybeRejectWithUnknownError("groupCreate failed");
          });
  return promise.forget();
}
already_AddRefed<mozilla::dom::Promise> MLS::GroupGet(
    const MLSBytesOrUint8Array& aJsGroupIdentifier,
    const MLSBytesOrUint8Array& aJsClientIdentifier, ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLS::GroupGet()"));
  // Handle the group identifier parameter
  nsTArray<uint8_t> groupIdentifier = ExtractMLSBytesOrUint8Array(
      MLSObjectType::Group_identifier, aJsGroupIdentifier, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the group identifier is empty
  if (NS_WARN_IF(groupIdentifier.IsEmpty())) {
    aRv.ThrowTypeError("The group identifier must not be empty");
    return nullptr;
  }
  // Handle the client identifier parameter
  nsTArray<uint8_t> clientIdentifier = ExtractMLSBytesOrUint8Array(
      MLSObjectType::Client_identifier, aJsClientIdentifier, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the client identifier is empty
  if (NS_WARN_IF(clientIdentifier.IsEmpty())) {
    aRv.ThrowTypeError("The client identifier must not be empty");
    return nullptr;
  }
  // Create a new Promise object for the result
  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Initialize label, context and len
  // We pass this through IPC to be able to reuse the same code for different
  // labels in the future
  AutoTArray<uint8_t, 7> label{'l', 'i', 'v', 'e', 'n', 'e', 's', 's'};
  AutoTArray<uint8_t, 1> context{0x00};
  uint64_t len = 32;
  // Use the static method or instance to send the IPC message
  mTransactionChild
      ->SendRequestExportSecret(groupIdentifier, clientIdentifier, label,
                                context, len)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise, self = RefPtr{this},
           groupIdentifier(std::move(groupIdentifier)),
           clientIdentifier(std::move(clientIdentifier))](
              Maybe<mozilla::security::mls::GkExporterOutput>&&
                  exporterOutput) mutable {
            // Check if the exporterOutput contains a value
            if (exporterOutput.isNothing()) {
              promise->MaybeReject(NS_ERROR_FAILURE);
              return;
            }
            RefPtr<MLSGroupView> group =
                new MLSGroupView(self, std::move(exporterOutput->group_id),
                                 std::move(clientIdentifier));
            promise->MaybeResolve(group);
          },
          [promise](::mozilla::ipc::ResponseRejectReason aReason) {
            promise->MaybeRejectWithUnknownError("exportSecret failed");
          });
  return promise.forget();
}
already_AddRefed<Promise> MLS::GroupJoin(
    const MLSBytesOrUint8Array& aJsClientIdentifier,
    const MLSBytesOrUint8Array& aJsWelcome, ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLS::GroupJoin()"));
  // Handle the client identifier parameter
  nsTArray<uint8_t> clientIdentifier = ExtractMLSBytesOrUint8Array(
      MLSObjectType::Client_identifier, aJsClientIdentifier, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the client identifier is empty
  if (NS_WARN_IF(clientIdentifier.IsEmpty())) {
    aRv.ThrowTypeError("The client identifier must not be empty");
    return nullptr;
  }
  // Handle the welcome parameter
  nsTArray<uint8_t> welcome =
      ExtractMLSBytesOrUint8Array(MLSObjectType::Welcome, aJsWelcome, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the welcome is empty
  if (NS_WARN_IF(welcome.IsEmpty())) {
    aRv.ThrowTypeError("The welcome must not be empty");
    return nullptr;
  }
  // Create a new Promise object for the result
  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  mTransactionChild->SendRequestGroupJoin(clientIdentifier, welcome)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise, self = RefPtr{this},
           clientIdentifier(std::move(clientIdentifier))](
              Maybe<mozilla::security::mls::GkGroupIdEpoch>&&
                  groupIdEpoch) mutable {
            // Check if the value is Nothing
            if (groupIdEpoch.isNothing()) {
              promise->MaybeReject(NS_ERROR_FAILURE);
              return;
            }
            // Returns groupId and epoch
            RefPtr<MLSGroupView> group =
                new MLSGroupView(self, std::move(groupIdEpoch->group_id),
                                 std::move(clientIdentifier));
            // Resolve the promise
            promise->MaybeResolve(group);
          },
          [promise](::mozilla::ipc::ResponseRejectReason aReason) {
            promise->MaybeRejectWithUnknownError("groupJoin failed");
          });
  return promise.forget();
}
already_AddRefed<Promise> MLS::GetGroupIdFromMessage(
    const MLSBytesOrUint8Array& aJsMessage, ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLS::GetGroupIdFromMessage()"));
  // Handle the message parameter
  nsTArray<uint8_t> message =
      ExtractMLSBytesOrUint8ArrayWithUnknownType(aJsMessage, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the message is empty
  if (NS_WARN_IF(message.IsEmpty())) {
    aRv.ThrowTypeError("The message must not be empty");
    return nullptr;
  }
  // Create a new Promise object for the result
  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  mTransactionChild->SendRequestGetGroupIdentifier(message)->Then(
      GetCurrentSerialEventTarget(), __func__,
      [promise, self = RefPtr{this},
       message(std::move(message))](Maybe<RawBytes>&& result) {
        // Check if the value is Nothing
        if (result.isNothing()) {
          promise->MaybeReject(NS_ERROR_FAILURE);
          return;
        }
        // Get the context from the GlobalObject
        AutoJSAPI jsapi;
        if (NS_WARN_IF(!jsapi.Init(self->mGlobalObject))) {
          MOZ_LOG(gMlsLog, mozilla::LogLevel::Error,
                  ("Failed to initialize JSAPI"));
          promise->MaybeReject(NS_ERROR_FAILURE);
          return;
        }
        JSContext* cx = jsapi.cx();
        // Construct the Uint8Array objects based on the tag
        ErrorResult error;
        JS::Rooted<JSObject*> jsGroupId(
            cx, Uint8Array::Create(cx, result->data(), error));
        error.WouldReportJSException();
        if (error.Failed()) {
          promise->MaybeReject(std::move(error));
          return;
        }
        // Construct the MLSBytes object for the groupId
        RootedDictionary<MLSBytes> rvalue(cx);
        rvalue.mType = MLSObjectType::Group_identifier;
        rvalue.mContent.Init(jsGroupId);
        // Log if in debug mode
        if (MOZ_LOG_TEST(gMlsLog, LogLevel::Debug)) {
          MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
                  ("Successfully constructed MLSBytes"));
        }
        // Resolve the promise
        promise->MaybeResolve(rvalue);
      },
      [promise](::mozilla::ipc::ResponseRejectReason aReason) {
        MOZ_LOG(
            gMlsLog, mozilla::LogLevel::Error,
            ("IPC call rejected with reason: %d", static_cast<int>(aReason)));
        promise->MaybeRejectWithUnknownError("getGroupIdFromMessage failed");
      });
  return promise.forget();
}
already_AddRefed<Promise> MLS::GetGroupEpochFromMessage(
    const MLSBytesOrUint8Array& aJsMessage, ErrorResult& aRv) {
  MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
          ("MLS::GetGroupEpochFromMessage()"));
  // Handle the message parameter
  nsTArray<uint8_t> message =
      ExtractMLSBytesOrUint8ArrayWithUnknownType(aJsMessage, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  // Check if the message is empty
  if (NS_WARN_IF(message.IsEmpty())) {
    aRv.ThrowTypeError("The message must not be empty");
    return nullptr;
  }
  // Create a new Promise object for the result
  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  mTransactionChild->SendRequestGetGroupEpoch(message)->Then(
      GetCurrentSerialEventTarget(), __func__,
      [promise, self = RefPtr{this},
       message(std::move(message))](Maybe<RawBytes>&& result) {
        // Check if the value is Nothing
        if (result.isNothing()) {
          promise->MaybeReject(NS_ERROR_FAILURE);
          return;
        }
        // Get the context from the GlobalObject
        AutoJSAPI jsapi;
        if (NS_WARN_IF(!jsapi.Init(self->mGlobalObject))) {
          MOZ_LOG(gMlsLog, mozilla::LogLevel::Error,
                  ("Failed to initialize JSAPI"));
          promise->MaybeReject(NS_ERROR_FAILURE);
          return;
        }
        JSContext* cx = jsapi.cx();
        // Construct the Uint8Array objects based on the tag
        ErrorResult error;
        JS::Rooted<JSObject*> jsGroupId(
            cx, Uint8Array::Create(cx, result->data(), error));
        error.WouldReportJSException();
        if (error.Failed()) {
          promise->MaybeReject(std::move(error));
          return;
        }
        // Construct the MLSBytes object for the groupId
        RootedDictionary<MLSBytes> rvalue(cx);
        rvalue.mType = MLSObjectType::Group_epoch;
        rvalue.mContent.Init(jsGroupId);
        // Log if in debug mode
        if (MOZ_LOG_TEST(gMlsLog, LogLevel::Debug)) {
          MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
                  ("Successfully constructed MLSBytes"));
        }
        // Resolve the promise
        promise->MaybeResolve(rvalue);
      },
      [promise](::mozilla::ipc::ResponseRejectReason aReason) {
        MOZ_LOG(
            gMlsLog, mozilla::LogLevel::Error,
            ("IPC call rejected with reason: %d", static_cast<int>(aReason)));
        promise->MaybeRejectWithUnknownError("getGroupEpochFromMessage failed");
      });
  return promise.forget();
}
}  // namespace mozilla::dom