/* -*- 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MLSGroupView.h" #include "mozilla/dom/MLSBinding.h" #include "mozilla/dom/TypedArray.h" #include "mozilla/dom/Promise.h" #include "nsTArray.h" #include "mozilla/dom/MLSTransactionChild.h" #include "mozilla/dom/MLSTransactionMessage.h" #include "ipc/IPCMessageUtilsSpecializations.h" #include "mozilla/BasePrincipal.h" #include "nsTArray.h" #include "mozilla/Logging.h" #include "mozilla/Span.h" #include "nsDebug.h" #include "MLSTypeUtils.h" namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(MLSGroupView, (mMLS), (mJsGroupId, mJsClientId)) NS_IMPL_CYCLE_COLLECTING_ADDREF(MLSGroupView) NS_IMPL_CYCLE_COLLECTING_RELEASE(MLSGroupView) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MLSGroupView) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END // Setup logging extern mozilla::LazyLogModule gMlsLog; MLSGroupView::MLSGroupView(MLS* aMLS, nsTArray&& aGroupId, nsTArray&& aClientId) : mMLS(aMLS), mGroupId(std::move(aGroupId)), mClientId(std::move(aClientId)) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::MLSGroupView()")); // Indicate that the object holds JS objects mozilla::HoldJSObjects(this); } JSObject* MLSGroupView::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return MLSGroupView_Binding::Wrap(aCx, this, aGivenProto); } // // API // void MLSGroupView::GetGroupId(JSContext* aCx, JS::MutableHandle aGroupId, ErrorResult& aRv) { if (!mJsGroupId) { mJsGroupId = Uint8Array::Create(aCx, this, mGroupId, aRv); if (aRv.Failed()) { return; } } aGroupId.set(mJsGroupId); } void MLSGroupView::GetClientId(JSContext* aCx, JS::MutableHandle aClientId, ErrorResult& aRv) { if (!mJsClientId) { mJsClientId = Uint8Array::Create(aCx, this, mClientId, aRv); if (aRv.Failed()) { return; } } aClientId.set(mJsClientId); } already_AddRefed MLSGroupView::DeleteState(ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::DeleteState()")); // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } mMLS->mTransactionChild->SendRequestGroupStateDelete(mGroupId, mClientId) ->Then( GetCurrentSerialEventTarget(), __func__, [promise]( Maybe&& groupIdEpoch) { // Check if the value is Nothing or Some with an empty group_epoch if (groupIdEpoch.isNothing()) { promise->MaybeRejectWithUnknownError( "Failed to delete group state"); return; } // Check if the epoch is 0xFFFF..FF bool isMaxEpoch = std::all_of(groupIdEpoch->group_epoch.begin(), groupIdEpoch->group_epoch.end(), [](uint8_t byte) { return byte == 0xFF; }); // If the epoch is 0xFFFF..FF, then the group has been deleted if (isMaxEpoch) { promise->MaybeResolveWithUndefined(); } else { promise->MaybeRejectWithUnknownError( "Group has not been deleted"); } }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { promise->MaybeRejectWithUnknownError( "Failed to delete group state"); }); return promise.forget(); } already_AddRefed MLSGroupView::Add( const MLSBytesOrUint8Array& aJsKeyPackage, ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Add()")); // Handle the key package parameter nsTArray keyPackage = ExtractMLSBytesOrUint8Array( MLSObjectType::Key_package, aJsKeyPackage, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Check if the key package is empty if (NS_WARN_IF(keyPackage.IsEmpty())) { aRv.ThrowTypeError("The key package must not be empty"); return nullptr; } // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } mMLS->mTransactionChild->SendRequestGroupAdd(mGroupId, mClientId, keyPackage) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)]( Maybe&& commitOutput) { // Check if the value is Nothing if (commitOutput.isNothing()) { promise->MaybeReject(NS_ERROR_FAILURE); return; } // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { promise->MaybeReject(NS_ERROR_FAILURE); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array objects ErrorResult error; JS::Rooted jsGroupId( cx, Uint8Array::Create(cx, self->mGroupId, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsClientId( cx, Uint8Array::Create(cx, commitOutput->identity, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsCommit( cx, Uint8Array::Create(cx, commitOutput->commit, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsWelcome( cx, Uint8Array::Create(cx, commitOutput->welcome, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsGroupInfo( cx, Uint8Array::Create(cx, commitOutput->group_info, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsRatchetTree( cx, Uint8Array::Create(cx, commitOutput->ratchet_tree, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Construct MLSCommitOutput with the parsed data RootedDictionary rvalue(cx); rvalue.mType = MLSObjectType::Commit_output; rvalue.mGroupId.Init(jsGroupId); rvalue.mCommit.Init(jsCommit); if (!commitOutput->welcome.IsEmpty()) { rvalue.mWelcome.Construct(); rvalue.mWelcome.Value().Init(jsWelcome); } if (!commitOutput->group_info.IsEmpty()) { rvalue.mGroupInfo.Construct(); rvalue.mGroupInfo.Value().Init(jsGroupInfo); } if (!commitOutput->ratchet_tree.IsEmpty()) { rvalue.mRatchetTree.Construct(); rvalue.mRatchetTree.Value().Init(jsRatchetTree); } if (!commitOutput->identity.IsEmpty()) { rvalue.mClientId.Construct(); rvalue.mClientId.Value().Init(jsClientId); } // Resolve the promise promise->MaybeResolve(rvalue); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { promise->MaybeRejectWithUnknownError("Failed to add to group"); }); return promise.forget(); } already_AddRefed MLSGroupView::ProposeAdd( const MLSBytesOrUint8Array& aJsKeyPackage, ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ProposeAdd()")); // Handle the key package parameter nsTArray keyPackage = ExtractMLSBytesOrUint8Array( MLSObjectType::Key_package, aJsKeyPackage, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Check if the key package is empty if (NS_WARN_IF(keyPackage.IsEmpty())) { aRv.ThrowTypeError("The key package must not be empty"); return nullptr; } // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } mMLS->mTransactionChild ->SendRequestGroupProposeAdd(mGroupId, mClientId, keyPackage) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)](Maybe&& proposal) { // Check if the value is Nothing if (proposal.isNothing()) { promise->MaybeRejectWithUnknownError( "Failed to propose add to group"); return; } // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { promise->MaybeReject(NS_ERROR_FAILURE); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array object ErrorResult error; JS::Rooted content( cx, Uint8Array::Create(cx, proposal->data(), error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Construct MLSBytes with the proposal as content RootedDictionary rvalue(cx); rvalue.mType = MLSObjectType::Proposal; rvalue.mContent.Init(content); // Resolve the promise promise->MaybeResolve(rvalue); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { promise->MaybeRejectWithUnknownError( "Failed to propose add to group"); }); return promise.forget(); } already_AddRefed MLSGroupView::Remove( const MLSBytesOrUint8Array& aJsRemClientIdentifier, ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Remove()")); // Handle the remove client identifier parameter nsTArray remClientIdentifier = ExtractMLSBytesOrUint8Array( MLSObjectType::Client_identifier, aJsRemClientIdentifier, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Check if the remove client identifier is empty if (NS_WARN_IF(remClientIdentifier.IsEmpty())) { aRv.ThrowTypeError("The remove client identifier must not be empty"); return nullptr; } // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Use the static method or instance to send the IPC message mMLS->mTransactionChild ->SendRequestGroupRemove(mGroupId, mClientId, remClientIdentifier) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)]( Maybe&& commitOutput) { // Check if the value is Nothing if (commitOutput.isNothing()) { promise->MaybeReject(NS_ERROR_FAILURE); return; } // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { promise->MaybeReject(NS_ERROR_FAILURE); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array objects ErrorResult error; JS::Rooted jsGroupId( cx, Uint8Array::Create(cx, self->mGroupId, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsClientId( cx, Uint8Array::Create(cx, commitOutput->identity, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsCommit( cx, Uint8Array::Create(cx, commitOutput->commit, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsWelcome( cx, Uint8Array::Create(cx, commitOutput->welcome, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsGroupInfo( cx, Uint8Array::Create(cx, commitOutput->group_info, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsRatchetTree( cx, Uint8Array::Create(cx, commitOutput->ratchet_tree, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Construct MLSCommitOutput with the parsed data RootedDictionary rvalue(cx); rvalue.mType = MLSObjectType::Commit_output; rvalue.mGroupId.Init(jsGroupId); rvalue.mCommit.Init(jsCommit); if (!commitOutput->welcome.IsEmpty()) { rvalue.mWelcome.Construct(); rvalue.mWelcome.Value().Init(jsWelcome); } if (!commitOutput->group_info.IsEmpty()) { rvalue.mGroupInfo.Construct(); rvalue.mGroupInfo.Value().Init(jsGroupInfo); } if (!commitOutput->ratchet_tree.IsEmpty()) { rvalue.mRatchetTree.Construct(); rvalue.mRatchetTree.Value().Init(jsRatchetTree); } if (!commitOutput->identity.IsEmpty()) { rvalue.mClientId.Construct(); rvalue.mClientId.Value().Init(jsClientId); } // Resolve the promise promise->MaybeResolve(rvalue); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { promise->MaybeRejectWithUnknownError("Failed to remove from group"); }); return promise.forget(); } already_AddRefed MLSGroupView::ProposeRemove( const MLSBytesOrUint8Array& aJsRemClientIdentifier, ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ProposeRemove()")); // Handle the remove client identifier parameter nsTArray remClientIdentifier = ExtractMLSBytesOrUint8Array( MLSObjectType::Client_identifier, aJsRemClientIdentifier, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Check if the removed client identifier is empty if (NS_WARN_IF(remClientIdentifier.IsEmpty())) { aRv.ThrowTypeError("The removed client identifier must not be empty"); return nullptr; } // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } mMLS->mTransactionChild ->SendRequestGroupProposeRemove(mGroupId, mClientId, remClientIdentifier) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)](Maybe&& proposal) { // Check if the value is Nothing if (proposal.isNothing()) { promise->MaybeReject(NS_ERROR_FAILURE); return; } // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { promise->MaybeReject(NS_ERROR_FAILURE); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array object ErrorResult error; JS::Rooted content( cx, Uint8Array::Create(cx, proposal->data(), error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Construct MLSBytes with the proposal as content RootedDictionary rvalue(cx); rvalue.mType = MLSObjectType::Proposal; rvalue.mContent.Init(content); // Resolve the promise promise->MaybeResolve(rvalue); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { promise->MaybeRejectWithUnknownError( "Failed to propose remove from group"); }); return promise.forget(); } already_AddRefed MLSGroupView::Close(ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Close()")); // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } mMLS->mTransactionChild->SendRequestGroupClose(mGroupId, mClientId) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)]( Maybe&& commitOutput) { // Check if the value is Nothing if (commitOutput.isNothing()) { promise->MaybeReject(NS_ERROR_FAILURE); return; } // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { promise->MaybeReject(NS_ERROR_FAILURE); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array objects ErrorResult error; JS::Rooted jsGroupId( cx, Uint8Array::Create(cx, self->mGroupId, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsClientId( cx, Uint8Array::Create(cx, commitOutput->identity, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsCommit( cx, Uint8Array::Create(cx, commitOutput->commit, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsWelcome( cx, Uint8Array::Create(cx, commitOutput->welcome, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsGroupInfo( cx, Uint8Array::Create(cx, commitOutput->group_info, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsRatchetTree( cx, Uint8Array::Create(cx, commitOutput->ratchet_tree, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Construct MLSCommitOutput with the parsed data RootedDictionary rvalue(cx); rvalue.mType = MLSObjectType::Commit_output; rvalue.mGroupId.Init(jsGroupId); rvalue.mCommit.Init(jsCommit); if (!commitOutput->welcome.IsEmpty()) { rvalue.mWelcome.Construct(); rvalue.mWelcome.Value().Init(jsWelcome); } if (!commitOutput->group_info.IsEmpty()) { rvalue.mGroupInfo.Construct(); rvalue.mGroupInfo.Value().Init(jsGroupInfo); } if (!commitOutput->ratchet_tree.IsEmpty()) { rvalue.mRatchetTree.Construct(); rvalue.mRatchetTree.Value().Init(jsRatchetTree); } if (!commitOutput->identity.IsEmpty()) { rvalue.mClientId.Construct(); rvalue.mClientId.Value().Init(jsClientId); } // Resolve the promise promise->MaybeResolve(rvalue); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { promise->MaybeRejectWithUnknownError("Failed to close group"); }); return promise.forget(); } already_AddRefed MLSGroupView::Details(ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Details()")); // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } mMLS->mTransactionChild->SendRequestGroupDetails(mGroupId, mClientId) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)]( Maybe&& groupDetails) { // Check if the value is Nothing if (groupDetails.isNothing()) { promise->MaybeReject(NS_ERROR_FAILURE); return; } // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { promise->MaybeReject(NS_ERROR_FAILURE); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array objects ErrorResult error; JS::Rooted jsGroupId( cx, Uint8Array::Create(cx, groupDetails->group_id, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsGroupEpoch( cx, Uint8Array::Create(cx, groupDetails->group_epoch, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Construct MLSGroupDetails RootedDictionary rvalue(cx); rvalue.mType = MLSObjectType::Group_info; rvalue.mGroupId.Init(jsGroupId); rvalue.mGroupEpoch.Init(jsGroupEpoch); if (!rvalue.mMembers.SetCapacity( groupDetails->group_members.Length(), fallible)) { promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); return; } for (const auto& member : groupDetails->group_members) { JS::Rooted jsClientId( cx, Uint8Array::Create(cx, member.identity, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsCredential( cx, Uint8Array::Create(cx, member.credential, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Guaranteed not to fail because of the SetCapacity above. MLSGroupMember& jsMember = *rvalue.mMembers.AppendElement(fallible); jsMember.mClientId.Init(jsClientId); jsMember.mCredential.Init(jsCredential); } // Resolve the promise promise->MaybeResolve(rvalue); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { promise->MaybeRejectWithUnknownError("Failed to get group details"); }); return promise.forget(); } already_AddRefed MLSGroupView::Send( const MLSBytesOrUint8ArrayOrUTF8String& aJsMessage, ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Send()")); // Handle the message parameter nsTArray message = ExtractMLSBytesOrUint8ArrayOrUTF8String( MLSObjectType::Application_message_plaintext, aJsMessage, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } mMLS->mTransactionChild->SendRequestSend(mGroupId, mClientId, message) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)](Maybe&& 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->mMLS->GetParentObject()))) { promise->MaybeReject(NS_ERROR_FAILURE); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array object ErrorResult error; JS::Rooted content( cx, Uint8Array::Create(cx, result->data(), error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Construct MLSBytes with the group identifier as content RootedDictionary rvalue(cx); rvalue.mType = MLSObjectType::Application_message_ciphertext; rvalue.mContent.Init(content); promise->MaybeResolve(rvalue); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { promise->MaybeRejectWithUnknownError("Failed to send message"); }); return promise.forget(); } already_AddRefed MLSGroupView::Receive( const MLSBytesOrUint8Array& aJsMessage, ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Receive()")); // Handle the message parameter nsTArray 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 receivedmessage must not be empty"); return nullptr; } // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Receive the message mMLS->mTransactionChild->SendRequestReceive(mClientId, message) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)](GkReceived&& received) { // Check if the Maybe contains a value if (received.tag == GkReceived::Tag::None) { promise->MaybeReject(NS_ERROR_FAILURE); return; } // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { promise->MaybeReject(NS_ERROR_FAILURE); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array objects based on the tag ErrorResult error; JS::Rooted jsGroupId( cx, Uint8Array::Create(cx, self->mGroupId, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Initialize the Received dictionary RootedDictionary rvalue(cx); rvalue.mGroupId.Init(jsGroupId); // Populate the Received object based on the tag switch (received.tag) { case GkReceived::Tag::GroupIdEpoch: { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("Processing GroupIdEpoch")); JS::Rooted jsGroupEpoch( cx, Uint8Array::Create( cx, received.group_id_epoch._0.group_epoch, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Populate the Received object rvalue.mType = MLSObjectType::Commit_processed; rvalue.mGroupEpoch.Construct(); rvalue.mGroupEpoch.Value().Init(jsGroupEpoch); break; } case GkReceived::Tag::ApplicationMessage: { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("Processing ApplicationMessage")); JS::Rooted jsApplicationMessage( cx, Uint8Array::Create(cx, received.application_message._0, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Populate the Received object rvalue.mType = MLSObjectType::Application_message_plaintext; rvalue.mContent.Construct(); rvalue.mContent.Value().Init(jsApplicationMessage); break; } case GkReceived::Tag::CommitOutput: { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("Processing CommitOutput")); JS::Rooted jsClientId( cx, Uint8Array::Create( cx, received.commit_output._0.identity, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsCommit( cx, Uint8Array::Create(cx, received.commit_output._0.commit, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsWelcome( cx, Uint8Array::Create( cx, received.commit_output._0.welcome, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsGroupInfo( cx, Uint8Array::Create( cx, received.commit_output._0.group_info, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsRatchetTree( cx, Uint8Array::Create( cx, received.commit_output._0.ratchet_tree, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Construct MLSCommitOutput with the parsed data rvalue.mType = MLSObjectType::Commit_output; rvalue.mGroupId.Init(jsGroupId); rvalue.mCommitOutput.Construct(); rvalue.mCommitOutput.Value().mType = MLSObjectType::Commit_output; rvalue.mCommitOutput.Value().mCommit.Init(jsCommit); rvalue.mCommitOutput.Value().mGroupId.Init(jsGroupId); if (!received.commit_output._0.welcome.IsEmpty()) { rvalue.mCommitOutput.Value().mWelcome.Construct(); rvalue.mCommitOutput.Value().mWelcome.Value().Init(jsWelcome); } if (!received.commit_output._0.group_info.IsEmpty()) { rvalue.mCommitOutput.Value().mGroupInfo.Construct(); rvalue.mCommitOutput.Value().mGroupInfo.Value().Init( jsGroupInfo); } if (!received.commit_output._0.ratchet_tree.IsEmpty()) { rvalue.mCommitOutput.Value().mRatchetTree.Construct(); rvalue.mCommitOutput.Value().mRatchetTree.Value().Init( jsRatchetTree); } if (!received.commit_output._0.identity.IsEmpty()) { rvalue.mCommitOutput.Value().mClientId.Construct(); rvalue.mCommitOutput.Value().mClientId.Value().Init( jsClientId); } MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("Finished processing CommitOutput")); break; } default: MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("Unhandled tag in received data")); promise->MaybeRejectWithUnknownError( "Unhandled tag in received data"); return; } MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("Successfully constructed MLSReceived")); // 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(aReason))); promise->MaybeRejectWithUnknownError("Failed to receive message"); }); return promise.forget(); } already_AddRefed MLSGroupView::HasPendingProposals(ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::HasPendingProposals()")); // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Receive the message mMLS->mTransactionChild->SendRequestHasPendingProposals(mGroupId, mClientId) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)](bool&& received) { // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("Failed to initialize JSAPI")); promise->MaybeRejectWithUnknownError( "Failed to initialize JSAPI"); return; } // Resolve the promise directly with the boolean value promise->MaybeResolve(received); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("IPC call rejected with reason: %d", static_cast(aReason))); promise->MaybeRejectWithUnknownError( "Failed to determine if there are pending proposals"); }); return promise.forget(); } already_AddRefed MLSGroupView::ClearPendingProposals( ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ClearPendingProposals()")); // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Receive the message mMLS->mTransactionChild->SendRequestClearPendingProposals(mGroupId, mClientId) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)](bool&& received) { // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("Failed to initialize JSAPI")); promise->MaybeRejectWithUnknownError( "Failed to initialize JSAPI"); return; } // Resolve the promise directly with the boolean value promise->MaybeResolve(received); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("IPC call rejected with reason: %d", static_cast(aReason))); promise->MaybeRejectWithUnknownError( "Failed to clear pending proposals"); }); return promise.forget(); } already_AddRefed MLSGroupView::HasPendingCommit(ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::HasPendingCommit()")); // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Receive the message mMLS->mTransactionChild->SendRequestHasPendingCommit(mGroupId, mClientId) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)](bool&& received) { // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("Failed to initialize JSAPI")); promise->MaybeRejectWithUnknownError( "Failed to initialize JSAPI"); return; } // Resolve the promise directly with the boolean value promise->MaybeResolve(received); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("IPC call rejected with reason: %d", static_cast(aReason))); promise->MaybeRejectWithUnknownError( "Failed to determine if there is a pending commit"); }); return promise.forget(); } already_AddRefed MLSGroupView::ClearPendingCommit(ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ClearPendingCommit()")); // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Receive the message mMLS->mTransactionChild->SendRequestClearPendingCommit(mGroupId, mClientId) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)](bool&& received) { // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("Failed to initialize JSAPI")); promise->MaybeRejectWithUnknownError( "Failed to initialize JSAPI"); return; } // Resolve the promise directly with the boolean value promise->MaybeResolve(received); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("IPC call rejected with reason: %d", static_cast(aReason))); promise->MaybeRejectWithUnknownError( "Failed to clear pending commit"); }); return promise.forget(); } already_AddRefed MLSGroupView::ApplyPendingCommit(ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ApplyPendingCommit()")); // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Receive the message mMLS->mTransactionChild->SendRequestApplyPendingCommit(mGroupId, mClientId) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)](GkReceived&& received) { // Check if the Maybe contains a value if (received.tag == GkReceived::Tag::None) { promise->MaybeRejectWithUnknownError( "Failed to apply pending commit"); return; } // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("Failed to initialize JSAPI")); promise->MaybeRejectWithUnknownError( "Failed to initialize JSAPI"); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array objects based on the tag ErrorResult error; JS::Rooted jsGroupId( cx, Uint8Array::Create(cx, self->mGroupId, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Initialize the Received dictionary RootedDictionary rvalue(cx); rvalue.mGroupId.Init(jsGroupId); // Populate the Received object based on the tag switch (received.tag) { case GkReceived::Tag::GroupIdEpoch: { JS::Rooted jsGroupEpoch( cx, Uint8Array::Create( cx, received.group_id_epoch._0.group_epoch, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Populate the Received object rvalue.mType = MLSObjectType::Commit_processed; rvalue.mGroupEpoch.Construct(); rvalue.mGroupEpoch.Value().Init(jsGroupEpoch); break; } default: MOZ_LOG(gMlsLog, mozilla::LogLevel::Error, ("Unhandled tag in received data")); promise->MaybeRejectWithUnknownError( "Unhandled tag in received data"); return; } MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("Successfully constructed MLSReceived")); // 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(aReason))); promise->MaybeRejectWithUnknownError( "Failed to apply pending commit"); }); return promise.forget(); } already_AddRefed MLSGroupView::ExportSecret( const MLSBytesOrUint8ArrayOrUTF8String& aJsLabel, const MLSBytesOrUint8Array& aJsContext, const uint64_t aLen, ErrorResult& aRv) { MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ExportSecret()")); // Handle the label parameter nsTArray label = ExtractMLSBytesOrUint8ArrayOrUTF8String( MLSObjectType::Exporter_label, aJsLabel, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // We allow the context to be empty if (NS_WARN_IF(label.IsEmpty())) { aRv.ThrowTypeError("The label must not be empty"); return nullptr; } // Handle the context parameter // Note: we allow the context to be empty nsTArray context = ExtractMLSBytesOrUint8Array( MLSObjectType::Exporter_context, aJsContext, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Create a new Promise object for the result RefPtr promise = Promise::Create(mMLS->GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } mMLS->mTransactionChild ->SendRequestExportSecret(mGroupId, mClientId, label, context, aLen) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, self = RefPtr(this)]( Maybe&& exporterOutput) { // Check if the Maybe contains a value if (exporterOutput.isNothing()) { promise->MaybeReject(NS_ERROR_FAILURE); return; } // Get the context from the GlobalObject AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) { promise->MaybeRejectWithUnknownError( "Failed to initialize JSAPI"); return; } JSContext* cx = jsapi.cx(); // Construct the Uint8Array objects ErrorResult error; JS::Rooted jsGroupId( cx, Uint8Array::Create(cx, exporterOutput->group_id, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsGroupEpoch( cx, Uint8Array::Create(cx, exporterOutput->group_epoch, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsLabel( cx, Uint8Array::Create(cx, exporterOutput->label, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsContext( cx, Uint8Array::Create(cx, exporterOutput->context, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } JS::Rooted jsExporter( cx, Uint8Array::Create(cx, exporterOutput->exporter, error)); error.WouldReportJSException(); if (error.Failed()) { promise->MaybeReject(std::move(error)); return; } // Construct MLSBytes with the group identifier as content RootedDictionary rvalue(cx); rvalue.mType = MLSObjectType::Exporter_output; rvalue.mGroupId.Init(jsGroupId); rvalue.mGroupEpoch.Init(jsGroupEpoch); rvalue.mLabel.Init(jsLabel); rvalue.mContext.Init(jsContext); rvalue.mSecret.Init(jsExporter); // Resolve the promise promise->MaybeResolve(rvalue); }, [promise](::mozilla::ipc::ResponseRejectReason aReason) { promise->MaybeRejectWithUnknownError("Failed to export secret"); }); return promise.forget(); } }; // namespace mozilla::dom