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 "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<uint8_t>&& aGroupId,
nsTArray<uint8_t>&& 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<JSObject*> aGivenProto) {
return MLSGroupView_Binding::Wrap(aCx, this, aGivenProto);
}
//
// API
//
void MLSGroupView::GetGroupId(JSContext* aCx,
JS::MutableHandle<JSObject*> 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<JSObject*> aClientId,
ErrorResult& aRv) {
if (!mJsClientId) {
mJsClientId = Uint8Array::Create(aCx, this, mClientId, aRv);
if (aRv.Failed()) {
return;
}
}
aClientId.set(mJsClientId);
}
already_AddRefed<Promise> MLSGroupView::DeleteState(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::DeleteState()"));
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mMLS->mTransactionChild->SendRequestGroupStateDelete(mGroupId, mClientId)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise](
Maybe<mozilla::security::mls::GkGroupIdEpoch>&& 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<Promise> MLSGroupView::Add(
const MLSBytesOrUint8Array& aJsKeyPackage, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Add()"));
// Handle the key package parameter
nsTArray<uint8_t> 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 = 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<MLSGroupView>(this)](
Maybe<mozilla::security::mls::GkMlsCommitOutput>&& 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<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsClientId(
cx, Uint8Array::Create(cx, commitOutput->identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsCommit(
cx, Uint8Array::Create(cx, commitOutput->commit, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsWelcome(
cx, Uint8Array::Create(cx, commitOutput->welcome, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupInfo(
cx, Uint8Array::Create(cx, commitOutput->group_info, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> 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<MLSCommitOutput> 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<Promise> MLSGroupView::ProposeAdd(
const MLSBytesOrUint8Array& aJsKeyPackage, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ProposeAdd()"));
// Handle the key package parameter
nsTArray<uint8_t> 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 = 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<MLSGroupView>(this)](Maybe<RawBytes>&& 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<JSObject*> 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<MLSBytes> 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<Promise> MLSGroupView::Remove(
const MLSBytesOrUint8Array& aJsRemClientIdentifier, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Remove()"));
// Handle the remove client identifier parameter
nsTArray<uint8_t> 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 = 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<MLSGroupView>(this)](
Maybe<mozilla::security::mls::GkMlsCommitOutput>&& 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<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsClientId(
cx, Uint8Array::Create(cx, commitOutput->identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsCommit(
cx, Uint8Array::Create(cx, commitOutput->commit, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsWelcome(
cx, Uint8Array::Create(cx, commitOutput->welcome, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupInfo(
cx, Uint8Array::Create(cx, commitOutput->group_info, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> 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<MLSCommitOutput> 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<Promise> MLSGroupView::ProposeRemove(
const MLSBytesOrUint8Array& aJsRemClientIdentifier, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ProposeRemove()"));
// Handle the remove client identifier parameter
nsTArray<uint8_t> 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 = 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<MLSGroupView>(this)](Maybe<RawBytes>&& 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<JSObject*> 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<MLSBytes> 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<Promise> MLSGroupView::Close(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Close()"));
// Create a new Promise object for the result
RefPtr<Promise> 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<MLSGroupView>(this)](
Maybe<mozilla::security::mls::GkMlsCommitOutput>&& 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<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsClientId(
cx, Uint8Array::Create(cx, commitOutput->identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsCommit(
cx, Uint8Array::Create(cx, commitOutput->commit, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsWelcome(
cx, Uint8Array::Create(cx, commitOutput->welcome, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupInfo(
cx, Uint8Array::Create(cx, commitOutput->group_info, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> 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<MLSCommitOutput> 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<Promise> MLSGroupView::Details(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Details()"));
// Create a new Promise object for the result
RefPtr<Promise> 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<MLSGroupView>(this)](
Maybe<GkGroupDetails>&& 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<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, groupDetails->group_id, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupEpoch(
cx, Uint8Array::Create(cx, groupDetails->group_epoch, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSGroupDetails
RootedDictionary<MLSGroupDetails> 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<JSObject*> jsClientId(
cx, Uint8Array::Create(cx, member.identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> 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<Promise> MLSGroupView::Send(
const MLSBytesOrUint8ArrayOrUTF8String& aJsMessage, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Send()"));
// Handle the message parameter
nsTArray<uint8_t> 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 = 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<MLSGroupView>(this)](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->mMLS->GetParentObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE);
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 group identifier as content
RootedDictionary<MLSBytes> 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<Promise> MLSGroupView::Receive(
const MLSBytesOrUint8Array& aJsMessage, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Receive()"));
// 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 receivedmessage must not be empty");
return nullptr;
}
// Create a new Promise object for the result
RefPtr<Promise> 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<MLSGroupView>(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<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Initialize the Received dictionary
RootedDictionary<MLSReceived> 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<JSObject*> 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<JSObject*> 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<JSObject*> jsClientId(
cx, Uint8Array::Create(
cx, received.commit_output._0.identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsCommit(
cx, Uint8Array::Create(cx, received.commit_output._0.commit,
error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsWelcome(
cx, Uint8Array::Create(
cx, received.commit_output._0.welcome, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> 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<JSObject*> 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<int>(aReason)));
promise->MaybeRejectWithUnknownError("Failed to receive message");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::HasPendingProposals(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSGroupView::HasPendingProposals()"));
// Create a new Promise object for the result
RefPtr<Promise> 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<MLSGroupView>(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<int>(aReason)));
promise->MaybeRejectWithUnknownError(
"Failed to determine if there are pending proposals");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::ClearPendingProposals(
ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSGroupView::ClearPendingProposals()"));
// Create a new Promise object for the result
RefPtr<Promise> 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<MLSGroupView>(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<int>(aReason)));
promise->MaybeRejectWithUnknownError(
"Failed to clear pending proposals");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::HasPendingCommit(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSGroupView::HasPendingCommit()"));
// Create a new Promise object for the result
RefPtr<Promise> 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<MLSGroupView>(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<int>(aReason)));
promise->MaybeRejectWithUnknownError(
"Failed to determine if there is a pending commit");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::ClearPendingCommit(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSGroupView::ClearPendingCommit()"));
// Create a new Promise object for the result
RefPtr<Promise> 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<MLSGroupView>(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<int>(aReason)));
promise->MaybeRejectWithUnknownError(
"Failed to clear pending commit");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::ApplyPendingCommit(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSGroupView::ApplyPendingCommit()"));
// Create a new Promise object for the result
RefPtr<Promise> 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<MLSGroupView>(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<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Initialize the Received dictionary
RootedDictionary<MLSReceived> rvalue(cx);
rvalue.mGroupId.Init(jsGroupId);
// Populate the Received object based on the tag
switch (received.tag) {
case GkReceived::Tag::GroupIdEpoch: {
JS::Rooted<JSObject*> 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<int>(aReason)));
promise->MaybeRejectWithUnknownError(
"Failed to apply pending commit");
});
return promise.forget();
}
already_AddRefed<Promise> 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<uint8_t> 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<uint8_t> 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 = 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<MLSGroupView>(this)](
Maybe<mozilla::security::mls::GkExporterOutput>&&
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<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, exporterOutput->group_id, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupEpoch(
cx, Uint8Array::Create(cx, exporterOutput->group_epoch, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsLabel(
cx, Uint8Array::Create(cx, exporterOutput->label, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsContext(
cx, Uint8Array::Create(cx, exporterOutput->context, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> 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<MLSExporterOutput> 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