Backed out 4 changesets (bug 1883321, bug 1900537) for causing multiple failures. CLOSED TREE

Backed out changeset 7b1ab2c1bfb6 (bug 1900537)
Backed out changeset 1306fe3ef6d3 (bug 1900537)
Backed out changeset e83b4e090bc0 (bug 1883321)
Backed out changeset b9e50942a0cd (bug 1883321)
This commit is contained in:
Goloman Adrian
2024-12-07 02:50:49 +02:00
parent 0093efa5b7
commit 813cf9e0cb
393 changed files with 106 additions and 78545 deletions

View File

@@ -15,21 +15,6 @@ git = "https://github.com/FirefoxGraphics/wpf-gpu-raster"
rev = "99979da091fd58fba8477e7fcdf5ec0727102916"
replace-with = "vendored-sources"
[source."git+https://github.com/beurdouche/mls-platform-api?rev=7fb935bb93fdcc80f7f5e76d516c85a540024b53"]
git = "https://github.com/beurdouche/mls-platform-api"
rev = "7fb935bb93fdcc80f7f5e76d516c85a540024b53"
replace-with = "vendored-sources"
[source."git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1"]
git = "https://github.com/beurdouche/mls-rs"
rev = "96eb66e158c86171c70ff8147c0e5f020e54f3d1"
replace-with = "vendored-sources"
[source."git+https://github.com/beurdouche/nss-gk-api?rev=82e780f47026b84a0e0a06bff17fa95661d129a3"]
git = "https://github.com/beurdouche/nss-gk-api"
rev = "82e780f47026b84a0e0a06bff17fa95661d129a3"
replace-with = "vendored-sources"
[source."git+https://github.com/chris-zen/coremidi.git?rev=fc68464b5445caf111e41f643a2e69ccce0b4f83"]
git = "https://github.com/chris-zen/coremidi.git"
rev = "fc68464b5445caf111e41f643a2e69ccce0b4f83"

211
Cargo.lock generated
View File

@@ -1416,15 +1416,6 @@ dependencies = [
"libdbus-sys",
]
[[package]]
name = "debug_tree"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d1ec383f2d844902d3c34e4253ba11ae48513cdaddc565cf1a6518db09a8e57"
dependencies = [
"once_cell",
]
[[package]]
name = "debugid"
version = "0.8.0"
@@ -2343,10 +2334,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -2451,7 +2440,6 @@ dependencies = [
"mdns_service",
"midir_impl",
"mime-guess-ffi",
"mls_gk",
"moz_asserts",
"mozannotation_client",
"mozannotation_server",
@@ -3790,17 +3778,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "maybe-async"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "md-5"
version = "0.10.5"
@@ -4054,169 +4031,6 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "mls-platform-api"
version = "0.1.0"
source = "git+https://github.com/beurdouche/mls-platform-api?rev=7fb935bb93fdcc80f7f5e76d516c85a540024b53#7fb935bb93fdcc80f7f5e76d516c85a540024b53"
dependencies = [
"bincode",
"hex",
"mls-rs",
"mls-rs-crypto-nss",
"mls-rs-provider-sqlite",
"serde",
"serde_json",
"sha2",
"thiserror",
]
[[package]]
name = "mls-rs"
version = "0.39.1"
source = "git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1#96eb66e158c86171c70ff8147c0e5f020e54f3d1"
dependencies = [
"async-trait",
"cfg-if",
"debug_tree",
"futures",
"getrandom",
"hex",
"itertools",
"maybe-async",
"mls-rs-codec",
"mls-rs-core",
"mls-rs-identity-x509",
"mls-rs-provider-sqlite",
"rand_core",
"rayon",
"serde",
"thiserror",
"wasm-bindgen",
"zeroize",
]
[[package]]
name = "mls-rs-codec"
version = "0.5.3"
source = "git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1#96eb66e158c86171c70ff8147c0e5f020e54f3d1"
dependencies = [
"mls-rs-codec-derive",
"thiserror",
"wasm-bindgen",
]
[[package]]
name = "mls-rs-codec-derive"
version = "0.1.1"
source = "git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1#96eb66e158c86171c70ff8147c0e5f020e54f3d1"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "mls-rs-core"
version = "0.18.0"
source = "git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1#96eb66e158c86171c70ff8147c0e5f020e54f3d1"
dependencies = [
"async-trait",
"hex",
"maybe-async",
"mls-rs-codec",
"serde",
"serde_bytes",
"thiserror",
"wasm-bindgen",
"zeroize",
]
[[package]]
name = "mls-rs-crypto-hpke"
version = "0.9.0"
source = "git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1#96eb66e158c86171c70ff8147c0e5f020e54f3d1"
dependencies = [
"async-trait",
"cfg-if",
"maybe-async",
"mls-rs-core",
"mls-rs-crypto-traits",
"thiserror",
"zeroize",
]
[[package]]
name = "mls-rs-crypto-nss"
version = "0.1.0"
source = "git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1#96eb66e158c86171c70ff8147c0e5f020e54f3d1"
dependencies = [
"getrandom",
"hex",
"maybe-async",
"mls-rs-core",
"mls-rs-crypto-hpke",
"mls-rs-crypto-traits",
"nss-gk-api",
"rand_core",
"serde",
"thiserror",
"zeroize",
]
[[package]]
name = "mls-rs-crypto-traits"
version = "0.10.0"
source = "git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1#96eb66e158c86171c70ff8147c0e5f020e54f3d1"
dependencies = [
"async-trait",
"maybe-async",
"mls-rs-core",
]
[[package]]
name = "mls-rs-identity-x509"
version = "0.11.0"
source = "git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1#96eb66e158c86171c70ff8147c0e5f020e54f3d1"
dependencies = [
"async-trait",
"maybe-async",
"mls-rs-core",
"thiserror",
"wasm-bindgen",
]
[[package]]
name = "mls-rs-provider-sqlite"
version = "0.11.0"
source = "git+https://github.com/beurdouche/mls-rs?rev=96eb66e158c86171c70ff8147c0e5f020e54f3d1#96eb66e158c86171c70ff8147c0e5f020e54f3d1"
dependencies = [
"async-trait",
"hex",
"maybe-async",
"mls-rs-core",
"rand",
"rusqlite",
"thiserror",
"zeroize",
]
[[package]]
name = "mls_gk"
version = "0.1.0"
dependencies = [
"hex",
"log",
"mls-platform-api",
"nserror",
"nss-gk-api",
"nsstring",
"rusqlite",
"static_prefs",
"thin-vec",
"xpcom",
]
[[package]]
name = "moz_asserts"
version = "0.1.0"
@@ -4678,10 +4492,10 @@ dependencies = [
[[package]]
name = "nss-gk-api"
version = "0.3.0"
source = "git+https://github.com/beurdouche/nss-gk-api?rev=82e780f47026b84a0e0a06bff17fa95661d129a3#82e780f47026b84a0e0a06bff17fa95661d129a3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c17aec6d4e1822c023689899f09311592a36cbf6de8f85dfaf5f01976790d8d"
dependencies = [
"bindgen 0.69.4",
"log",
"mozbuild",
"once_cell",
"pkcs11-bindings",
@@ -7731,27 +7545,6 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
dependencies = [
"serde",
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zerovec"
version = "0.10.4"

View File

@@ -14,7 +14,6 @@ members = [
"security/manager/ssl/tests/unit/test_builtins",
"security/manager/ssl/ipcclientcerts",
"security/manager/ssl/osclientcerts",
"security/mls/mls_gk",
"testing/geckodriver",
"toolkit/components/uniffi-bindgen-gecko-js",
"toolkit/crashreporter/client/app",
@@ -200,7 +199,6 @@ plist = { path = "third_party/rust/plist" }
# To-be-published changes.
unicode-bidi = { git = "https://github.com/servo/unicode-bidi", rev = "ca612daf1c08c53abe07327cb3e6ef6e0a760f0c" }
nss-gk-api = { git = "https://github.com/beurdouche/nss-gk-api", rev = "82e780f47026b84a0e0a06bff17fa95661d129a3"}
# Other overrides
any_all_workaround = { git = "https://github.com/hsivonen/any_all_workaround", rev = "7fb1b7034c9f172aade21ee1c8554e8d8a48af80" }

View File

@@ -45,7 +45,6 @@
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/Record.h"
#include "mozilla/dom/ReportingHeader.h"
#include "mozilla/dom/UnionTypes.h"
@@ -60,7 +59,6 @@
#include "mozilla/RemoteDecoderManagerChild.h"
#include "mozilla/KeySystemConfig.h"
#include "mozilla/WheelHandlingHelper.h"
#include "nsString.h"
#include "nsNativeTheme.h"
#include "nsThreadUtils.h"
#include "mozJSModuleLoader.h"
@@ -88,9 +86,6 @@
namespace mozilla::dom {
// Setup logging
extern mozilla::LazyLogModule gMlsLog;
/* static */
void ChromeUtils::NondeterministicGetWeakMapKeys(
GlobalObject& aGlobal, JS::Handle<JS::Value> aMap,
@@ -1422,279 +1417,6 @@ void ChromeUtils::ClearStyleSheetCache(GlobalObject&) {
SharedStyleSheetCache::Clear();
}
void ChromeUtils::ClearMessagingLayerSecurityStateByPrincipal(
GlobalObject&, nsIPrincipal* aPrincipal, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, LogLevel::Debug,
("ClearMessagingLayerSecurityStateByPrincipal"));
if (NS_WARN_IF(!aPrincipal)) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Principal is null"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
// Get the storage origin key
nsAutoCString originKey;
aRv = aPrincipal->GetStorageOriginKey(originKey);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to get storage origin key"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
// Get the origin attributes suffix
nsAutoCString originAttrSuffix;
aRv = aPrincipal->GetOriginSuffix(originAttrSuffix);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to get origin attributes suffix"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
// Construct the full origin key
nsAutoCString fullOriginKey = originKey + originAttrSuffix;
// Get the profile directory
nsCOMPtr<nsIFile> file;
aRv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(file));
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to get profile directory"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aRv = file->AppendNative("mls"_ns);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to append 'mls' to directory path"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aRv = file->AppendNative(fullOriginKey);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to append full origin key to the file path"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
// Remove the directory recursively
aRv = file->Remove(/* recursive */ true);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to remove : %s", file->HumanReadablePath().get()));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
MOZ_LOG(gMlsLog, LogLevel::Debug,
("Successfully cleared MLS state for principal"));
}
void ChromeUtils::ClearMessagingLayerSecurityStateBySite(
GlobalObject&, const nsACString& aSchemelessSite,
const dom::OriginAttributesPatternDictionary& aPattern, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, LogLevel::Debug, ("ClearMessagingLayerSecurityStateBySite"));
// Check if the schemeless site is empty
if (NS_WARN_IF(aSchemelessSite.IsEmpty())) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Schemeless site is empty"));
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
// Site pattern
OriginAttributesPattern pattern(aPattern);
// Partition pattern
// This pattern is used to (additionally) clear state partitioned under
// aSchemelessSite.
OriginAttributesPattern partitionPattern = pattern;
partitionPattern.mPartitionKeyPattern.Construct();
partitionPattern.mPartitionKeyPattern.Value().mBaseDomain.Construct(
NS_ConvertUTF8toUTF16(aSchemelessSite));
// Get the profile directory
nsCOMPtr<nsIFile> profileDir;
aRv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(profileDir));
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to get profile directory"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
// Get the 'mls' directory
nsCOMPtr<nsIFile> mlsDir;
aRv = profileDir->Clone(getter_AddRefs(mlsDir));
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to clone profile directory"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aRv = mlsDir->AppendNative("mls"_ns);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to append 'mls' to directory path"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
// Reverse the base domain using the existing function
nsAutoCString targetReversedBaseDomain(aSchemelessSite);
std::reverse(targetReversedBaseDomain.BeginWriting(),
targetReversedBaseDomain.EndWriting());
MOZ_LOG(gMlsLog, LogLevel::Debug,
("Reversed base domain: %s", targetReversedBaseDomain.get()));
// Enumerate files in the 'mls' directory
nsCOMPtr<nsIDirectoryEnumerator> dirEnum;
aRv = mlsDir->GetDirectoryEntries(getter_AddRefs(dirEnum));
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to get directory entries in 'mls' directory"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
// Iterate through all entries in the directory
nsCOMPtr<nsIFile> entry;
while (NS_SUCCEEDED(dirEnum->GetNextFile(getter_AddRefs(entry))) && entry) {
nsAutoCString entryName;
aRv = entry->GetNativeLeafName(entryName);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to get native leaf name for entry"));
continue;
}
// Find the position of .sqlite.enc or .key in the entry name
int32_t sqliteEncPos = entryName.RFind(".sqlite.enc");
int32_t keyPos = entryName.RFind(".key");
// Remove the .sqlite.enc or .key suffix from the entryName
if (sqliteEncPos != kNotFound) {
entryName.SetLength(sqliteEncPos);
} else if (keyPos != kNotFound) {
entryName.SetLength(keyPos);
}
// Decode the entry name
nsAutoCString decodedEntryName;
aRv = mozilla::Base64Decode(entryName, decodedEntryName);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Debug,
("Failed to decode entry name: %s", entryName.get()));
continue;
}
// Find the origin attributes suffix in the entry name by taking the
// value of the entry name after the ^ separator
int32_t separatorPos = decodedEntryName.FindChar('^');
// We extract the origin attributes suffix from the entry name
nsAutoCString originSuffix;
originSuffix.Assign(Substring(decodedEntryName, separatorPos));
// Populate the origin attributes from the suffix
OriginAttributes originAttrs;
if (NS_WARN_IF(!originAttrs.PopulateFromSuffix(originSuffix))) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to populate origin attributes from suffix"));
continue;
}
// Check if the entry name starts with the reversed base domain
if (StringBeginsWith(decodedEntryName, targetReversedBaseDomain)) {
MOZ_LOG(gMlsLog, LogLevel::Debug,
("Entry file: %s", entry->HumanReadablePath().get()));
// If there is a valid origin attributes suffix, we remove the entry
// only if it matches.
if (pattern.Matches(originAttrs)) {
aRv = entry->Remove(/* recursive */ false);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to remove file: %s", decodedEntryName.get()));
}
MOZ_LOG(gMlsLog, LogLevel::Debug,
("Removed file: %s", decodedEntryName.get()));
}
}
// If there is a valid origin attributes suffix, we remove the entry
// only if it matches. We are checking for state partitioned under
// aSchemelessSite.
if (partitionPattern.Matches(originAttrs)) {
aRv = entry->Remove(/* recursive */ false);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to remove file: %s", decodedEntryName.get()));
}
MOZ_LOG(gMlsLog, LogLevel::Debug,
("Removed file: %s", decodedEntryName.get()));
}
}
// Close the directory enumerator
dirEnum->Close();
}
void ChromeUtils::ClearMessagingLayerSecurityState(GlobalObject&,
ErrorResult& aRv) {
MOZ_LOG(gMlsLog, LogLevel::Debug, ("ClearMessagingLayerSecurityState"));
// Get the profile directory
nsCOMPtr<nsIFile> profileDir;
aRv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(profileDir));
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to get profile directory"));
return;
}
// Construct the MLS directory path
nsCOMPtr<nsIFile> mlsDir;
aRv = profileDir->Clone(getter_AddRefs(mlsDir));
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to clone profile directory"));
return;
}
aRv = mlsDir->AppendNative("mls"_ns);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error,
("Failed to append 'mls' to directory path"));
return;
}
// Check if the directory exists
bool exists;
aRv = mlsDir->Exists(&exists);
if (NS_WARN_IF(aRv.Failed() || !exists)) {
MOZ_LOG(gMlsLog, LogLevel::Debug, ("MLS directory does not exist"));
return;
}
// Remove the MLS directory recursively
aRv = mlsDir->Remove(/* recursive */ true);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to remove MLS directory"));
return;
}
// Log the directory path
MOZ_LOG(gMlsLog, LogLevel::Debug,
("Deleted MLS directory: %s", mlsDir->HumanReadablePath().get()));
// Recreate the MLS directory
aRv = mlsDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
if (NS_WARN_IF(aRv.Failed())) {
MOZ_LOG(gMlsLog, LogLevel::Error, ("Failed to recreate MLS directory"));
return;
}
MOZ_LOG(gMlsLog, LogLevel::Debug, ("Successfully cleared all MLS state"));
}
void ChromeUtils::ClearScriptCacheByPrincipal(GlobalObject&,
nsIPrincipal* aForPrincipal) {
SharedScriptCache::Clear(Some(aForPrincipal));

View File

@@ -193,16 +193,6 @@ class ChromeUtils {
static void ClearStyleSheetCache(GlobalObject& aGlobal);
static void ClearMessagingLayerSecurityStateByPrincipal(
GlobalObject&, nsIPrincipal* aPrincipal, ErrorResult& aRv);
static void ClearMessagingLayerSecurityStateBySite(
GlobalObject& aGlobal, const nsACString& aSchemelessSite,
const dom::OriginAttributesPatternDictionary& aPattern, ErrorResult& aRv);
static void ClearMessagingLayerSecurityState(GlobalObject& aGlobal,
ErrorResult& aRv);
static void ClearScriptCacheByPrincipal(GlobalObject&,
nsIPrincipal* aForPrincipal);

View File

@@ -137,24 +137,6 @@ custom GetUserMediaInsec calls MediaDevices.getUserMedia from an insecure contex
custom MozGetUserMediaInsec calls Navigator.mozGetUserMedia from an insecure context
method MediaDevices.getDisplayMedia
// Non-standard Messaging Layer Security
method MLS.stateDelete
method MLS.stateDeleteGroup
method MLS.generateSignatureKeypair
method MLS.generateCredentialBasic
method MLS.generateKeyPackage
method MLS.groupCreate
method MLS.groupJoin
method MLS.groupAdd
method MLS.groupProposeAdd
method MLS.groupRemove
method MLS.groupProposeRemove
method MLS.groupClose
method MLS.groupMembers
method MLS.receive
method MLS.send
method MLS.deriveExporter
// Non-standard Document.mozSetImageElement.
method Document.mozSetImageElement

View File

@@ -65,24 +65,6 @@ method console.timeStamp
method console.profile
method console.profileEnd
// Non-standard Messaging Layer Security
method MLS.stateDelete
method MLS.stateDeleteGroup
method MLS.generateSignatureKeypair
method MLS.generateCredentialBasic
method MLS.generateKeyPackage
method MLS.groupCreate
method MLS.groupJoin
method MLS.groupAdd
method MLS.groupProposeAdd
method MLS.groupRemove
method MLS.groupProposeRemove
method MLS.groupClose
method MLS.groupMembers
method MLS.receive
method MLS.send
method MLS.deriveExporter
// Unsupported web APIs in Private Browsing Mode
custom PrivateBrowsingIDBFactoryOpen calls indexedDB.open in Private Browsing Mode
custom PrivateBrowsingIDBFactoryDeleteDatabase calls indexedDB.deleteDatabase in Private Browsing Mode

File diff suppressed because it is too large Load Diff

View File

@@ -243,25 +243,6 @@ namespace ChromeUtils {
*/
undefined clearStyleSheetCache();
/**
* Clears the Messaging Layer Security state by schemeless site.
* This includes associated state-partitioned cache.
*/
[Throws]
undefined clearMessagingLayerSecurityStateBySite(UTF8String schemelessSite, optional OriginAttributesPatternDictionary pattern = {});
/**
* Clears the Messaging Layer Security state by principal.
*/
[Throws]
undefined clearMessagingLayerSecurityStateByPrincipal(Principal principal);
/**
* Clears all Messaging Layer Security related state across domains
*/
[Throws]
undefined clearMessagingLayerSecurityState();
/**
* Clears the JavaScript cache by schemeless site. This includes associated
* state-partitioned cache.

View File

@@ -1,635 +0,0 @@
/* -*- 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 "mozilla/dom/MLS.h"
#include "mozilla/dom/MLSGroupView.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/Promise.h"
#include "nsTArray.h"
#include "nsCOMPtr.h"
#include "nsIGlobalObject.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/dom/MLSTransactionChild.h"
#include "mozilla/dom/MLSTransactionMessage.h"
#include "mozilla/dom/PMLSTransaction.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/BasePrincipal.h"
#include "MLSGroupView.h"
#include "nsTArray.h"
#include "mozilla/Logging.h"
#include "mozilla/Span.h"
#include "nsDebug.h"
#include "MLSLogging.h"
#include "MLSTypeUtils.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();
}
} // namespace mozilla::dom

View File

@@ -1,74 +0,0 @@
/* -*- 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/. */
#ifndef mozilla_dom_MLS_h
#define mozilla_dom_MLS_h
// #include "mozilla/dom/TypedArray.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/MLSBinding.h"
#include "mozilla/dom/MLSTransactionChild.h"
#include "nsIGlobalObject.h"
class nsIGlobalObject;
namespace mozilla::dom {
class MLSGroupView;
class MLS final : public nsISupports, public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MLS)
static already_AddRefed<MLS> Constructor(GlobalObject& aGlobal,
ErrorResult& aRv);
explicit MLS(nsIGlobalObject* aGlobalObject, MLSTransactionChild* aActor);
nsIGlobalObject* GetParentObject() const { return mGlobalObject; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
already_AddRefed<mozilla::dom::Promise> DeleteState(ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> GenerateIdentity(ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> GenerateCredential(
const MLSBytesOrUint8ArrayOrUTF8String& aJsCredContent, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> GenerateKeyPackage(
const MLSBytesOrUint8Array& aJsClientIdentifier,
const MLSBytesOrUint8Array& aJsCredential, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> GroupCreate(
const MLSBytesOrUint8Array& aJsClientIdentifier,
const MLSBytesOrUint8Array& aJsCredential, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> GroupGet(
const MLSBytesOrUint8Array& aJsGroupIdentifier,
const MLSBytesOrUint8Array& aJsClientIdentifier, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> GroupJoin(
const MLSBytesOrUint8Array& aJsClientIdentifier,
const MLSBytesOrUint8Array& aJsWelcome, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> GetGroupIdFromMessage(
const MLSBytesOrUint8Array& aJsMessage, ErrorResult& aRv);
private:
friend class MLSGroupView;
virtual ~MLS();
nsCOMPtr<nsIGlobalObject> mGlobalObject;
RefPtr<MLSTransactionChild> mTransactionChild;
};
} // namespace mozilla::dom
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +0,0 @@
/* -*- 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/. */
#ifndef mozilla_dom_MLSGroup_h
#define mozilla_dom_MLSGroup_h
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/MLS.h"
class nsIGlobalObject;
namespace mozilla::dom {
class MLSGroupView final : public nsISupports, public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MLSGroupView)
explicit MLSGroupView(MLS* aMLS, nsTArray<uint8_t>&& aGroupId,
nsTArray<uint8_t>&& aClientId);
nsISupports* GetParentObject() const { return mMLS; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
void GetGroupId(JSContext* aCx, JS::MutableHandle<JSObject*> aGroupId,
ErrorResult& aRv);
void GetClientId(JSContext* aCx, JS::MutableHandle<JSObject*> aClientId,
ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> DeleteState(ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> Add(
const MLSBytesOrUint8Array& aJsKeyPackage, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> ProposeAdd(
const MLSBytesOrUint8Array& aJsKeyPackage, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> Remove(
const MLSBytesOrUint8Array& aJsRemClientIdentifier, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> ProposeRemove(
const MLSBytesOrUint8Array& aJsRemClientIdentifier, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> Close(ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> Details(ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> Send(
const MLSBytesOrUint8ArrayOrUTF8String& aJsMessage, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> Receive(
const MLSBytesOrUint8Array& aJsMessage, ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> ApplyPendingCommit(ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> ExportSecret(
const MLSBytesOrUint8ArrayOrUTF8String& aJsLabel,
const MLSBytesOrUint8Array& aJsContext, const uint64_t aLen,
ErrorResult& aRv);
private:
virtual ~MLSGroupView() { mozilla::DropJSObjects(this); }
RefPtr<MLS> mMLS;
nsTArray<uint8_t> mGroupId;
nsTArray<uint8_t> mClientId;
JS::Heap<JSObject*> mJsGroupId;
JS::Heap<JSObject*> mJsClientId;
};
} // namespace mozilla::dom
#endif

View File

@@ -1,16 +0,0 @@
/* -*- 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/. */
#ifndef mozilla_dom_MLSLogging_h
#define mozilla_dom_MLSLogging_h
#include "mozilla/Logging.h"
namespace mozilla::dom {
extern LazyLogModule gMlsLog;
}
#endif // mozilla_dom_MLSLogging_h

View File

@@ -1,22 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/MLSTransactionChild.h"
#include "MLSLogging.h"
namespace mozilla::dom {
MLSTransactionChild::MLSTransactionChild() {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionChild::MLSTransactionChild() - Constructor called"));
}
MLSTransactionChild::~MLSTransactionChild() {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionChild::~MLSTransactionChild() - Destructor called"));
}
} // namespace mozilla::dom

View File

@@ -1,27 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_MLSTransactionChild_h
#define mozilla_dom_MLSTransactionChild_h
#include "mozilla/dom/PMLSTransaction.h"
#include "mozilla/dom/PMLSTransactionChild.h"
namespace mozilla::dom {
class MLSTransactionChild final : public PMLSTransactionChild {
public:
NS_INLINE_DECL_REFCOUNTING(MLSTransactionChild, override)
MLSTransactionChild();
protected:
virtual ~MLSTransactionChild();
};
} // namespace mozilla::dom
#endif // mozilla_dom_MLSTransactionChild_h

View File

@@ -1,65 +0,0 @@
/* -*- 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 "nsTArray.h"
#include "ipc/IPCMessageUtils.h"
#include "ipc/IPCMessageUtilsSpecializations.h"
#include "mozilla/Assertions.h"
#include "mozilla/security/mls/mls_gk_ffi_generated.h"
#include "MLSTransactionMessage.h"
#include "mozilla/dom/MLSTransactionMessage.h"
using namespace mozilla::security::mls;
void IPC::ParamTraits<mozilla::security::mls::GkReceived>::Write(
MessageWriter* aWriter, const paramType& aValue) {
// Serialize the enum variant tag
IPC::WriteParam(aWriter, aValue.tag);
switch (aValue.tag) {
case paramType::Tag::ApplicationMessage:
IPC::WriteParam(aWriter, aValue.application_message._0);
break;
case paramType::Tag::GroupIdEpoch:
IPC::WriteParam(aWriter, aValue.group_id_epoch._0);
break;
case paramType::Tag::CommitOutput:
IPC::WriteParam(aWriter, aValue.commit_output._0);
break;
default:
break;
}
}
bool IPC::ParamTraits<mozilla::security::mls::GkReceived>::Read(
MessageReader* aReader, paramType* aResult) {
MOZ_ASSERT(aResult->tag == paramType::Tag::None,
"Clobbering already-initialized result");
// Deserialize the tag
if (!IPC::ReadParam(aReader, &aResult->tag)) {
// Ensure that the tag has a safe value for the destructor before returning.
aResult->tag = paramType::Tag::None;
return false;
}
// Use placement-new to initialize the relevant variant of the internal union,
// then deserialize the bodies.
switch (aResult->tag) {
case paramType::Tag::None:
return true; // No data payload
case paramType::Tag::ApplicationMessage:
new (&aResult->application_message) paramType::ApplicationMessage_Body;
return IPC::ReadParam(aReader, &aResult->application_message._0);
case paramType::Tag::GroupIdEpoch:
new (&aResult->group_id_epoch) paramType::GroupIdEpoch_Body;
return IPC::ReadParam(aReader, &aResult->group_id_epoch._0);
case paramType::Tag::CommitOutput:
new (&aResult->commit_output) paramType::CommitOutput_Body;
return IPC::ReadParam(aReader, &aResult->commit_output._0);
}
return false;
}

View File

@@ -1,52 +0,0 @@
/* -*- 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/. */
#ifndef mozilla_dom_MLSTransactionMessage_h
#define mozilla_dom_MLSTransactionMessage_h
#include "nsTArray.h"
#include "ipc/IPCMessageUtilsSpecializations.h"
#include "mozilla/security/mls/mls_gk_ffi_generated.h"
#include "ipc/EnumSerializer.h"
#include "ipc/IPCMessageUtils.h"
using namespace mozilla::security::mls;
namespace IPC {
template <>
struct ParamTraits<mozilla::security::mls::GkReceived::Tag>
: public ContiguousEnumSerializerInclusive<
mozilla::security::mls::GkReceived::Tag,
mozilla::security::mls::GkReceived::Tag::None,
mozilla::security::mls::GkReceived::Tag::CommitOutput> {};
template <>
struct ParamTraits<mozilla::security::mls::GkReceived> {
using paramType = mozilla::security::mls::GkReceived;
static void Write(MessageWriter* aWriter, const paramType& aValue);
static bool Read(MessageReader* aReader, paramType* aResult);
};
DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::security::mls::GkGroupIdEpoch,
group_id, group_epoch);
DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::security::mls::GkMlsCommitOutput,
commit, welcome, group_info, ratchet_tree,
identity);
DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::security::mls::GkClientIdentifiers,
identity, credential);
DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::security::mls::GkGroupMembers,
group_id, group_epoch, group_members);
DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::security::mls::GkExporterOutput,
group_id, group_epoch, label, context,
exporter);
}; // namespace IPC
#endif // mozilla_dom_MLSTransactionMessage_h

View File

@@ -1,574 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MLSTransactionParent.h"
#include "MLSTransactionMessage.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/security/mls/mls_gk_ffi_generated.h"
#include "MLSLogging.h"
#include "mozilla/Base64.h"
#include "nsIFile.h"
#include "nsIPrincipal.h"
#include "nsString.h"
#include "nsCOMPtr.h"
using mozilla::dom::quota::QuotaManager;
namespace mozilla::dom {
/* static */ nsresult MLSTransactionParent::CreateDirectoryIfNotExists(
nsIFile* aDir) {
nsresult rv = aDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
// Evaluate if the file is a directory
bool isDirectory = false;
rv = aDir->IsDirectory(&isDirectory);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Check if the file is actually a directory
if (!isDirectory) {
return NS_ERROR_FILE_NOT_DIRECTORY;
}
return NS_OK;
}
return rv;
}
/* static */ nsresult MLSTransactionParent::ConstructDatabasePrefixPath(
nsCOMPtr<nsIFile>& aFile) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabasePath()"));
// Get the base path from the quota manager
QuotaManager* quotaManager = QuotaManager::Get();
if (NS_WARN_IF(!quotaManager)) {
return NS_ERROR_FAILURE;
}
// Create an nsIFile object from the path
nsresult rv =
NS_NewLocalFile(quotaManager->GetBasePath(), getter_AddRefs(aFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Append the hardcoded "mls" directory name to the path
rv = aFile->AppendNative("mls"_ns);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
/* static */ nsresult MLSTransactionParent::ConstructDatabaseFullPath(
nsCOMPtr<nsIFile>& aFile, nsIPrincipal* aPrincipal,
nsCString& aDatabasePath) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabaseFullPath()"));
// Get StorageOriginKey
nsAutoCString originKey;
nsresult rv = aPrincipal->GetStorageOriginKey(originKey);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Get OriginSuffix
nsAutoCString originAttrSuffix;
rv = aPrincipal->GetOriginSuffix(originAttrSuffix);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Set the base path and origin
nsAutoCString origin = originKey + originAttrSuffix;
// Encode the origin with its suffix
nsAutoCString encodedOrigin;
rv = mozilla::Base64Encode(origin, encodedOrigin);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabasePath() - origin: %s",
origin.get()));
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabasePath() - encodedOrigin: "
"%s",
encodedOrigin.get()));
// Append the origin to the path
rv = aFile->AppendNative(encodedOrigin);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Get the updated path back into the nsCString
nsAutoString databasePathUTF16;
rv = aFile->GetPath(databasePathUTF16);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aDatabasePath = NS_ConvertUTF16toUTF8(databasePathUTF16);
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabasePath() - databasePath: %s",
aDatabasePath.get()));
return NS_OK;
}
void MLSTransactionParent::ActorDestroy(ActorDestroyReason) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ActorDestroy()"));
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestStateDelete(
RequestStateDeleteResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestStateDelete()"));
// Call to the MLS rust code
nsresult rv = security::mls::mls_state_delete(&mDatabasePath);
aResolver(NS_SUCCEEDED(rv));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupStateDelete(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
RequestGroupStateDeleteResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupStateDelete()"));
// Call to the MLS rust code
security::mls::GkGroupIdEpoch groupIdEpoch;
nsresult rv = security::mls::mls_state_delete_group(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), &groupIdEpoch);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(groupIdEpoch)));
return IPC_OK();
}
mozilla::ipc::IPCResult
MLSTransactionParent::RecvRequestGenerateIdentityKeypair(
RequestGenerateIdentityKeypairResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGenerateIdentityKeypair()"));
// Call to the MLS rust code
nsTArray<uint8_t> signatureIdentifier;
nsresult rv = security::mls::mls_generate_signature_keypair(
&mDatabasePath, &signatureIdentifier);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(signatureIdentifier)}));
return IPC_OK();
}
mozilla::ipc::IPCResult
MLSTransactionParent::RecvRequestGenerateCredentialBasic(
const nsTArray<uint8_t>& aCredContent,
RequestGenerateCredentialBasicResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGenerateCredentialBasic()"));
// Call to the MLS rust code
nsTArray<uint8_t> credential;
nsresult rv = security::mls::mls_generate_credential_basic(
aCredContent.Elements(), aCredContent.Length(), &credential);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(credential)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGenerateKeyPackage(
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aCredential,
RequestGenerateKeyPackageResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGenerateKeyPackage()"));
// Call to the MLS rust code
nsTArray<uint8_t> keyPackage;
nsresult rv = security::mls::mls_generate_keypackage(
&mDatabasePath, aIdentifier.Elements(), aIdentifier.Length(),
aCredential.Elements(), aCredential.Length(), &keyPackage);
// Return Nothing if failed
if (NS_FAILED(rv)) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(keyPackage)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupCreate(
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aCredential,
const nsTArray<uint8_t>& aInOptGroupIdentifier,
RequestGroupCreateResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupCreate()"));
// Call to the MLS rust code
security::mls::GkGroupIdEpoch groupIdEpoch;
nsresult rv = security::mls::mls_group_create(
&mDatabasePath, aIdentifier.Elements(), aIdentifier.Length(),
aCredential.Elements(), aCredential.Length(),
aInOptGroupIdentifier.Elements(), aInOptGroupIdentifier.Length(),
&groupIdEpoch);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(groupIdEpoch)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupJoin(
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aWelcome,
RequestGroupJoinResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupJoin()"));
// Call to the MLS rust code
security::mls::GkGroupIdEpoch groupIdEpoch;
nsresult rv = security::mls::mls_group_join(
&mDatabasePath, aIdentifier.Elements(), aIdentifier.Length(),
aWelcome.Elements(), aWelcome.Length(), &groupIdEpoch);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(groupIdEpoch)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupAdd(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aKeyPackage,
RequestGroupAddResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupAdd()"));
// Call to the MLS rust code
security::mls::GkMlsCommitOutput commitOutput;
nsresult rv = security::mls::mls_group_add(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aKeyPackage.Elements(),
aKeyPackage.Length(), &commitOutput);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(commitOutput)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupProposeAdd(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aKeyPackage,
RequestGroupProposeAddResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupProposeAdd()"));
// Call to the MLS rust code
nsTArray<uint8_t> proposal;
nsresult rv = security::mls::mls_group_propose_add(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aKeyPackage.Elements(),
aKeyPackage.Length(), &proposal);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(proposal)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupRemove(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aRemIdentifier,
RequestGroupRemoveResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupRemove()"));
// Call to the MLS rust code
security::mls::GkMlsCommitOutput commitOutput;
nsresult rv = security::mls::mls_group_remove(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aRemIdentifier.Elements(),
aRemIdentifier.Length(), &commitOutput);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(commitOutput)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupProposeRemove(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aRemIdentifier,
RequestGroupProposeRemoveResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupProposeRemove()"));
nsTArray<uint8_t> proposal;
nsresult rv = security::mls::mls_group_propose_remove(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aRemIdentifier.Elements(),
aRemIdentifier.Length(), &proposal);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(proposal)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupClose(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
RequestGroupCloseResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupClose()"));
// Call to the MLS rust code
security::mls::GkMlsCommitOutput commitOutput;
nsresult rv = security::mls::mls_group_close(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), &commitOutput);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(commitOutput)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupDetails(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
RequestGroupDetailsResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupDetails()"));
// Call to the MLS rust code
security::mls::GkGroupMembers members;
nsresult rv = security::mls::mls_group_members(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), &members);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(members)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestReceive(
const nsTArray<uint8_t>& aClientIdentifier,
const nsTArray<uint8_t>& aMessage, RequestReceiveResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestReceive()"));
// Call to the MLS rust code
GkReceived received;
nsTArray<uint8_t> group_id_bytes;
nsresult rv = security::mls::mls_receive(
&mDatabasePath, aClientIdentifier.Elements(), aClientIdentifier.Length(),
aMessage.Elements(), aMessage.Length(), &group_id_bytes, &received);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(GkReceived());
return IPC_OK();
}
// Return the result if success
aResolver(received);
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestApplyPendingCommit(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aClientIdentifier,
RequestApplyPendingCommitResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestApplyPendingCommit()"));
// Call to the MLS rust code
GkReceived received;
nsresult rv = security::mls::mls_receive_ack(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aClientIdentifier.Elements(), aClientIdentifier.Length(), &received);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(GkReceived());
return IPC_OK();
}
// Return the result if success
aResolver(received);
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestSend(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aMessage,
RequestSendResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestSend()"));
// Call to the MLS rust code
nsTArray<uint8_t> outputMessage;
nsresult rv = security::mls::mls_send(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aMessage.Elements(),
aMessage.Length(), &outputMessage);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(outputMessage)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestExportSecret(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aLabel,
const nsTArray<uint8_t>& aContext, uint64_t aLen,
RequestExportSecretResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestExportSecret()"));
// Call to the MLS rust code
security::mls::GkExporterOutput exporterOutput;
nsresult rv = security::mls::mls_derive_exporter(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aLabel.Elements(),
aLabel.Length(), aContext.Elements(), aContext.Length(), aLen,
&exporterOutput);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(exporterOutput)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGetGroupIdentifier(
const nsTArray<uint8_t>& aMessage,
RequestGetGroupIdentifierResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGetGroupIdentifier()"));
nsTArray<uint8_t> groupId;
nsresult rv = security::mls::mls_get_group_id(aMessage.Elements(),
aMessage.Length(), &groupId);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(groupId)}));
return IPC_OK();
}
} // namespace mozilla::dom

View File

@@ -1,128 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_MLSTransactionParent_h
#define mozilla_dom_MLSTransactionParent_h
#include "mozilla/dom/PMLSTransaction.h"
#include "mozilla/dom/PMLSTransactionParent.h"
#include "nsIPrincipal.h"
namespace mozilla::dom {
class MLSTransactionParent final : public PMLSTransactionParent {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MLSTransactionParent, override);
explicit MLSTransactionParent(const nsACString& aDatabasePath)
: mDatabasePath(aDatabasePath) {};
static nsresult CreateDirectoryIfNotExists(nsIFile* aDir);
static nsresult ConstructDatabasePrefixPath(nsCOMPtr<nsIFile>& aFile);
static nsresult ConstructDatabaseFullPath(nsCOMPtr<nsIFile>& aFile,
nsIPrincipal* aPrincipal,
nsCString& aDatabasePath);
void ActorDestroy(ActorDestroyReason) override;
mozilla::ipc::IPCResult RecvRequestStateDelete(
RequestStateDeleteResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGroupStateDelete(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
RequestGroupStateDeleteResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGenerateIdentityKeypair(
RequestGenerateIdentityKeypairResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGenerateCredentialBasic(
const nsTArray<uint8_t>& aCredContent,
RequestGenerateCredentialBasicResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGenerateKeyPackage(
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aCredential,
RequestGenerateKeyPackageResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGroupCreate(
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aCredential,
const nsTArray<uint8_t>& aInOptGroupIdentifier,
RequestGroupCreateResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGroupJoin(
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aWelcome,
RequestGroupJoinResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGroupAdd(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aKeyPackage,
RequestGroupAddResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGroupProposeAdd(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aKeyPackage,
RequestGroupProposeAddResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGroupRemove(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aRemIdentifier,
RequestGroupRemoveResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGroupProposeRemove(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aRemIdentifier,
RequestGroupProposeRemoveResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGroupClose(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
RequestGroupCloseResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGroupDetails(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
RequestGroupDetailsResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestReceive(
const nsTArray<uint8_t>& aClientIdentifier,
const nsTArray<uint8_t>& aMessage, RequestReceiveResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestApplyPendingCommit(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aClientIdentifier,
RequestApplyPendingCommitResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestSend(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aMessage,
RequestSendResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestExportSecret(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aLabel,
const nsTArray<uint8_t>& aContext, uint64_t aLen,
RequestExportSecretResolver&& aResolver);
mozilla::ipc::IPCResult RecvRequestGetGroupIdentifier(
const nsTArray<uint8_t>& aMessage,
RequestGetGroupIdentifierResolver&& aResolver);
protected:
~MLSTransactionParent() = default;
nsCString mDatabasePath;
};
} // namespace mozilla::dom
#endif

View File

@@ -1,95 +0,0 @@
/* -*- 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 "mozilla/dom/MLSBinding.h"
#include "mozilla/dom/TypedArray.h"
#include "nsTArray.h"
namespace mozilla::dom {
nsTArray<uint8_t> ExtractMLSBytesOrUint8ArrayWithUnknownType(
const MLSBytesOrUint8Array& aArgument, ErrorResult& aRv) {
// The data can be in a Uint8Array or MLSBytes.
const Uint8Array* array = nullptr;
if (aArgument.IsMLSBytes()) {
array = &aArgument.GetAsMLSBytes().mContent;
} else {
MOZ_ASSERT(aArgument.IsUint8Array());
array = &aArgument.GetAsUint8Array();
}
// Append the data from the Uint8Array to the output array
nsTArray<uint8_t> bytes;
if (array && !array->AppendDataTo(bytes)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nsTArray<uint8_t>();
}
return bytes;
}
nsTArray<uint8_t> ExtractMLSBytesOrUint8Array(
MLSObjectType aExpectedType, const MLSBytesOrUint8Array& aArgument,
ErrorResult& aRv) {
// The data can be in a Uint8Array or MLSBytes.
const Uint8Array* array = nullptr;
if (aArgument.IsMLSBytes()) {
// Check if the type of MLSBytes matches the expected type
if (aArgument.GetAsMLSBytes().mType != aExpectedType) {
aRv.ThrowTypeError("Input data has an invalid type");
return nsTArray<uint8_t>();
}
array = &aArgument.GetAsMLSBytes().mContent;
} else {
MOZ_ASSERT(aArgument.IsUint8Array());
array = &aArgument.GetAsUint8Array();
}
// Append the data from the Uint8Array to the output array
nsTArray<uint8_t> bytes;
if (array && !array->AppendDataTo(bytes)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nsTArray<uint8_t>();
}
return bytes;
}
nsTArray<uint8_t> ExtractMLSBytesOrUint8ArrayOrUTF8String(
MLSObjectType aExpectedType,
const MLSBytesOrUint8ArrayOrUTF8String& aArgument, ErrorResult& aRv) {
// The data can be in a Uint8Array, MLSBytes, or UTF8String.
const Uint8Array* array = nullptr;
nsTArray<uint8_t> bytes;
if (aArgument.IsMLSBytes()) {
// Check if the type of MLSBytes matches the expected type
if (aArgument.GetAsMLSBytes().mType != aExpectedType) {
aRv.ThrowTypeError("Input data has an invalid type");
return nsTArray<uint8_t>();
}
array = &aArgument.GetAsMLSBytes().mContent;
} else if (aArgument.IsUint8Array()) {
array = &aArgument.GetAsUint8Array();
} else {
MOZ_ASSERT(aArgument.IsUTF8String());
const nsACString& string = aArgument.GetAsUTF8String();
if (!bytes.AppendElements(string.BeginReading(), string.Length(),
fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nsTArray<uint8_t>();
}
return bytes;
}
// Append the data from the Uint8Array to the output array
if (array && !array->AppendDataTo(bytes)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nsTArray<uint8_t>();
}
return bytes;
}
} // namespace mozilla::dom

View File

@@ -1,28 +0,0 @@
/* -*- 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/. */
#ifndef mozilla_dom_MLSTypeUtils_h
#define mozilla_dom_MLSTypeUtils_h
#include "mozilla/dom/MLSBinding.h"
#include "nsTArray.h"
namespace mozilla::dom {
nsTArray<uint8_t> ExtractMLSBytesOrUint8ArrayWithUnknownType(
const MLSBytesOrUint8Array& aArgument, ErrorResult& aRv);
nsTArray<uint8_t> ExtractMLSBytesOrUint8Array(
MLSObjectType aExpectedType, const MLSBytesOrUint8Array& aArgument,
ErrorResult& aRv);
nsTArray<uint8_t> ExtractMLSBytesOrUint8ArrayOrUTF8String(
MLSObjectType aExpectedType,
const MLSBytesOrUint8ArrayOrUTF8String& aArgument, ErrorResult& aRv);
} // namespace mozilla::dom
#endif // mozilla_dom_MLSTypeUtils_h

View File

@@ -1,45 +0,0 @@
/* 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 protocol PBackground;
using struct mozilla::security::mls::GkGroupIdEpoch from "mozilla/dom/MLSTransactionMessage.h";
using struct mozilla::security::mls::GkMlsCommitOutput from "mozilla/dom/MLSTransactionMessage.h";
using struct mozilla::security::mls::GkGroupMembers from "mozilla/dom/MLSTransactionMessage.h";
using struct mozilla::security::mls::GkExporterOutput from "mozilla/dom/MLSTransactionMessage.h";
using struct mozilla::security::mls::GkReceived from "mozilla/dom/MLSTransactionMessage.h";
namespace mozilla {
namespace dom {
/* This type exists because we cannot write uint8_t[]? */
struct RawBytes {
uint8_t[] data;
};
[ChildProc=anydom]
async protocol PMLSTransaction
{
parent:
async RequestStateDelete() returns (bool result);
async RequestGroupStateDelete(uint8_t[] groupIdentifier, uint8_t[] identifier) returns (GkGroupIdEpoch? result);
async RequestGenerateIdentityKeypair() returns (RawBytes? result);
async RequestGenerateCredentialBasic(uint8_t[] credName) returns (RawBytes? result);
async RequestGenerateKeyPackage(uint8_t[] identity, uint8_t[] credential) returns (RawBytes? result);
async RequestGroupCreate(uint8_t[] identifier, uint8_t[] credential, uint8_t[] inOptGroupIdentifier) returns (GkGroupIdEpoch? result);
async RequestGroupJoin(uint8_t[] identifier, uint8_t[] welcome) returns (GkGroupIdEpoch? result);
async RequestGroupAdd(uint8_t[] groupIdentifier, uint8_t[] identifier, uint8_t[] keyPackage) returns (GkMlsCommitOutput? result);
async RequestGroupProposeAdd(uint8_t[] groupIdentifier, uint8_t[] identifier, uint8_t[] keyPackage) returns (RawBytes? result);
async RequestGroupRemove(uint8_t[] groupIdentifier, uint8_t[] identifier, uint8_t[] remIdentifier) returns (GkMlsCommitOutput? result);
async RequestGroupProposeRemove(uint8_t[] groupIdentifier, uint8_t[] identifier, uint8_t[] remIdentifier) returns (RawBytes? result);
async RequestGroupClose(uint8_t[] groupIdentifier, uint8_t[] identifier) returns (GkMlsCommitOutput? result);
async RequestGroupDetails(uint8_t[] groupIdentifier, uint8_t[] identifier) returns (GkGroupMembers? result);
async RequestReceive(uint8_t[] identifier, uint8_t[] message) returns (GkReceived result);
async RequestApplyPendingCommit(uint8_t[] groupIdentifier, uint8_t[] identifier) returns (GkReceived result);
async RequestSend(uint8_t[] groupIdentifier, uint8_t[] identifier, uint8_t[] message) returns (RawBytes? result);
async RequestExportSecret(uint8_t[] groupIdentifier, uint8_t[] identifier, uint8_t[] label, uint8_t[] content, uint64_t len) returns (GkExporterOutput? result);
async RequestGetGroupIdentifier(uint8_t[] message) returns (RawBytes? result);
};
} // namespace dom
} // namespace mozilla

View File

@@ -1,37 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
with Files("**"):
BUG_COMPONENT = ("Core", "DOM: Security")
IPDL_SOURCES += [
"PMLSTransaction.ipdl",
]
EXPORTS.mozilla.dom += [
"MLS.h",
"MLSGroupView.h",
"MLSLogging.h",
"MLSTransactionChild.h",
"MLSTransactionMessage.h",
"MLSTransactionParent.h",
"MLSTypeUtils.h",
]
UNIFIED_SOURCES += [
"MLS.cpp",
"MLSGroupView.cpp",
"MLSTransactionChild.cpp",
"MLSTransactionMessage.cpp",
"MLSTransactionParent.cpp",
"MLSTypeUtils.cpp",
]
include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul"
MOCHITEST_MANIFESTS += ["tests/mochitest.toml"]

View File

@@ -1,47 +0,0 @@
/* 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/.
*/
"use strict";
//
// Array equality
//
function arraysAreEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
//
// Serialization / Derserialization helpers
//
function stringToByteArray(str) {
return new TextEncoder().encode(str);
}
function byteArrayToString(byteArray) {
return new TextDecoder().decode(new Uint8Array(byteArray).buffer);
}
function stringToArrayBuffer(str) {
return new Uint8Array(new TextEncoder().encode(str)).buffer;
}
function byteArrayToHexString(buffer) {
const byteArray = new Uint8Array(buffer);
const hexParts = [];
for (let i = 0; i < byteArray.length; i++) {
const hex = byteArray[i].toString(16);
const paddedHex = ("00" + hex).slice(-2);
hexParts.push(paddedHex);
}
return hexParts.join("");
}

View File

@@ -1,24 +0,0 @@
[DEFAULT]
support-files = [
"head_mls.js",
]
scheme = "https"
prefs = [
"security.mls.enabled=true",
]
["test_derive_exporter.html"]
["test_group_add.html"]
["test_group_close.html"]
["test_group_create.html"]
["test_group_join.html"]
["test_group_remove.html"]
["test_scenario.html"]
["test_send_receive.html"]

View File

@@ -1,108 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Messaging Layer Security</title>
<!-- SimpleTest Helpers -->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!-- Local Helpers -->
<script src="head_mls.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
async function test_derive_exporter() {
const mls = new MLS();
// Generate Identity KeyPairs for Alice and Bob
let alice = await mls.generateIdentity();
let bob = await mls.generateIdentity();
info("Alice Client ID:", byteArrayToHexString(alice.content));
info("Bob Client ID:", byteArrayToHexString(bob.content));
// Generate Credentials for Alice and Bob
let credential_alice = await mls.generateCredential("alice");
let credential_bob = await mls.generateCredential("bob");
// Generate a KeyPackage for Bob
let kp_bob = await mls.generateKeyPackage(bob, credential_bob);
// Creation of a Group by Alice
let group_alice = await mls.groupCreate(alice, credential_alice);
info("Group Alice:", JSON.stringify(group_alice));
// Get membership of the group
let members_alice_0 = await group_alice.details();
// Test that the returned group membership is not null
info("Membership @ Epoch 0:", JSON.stringify(members_alice_0));
is(members_alice_0.members.length, 1, "There should be exactly one member in the group");
info("Member Client ID:", byteArrayToHexString(members_alice_0.members[0].clientId));
info("Alice Client ID:", byteArrayToHexString(alice.content));
is(byteArrayToHexString(members_alice_0.members[0].clientId), byteArrayToHexString(alice.content), "The client ID of the member should match Alice's client ID");
// Alice adds Bob to a group
let commit_output = await group_alice.add(kp_bob);
// Test that the returned commit output is not null
info("Commit Output 1:", JSON.stringify(commit_output));
isnot(byteArrayToHexString(commit_output.commit), "", "Commit Output commit should not be an empty string");
// Alice receives the commit
let group_and_epoch_1_alice = await group_alice.receive(commit_output.commit);
// Test that the new group identifier and epoch are valid
info("Alice's Group Identifier and Epoch:", JSON.stringify(group_and_epoch_1_alice));
isnot(byteArrayToHexString(group_and_epoch_1_alice.groupId), "", "Group ID should not be an empty string");
isnot(byteArrayToHexString(group_and_epoch_1_alice.groupEpoch), "", "Group Epoch should not be an empty string");
// Get membership of the group
let members_alice_1 = await group_alice.details();
// Test that the returned group contain both Alice and Bob
info("Membership @ Epoch 1:", JSON.stringify(members_alice_1));
// Test: the group should have exactly two members at epoch 1
is(members_alice_1.members.length, 2, "There should be exactly two members in the group");
// Test: Bob should be in the group
is(members_alice_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(bob.content)), true, "Bob should be in the group");
// Test: Alice should be in the group
is(members_alice_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(alice.content)), true, "Alice should be in the group");
// Bob joins the group
let group_bob = await mls.groupJoin(bob, commit_output.welcome);
// Test: compare the group identifier after the join
is(byteArrayToHexString(group_alice.groupId), byteArrayToHexString(group_bob.groupId), "Alice GID == Bob GID");
// Create exporter labels and context
const context_bytes = new Uint8Array([99, 111, 110, 116, 101, 120, 116]); // "context" in ASCII
const exporter_len = 32;
// Alice generates an Exporter
let exporter_alice = await group_alice.exportSecret(
"label", context_bytes, exporter_len);
// Bob generates an Exporter
let exporter_bob = await group_bob.exportSecret(
"label", context_bytes, exporter_len);
// Test that exporters are identical on both side
is(byteArrayToHexString(exporter_alice.exporter), byteArrayToHexString(exporter_bob.exporter), "Exporter Alice == Exporter Bob");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
test_derive_exporter();
</script>
</pre>
</body>
</html>

View File

@@ -1,76 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Messaging Layer Security</title>
<!-- SimpleTest Helpers -->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!-- Local Helpers -->
<script src="head_mls.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
async function test_group_add() {
const mls = new MLS();
// Generate Signature KeyPairs for Alice and Bob
let alice = await mls.generateIdentity();
let bob = await mls.generateIdentity();
// Generate Credentials for Alice and Bob
let credential_alice = await mls.generateCredential("alice");
let credential_bob = await mls.generateCredential("bob");
// Generate a KeyPackage for Bob
let kp_bob = await mls.generateKeyPackage(bob, credential_bob);
// Creation of a Group by Alice
let group_alice = await mls.groupCreate(alice, credential_alice);
let members_alice_0 = await group_alice.details();
// Test: compare the group identifier to the invalid value
info("Group ID:", byteArrayToHexString(group_alice.groupId));
isnot(byteArrayToHexString(group_alice.groupId), "", "Group Identifier != ''");
// Alice adds Bob to a group
let commit_output = await group_alice.add(kp_bob);
// Test: compare the commit output to the invalid value
info("Commit Output:", byteArrayToHexString(commit_output.commit));
isnot(byteArrayToHexString(commit_output.commit), "", "Commit != ''");
// Alice receives the commit
let group_and_epoch_1_alice = await group_alice.receive(commit_output.commit);
// Test: make sure that the group epoch has been incremented by one
const expectedEpoch = new Uint8Array(new BigUint64Array([1n]).buffer);
is(byteArrayToHexString(group_and_epoch_1_alice.groupEpoch), byteArrayToHexString(expectedEpoch), "Group Epoch = 1");
// Get the group details
let members_alice_1 = await group_alice.details();
// Test: the group should have exactly one member at epoch 0
is(members_alice_0.members.length, 1, "There should be exactly one member in the group");
// Test: the group should have exactly two members at epoch 1
is(members_alice_1.members.length, 2, "There should be exactly two members in the group");
// Test: compare the group members to the expected value
is(members_alice_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(bob.content)), true, "Bob should be in the group");
is(members_alice_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(alice.content)), true, "Alice should be in the group");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
test_group_add();
</script>
</pre>
</body>
</html>

View File

@@ -1,116 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Messaging Layer Security</title>
<!-- SimpleTest Helpers -->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!-- Local Helpers -->
<script src="head_mls.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
async function test_group_close() {
const mls = new MLS();
// Generate Identity KeyPairs for Alice and Bob
let alice = await mls.generateIdentity();
let bob = await mls.generateIdentity();
// Generate Credentials for Alice and Bob
let credential_alice = await mls.generateCredential("alice");
let credential_bob = await mls.generateCredential("bob");
// Generate a KeyPackage for Bob
let kp_bob = await mls.generateKeyPackage(bob, credential_bob);
// Creation of a Group by Alice
let group_alice = await mls.groupCreate(alice, credential_alice);
// Alice adds Bob to a group
let commit_output = await group_alice.add(kp_bob);
// Alice receives her commit
await group_alice.receive(commit_output.commit);
// Bob joins the group
let group_bob = await mls.groupJoin(bob, commit_output.welcome);
// Test: compare the group identifier after the join
is(byteArrayToHexString(group_alice.groupId), byteArrayToHexString(group_bob.groupId), "Alice Group ID == Bob Group ID");
// Test: compare the group members after the join
let members_alice_1 = await group_alice.details();
let members_bob_1 = await group_bob.details();
// Test: the group should have exactly two members at epoch 1
is(members_alice_1.members.length, 2, "There should be exactly two members in the group");
is(members_bob_1.members.length, 2, "There should be exactly two members in the group");
// Test: Bob should be in the group according to Alice's view
is(members_alice_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(bob.content)), true, "Bob should be in the group");
// Test: Alice should be in the group according to Alice's view
is(members_alice_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(alice.content)), true, "Alice should be in the group");
// Test: Bob should be in the group according to Bob's view
is(members_bob_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(bob.content)), true, "Bob should be in the group");
// Test: Alice should be in the group according to Bob's view
is(members_bob_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(alice.content)), true, "Alice should be in the group");
// Bob closes the group
let commit_output_2 = await group_bob.close();
// Info: print the commit output
info("Commit Output:", JSON.stringify(commit_output_2));
// Alice receives the close of the group
let group_and_epoch_final_alice = await group_alice.receive(commit_output_2.commit);
// Info: print the group and epoch final alice
info("Group and Epoch Final Alice:", JSON.stringify(group_and_epoch_final_alice.groupIdEpoch));
// Bob processes its close of the group
// This leaves Bob alone in the new epoch which is NOT 0xFF..FF
let group_and_epoch_final_bob = await group_bob.receive(commit_output_2.commit);
// Info: print the group and epoch final Bob
info("Group and Epoch Final Bob:", JSON.stringify(group_and_epoch_final_bob.groupIdEpoch));
// Test: compare the group members after the close
let members_alice_2 = await group_alice.details();
let members_bob_2 = await group_bob.details();
// This is counter intuitive, but true: Alice has two members in the group
// after being removed by Bob, basically because she is left in her epoch.
// Technically she can send messages in the previous epoch, but not in the current one.
is(members_alice_2.members.length, 2, "Alice should have two members in the group");
is(members_bob_2.members.length, 1, "Bob should be alone in the group");
// Test: check that alice has transitioned to the 0xFF..FF epoch
is(byteArrayToHexString(group_and_epoch_final_alice.groupEpoch), "ffffffffffffffff", "Alice should have a returned epoch set to 0xFF..FF");
// Test: check that bob has transitioned to epoch 2
const expectedEpoch = new Uint8Array(new BigUint64Array([2n]).buffer);
is(byteArrayToHexString(group_and_epoch_final_bob.groupEpoch), byteArrayToHexString(expectedEpoch), "Bob should have transitioned to epoch 2");
// Bob is alone in the group and can remove its state
let bob_deleted = await group_bob.deleteState();
is(bob_deleted, undefined, "Bob should have deleted his state for this group");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
test_group_close();
</script>
</pre>
</body>
</html>

View File

@@ -1,44 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Messaging Layer Security</title>
<!-- SimpleTest Helpers -->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!-- Local Helpers -->
<script src="head_mls.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
async function test_group_create() {
const mls = new MLS();
// Alice: Create signature keypair and credential
const alice = await mls.generateIdentity();
const credential_alice = await mls.generateCredential("alice");
// Alice: Create a group
const group_alice = await mls.groupCreate(alice, credential_alice);
// Test: compare the generated group identifier to incorrect values
// Note: there is no deterministic test for this value as it is generated randomly
isnot(byteArrayToHexString(group_alice.groupId), "", "Group Identifier != ''");
// Test: the generated group epoch is of size 32
is(group_alice.groupId.length, 32, "Group Epoch should be of size 32");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
test_group_create();
</script>
</pre>
</body>
</html>

View File

@@ -1,107 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Messaging Layer Security</title>
<!-- SimpleTest Helpers -->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!-- Local Helpers -->
<script src="head_mls.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
async function test_group_join() {
const mls = new MLS();
// Generate Identity KeyPairs for Alice and Bob
let alice = await mls.generateIdentity();
let bob = await mls.generateIdentity();
info("Alice Client ID:", byteArrayToHexString(alice.content));
info("Bob Client ID:", byteArrayToHexString(bob.content));
// Generate Credentials for Alice and Bob
let credential_alice = await mls.generateCredential("alice");
let credential_bob = await mls.generateCredential("bob");
// Generate a KeyPackage for Bob
let kp_bob = await mls.generateKeyPackage(bob, credential_bob);
// Creation of a Group by Alice
let group_alice = await mls.groupCreate(alice, credential_alice);
info("Group Alice:", JSON.stringify(group_alice));
// Get membership of the group
let members_alice_0 = await group_alice.details();
// Test that the returned group membership is not null
info("Membership @ Epoch 0:", JSON.stringify(members_alice_0));
is(members_alice_0.members.length, 1, "There should be exactly one member in the group");
info("Member Client ID:", byteArrayToHexString(members_alice_0.members[0].clientId));
info("Alice Client ID:", byteArrayToHexString(alice.content));
is(byteArrayToHexString(members_alice_0.members[0].clientId), byteArrayToHexString(alice.content), "The client ID of the member should match Alice's client ID");
// Alice adds Bob to a group
let commit_output = await group_alice.add(kp_bob);
// Test that the returned commit output is not null
info("Commit Output 1:", JSON.stringify(commit_output));
isnot(byteArrayToHexString(commit_output.commit), "", "Commit Output commit should not be an empty string");
// Alice receives the commit
let group_and_epoch_1_alice = await group_alice.receive(commit_output.commit);
// Test that the new group identifier and epoch are valid
info("Alice's Group Identifier and Epoch:", JSON.stringify(group_and_epoch_1_alice));
isnot(byteArrayToHexString(group_and_epoch_1_alice.groupId), "", "Group ID should not be an empty string");
isnot(byteArrayToHexString(group_and_epoch_1_alice.groupEpoch), "", "Group Epoch should not be an empty string");
// Get membership of the group
let members_alice_1 = await group_alice.details();
// Bob joins the group
let group_bob = await mls.groupJoin(bob, commit_output.welcome);
let members_bob_1 = await group_bob.details();
// Test: compare the group identifier after the join
is(byteArrayToHexString(group_alice.groupId), byteArrayToHexString(group_bob.groupId), "Alice GID == Bob GID");
// Test: the group should have two members
info("Membership @ Epoch 1:", JSON.stringify(members_alice_1));
is(members_alice_1.members.length, 2, "There should be exactly two members in the group");
// Test: the group should have exactly two members at epoch 0
is(members_alice_0.members.length, 1, "There should be exactly one member in the group");
// Test: the group should have exactly two members at epoch 1
is(members_alice_1.members.length, 2, "There should be exactly two members in the group");
is(members_bob_1.members.length, 2, "There should be exactly two members in the group");
// Test: Bob should be in the group according to Alice's view
is(members_alice_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(bob.content)), true, "Bob should be in the group");
// Test: Alice should be in the group according to Alice's view
is(members_alice_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(alice.content)), true, "Alice should be in the group");
// Test: Bob should be in the group according to Bob's view
is(members_bob_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(bob.content)), true, "Bob should be in the group");
// Test: Alice should be in the group according to Bob's view
is(members_bob_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(alice.content)), true, "Alice should be in the group");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
test_group_join();
</script>
</pre>
</body>
</html>

View File

@@ -1,94 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Messaging Layer Security</title>
<!-- SimpleTest Helpers -->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!-- Local Helpers -->
<script src="head_mls.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
async function test_group_remove() {
const mls = new MLS();
// Generate Signature KeyPairs for Alice and Bob
let alice = await mls.generateIdentity();
let bob = await mls.generateIdentity();
// Generate Credentials for Alice and Bob
let credential_alice = await mls.generateCredential("alice");
let credential_bob = await mls.generateCredential("bob");
// Generate a KeyPackage for Bob
let kp_bob = await mls.generateKeyPackage(bob, credential_bob);
// Creation of a Group by Alice
let group = await mls.groupCreate(alice, credential_alice);
// Get membership of the group
let membership_0 = await group.details();
// Test that the returned group membership is not null
info("Membership @ Epoch 0:", JSON.stringify(membership_0));
is(membership_0.members.length, 1, "There should be one member in the group");
// Alice adds Bob to a group
let commit_output = await group.add(kp_bob);
// Test that the returned commit output is not null
info("Commit 1:", byteArrayToHexString(commit_output.commit));
isnot(byteArrayToHexString(commit_output.commit), "", "Commit != ''");
// Alice receives the commit
await group.receive(commit_output.commit);
// Get membership of the group
let membership_1 = await group.details();
// Test that the returned group membership is not null
info("Membership @ Epoch 1:", JSON.stringify(membership_1));
is(membership_1.members.length, 2, "There should be two members in the group");
// Alice removes Bob from the group
let commit_output_2 = await group.remove(bob);
// Alice receives the commit
await group.receive(commit_output_2.commit);
// Get membership of the group
let membership_2 = await group.details();
// Test that the returned group membership is not null
info("Membership @ Epoch 2:", JSON.stringify(membership_2));
is(membership_2.members.length, 1, "There should be one member in the group");
// Verify that Alice is the only member in the group at epoch 0
is(membership_0.members.length, 1, "Alice should be alone in the group at epoch 0");
is(membership_0.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(alice.content)), true, "Alice should be alone in the group at epoch 0");
// Verify that both Alice and Bob are members in the group at epoch 1
is(membership_1.members.length, 2, "There should be two members in the group at epoch 1");
is(membership_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(bob.content)), true, "Bob should be in the group at epoch 1");
is(membership_1.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(alice.content)), true, "Alice should be in the group at epoch 1");
// Verify that Alice is the only member in the group at epoch 2
is(membership_2.members.length, 1, "Alice should be alone in the group at epoch 2");
is(membership_2.members.some(member => byteArrayToHexString(member.clientId) === byteArrayToHexString(alice.content)), true, "Alice should be alone in the group at epoch 2");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
test_group_remove();
</script>
</pre>
</body>
</html>

View File

@@ -1,77 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Messaging Layer Security</title>
<!-- SimpleTest Helpers -->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!-- Local Helpers -->
<script src="head_mls.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
async function test_scenario() {
const mls = new MLS();
// Alice: Create signature keypair and credential
const alice = await mls.generateIdentity();
const alice_credential = await mls.generateCredential("alice");
// Bob: Create signature keypair and credential
const bob = await mls.generateIdentity();
const bob_credential = await mls.generateCredential("bob");
// Bob: Generate a key package
const bob_key_package = await mls.generateKeyPackage(bob, bob_credential);
// Alice: Create a group
let group_alice = await mls.groupCreate(alice, alice_credential);
// Alice: Add Bob to the group
let commit_output = await group_alice.add(bob_key_package);
// Alice: process her Add commit
await group_alice.receive(commit_output.commit);
// Bob: Join the group
let group_bob = await mls.groupJoin(bob, commit_output.welcome);
// Test: compare group identifier from Alice and Bob
is(byteArrayToHexString(group_alice.groupId), byteArrayToHexString(group_bob.groupId), "Alice GID == Bob GID");
// Alice & Bob: Export a secret
const context_bytes = new Uint8Array([99, 111, 110, 116, 101, 120, 116]); // "context" in ASCII
const exportAlice = await group_alice.exportSecret("label", context_bytes, 15);
const exportBob = await group_bob.exportSecret("label", context_bytes, 15);
// Test: compare exporter from Alice and Bob
is(byteArrayToHexString(exportAlice.exporter), byteArrayToHexString(exportBob.exporter), "Exporter Alice == Exporter Bob");
// Bob: send a message to the group
const message = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 32, 33]); // "Hello World !" in ASCII
const ctx = await group_bob.send(message);
// Alice: receive a message from the group
const pt = await group_alice.receive(ctx);
info("Alice received a message from Bob: " + JSON.stringify(pt));
// Test: compare the message and the decrypted message
is(byteArrayToHexString(message), byteArrayToHexString(pt.content), "Plaintext == Decrypted Message");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
test_scenario();
</script>
</pre>
</body>
</html>

View File

@@ -1,73 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Messaging Layer Security</title>
<!-- SimpleTest Helpers -->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!-- Local Helpers -->
<script src="head_mls.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
async function test_send_receive() {
const mls = new MLS();
// Generate Signature KeyPairs for Alice and Bob
let alice = await mls.generateIdentity();
let bob = await mls.generateIdentity();
// Generate Credentials for Alice and Bob
let credential_alice = await mls.generateCredential("alice");
let credential_bob = await mls.generateCredential("bob");
// Generate a KeyPackage for Bob
let kp_bob = await mls.generateKeyPackage(bob, credential_bob);
// Creation of a Group by Alice
let group_alice = await mls.groupCreate(alice, credential_alice);
// Alice adds Bob to a group
let commit_output = await group_alice.add(kp_bob);
// Test: the returned commit output is not null
info("Commit Output 1:", JSON.stringify(commit_output));
isnot(JSON.stringify(commit_output), "", "Commit Output != ''");
// Alice receives the commit
let group_and_epoch_1_alice = await group_alice.receive(commit_output.commit);
// Info: the new group identifier and epoch are valid
info("Alice Group Identifier and Epoch:", JSON.stringify(group_and_epoch_1_alice));
// Bob joins the group
let group_bob = await mls.groupJoin(bob, commit_output.welcome);
// Test: compare the group identifier after the join
is(byteArrayToHexString(group_alice.groupId), byteArrayToHexString(group_bob.groupId), "Alice GID == Bob GID");
// Bob sends a message to Alice
const message = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 32, 33]); // "Hello World !" in ASCII
let ciphertext_bytes = await group_bob.send(message);
// Alice decrypts the message from Bob
let received_message = await group_alice.receive(ciphertext_bytes);
// Test: compare the generated group identifier to incorrect values
is(byteArrayToHexString(message), byteArrayToHexString(received_message.content), "Message Sent == Message Decrypted");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
test_send_receive();
</script>
</pre>
</body>
</html>

View File

@@ -55,7 +55,6 @@ DIRS += [
"mathml",
"media",
"midi",
"mls",
"notification",
"power",
"push",

View File

@@ -1,133 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
enum MLSObjectType {
"group-epoch",
"group-identifier",
"group-info",
"client-identifier",
"credential-basic",
"key-package",
"proposal",
"commit-output",
"commit-processed",
"welcome",
"exporter-output",
"exporter-label",
"exporter-context",
"application-message-ciphertext",
"application-message-plaintext",
};
dictionary MLSBytes {
required MLSObjectType type;
required Uint8Array content;
};
dictionary MLSGroupMember {
required Uint8Array clientId;
required Uint8Array credential;
};
dictionary MLSGroupDetails {
required MLSObjectType type;
required Uint8Array groupId;
required Uint8Array groupEpoch;
required sequence<MLSGroupMember> members;
};
dictionary MLSCommitOutput {
required MLSObjectType type;
required Uint8Array groupId;
required Uint8Array commit;
Uint8Array welcome;
Uint8Array groupInfo;
Uint8Array ratchetTree;
Uint8Array clientId;
};
dictionary MLSExporterOutput {
required MLSObjectType type;
required Uint8Array groupId;
required Uint8Array groupEpoch;
required Uint8Array label;
required Uint8Array context;
required Uint8Array secret;
};
dictionary MLSReceived {
required MLSObjectType type;
required Uint8Array groupId;
Uint8Array groupEpoch;
Uint8Array content;
MLSCommitOutput commitOutput;
};
typedef MLSBytes MLSClientId;
typedef MLSBytes MLSGroupId;
typedef MLSBytes MLSCredential;
typedef MLSBytes MLSKeyPackage;
typedef MLSBytes MLSProposal;
typedef (MLSBytes or Uint8Array) MLSBytesOrUint8Array;
typedef (Uint8Array or UTF8String) Uint8ArrayOrUTF8String;
typedef (MLSBytes or Uint8Array or UTF8String) MLSBytesOrUint8ArrayOrUTF8String;
[Pref="security.mls.enabled",
SecureContext,
Exposed=(Window,Worker)]
interface MLS {
[Throws]
constructor();
[Throws]
Promise<undefined> deleteState();
[Throws]
Promise<MLSClientId> generateIdentity();
[Throws]
Promise<MLSCredential> generateCredential(MLSBytesOrUint8ArrayOrUTF8String credentialContent);
[Throws]
Promise<MLSKeyPackage> generateKeyPackage(MLSBytesOrUint8Array clientId, MLSBytesOrUint8Array credential);
[Throws]
Promise<MLSGroupView> groupCreate(MLSBytesOrUint8Array clientId, MLSBytesOrUint8Array credential);
[Throws]
Promise<MLSGroupView?> groupGet(MLSBytesOrUint8Array groupId, MLSBytesOrUint8Array clientId);
[Throws]
Promise<MLSGroupView> groupJoin(MLSBytesOrUint8Array clientId, MLSBytesOrUint8Array welcome);
// Utility functions
[Throws]
Promise<MLSGroupId> getGroupIdFromMessage(MLSBytesOrUint8Array message);
};
[Pref="security.mls.enabled",
SecureContext,
Exposed=(Window,Worker)]
interface MLSGroupView {
[Throws]
readonly attribute Uint8Array groupId;
[Throws]
readonly attribute Uint8Array clientId;
[Throws]
Promise<undefined> deleteState();
[Throws]
Promise<MLSCommitOutput> add(MLSBytesOrUint8Array keyPackage);
[Throws]
Promise<MLSProposal> proposeAdd(MLSBytesOrUint8Array keyPackage);
[Throws]
Promise<MLSCommitOutput> remove(MLSBytesOrUint8Array remClientId);
[Throws]
Promise<MLSProposal> proposeRemove(MLSBytesOrUint8Array remClientId);
[Throws]
Promise<MLSCommitOutput> close();
[Throws]
Promise<MLSGroupDetails> details();
[Throws]
Promise<MLSBytes> send(MLSBytesOrUint8ArrayOrUTF8String message);
[Throws]
Promise<MLSReceived> receive(MLSBytesOrUint8Array message);
[Throws]
Promise<MLSReceived> applyPendingCommit();
[Throws]
Promise<MLSExporterOutput> exportSecret(MLSBytesOrUint8ArrayOrUTF8String label, MLSBytesOrUint8Array context, unsigned long long length);
};

View File

@@ -766,7 +766,6 @@ WEBIDL_FILES = [
"MIDIPort.webidl",
"MimeType.webidl",
"MimeTypeArray.webidl",
"MLS.webidl",
"MouseEvent.webidl",
"MouseScrollEvent.webidl",
"MozFrameLoaderOwner.webidl",

View File

@@ -30,7 +30,6 @@
#include "mozilla/dom/MIDIManagerParent.h"
#include "mozilla/dom/MIDIPlatformService.h"
#include "mozilla/dom/MIDIPortParent.h"
#include "mozilla/dom/MLSTransactionParent.h"
#include "mozilla/dom/MessagePortParent.h"
#include "mozilla/dom/PGamepadEventChannelParent.h"
#include "mozilla/dom/PGamepadTestChannelParent.h"
@@ -83,7 +82,6 @@ using mozilla::dom::MIDIPortParent;
using mozilla::dom::PMessagePortParent;
using mozilla::dom::PMIDIManagerParent;
using mozilla::dom::PMIDIPortParent;
using mozilla::dom::PMLSTransactionParent;
using mozilla::dom::PServiceWorkerContainerParent;
using mozilla::dom::PServiceWorkerParent;
using mozilla::dom::PServiceWorkerRegistrationParent;
@@ -1165,82 +1163,6 @@ mozilla::ipc::IPCResult BackgroundParentImpl::RecvHasMIDIDevice(
return IPC_OK();
}
// NOTE: Only accessed on the background thread.
static StaticRefPtr<nsISerialEventTarget> sMLSTaskQueue;
class MLSTaskQueueShutdownTask final : public nsITargetShutdownTask {
public:
NS_DECL_THREADSAFE_ISUPPORTS
void TargetShutdown() override { sMLSTaskQueue = nullptr; }
private:
~MLSTaskQueueShutdownTask() = default;
};
NS_IMPL_ISUPPORTS(MLSTaskQueueShutdownTask, nsITargetShutdownTask)
mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateMLSTransaction(
Endpoint<PMLSTransactionParent>&& aEndpoint,
NotNull<nsIPrincipal*> aPrincipal) {
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
if (!aEndpoint.IsValid()) {
return IPC_FAIL(this, "invalid endpoint for MLSTransaction");
}
if (!sMLSTaskQueue) {
nsCOMPtr<nsISerialEventTarget> taskQueue;
MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
"MLSTaskQueue", getter_AddRefs(taskQueue)));
sMLSTaskQueue = taskQueue.forget();
// Clean up the sMLSTaskQueue static when the PBackground thread shuts down.
nsCOMPtr<nsITargetShutdownTask> shutdownTask =
new MLSTaskQueueShutdownTask();
MOZ_ALWAYS_SUCCEEDS(
GetCurrentSerialEventTarget()->RegisterShutdownTask(shutdownTask));
}
// Construct the database's prefix path
nsCOMPtr<nsIFile> file;
nsresult rv =
mozilla::dom::MLSTransactionParent::ConstructDatabasePrefixPath(file);
if (NS_WARN_IF(NS_FAILED(rv))) {
// The enpoint's destructor will close the actor
return IPC_OK();
}
// Dispatch the task to the MLS task queue
sMLSTaskQueue->Dispatch(NS_NewRunnableFunction(
"CreateMLSTransactionRunnable",
[endpoint = std::move(aEndpoint), file,
principal = RefPtr{aPrincipal.get()}]() mutable {
// Create the mls directory if it doesn't exist
nsresult rv =
mozilla::dom::MLSTransactionParent::CreateDirectoryIfNotExists(
file);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
// Construct the database's full path
nsAutoCString databasePath;
rv = mozilla::dom::MLSTransactionParent::ConstructDatabaseFullPath(
file, principal, databasePath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
// Create the MLS transaction parent and bind it to the endpoint
RefPtr<PMLSTransactionParent> result =
new mozilla::dom::MLSTransactionParent(databasePath);
endpoint.Bind(result);
}));
return IPC_OK();
}
already_AddRefed<mozilla::dom::PClientManagerParent>
BackgroundParentImpl::AllocPClientManagerParent() {
return mozilla::dom::AllocClientManagerParent();

View File

@@ -296,10 +296,6 @@ class BackgroundParentImpl : public PBackgroundParent {
mozilla::ipc::IPCResult RecvHasMIDIDevice(
HasMIDIDeviceResolver&& aResolver) override;
mozilla::ipc::IPCResult RecvCreateMLSTransaction(
Endpoint<PMLSTransactionParent>&& aEndpoint,
NotNull<nsIPrincipal*> aPrincipal) override;
mozilla::ipc::IPCResult RecvStorageActivity(
const PrincipalInfo& aPrincipalInfo) override;

View File

@@ -37,7 +37,6 @@ include protocol PCameras;
include protocol PLockManager;
include protocol PMIDIManager;
include protocol PMIDIPort;
include protocol PMLSTransaction;
include protocol PQuota;
include protocol PServiceWorker;
include protocol PServiceWorkerContainer;
@@ -265,8 +264,6 @@ parent:
MIDIPortInfo portInfo, bool sysexEnabled);
async HasMIDIDevice() returns (bool hasDevice);
async CreateMLSTransaction(Endpoint<PMLSTransactionParent> aEndpoint, nsIPrincipal aPrincipal);
// This method is used to propagate storage activities from the child actor
// to the parent actor. See StorageActivityService.
async StorageActivity(PrincipalInfo principalInfo);

View File

@@ -16271,12 +16271,6 @@
value: true
mirror: always
# MLS
- name: security.mls.enabled
type: RelaxedAtomicBool
value: false
mirror: always
#---------------------------------------------------------------------------
# Prefs starting with "signon."
#---------------------------------------------------------------------------

View File

@@ -1,20 +0,0 @@
[package]
name = "mls_gk"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0 OR MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nserror = { path = "../../../xpcom/rust/nserror" }
nsstring = { path = "../../../xpcom/rust/nsstring" }
xpcom = { path = "../../../xpcom/rust/xpcom" }
static_prefs = { path = "../../../modules/libpref/init/static_prefs" }
mls-platform-api = { git = "https://github.com/beurdouche/mls-platform-api", rev="7fb935bb93fdcc80f7f5e76d516c85a540024b53", features = ["gecko"] }
nss-gk-api = { git = "https://github.com/beurdouche/nss-gk-api", rev = "82e780f47026b84a0e0a06bff17fa95661d129a3", default-features = false }
thin-vec = { version = "^0.2.12", features = ["gecko-ffi"] }
hex = "^0.4.3"
rusqlite = "^0.31.0"
log = "^0.4.20"

View File

@@ -1,43 +0,0 @@
header = """/* 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/. */"""
autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
"""
include_guard = "mozilla_dom_MlsGkFFI_h"
include_version = true
braces = "SameLine"
line_length = 100
tab_width = 2
language = "C++"
includes = ["nsStringFwd.h", "nsTArrayForwardDeclare.h"]
# Put FFI calls in the `mozilla::security::mls` namespace.
namespaces = ["mozilla", "security", "mls"]
[enum]
derive_const_casts = true
derive_tagged_enum_destructor = true
cast_assert_name = "MOZ_DIAGNOSTIC_ASSERT"
[export.body]
"GkReceived" = """
GkReceived() : tag(Tag::None) {}
GkReceived(GkReceived&& other) : tag(other.tag) {
switch (tag) {
case Tag::ApplicationMessage:
new (&application_message) ApplicationMessage_Body(std::move(other.application_message));
break;
case Tag::GroupIdEpoch:
new (&group_id_epoch) GroupIdEpoch_Body(std::move(other.group_id_epoch));
break;
case Tag::CommitOutput:
new (&commit_output) CommitOutput_Body(std::move(other.commit_output));
break;
case Tag::None:
break;
}
}
"""
# Export `ThinVec` references as `nsTArray`.
[export.rename]
"ThinVec" = "nsTArray"

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +0,0 @@
/* 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/. */
use nsstring::nsACString;
use std::fs;
use std::io;
pub fn get_storage_path(storage_prefix: &nsACString) -> String {
format!("{storage_prefix}.sqlite.enc")
}
pub fn get_key_path(storage_prefix: &nsACString) -> String {
format!("{storage_prefix}.key")
}
fn read_existing_storage_key(key_path: &str) -> io::Result<[u8; 32]> {
let key_hex = fs::read_to_string(key_path)?;
let bytes = hex::decode(&key_hex).map_err(|e| io::Error::other(e))?;
bytes[..].try_into().map_err(|e| io::Error::other(e))
}
pub fn get_storage_key(storage_prefix: &nsACString) -> io::Result<[u8; 32]> {
// Get the key path
let key_path = get_key_path(storage_prefix);
// Try to read the existing key
if let Ok(key) = read_existing_storage_key(&key_path) {
return Ok(key);
}
// We failed to read the key, so it must either not exist, or is invalid.
// Generate a new one.
nss_gk_api::init();
let key: [u8; 32] = nss_gk_api::p11::random(32)[..]
.try_into()
.expect("nss returned the wrong number of bytes");
// Write the key to the file
std::fs::write(key_path, &hex::encode(&key))?;
// Return the key
Ok(key)
}

View File

@@ -1,17 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
with Files("**"):
BUG_COMPONENT = ("Core", "Security: MLS")
if CONFIG["COMPILE_ENVIRONMENT"]:
CbindgenHeader("mls_gk_ffi_generated.h", inputs=["/security/mls/mls_gk"])
EXPORTS.mozilla.security.mls += [
"!mls_gk_ffi_generated.h",
]
FINAL_LIBRARY = "xul"

View File

@@ -7,10 +7,6 @@
with Files("**"):
BUG_COMPONENT = ("Core", "Security: PSM")
DIRS += [
"/security/mls",
]
with Files("generate*.py"):
BUG_COMPONENT = ("Firefox Build System", "General")

View File

@@ -1679,11 +1679,6 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
delta = "2.3.2 -> 2.3.3"
[[audits.debug_tree]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.4.0"
[[audits.debugid]]
who = "Gabriele Svelto <gsvelto@mozilla.com>"
criteria = "safe-to-deploy"
@@ -3036,11 +3031,6 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
delta = "0.1.9 -> 0.1.10"
[[audits.maybe-async]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.2.10"
[[audits.md-5]]
who = "Dana Keeler <dkeeler@mozilla.com>"
criteria = "safe-to-deploy"
@@ -3228,95 +3218,6 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
delta = "0.8.8 -> 1.0.1"
[[audits.mls-rs]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.39.1"
[[audits.mls-rs]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.39.1 -> 0.39.1@git:96eb66e158c86171c70ff8147c0e5f020e54f3d1"
importable = false
[[audits.mls-rs-codec]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.5.3"
[[audits.mls-rs-codec]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.5.3 -> 0.5.3@git:96eb66e158c86171c70ff8147c0e5f020e54f3d1"
importable = false
[[audits.mls-rs-codec-derive]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.1.1"
notes = "No unsafe code"
[[audits.mls-rs-codec-derive]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.1.1 -> 0.1.1@git:96eb66e158c86171c70ff8147c0e5f020e54f3d1"
importable = false
[[audits.mls-rs-core]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.18.0"
[[audits.mls-rs-core]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.18.0 -> 0.18.0@git:96eb66e158c86171c70ff8147c0e5f020e54f3d1"
importable = false
[[audits.mls-rs-crypto-hpke]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.9.0"
[[audits.mls-rs-crypto-hpke]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.9.0 -> 0.9.0@git:96eb66e158c86171c70ff8147c0e5f020e54f3d1"
importable = false
[[audits.mls-rs-crypto-traits]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.10.0"
[[audits.mls-rs-crypto-traits]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.10.0 -> 0.10.0@git:96eb66e158c86171c70ff8147c0e5f020e54f3d1"
importable = false
[[audits.mls-rs-identity-x509]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.11.0"
[[audits.mls-rs-identity-x509]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.11.0 -> 0.11.0@git:96eb66e158c86171c70ff8147c0e5f020e54f3d1"
importable = false
[[audits.mls-rs-provider-sqlite]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "0.11.0"
[[audits.mls-rs-provider-sqlite]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.11.0 -> 0.11.0@git:96eb66e158c86171c70ff8147c0e5f020e54f3d1"
importable = false
[[audits.moz_cbor]]
who = "Bobby Holley <bobbyholley@gmail.com>"
criteria = "safe-to-deploy"
@@ -3457,12 +3358,6 @@ criteria = "safe-to-deploy"
version = "0.2.1"
notes = "Maintained by the CryptoEng team at Mozilla."
[[audits.nss-gk-api]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.3.0 -> 0.3.0@git:82e780f47026b84a0e0a06bff17fa95661d129a3"
importable = false
[[audits.ntapi]]
who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
@@ -5829,20 +5724,6 @@ who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.1.3"
[[audits.zeroize]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "1.8.1"
notes = """
This code DOES contain unsafe code required to internally call volatiles
for deleting data. This is expected and documented behavior.
"""
[[audits.zeroize_derive]]
who = "Benjamin Beurdouche <beurdouche@mozilla.com>"
criteria = "safe-to-deploy"
version = "1.4.2"
[[audits.zerovec]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"

View File

@@ -107,38 +107,6 @@ notes = "This is a first-party crate which is also published to crates.io. We ce
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, presumably to get a fix that hadn't been released yet. We should consider switching to the latest official release."
[policy.mls-rs]
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, pending update of the crate."
[policy.mls-rs-codec]
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, pending update of the crate."
[policy.mls-rs-codec-derive]
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, pending update of the crate."
[policy.mls-rs-core]
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, pending update of the crate."
[policy.mls-rs-crypto-hpke]
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, pending update of the crate."
[policy.mls-rs-crypto-traits]
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, pending update of the crate."
[policy.mls-rs-identity-x509]
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, pending update of the crate."
[policy.mls-rs-provider-sqlite]
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, pending update of the crate."
[policy.mozbuild]
audit-as-crates-io = false
notes = "The crates.io version of this is just a placeholder to allow public crates to depend on mozbuild."
@@ -178,10 +146,6 @@ audit-as-crates-io = false
audit-as-crates-io = true
notes = "Part of the wgpu repository, pinned as the rest of wgpu crates."
[policy.nss-gk-api]
audit-as-crates-io = true
notes = "This is a pinned version of the upstream code, pending update of the crate."
[policy.peek-poke]
audit-as-crates-io = false

View File

@@ -1 +0,0 @@
{"files":{"Cargo.lock":"5b4889dcaf005a28bd9848285763cda32c56c79f962572a7c9fdceee055752d7","Cargo.toml":"e95e92797cc7fdbdabef380743d3059e2dbb1f75986fd85937734431f5dfe49c","README.md":"46f7f4ae53e01156d7cdcd001c0b015ba506ae214520515686e6291e8c75b56e","doc/build/LICENSE.adoc":"56cd47a25f2bbb4f2f870c933aa04c47a2b68dea497f974c5c9c215583dec3dc","doc/build/asciidoc-coalescer.rb":"d3b8c4a9d02862b31e4fae1d3eba88104d24eae2e96e16cce50830931db49564","doc/readme_template.adoc":"d4fc1343d2a4caa1e2a76962c94d382c7cb6b38fcd82a34794670a593bfe78e2","examples/fibonacci.rs":"3c7b804e9ba14d3fe16f34240f5aae81c3b15a56db245c3d22ea0940fff3f9e1","examples/multi_line.rs":"21819b40021d852501e42aa4903131a257774d4972c28c4370967869e3f4bed8","examples/multiple_trees.rs":"0acbdfbf8a786de05a2272e24b0b54f0f2b8b37b245e8d032e92014fbe6d1e53","examples/nested.rs":"731e5d6993aac90a580050b871bb0b2399c9ae85d48efbf101c19118ae51223e","examples/no_macros.rs":"e9a92b2c444b67804e0949150dcb84b084efe1c2d74201a01f2f8a7d9419f93d","examples/out/fibonacci.txt":"75f837935a3f905010f9d8d7c825c9d8a444cb0930ad0c16f809abeee12ef782","examples/out/multi_line.txt":"a435cc9100fb9b2fed67e64a0aa337beebe818846de91c0c0c6e46a12e3122c7","examples/out/multiple_trees_A.txt":"0b9e72ec56efdd95052ffc1429ba14e548d72254abee89d737c38707c93152a3","examples/out/multiple_trees_B.txt":"380271246c3b7b7261701d5c845990d90f9bc5317bdb07ed03a395c47c09cbcf","examples/out/nested.txt":"089ade90dd6ffd2d3b86e8a60ed3be98895ccf4e2f22cdf069c5391c3161fc4f","examples/out/no_macros.txt":"3f9781067a454a462873683bb148a1b776cd8b7be30b10b25910fb734987f14e","examples/out/panic.txt":"0a95e6dcbb3be0721a21e3762d7e6fe21458ff773c0c9d141c92b63f9f03851a","examples/panic.rs":"3e8decf97d800539b6b2e7cbd681b114ed8a757d74841f717a7c3786f6d60c3e","src/default.rs":"1ef1cad3332cdda1f4c5ae09208c3b7d19244db186f671c796beda38135d268a","src/defer.rs":"621ca6e25f054765b995108916cb607b064e852cceaf2f21c20ef50fd2a5386c","src/internal.rs":"11756608f9968f649a9a2b8ed435126711a530b500d47c6a59fa0a7557ecb028","src/lib.rs":"3c20a1971517a8fb397bd9af54aee2caea7aaf3d9c973272a7b5238a93a2c495","src/scoped_branch.rs":"ff693738aff85ea925e3613f52787dbb6bb0add753ea8792207728ea403b2e1e","src/test.rs":"acc5cbb59203a62727c6e788e656f48df24ab744ccff98f6f0a0db03ac1ecdb1","src/tree_config.rs":"4d0f77f5e81f5c74996d6d8c44019ade6d5e3a1ff88c1be466af2f0d675ca0b3"},"package":"2d1ec383f2d844902d3c34e4253ba11ae48513cdaddc565cf1a6518db09a8e57"}

View File

@@ -1,212 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "bytes"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
[[package]]
name = "debug_tree"
version = "0.4.0"
dependencies = [
"futures",
"once_cell",
"tokio",
]
[[package]]
name = "futures"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
[[package]]
name = "futures-executor"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6"
[[package]]
name = "futures-macro"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6"
[[package]]
name = "futures-task"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
[[package]]
name = "futures-util"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab",
]
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "once_cell"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b"
[[package]]
name = "pin-project-lite"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
[[package]]
name = "pin-utils"
version = "0.1.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
[[package]]
name = "proc-macro-hack"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f918f2b601f93baa836c1c2945faef682ba5b6d4828ecb45eeb7cc3c71b811b4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro-nested"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
[[package]]
name = "proc-macro2"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "syn"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "tokio"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616"
dependencies = [
"bytes",
"memchr",
"pin-project-lite",
"tokio-macros",
]
[[package]]
name = "tokio-macros"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"

View File

@@ -1,33 +0,0 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
[package]
edition = "2018"
name = "debug_tree"
version = "0.4.0"
authors = ["Marty Papamanolis <marty@mindpipess.com>"]
description = "Build a tree one element at a time and output it as a pretty string."
readme = "README.md"
license = "MIT"
repository = "https://github.com/martypapa/debug-tree"
[lib]
name = "debug_tree"
crate-type = ["lib"]
[dependencies.once_cell]
version = "1"
[dev-dependencies.futures]
version = "0.3.4"
[dev-dependencies.tokio]
version = "0.2.9"
features = ["macros", "fs"]

View File

@@ -1,284 +0,0 @@
# Debug Tree
This library allows you to build a tree one element at a time and output it as a pretty string.
The tree can easily be output to a `String`, `stdout` or a file.
This is particularly convenient for generating clean output from nested and recursive functions.
* [Recursive Fibonacci Example](#recursive-fibonacci-example)
* [Overview](#overview)
* [More Examples](#more-examples)
* [Multiple Tagged Trees](#multiple-tagged-trees)
* [Nested Functions](#nested-functions)
* [Panic](#panics)
* [Without Macros](#without-macros)
## Recursive Fibonacci Example
Using the `add_branch!()` macro at the start of the `factors()` function, you can generate an entire call tree, with minimal effort.
<!--{ fibonacci.rs | code: rust }-->
```rust
use debug_tree::*;
fn factors(x: usize) {
add_branch!("{}", x); // <~ THE MAGIC LINE
for i in 1..x {
if x % i == 0 {
factors(i);
}
}
}
fn main() {
// output to file at the end of this block
defer_write!("examples/out/fibonacci.txt");
add_branch!("A Fibonacci Tree");
factors(6);
add_leaf!("That's All Folks!");
}
```
<!--{ end }-->
<!--{ out/fibonacci.txt | code }-->
```
A Fibonacci Tree
├╼ 6
│ ├╼ 1
│ ├╼ 2
│ │ └╼ 1
│ └╼ 3
│ └╼ 1
└╼ That's All Folks!
```
<!--{ end }-->
## Overview
- Add a branch
- `add_branch!("Hello, {}", "World")`
- The branch will exit at the end of the current block
- Add a leaf
- `add_leaf!("I am a {}", "leaf")`
- Added to the current scoped branch
- Print a tree, or write it to file at the end of a block
- `defer_print!()`
- `defer_write!("filename.txt")`
- The tree will be empty after these calls
- To prevent clearing, use `defer_peek_print!` and `defer_peek_write!`
- Handle multiple trees using named trees
- `add_branch_to!("A", "I'm a branch on tree 'A'")`
- `add_leaf_to!("A", "I'm a leaf on tree 'A'")`
- `defer_print!("A")`
- `defer_write!("A", "filename.txt")`
- Get a named tree
- `tree("TREE_NAME")`
- Retrieve the pretty-string from a tree
- `tree("TREE_NAME").string()`
- Usage across threads
- `default_tree()` is local to each thread
- Named trees are shared between threads
## More Examples
### Multiple Tagged Trees
If you need multiple, separated trees you can use a name tag.
<!--{ multiple_trees.rs | code: rust }-->
```rust
use debug_tree::*;
fn populate(tree_name: &str, n_children: usize) {
add_branch_to!(tree_name, "{} TREE", tree_name);
for _ in 0..n_children {
populate(tree_name, n_children / 2);
}
}
fn main() {
// Override tree config (just for "B")
let b_tree = tree("B");
b_tree.set_config_override(
TreeConfig::new()
.indent(4)
.symbols(TreeSymbols::with_rounded().leaf("> ")),
);
defer_write!(b_tree, "examples/out/multiple_trees_B.txt");
defer_write!("A", "examples/out/multiple_trees_A.txt");
populate("A", 2);
populate("B", 3);
}
```
<!--{ end }-->
<!--{ out/multiple_trees_A.txt | code }-->
```
A TREE
├╼ A TREE
│ └╼ A TREE
└╼ A TREE
└╼ A TREE
```
<!--{ end }-->
<!--{ out/multiple_trees_B.txt | code }-->
```
B TREE
├──> B TREE
│ ╰──> B TREE
├──> B TREE
│ ╰──> B TREE
╰──> B TREE
╰──> B TREE
```
<!--{ end }-->
### Nested Functions
Branches also make nested function calls a lot easier to follow.
<!--{ nested.rs | code: rust }-->
```rust
use debug_tree::*;
fn a() {
add_branch!("a");
b();
c();
}
fn b() {
add_branch!("b");
c();
}
fn c() {
add_branch!("c");
add_leaf!("Nothing to see here");
}
fn main() {
defer_write!("examples/out/nested.txt");
a();
}
```
<!--{ end }-->
<!--{ out/nested.txt | code }-->
```
a
├╼ b
│ └╼ c
│ └╼ Nothing to see here
└╼ c
└╼ Nothing to see here
```
<!--{ end }-->
### Line Breaks
Newlines in multi-line strings are automatically indented.
<!--{ multi_line.rs | code: rust }-->
```rust
use debug_tree::*;
fn main() {
// output to file at the end of this block
defer_write!("examples/out/multi_line.txt");
add_branch!("1");
add_leaf!("1.1\nAnother line...\n... and one more line");
add_leaf!("1.2");
}
```
<!--{ end }-->
<!--{ out/multi_line.txt | code }-->
```
1
├╼ 1.1
│ Another line...
│ ... and one more line
└╼ 1.2
```
<!--{ end }-->
### Panics
Even if there is a panic, the tree is not lost!
The `defer_` functions were introduced to allow the tree
to be printed our written to file in the case of a `panic!` or early return.
<!--{ panic.rs | code: rust }-->
```rust
use debug_tree::*;
fn i_will_panic() {
add_branch!("Here are my last words");
add_leaf!("Stay calm, and try not to panic");
panic!("I told you so...")
}
fn main() {
// output to file at the end of this block
defer_write!("examples/out/panic.txt");
// print at the end of this block
{
add_branch!("By using the 'defer_' functions");
add_branch!("Output will still be generated");
add_branch!("Otherwise you might lose your valuable tree!");
}
add_branch!("Now for something crazy...");
i_will_panic();
}
```
<!--{ end }-->
<!--{ out/panic.txt | code }-->
```
By using the 'defer_' functions
└╼ Output will still be generated
└╼ Otherwise you might lose your valuable tree!
Now for something crazy...
└╼ Here are my last words
└╼ Stay calm, and try not to panic
```
<!--{ end }-->
### Without Macros
If you prefer not using macros, you can construct `TreeBuilder`s manually.
<!--{ no_macros.rs | code: rust }-->
```rust
use debug_tree::TreeBuilder;
fn main() {
// Make a new tree.
let tree = TreeBuilder::new();
// Add a scoped branch. The next item added will belong to the branch.
let mut branch = tree.add_branch("1 Branch");
// Add a leaf to the current branch
tree.add_leaf("1.1 Child");
// Leave scope early
branch.release();
tree.add_leaf("2 Sibling");
// output to file
tree.write("examples/out/no_macros.txt").ok(); // Write and flush.
}
```
<!--{ end }-->
<!--{ out/no_macros.txt | code }-->
```
1 Branch
└╼ 1.1 Child
2 Sibling
```
<!--{ end }-->

View File

@@ -1,19 +0,0 @@
Copyright (C) 2014-2019 The Asciidoctor Project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,66 +0,0 @@
#!/usr/bin/env ruby
# This script coalesces the AsciiDoc content from a document master into a
# single output file. It does so by resolving all preprocessor directives in
# the document, and in any files which are included. The resolving of include
# directives is likely of most interest to users of this script.
#
# This script works by using Asciidoctor's PreprocessorReader to read and
# resolve all the lines in the specified input file. The script then writes the
# result to the output.
#
# The script only recognizes attributes passed in as options or those defined
# in the document header. It does not currently process attributes defined in
# other, arbitrary locations within the document.
#
# You can find a similar extension written against AsciidoctorJ here:
# https://github.com/hibernate/hibernate-asciidoctor-extensions/blob/master/src/main/java/org/hibernate/infra/asciidoctor/extensions/savepreprocessed/SavePreprocessedOutputPreprocessor.java
# TODO
# - add cli option to write attributes passed to cli to header of document
# - escape all preprocessor directives after lines are processed (these are preprocessor directives that were escaped in the input)
# - wrap in a custom converter so it can be used as an extension
require 'asciidoctor'
require 'optparse'
options = { attributes: [], output: '-' }
OptionParser.new do |opts|
opts.banner = 'Usage: ruby asciidoc-coalescer.rb [OPTIONS] FILE'
opts.on('-a', '--attribute key[=value]', 'A document attribute to set in the form of key[=value]') do |a|
options[:attributes] << a
end
opts.on('-o', '--output FILE', 'Write output to FILE instead of stdout.') do |o|
options[:output] = o
end
end.parse!
unless (source_file = ARGV.shift)
warn 'Please specify an AsciiDoc source file to coalesce.'
exit 1
end
unless (output_file = options[:output]) == '-'
if (output_file = File.expand_path output_file) == (File.expand_path source_file)
warn 'Source and output cannot be the same file.'
exit 1
end
end
# NOTE first, resolve attributes defined at the end of the document header
# QUESTION can we do this in a single load?
doc = Asciidoctor.load_file source_file, safe: :unsafe, header_only: true, attributes: options[:attributes]
# NOTE quick and dirty way to get the attributes set or unset by the document header
header_attr_names = (doc.instance_variable_get :@attributes_modified).to_a
header_attr_names.each {|k| doc.attributes[%(#{k}!)] = '' unless doc.attr? k }
doc = Asciidoctor.load_file source_file, safe: :unsafe, parse: false, attributes: doc.attributes
# FIXME also escape ifdef, ifndef, ifeval and endif directives
# FIXME do this more carefully by reading line by line; if input differs by output by leading backslash, restore original line
lines = doc.reader.read.gsub(/^include::(?=.*\[\]$)/m, '\\include::')
if output_file == '-'
puts lines
else
File.open(output_file, 'w') {|f| f.write lines }
end

View File

@@ -1,127 +0,0 @@
:examples: ../examples/
= Debug Tree
This library allows you to build a tree one element at a time and output it as a pretty string.
The tree can easily be output to a `String`, `stdout` or a file.
This is particularly convenient for generating clean output from nested and recursive functions.
:toc:
== Recursive Fibonacci Example
Using the `add_branch!()` macro at the start of the `factors()` function, you can generate an entire call tree, with minimal effort.
[source,rust]
----
include::{examples}fibonacci.rs[]
----
----
include::{examples}out/fibonacci.txt[]
----
== Overview
* Add a branch
- `add_branch!("Hello, {}", "World")`
- The branch will exit at the end of the current block
* Add a leaf
- `add_leaf!("I am a {}", "leaf")`
- Added to the current scoped branch
* Print a tree, or write it to file at the end of a block
- `defer_print!()`
- `defer_write!("filename.txt")`
- The tree will be empty after these calls
- To prevent clearing, use `defer_peek_print!` and `defer_peek_write!`
* Get the trees pretty-string
-
* Handle multiple trees using named trees
- `add_branch_to!("A", "I'm a branch on tree 'A'")`
- `add_leaf_to!("A", "I'm a leaf on tree 'A'")`
- `defer_print!("A")`
- `defer_write!("A", "filename.txt")`
* Get a named tree
- `tree("TREE_NAME")`
* Retrieve the pretty-string from a tree
- `tree("TREE_NAME").string()`
* Usage across threads
- `default_tree()` is local to each thread
- Named trees are shared between threads
== More Examples
=== Multiple Tagged Trees
If you need multiple, separated trees you can use a name tag.
[source,rust]
----
include::{examples}multiple_trees.rs[]
----
----
include::{examples}out/multiple_trees_A.txt[]
----
----
include::{examples}out/multiple_trees_B.txt[]
----
=== Nested Functions
Branches also make nested function calls a lot easier to follow.
[source,rust]
----
include::{examples}nested.rs[]
----
----
include::{examples}out/nested.txt[]
----
=== Line Breaks
Newlines in multi-line strings are automatically indented.
[source,rust]
----
include::{examples}multi_line.rs[]
----
----
include::{examples}out/multi_line.txt[]
----
=== Panics
Even if there is a panic, the tree is not lost!
The `defer_` functions were introduced to allow the tree
to be printed our written to file in the case of a `panic!` or early return.
[source,rust]
----
include::{examples}panic.rs[]
----
----
include::{examples}out/panic.txt[]
----
=== Without Macros
If you prefer not using macros, you can construct `TreeBuilder`s manually.
[source,rust]
----
include::{examples}no_macros.rs[]
----
----
include::{examples}out/no_macros.txt[]
----

View File

@@ -1,18 +0,0 @@
use debug_tree::*;
fn factors(x: usize) {
add_branch!("{}", x); // <~ THE MAGIC LINE
for i in 1..x {
if x % i == 0 {
factors(i);
}
}
}
fn main() {
// output to file at the end of this block
defer_write!("examples/out/fibonacci.txt");
add_branch!("A Fibonacci Tree");
factors(6);
add_leaf!("That's All Folks!");
}

View File

@@ -1,8 +0,0 @@
use debug_tree::*;
fn main() {
// output to file at the end of this block
defer_write!("examples/out/multi_line.txt");
add_branch!("1");
add_leaf!("1.1\nAnother line...\n... and one more line");
add_leaf!("1.2");
}

View File

@@ -1,22 +0,0 @@
use debug_tree::*;
fn populate(tree_name: &str, n_children: usize) {
add_branch_to!(tree_name, "{} TREE", tree_name);
for _ in 0..n_children {
populate(tree_name, n_children / 2);
}
}
fn main() {
// Override tree config (just for "B")
let b_tree = tree("B");
b_tree.set_config_override(
TreeConfig::new()
.indent(4)
.symbols(TreeSymbols::with_rounded().leaf("> ")),
);
defer_write!(b_tree, "examples/out/multiple_trees_B.txt");
defer_write!("A", "examples/out/multiple_trees_A.txt");
populate("A", 2);
populate("B", 3);
}

View File

@@ -1,19 +0,0 @@
use debug_tree::*;
fn a() {
add_branch!("a");
b();
c();
}
fn b() {
add_branch!("b");
c();
}
fn c() {
add_branch!("c");
add_leaf!("Nothing to see here");
}
fn main() {
defer_write!("examples/out/nested.txt");
a();
}

View File

@@ -1,18 +0,0 @@
use debug_tree::TreeBuilder;
fn main() {
// Make a new tree.
let tree = TreeBuilder::new();
// Add a scoped branch. The next item added will belong to the branch.
let mut branch = tree.add_branch("1 Branch");
// Add a leaf to the current branch
tree.add_leaf("1.1 Child");
// Leave scope early
branch.release();
tree.add_leaf("2 Sibling");
// output to file
tree.write("examples/out/no_macros.txt").ok(); // Write and flush.
}

View File

@@ -1,8 +0,0 @@
A Fibonacci Tree
├╼ 6
│ ├╼ 1
│ ├╼ 2
│ │ └╼ 1
│ └╼ 3
│ └╼ 1
└╼ That's All Folks!

View File

@@ -1,5 +0,0 @@
1
├╼ 1.1
│ Another line...
│ ... and one more line
└╼ 1.2

View File

@@ -1,5 +0,0 @@
A TREE
├╼ A TREE
│ └╼ A TREE
└╼ A TREE
└╼ A TREE

View File

@@ -1,7 +0,0 @@
B TREE
├──> B TREE
│ ╰──> B TREE
├──> B TREE
│ ╰──> B TREE
╰──> B TREE
╰──> B TREE

View File

@@ -1,6 +0,0 @@
a
├╼ b
│ └╼ c
│ └╼ Nothing to see here
└╼ c
└╼ Nothing to see here

View File

@@ -1,3 +0,0 @@
1 Branch
└╼ 1.1 Child
2 Sibling

View File

@@ -1,6 +0,0 @@
By using the 'defer_' functions
└╼ Output will still be generated
└╼ Otherwise you might lose your valuable tree!
Now for something crazy...
└╼ Here are my last words
└╼ Stay calm, and try not to panic

View File

@@ -1,20 +0,0 @@
use debug_tree::*;
fn i_will_panic() {
add_branch!("Here are my last words");
add_leaf!("Stay calm, and try not to panic");
panic!("I told you so...")
}
fn main() {
// output to file at the end of this block
defer_write!("examples/out/panic.txt");
// print at the end of this block
{
add_branch!("By using the 'defer_' functions");
add_branch!("Output will still be generated");
add_branch!("Otherwise you might lose your valuable tree!");
}
add_branch!("Now for something crazy...");
i_will_panic();
}

View File

@@ -1,167 +0,0 @@
use crate::TreeBuilder;
/// Returns the default tree for the current thread
///
/// # Example
///
/// ```
/// use debug_tree::default_tree;
/// default_tree().add_leaf("A new leaf");
/// assert_eq!("A new leaf", default_tree().peek_string());
/// ```
pub fn default_tree() -> TreeBuilder {
thread_local! {
static DEFAULT_BUILDER: TreeBuilder = TreeBuilder::new();
}
DEFAULT_BUILDER.with(|f| f.clone())
}
/// Adds a leaf to the default tree with the given text and formatting arguments
///
/// # Arguments
/// * `text...` - Formatted text arguments, as per `format!(...)`.
///
/// # Example
///
/// ```
/// #[macro_use]
/// use debug_tree::{default_tree, add_leaf};
/// fn main() {
/// add_leaf!("A {} leaf", "new");
/// assert_eq!("A new leaf", &default_tree().peek_string());
/// }
/// ```
#[macro_export]
macro_rules! add_leaf {
($($arg:tt)*) => {
if $crate::default::default_tree().is_enabled() {
$crate::default::default_tree().add_leaf(&format!($($arg)*))
}
};
}
/// Adds the value as a leaf to the default tree.
///
/// Returns the given `value` argument.
///
/// # Arguments
/// * `value` - An expression that implements the `Display` trait.
///
/// # Example
///
/// ```
/// #[macro_use]
/// use debug_tree::{default_tree, add_leaf_value};
/// fn main() {
/// let value = add_leaf_value!(10);
/// assert_eq!("10", &default_tree().string());
/// assert_eq!(10, value);
/// }
/// ```
#[macro_export]
macro_rules! add_leaf_value {
($value:expr) => {{
let v = $value;
if $crate::default::default_tree().is_enabled() {
$crate::default::default_tree().add_leaf(&format!("{}", &v));
}
v
}};
}
/// Adds a scoped branch to the default tree with the given text and formatting arguments
/// The branch will be exited at the end of the current block.
///
/// # Arguments
/// * `text...` - Formatted text arguments, as per `format!(...)`.
///
/// # Example
///
/// ```
/// #[macro_use]
/// use debug_tree::{default_tree, add_branch, add_leaf};
/// fn main() {
/// {
/// add_branch!("New {}", "Branch"); // _branch enters scope
/// // tree is now pointed inside new branch.
/// add_leaf!("Child of {}", "Branch");
/// // Block ends, so tree exits the current branch.
/// }
/// add_leaf!("Sibling of {}", "Branch");
/// assert_eq!("\
/// New Branch
/// └╼ Child of Branch
/// Sibling of Branch" , &default_tree().string());
/// }
/// ```
#[macro_export]
macro_rules! add_branch {
() => {
let _debug_tree_branch = if $crate::default::default_tree().is_enabled() {
$crate::default::default_tree().enter_scoped()
} else {
$crate::scoped_branch::ScopedBranch::none()
};
};
($($arg:tt)*) => {
let _debug_tree_branch = if $crate::default::default_tree().is_enabled() {
$crate::default::default_tree().add_branch(&format!($($arg)*))
} else {
$crate::scoped_branch::ScopedBranch::none()
};
};
}
#[cfg(test)]
mod test {
use crate::default_tree;
use crate::*;
#[test]
fn unnamed_branch() {
add_leaf!("1");
add_branch!();
add_leaf!("1.1");
{
add_branch!();
add_leaf!("1.1.1");
}
add_leaf!("1.2");
default_tree().peek_print();
assert_eq!(
"\
1
├╼ 1.1
│ └╼ 1.1.1
└╼ 1.2",
default_tree().string()
);
}
#[test]
fn named_branch() {
add_branch!("11");
{
add_branch!("11.1");
add_leaf!("11.1.1");
}
add_leaf!("11.2");
default_tree().peek_print();
assert_eq!(
"\
11
├╼ 11.1
│ └╼ 11.1.1
└╼ 11.2",
default_tree().string()
);
}
#[test]
fn leaf_with_value() {
let value = add_leaf_value!(10);
default_tree().peek_print();
assert_eq!("10", default_tree().string());
assert_eq!(10, value);
}
}

View File

@@ -1,46 +0,0 @@
use crate::TreeBuilder;
/// A deferred function called with an argument, `TreeBuilder`
pub struct DeferredFn<F: Fn(TreeBuilder) -> ()> {
tree: Option<TreeBuilder>,
action: Option<F>,
}
impl<F> DeferredFn<F>
where
F: Fn(TreeBuilder) -> (),
{
/// Create a new deferred function based on `tree`
pub fn new(tree: TreeBuilder, action: F) -> Self {
DeferredFn {
tree: Some(tree),
action: Some(action),
}
}
/// Create an empty deferred function
/// This does nothing when scope ends
pub fn none() -> Self {
DeferredFn {
tree: None,
action: None,
}
}
/// Disables the deferred function
/// This prevents the function from executing when the scope ends
pub fn cancel(&mut self) {
self.tree = None;
self.action = None;
}
}
impl<F> Drop for DeferredFn<F>
where
F: Fn(TreeBuilder) -> (),
{
fn drop(&mut self) {
if let (Some(x), Some(action)) = (&self.tree, &self.action) {
action(x.clone());
}
}
}

View File

@@ -1,281 +0,0 @@
use crate::tree_config::{tree_config, TreeConfig};
use std::cmp::max;
use std::sync::{Arc, Mutex};
/// Tree that holds `text` for the current leaf and a list of `children` that are the branches.
#[derive(Debug)]
pub struct Tree {
pub text: Option<String>,
pub children: Vec<Tree>,
}
/// Position of the element relative to its siblings
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Position {
Inside,
First,
Last,
Only,
}
impl Tree {
/// Create a new tree with some optional text.
pub fn new(text: Option<&str>) -> Tree {
Tree {
text: text.map(|x| x.to_string()),
children: Vec::new(),
}
}
/// Navigate to the branch at the given `path` relative to this tree.
/// If a valid branch is found by following the path, it is returned.
pub fn at_mut(&mut self, path: &[usize]) -> Option<&mut Tree> {
match path.first() {
Some(&i) => match self.children.get_mut(i) {
Some(x) => x.at_mut(&path[1..]),
_ => None,
},
_ => Some(self),
}
}
/// "Render" this tree as a list of `String`s.
/// Each string represents a line in the tree.
/// `does_continue` is a bool for each column indicating whether the tree continues.
pub fn lines(
&self,
does_continue: &Vec<bool>,
index: usize,
pool_size: usize,
config: &TreeConfig,
) -> Vec<String> {
let does_continue = if config.show_first_level && does_continue.is_empty() {
vec![true]
} else {
does_continue.clone()
};
let position = match index {
_ if pool_size == 1 => Position::Only,
_ if (index + 1) == pool_size => Position::Last,
0 => Position::First,
_ => Position::Inside,
};
let mut next_continue = does_continue.clone();
next_continue.push(match position {
Position::Inside | Position::First => true,
Position::Last | Position::Only => false,
});
let mut txt = String::new();
let pad: String;
if does_continue.len() > 1 {
for &i in &does_continue[2..] {
txt.push_str(&format!(
"{}{:indent$}",
if i { config.symbols.continued } else { " " },
"",
indent = max(config.indent, 1) - 1
));
}
pad = txt.clone();
let branch_size = max(config.indent, 2usize) - 2;
let branch = match config.symbols.branch.len() {
0 => "-".repeat(branch_size),
1 => config.symbols.branch.repeat(branch_size),
_n => config
.symbols
.branch
.repeat(branch_size)
.chars()
.take(branch_size)
.collect::<String>(),
};
let is_multiline = self
.text
.as_ref()
.map(|x| x.contains("\n"))
.unwrap_or(false);
let first_leaf = match (is_multiline, config.symbols.multiline_first) {
(true, Some(x)) => x,
_ => config.symbols.leaf,
};
txt.push_str(&format!(
"{}{}{}",
match position {
Position::Only => config.symbols.join_only,
Position::First => config.symbols.join_first,
Position::Last => config.symbols.join_last,
Position::Inside => config.symbols.join_inner,
},
branch,
first_leaf,
));
let s = match &self.text {
Some(x) => match is_multiline {
true => format!(
"{}",
x.replace(
"\n",
&format!(
"\n{}{}{}{}",
&pad,
match position {
Position::Only | Position::Last =>
" ".repeat(config.symbols.continued.chars().count()),
_ => config.symbols.continued.to_string(),
},
" ".repeat(branch_size),
match &config.symbols.multiline_continued {
Some(multi) => multi.to_string(),
_ => " ".repeat(first_leaf.chars().count()),
}
),
)
),
false => x.clone(),
},
_ => String::new(),
};
txt.push_str(&s);
} else {
if let Some(x) = &self.text {
txt.push_str(&x);
}
}
let mut ret = vec![txt];
for (index, x) in self.children.iter().enumerate() {
for line in x.lines(&next_continue, index, self.children.len(), config) {
ret.push(line);
}
}
ret
}
}
/// Holds the current state of the tree, including the path to the branch.
/// Multiple trees may point to the same data.
#[derive(Debug, Clone)]
pub(crate) struct TreeBuilderBase {
data: Arc<Mutex<Tree>>,
path: Vec<usize>,
dive_count: usize,
config: Option<TreeConfig>,
is_enabled: bool,
}
impl TreeBuilderBase {
/// Create a new state
pub fn new() -> TreeBuilderBase {
TreeBuilderBase {
data: Arc::new(Mutex::new(Tree::new(None))),
path: vec![],
dive_count: 1,
config: None,
is_enabled: true,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.is_enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.is_enabled
}
pub fn add_leaf(&mut self, text: &str) {
let &dive_count = &self.dive_count;
if dive_count > 0 {
for i in 0..dive_count {
let mut n = 0;
if let Some(x) = self.data.lock().unwrap().at_mut(&self.path) {
x.children.push(Tree::new(if i == max(1, dive_count) - 1 {
Some(&text)
} else {
None
}));
n = x.children.len() - 1;
}
self.path.push(n);
}
self.dive_count = 0;
} else {
if let Some(x) = self
.data
.lock()
.unwrap()
.at_mut(&self.path[..max(1, self.path.len()) - 1])
{
x.children.push(Tree::new(Some(&text)));
let n = match self.path.last() {
Some(&x) => x + 1,
_ => 0,
};
self.path.last_mut().map(|x| *x = n);
}
}
}
pub fn set_config_override(&mut self, config: Option<TreeConfig>) {
self.config = config;
}
pub fn config_override(&self) -> &Option<TreeConfig> {
&self.config
}
pub fn config_override_mut(&mut self) -> &mut Option<TreeConfig> {
&mut self.config
}
pub fn enter(&mut self) {
self.dive_count += 1;
}
/// Try stepping up to the parent tree branch.
/// Returns false if already at the top branch.
pub fn exit(&mut self) -> bool {
if self.dive_count > 0 {
self.dive_count -= 1;
true
} else {
if self.path.len() > 1 {
self.path.pop();
true
} else {
false
}
}
}
pub fn depth(&self) -> usize {
max(1, self.path.len() + self.dive_count) - 1
}
pub fn peek_print(&self) {
println!("{}", self.peek_string());
}
pub fn print(&mut self) {
self.peek_print();
self.clear();
}
pub fn clear(&mut self) {
*self = Self::new();
}
pub fn string(&mut self) -> String {
let s = self.peek_string();
self.clear();
s
}
pub fn peek_string(&self) -> String {
let config = self
.config_override()
.clone()
.unwrap_or_else(|| tree_config().clone());
(&self.data.lock().unwrap().lines(&vec![], 0, 1, &config)[1..]).join("\n")
}
}

View File

@@ -1,805 +0,0 @@
use std::sync::{Arc, Mutex};
#[macro_use]
pub mod default;
mod internal;
pub mod scoped_branch;
pub mod defer;
mod test;
pub mod tree_config;
pub use default::default_tree;
use once_cell::sync::Lazy;
use scoped_branch::ScopedBranch;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Write;
pub use crate::tree_config::*;
/// Reference wrapper for `TreeBuilderBase`
#[derive(Debug, Clone)]
pub struct TreeBuilder(Arc<Mutex<internal::TreeBuilderBase>>);
impl TreeBuilder {
/// Returns a new `TreeBuilder` with an empty `Tree`.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// ```
pub fn new() -> TreeBuilder {
TreeBuilder {
0: Arc::new(Mutex::new(internal::TreeBuilderBase::new())),
}
}
/// Set the configuration override for displaying trees
///
/// # Example
///
/// ```
/// use debug_tree::{TreeBuilder, add_branch_to, add_leaf_to, TreeSymbols, TreeConfig};
/// let tree = TreeBuilder::new();
/// {
/// add_branch_to!(tree, "1");
/// {
/// add_branch_to!(tree, "1.1");
/// add_leaf_to!(tree, "1.1.1");
/// add_leaf_to!(tree, "1.1.2");
/// }
/// add_leaf_to!(tree, "1.2");
/// }
/// add_leaf_to!(tree, "2");
/// tree.set_config_override(TreeConfig::new()
/// .show_first_level()
/// .symbols(TreeSymbols::with_rounded()));
/// tree.peek_print();
/// assert_eq!("\
/// ├╼ 1
/// │ ├╼ 1.1
/// │ │ ├╼ 1.1.1
/// │ │ ╰╼ 1.1.2
/// │ ╰╼ 1.2
/// ╰╼ 2" , &tree.string());
/// ```
pub fn set_config_override(&self, config: TreeConfig) {
let mut lock = self.0.lock().unwrap();
lock.set_config_override(Some(config))
}
/// Remove the configuration override
/// The default configuration will be used instead
pub fn remove_config_override(&self) {
self.0.lock().unwrap().set_config_override(None);
}
/// Update the configuration override for displaying trees
/// If an override doesn't yet exist, it is created.
///
/// # Example
///
/// ```
/// use debug_tree::{TreeBuilder, add_branch_to, add_leaf_to, TreeSymbols};
/// let tree = TreeBuilder::new();
/// {
/// add_branch_to!(tree, "1");
/// {
/// add_branch_to!(tree, "1.1");
/// add_leaf_to!(tree, "1.1.1");
/// add_leaf_to!(tree, "1.1.2");
/// }
/// add_leaf_to!(tree, "1.2");
/// }
/// add_leaf_to!(tree, "2");
/// tree.update_config_override(|x|{
/// x.indent = 3;
/// x.symbols = TreeSymbols::with_rounded();
/// x.show_first_level = true;
/// });
/// tree.peek_print();
/// assert_eq!("\
/// ├─╼ 1
/// │ ├─╼ 1.1
/// │ │ ├─╼ 1.1.1
/// │ │ ╰─╼ 1.1.2
/// │ ╰─╼ 1.2
/// ╰─╼ 2" , &tree.string());
/// ```
pub fn update_config_override<F: Fn(&mut TreeConfig)>(&self, update: F) {
let mut lock = self.0.lock().unwrap();
match lock.config_override_mut() {
Some(x) => update(x),
None => {
let mut x = TreeConfig::default();
update(&mut x);
lock.set_config_override(Some(x));
}
}
}
/// Returns the optional configuration override.
pub fn get_config_override(&self) -> Option<TreeConfig> {
let lock = self.0.lock().unwrap();
lock.config_override().clone()
}
/// Returns whether a configuration override is set.
pub fn has_config_override(&self) -> bool {
let lock = self.0.lock().unwrap();
lock.config_override().is_some()
}
/// Adds a new branch with text, `text` and returns a `ScopedBranch`.
/// When the returned `ScopedBranch` goes out of scope, (likely the end of the current block),
/// or if its `release()` method is called, the tree will step back out of the added branch.
///
/// # Arguments
/// * `text` - A string slice to use as the newly added branch's text.
///
/// # Examples
///
/// Exiting branch when end of scope is reached.
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// {
/// let _branch = tree.add_branch("Branch"); // _branch enters scope
/// // tree is now pointed inside new branch.
/// tree.add_leaf("Child of Branch");
/// // _branch leaves scope, tree moves up to parent branch.
/// }
/// tree.add_leaf("Sibling of Branch");
/// assert_eq!("\
/// Branch
/// └╼ Child of Branch
/// Sibling of Branch" , &tree.string());
/// ```
///
/// Using `release()` before out of scope.
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// {
/// let mut branch = tree.add_branch("Branch"); // branch enters scope
/// // tree is now pointed inside new branch.
/// tree.add_leaf("Child of Branch");
/// branch.release();
/// tree.add_leaf("Sibling of Branch");
/// // branch leaves scope, but no effect because its `release()` method has already been called
/// }
/// assert_eq!("\
/// Branch
/// └╼ Child of Branch
/// Sibling of Branch", &tree.string());
/// ```
pub fn add_branch(&self, text: &str) -> ScopedBranch {
self.add_leaf(text);
ScopedBranch::new(self.clone())
}
/// Adds a new branch with text, `text` and returns a `ScopedBranch`.
/// When the returned `ScopedBranch` goes out of scope, (likely the end of the current block),
/// or if its `release()` method is called, the tree tree will step back out of the added branch.
///
/// # Arguments
/// * `text` - A string slice to use as the newly added branch's text.
///
/// # Examples
///
/// Stepping out of branch when end of scope is reached.
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// {
/// tree.add_leaf("Branch");
/// let _branch = tree.enter_scoped(); // _branch enters scope
/// // tree is now pointed inside new branch.
/// tree.add_leaf("Child of Branch");
/// // _branch leaves scope, tree moves up to parent branch.
/// }
/// tree.add_leaf("Sibling of Branch");
/// assert_eq!("\
/// Branch
/// └╼ Child of Branch
/// Sibling of Branch", &tree.string());
/// ```
///
/// Using `release()` before out of scope.
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// {
/// tree.add_leaf("Branch");
/// let mut branch = tree.enter_scoped(); // branch enters scope
/// // tree is now pointed inside new branch.
/// tree.add_leaf("Child of Branch");
/// branch.release();
/// tree.add_leaf("Sibling of Branch");
/// // branch leaves scope, but no effect because its `release()` method has already been called
/// }
/// assert_eq!("\
/// Branch
/// └╼ Child of Branch
/// Sibling of Branch", &tree.string());
/// ```
pub fn enter_scoped(&self) -> ScopedBranch {
if self.is_enabled() {
ScopedBranch::new(self.clone())
} else {
ScopedBranch::none()
}
}
/// Adds a leaf to current branch with the given text, `text`.
///
/// # Arguments
/// * `text` - A string slice to use as the newly added leaf's text.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// tree.add_leaf("New leaf");
/// ```
pub fn add_leaf(&self, text: &str) {
let mut x = self.0.lock().unwrap();
if x.is_enabled() {
x.add_leaf(&text);
}
}
/// Steps into a new child branch.
/// Stepping out of the branch requires calling `exit()`.
///
/// # Example
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// tree.add_leaf("Branch");
/// tree.enter();
/// tree.add_leaf("Child of Branch");
/// assert_eq!("\
/// Branch
/// └╼ Child of Branch", &tree.string());
/// ```
pub fn enter(&self) {
let mut x = self.0.lock().unwrap();
if x.is_enabled() {
x.enter();
}
}
/// Exits the current branch, to the parent branch.
/// If no parent branch exists, no action is taken
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// tree.add_leaf("Branch");
/// tree.enter();
/// tree.add_leaf("Child of Branch");
/// tree.exit();
/// tree.add_leaf("Sibling of Branch");
/// assert_eq!("\
/// Branch
/// └╼ Child of Branch
/// Sibling of Branch", &tree.string());
/// ```
pub fn exit(&self) -> bool {
let mut x = self.0.lock().unwrap();
if x.is_enabled() {
x.exit()
} else {
false
}
}
/// Returns the depth of the current branch
/// The initial depth when no branches have been adeed is 0.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// assert_eq!(0, tree.depth());
/// let _b = tree.add_branch("Branch");
/// assert_eq!(1, tree.depth());
/// let _b = tree.add_branch("Child branch");
/// assert_eq!(2, tree.depth());
/// ```
pub fn depth(&self) -> usize {
self.0.lock().unwrap().depth()
}
/// Prints the tree without clearing.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// tree.add_leaf("Leaf");
/// tree.peek_print();
/// // Leaf
/// tree.peek_print();
/// // Leaf
/// // Leaf 2
/// ```
pub fn peek_print(&self) {
self.0.lock().unwrap().peek_print();
}
/// Prints the tree and then clears it.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// tree.add_leaf("Leaf");
/// tree.print();
/// // Leaf
/// tree.add_leaf("Leaf 2");
/// tree.print();
/// // Leaf 2
/// ```
pub fn print(&self) {
self.0.lock().unwrap().print();
}
/// Returns the tree as a string without clearing the tree.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// tree.add_leaf("Leaf");
/// assert_eq!("Leaf", tree.peek_string());
/// tree.add_leaf("Leaf 2");
/// assert_eq!("Leaf\nLeaf 2", tree.peek_string());
/// ```
pub fn peek_string(&self) -> String {
self.0.lock().unwrap().peek_string()
}
/// Returns the tree as a string and clears the tree.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// tree.add_leaf("Leaf");
/// assert_eq!("Leaf", tree.string());
/// tree.add_leaf("Leaf 2");
/// assert_eq!("Leaf 2", tree.string());
/// ```
pub fn string(&self) -> String {
self.0.lock().unwrap().string()
}
/// Writes the tree to file without clearing.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// use std::fs::{read_to_string, create_dir};
/// use std::io::Read;
/// let tree = TreeBuilder::new();
/// create_dir("test_out").ok();
/// tree.add_leaf("Leaf");
/// assert_eq!(tree.peek_string(), "Leaf");
/// tree.peek_write("test_out/peek_write.txt");
/// assert_eq!(read_to_string("test_out/peek_write.txt").unwrap(), "Leaf");
/// assert_eq!(tree.peek_string(), "Leaf");
/// ```
pub fn peek_write(&self, path: &str) -> std::io::Result<()> {
let mut file = File::create(path)?;
file.write_all(self.peek_string().as_bytes())
}
/// Writes the tree to file without clearing.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// use std::io::Read;
/// use std::fs::{read_to_string, create_dir};
/// let tree = TreeBuilder::new();
/// create_dir("test_out").ok();
/// tree.add_leaf("Leaf");
/// assert_eq!(tree.peek_string(), "Leaf");
/// tree.write("test_out/write.txt");
/// assert_eq!(read_to_string("test_out/write.txt").unwrap(), "Leaf");
/// assert_eq!(tree.peek_string(), "");
/// ```
pub fn write(&self, path: &str) -> std::io::Result<()> {
let mut file = File::create(path)?;
file.write_all(self.string().as_bytes())
}
/// Clears the tree.
///
/// # Example
///
/// ```
/// use debug_tree::TreeBuilder;
/// let tree = TreeBuilder::new();
/// tree.add_leaf("Leaf");
/// assert_eq!("Leaf", tree.peek_string());
/// tree.clear();
/// assert_eq!("", tree.peek_string());
/// ```
pub fn clear(&self) {
self.0.lock().unwrap().clear()
}
/// Sets the enabled state of the tree.
///
/// If not enabled, the tree will not be modified by adding leaves or branches.
/// Additionally, if called using the `add_`... macros, arguments will not be processed.
/// This is particularly useful for suppressing output in production, with very little overhead.
///
/// # Example
/// ```
/// #[macro_use]
/// use debug_tree::{TreeBuilder, add_leaf_to};
/// let mut tree = TreeBuilder::new();
/// tree.add_leaf("Leaf 1");
/// tree.set_enabled(false);
/// add_leaf_to!(tree, "Leaf 2");
/// tree.set_enabled(true);
/// add_leaf_to!(tree, "Leaf 3");
/// assert_eq!("Leaf 1\nLeaf 3", tree.peek_string());
/// ```
pub fn set_enabled(&self, enabled: bool) {
self.0.lock().unwrap().set_enabled(enabled);
}
/// Returns the enabled state of the tree.
///
/// # Example
/// ```
/// use debug_tree::TreeBuilder;
/// let mut tree = TreeBuilder::new();
/// assert_eq!(true, tree.is_enabled());
/// tree.set_enabled(false);
/// assert_eq!(false, tree.is_enabled());
/// ```
pub fn is_enabled(&self) -> bool {
self.0.lock().unwrap().is_enabled()
}
}
pub trait AsTree {
fn as_tree(&self) -> TreeBuilder;
fn is_tree_enabled(&self) -> bool {
self.as_tree().is_enabled()
}
}
impl AsTree for TreeBuilder {
fn as_tree(&self) -> TreeBuilder {
self.clone()
}
}
pub(crate) fn get_or_add_tree<T: AsRef<str>>(name: T) -> TreeBuilder {
let mut map = TREE_MAP.lock().unwrap();
match map.get(name.as_ref()) {
Some(x) => x.clone(),
_ => {
let val = TreeBuilder::new();
map.insert(name.as_ref().to_string(), val.clone());
val
}
}
}
pub(crate) fn get_tree<T: AsRef<str>>(name: T) -> Option<TreeBuilder> {
TREE_MAP.lock().unwrap().get(name.as_ref()).cloned()
}
type TreeMap = BTreeMap<String, TreeBuilder>;
static TREE_MAP: Lazy<Arc<Mutex<TreeMap>>> =
Lazy::new(|| -> Arc<Mutex<TreeMap>> { Arc::new(Mutex::new(TreeMap::new())) });
/// Sets the enabled state of the tree.
///
/// # Arguments
/// * `name` - The tree name
/// * `enabled` - The enabled state
///
pub fn set_enabled<T: AsRef<str>>(name: T, enabled: bool) {
let mut map = TREE_MAP.lock().unwrap();
match map.get_mut(name.as_ref()) {
Some(x) => x.set_enabled(enabled),
_ => {
let tree = TreeBuilder::new();
tree.set_enabled(enabled);
map.insert(name.as_ref().to_string(), tree);
}
}
}
impl<T: AsRef<str>> AsTree for T {
fn as_tree(&self) -> TreeBuilder {
get_or_add_tree(self)
}
/// Check if the named tree is enabled and exists
/// This does not create a new tree if non-existent
///
/// # Arguments
/// * `tree_name` - The tree name
///
fn is_tree_enabled(&self) -> bool {
get_tree(self).map(|x| x.is_enabled()).unwrap_or(false)
}
}
/// Returns the tree
/// If there is no tree then one is created and then returned.
pub fn tree<T: AsTree>(tree: T) -> TreeBuilder {
tree.as_tree()
}
/// Returns the tree named `name`
/// If there is no tree named `name` then one is created and then returned.
pub fn is_tree_enabled<T: AsTree>(tree: &T) -> bool {
tree.is_tree_enabled()
}
/// Calls [clear](TreeBuilder::clear) for the tree named `name`
/// If there is no tree named `name` then one is created
pub fn clear<T: AsRef<str>>(name: T) {
name.as_tree().clear();
}
/// Returns [string](TreeBuilder::string) for the tree named `name`
/// If there is no tree named `name` then one is created
pub fn string<T: AsRef<str>>(name: T) -> String {
name.as_tree().string()
}
/// Returns [peek_string](TreeBuilder::peek_string) for the tree named `name`
/// If there is no tree named `name` then one is created
pub fn peek_string<T: AsRef<str>>(name: T) -> String {
name.as_tree().peek_string()
}
/// Calls [print](TreeBuilder::print) for the tree named `name`
/// If there is no tree named `name` then one is created
pub fn print<T: AsRef<str>>(name: T) {
name.as_tree().print();
}
/// Calls [peek_print](TreeBuilder::peek_print) for the tree named `name`
/// If there is no tree named `name` then one is created
pub fn peek_print<T: AsRef<str>>(name: T) {
name.as_tree().peek_print();
}
/// Calls [write](TreeBuilder::write) for the tree named `name`
/// If there is no tree named `name` then one is created
pub fn write<T: AsRef<str>, P: AsRef<str>>(name: T, path: P) -> std::io::Result<()> {
name.as_tree().write(path.as_ref())
}
/// Calls [peek_print](TreeBuilder::peek_print) for the tree named `name`
/// If there is no tree named `name` then one is created
pub fn peek_write<T: AsRef<str>, P: AsRef<str>>(name: T, path: P) -> std::io::Result<()> {
name.as_tree().peek_write(path.as_ref())
}
/// Adds a leaf to given tree with the given text and formatting arguments
///
/// # Arguments
/// * `tree` - The tree that the leaf should be added to
/// * `text...` - Formatted text arguments, as per `format!(...)`.
///
/// # Example
///
/// ```
/// #[macro_use]
/// use debug_tree::{TreeBuilder, add_leaf_to};
/// fn main() {
/// let tree = TreeBuilder::new();
/// add_leaf_to!(tree, "A {} leaf", "new");
/// assert_eq!("A new leaf", &tree.peek_string());
/// }
/// ```
#[macro_export]
macro_rules! add_leaf_to {
($tree:expr, $($arg:tt)*) => (if $crate::is_tree_enabled(&$tree) {
use $crate::AsTree;
$tree.as_tree().add_leaf(&format!($($arg)*))
});
}
/// Adds a leaf to given tree with the given `value` argument
///
/// # Arguments
/// * `tree` - The tree that the leaf should be added to
/// * `value` - An expression that implements the `Display` trait.
///
/// # Example
///
/// ```
/// #[macro_use]
/// use debug_tree::{TreeBuilder, add_leaf_value_to};
/// fn main() {
/// let tree = TreeBuilder::new();
/// let value = add_leaf_value_to!(tree, 5 * 4 * 3 * 2);
/// assert_eq!(120, value);
/// assert_eq!("120", &tree.peek_string());
/// }
/// ```
#[macro_export]
macro_rules! add_leaf_value_to {
($tree:expr, $value:expr) => {{
let v = $value;
if $crate::is_tree_enabled(&$tree) {
use $crate::AsTree;
$tree.as_tree().add_leaf(&format!("{}", &v));
}
v
}};
}
/// Adds a scoped branch to given tree with the given text and formatting arguments
/// The branch will be exited at the end of the current block.
///
/// # Arguments
/// * `tree` - The tree that the leaf should be added to
/// * `text...` - Formatted text arguments, as per `format!(...)`.
///
/// # Example
///
/// ```
/// #[macro_use]
/// use debug_tree::{TreeBuilder, add_branch_to, add_leaf_to};
/// fn main() {
/// let tree = TreeBuilder::new();
/// {
/// add_branch_to!(tree, "New {}", "Branch"); // _branch enters scope
/// // tree is now pointed inside new branch.
/// add_leaf_to!(tree, "Child of {}", "Branch");
/// // Block ends, so tree exits the current branch.
/// }
/// add_leaf_to!(tree, "Sibling of {}", "Branch");
/// assert_eq!("\
/// New Branch
/// └╼ Child of Branch
/// Sibling of Branch" , &tree.string());
/// }
/// ```
#[macro_export]
macro_rules! add_branch_to {
($tree:expr) => {
let _debug_tree_branch = if $crate::is_tree_enabled(&$tree) {
use $crate::AsTree;
$tree.as_tree().enter_scoped()
} else {
$crate::scoped_branch::ScopedBranch::none()
};
};
($tree:expr, $($arg:tt)*) => {
let _debug_tree_branch = if $crate::is_tree_enabled(&$tree) {
use $crate::AsTree;
$tree.as_tree().add_branch(&format!($($arg)*))
} else {
$crate::scoped_branch::ScopedBranch::none()
};
};
}
/// Calls `function` with argument, `tree`, at the end of the current scope
/// The function will only be executed if the tree is enabled when this macro is called
#[macro_export]
macro_rules! defer {
($function:expr) => {
let _debug_tree_defer = {
use $crate::AsTree;
if $crate::default::default_tree().is_enabled() {
use $crate::AsTree;
$crate::defer::DeferredFn::new($crate::default::default_tree(), $function)
} else {
$crate::defer::DeferredFn::none()
}
};
};
($tree:expr, $function:expr) => {
let _debug_tree_defer = {
use $crate::AsTree;
if $tree.as_tree().is_enabled() {
$crate::defer::DeferredFn::new($tree.as_tree(), $function)
} else {
$crate::defer::DeferredFn::none()
}
};
};
}
/// Calls [print](TreeBuilder::print) on `tree` at the end of the current scope.
/// The function will only be executed if the tree is enabled when this macro is called
#[macro_export]
macro_rules! defer_print {
() => {
$crate::defer!(|x| {
x.print();
})
};
($tree:expr) => {
$crate::defer!($tree, |x| {
x.print();
})
};
}
/// Calls [peek_print](TreeBuilder::peek_print) on `tree` at the end of the current scope.
/// The function will only be executed if the tree is enabled when this macro is called
#[macro_export]
macro_rules! defer_peek_print {
() => {
$crate::defer!(|x| {
x.peek_print();
})
};
($tree:expr) => {
$crate::defer!($tree, |x| {
x.peek_print();
})
};
}
/// Calls [write](TreeBuilder::write) on `tree` at the end of the current scope.
/// The function will only be executed if the tree is enabled when this macro is called
#[macro_export]
macro_rules! defer_write {
($tree:expr, $path:expr) => {
$crate::defer!($tree, |x| {
if let Err(err) = x.write($path) {
eprintln!("error during `defer_write`: {}", err);
}
})
};
($path:expr) => {
$crate::defer!(|x| {
if let Err(err) = x.write($path) {
eprintln!("error during `defer_write`: {}", err);
}
})
};
}
/// Calls [peek_write](TreeBuilder::peek_write) on `tree` at the end of the current scope.
/// The function will only be executed if the tree is enabled when this macro is called
#[macro_export]
macro_rules! defer_peek_write {
($tree:expr, $path:expr) => {
$crate::defer!($tree, |x| {
if let Err(err) = x.peek_write($path) {
eprintln!("error during `defer_peek_write`: {}", err);
}
})
};
($path:expr) => {
$crate::defer!(|x| {
if let Err(err) = x.peek_write($path) {
eprintln!("error during `defer_peek_write`: {}", err);
}
})
};
}

View File

@@ -1,26 +0,0 @@
use crate::TreeBuilder;
pub struct ScopedBranch {
state: Option<TreeBuilder>,
}
impl ScopedBranch {
pub fn new(state: TreeBuilder) -> ScopedBranch {
state.enter();
ScopedBranch { state: Some(state) }
}
pub fn none() -> ScopedBranch {
ScopedBranch { state: None }
}
pub fn release(&mut self) {
if let Some(x) = &self.state {
x.exit();
}
self.state = None;
}
}
impl Drop for ScopedBranch {
fn drop(&mut self) {
self.release();
}
}

View File

@@ -1,596 +0,0 @@
#[cfg(test)]
mod test {
use crate::*;
use futures::future::join5;
use std::cmp::{max, min};
use std::fs::{create_dir, read_to_string, remove_file};
#[test]
fn test_branch() {
let d: TreeBuilder = TreeBuilder::new();
d.add_leaf("1");
{
let _l = d.enter_scoped();
d.add_leaf("1.1");
d.add_leaf("1.2");
}
d.add_leaf("2");
d.add_leaf("3");
let _l = d.enter_scoped();
d.add_leaf("3.1");
d.add_leaf("3.2");
d.peek_print();
assert_eq!(
"\
1
├╼ 1.1
└╼ 1.2
2
3
├╼ 3.1
└╼ 3.2",
d.string()
);
}
#[test]
fn test_branch2() {
let d = TreeBuilder::new();
d.add_leaf("1");
{
let _scope = d.enter_scoped();
d.add_leaf("1.1");
{
let _scope = d.enter_scoped();
d.add_leaf("1.1.1");
}
}
d.add_leaf("2");
d.enter();
d.add_leaf("2.1");
d.enter();
d.add_leaf("2.1.1");
d.peek_print();
assert_eq!(
"\
1
└╼ 1.1
└╼ 1.1.1
2
└╼ 2.1
└╼ 2.1.1",
d.string()
);
}
#[test]
fn simple() {
let d = TreeBuilder::new();
d.add_leaf("Hi");
assert_eq!("Hi", d.string());
}
#[test]
fn depth() {
let d = TreeBuilder::new();
assert_eq!(0, d.depth());
d.add_leaf("Hi");
assert_eq!(0, d.depth());
let _b = d.add_branch("Hi");
assert_eq!(1, d.depth());
d.add_leaf("Hi");
assert_eq!(1, d.depth());
}
#[test]
fn indent() {
let d = TreeBuilder::new();
d.add_leaf("1");
add_branch_to!(d);
d.add_leaf("1.1");
{
add_branch_to!(d);
d.add_leaf("1.1.1");
}
d.set_config_override(TreeConfig::new().indent(4));
d.peek_print();
assert_eq!(
"\
1
└──╼ 1.1
└──╼ 1.1.1",
d.string()
);
}
#[test]
fn macros() {
let d = TreeBuilder::new();
add_leaf_to!(d, "1");
{
add_branch_to!(d);
add_leaf_to!(d, "1.1")
}
d.peek_print();
assert_eq!(
"\
1
└╼ 1.1",
d.string()
);
}
#[test]
fn macros_with_fn() {
let d = TreeBuilder::new();
let tree = || d.clone();
add_leaf_to!(tree(), "1");
{
add_branch_to!(tree());
add_leaf_to!(tree(), "1.1")
}
tree().peek_print();
assert_eq!(
"\
1
└╼ 1.1",
d.string()
);
}
#[test]
fn leaf_with_value() {
let d = TreeBuilder::new();
let value = add_leaf_value_to!(d, 1);
d.peek_print();
assert_eq!("1", d.string());
assert_eq!(1, value);
}
#[test]
fn macros2() {
let d = TreeBuilder::new();
add_branch_to!(d, "1");
add_leaf_to!(d, "1.1");
d.peek_print();
assert_eq!(
"\
1
└╼ 1.1",
d.string()
);
}
#[test]
fn mid() {
let d = TreeBuilder::new();
d.add_leaf(&format!("{}{}", "1", "0"));
d.enter();
d.add_leaf("10.1");
d.add_leaf("10.2");
d.enter();
d.add_leaf("10.1.1");
d.add_leaf("10.1.2\nNext line");
d.exit();
d.add_leaf(&format!("10.3"));
d.peek_print();
assert_eq!(
"\
10
├╼ 10.1
├╼ 10.2
│ ├╼ 10.1.1
│ └╼ 10.1.2
│ Next line
└╼ 10.3",
d.string()
);
}
fn factors(x: usize) {
add_branch!("{}", x);
for i in 1..x {
if x % i == 0 {
factors(i);
}
}
}
#[test]
fn recursive() {
factors(6);
default_tree().peek_print();
assert_eq!(
"\
6
├╼ 1
├╼ 2
│ └╼ 1
└╼ 3
└╼ 1",
default_tree().string()
);
}
fn a() {
add_branch!("a");
b();
c();
}
fn b() {
add_branch!("b");
c();
}
fn c() {
add_branch!("c");
add_leaf!("Nothing to see here");
}
#[test]
fn nested() {
a();
default_tree().peek_print();
assert_eq!(
"\
a
├╼ b
│ └╼ c
│ └╼ Nothing to see here
└╼ c
└╼ Nothing to see here",
default_tree().string()
);
}
#[test]
fn disabled_output() {
let tree = TreeBuilder::new();
tree.set_enabled(false);
add_leaf_to!(tree, "Leaf");
tree.add_leaf("Leaf");
add_branch_to!(tree, "Branch");
tree.add_branch("Branch");
assert_eq!("", tree.string());
}
#[test]
fn enabled_output() {
let tree = TreeBuilder::new();
tree.set_enabled(false);
add_branch_to!(tree, "Ignored branch");
add_leaf_to!(tree, "Ignored leaf");
tree.set_enabled(true);
add_leaf_to!(tree, "Leaf");
tree.add_leaf("Leaf");
add_branch_to!(tree, "Branch");
tree.add_branch("Branch");
assert_eq!(
"Leaf
Leaf
Branch
└╼ Branch",
tree.string()
);
}
#[test]
fn tree_by_name() {
clear("A");
let b = tree("B");
b.clear();
{
add_branch_to!("A", "1");
add_branch_to!(b, "3");
add_leaf_to!("A", "1.1");
add_leaf_to!("B", "3.1");
}
add_leaf_to!("A", "2");
peek_print("A");
b.peek_print();
assert_eq!(
"1
└╼ 1.1
2",
string("A")
);
assert_eq!(
"3
└╼ 3.1",
b.string()
);
}
#[test]
fn tree_by_name_disabled() {
let d = tree("D");
d.clear();
d.set_enabled(true);
clear("C");
set_enabled("C", false);
{
add_branch_to!("C", "1");
set_enabled("C", true);
add_branch_to!(d, "3");
add_leaf_to!("C", "1.1");
d.set_enabled(false);
add_leaf_to!("D", "3.1");
}
add_leaf_to!("C", "2");
peek_print("C");
d.peek_print();
assert_eq!(
"1.1
2",
string("C")
);
assert_eq!("3", d.string());
}
#[test]
fn defer_write() {
let tree = TreeBuilder::new();
{
create_dir("test_out").ok();
remove_file("test_out/defer_write.txt").ok();
File::create("test_out/defer_write.txt").unwrap();
defer_write!(tree, "test_out/defer_write.txt");
tree.add_leaf("Branch");
assert_eq!(read_to_string("test_out/defer_write.txt").unwrap(), "");
assert_eq!(tree.peek_string(), "Branch");
}
assert_eq!(tree.peek_string(), "");
assert_eq!(
read_to_string("test_out/defer_write.txt").unwrap(),
"Branch"
);
}
#[test]
fn defer_peek_write() {
let tree = TreeBuilder::new();
{
create_dir("test_out").ok();
remove_file("test_out/defer_peek_write.txt").ok();
File::create("test_out/defer_peek_write.txt").unwrap();
defer_peek_write!(tree, "test_out/defer_peek_write.txt");
tree.add_leaf("Branch");
assert_eq!(read_to_string("test_out/defer_peek_write.txt").unwrap(), "");
assert_eq!(tree.peek_string(), "Branch");
}
assert_eq!(tree.peek_string(), "Branch");
assert_eq!(
read_to_string("test_out/defer_peek_write.txt").unwrap(),
"Branch"
);
}
#[test]
#[should_panic]
#[allow(unreachable_code)]
fn defer_peek_write_panic() {
let tree = TreeBuilder::new();
{
create_dir("test_out").ok();
remove_file("test_out/defer_peek_write_panic.txt").ok();
File::create("test_out/defer_peek_write_panic.txt").unwrap();
defer_peek_write!(tree, "test_out/defer_peek_write_panic.txt");
tree.add_leaf("This should be the only line in this file");
assert_eq!(read_to_string("test_out/defer_peek_write.txt").unwrap(), "");
assert_eq!(
tree.peek_string(),
"This should be the only line in this file"
);
panic!();
tree.add_leaf("This line should not exist");
}
}
fn example_tree() -> TreeBuilder {
let tree = TreeBuilder::new();
{
add_branch_to!(tree, "1");
{
add_branch_to!(tree, "1.1");
add_leaf_to!(tree, "1.1.1");
add_leaf_to!(tree, "1.1.2\nWith two\nextra lines");
add_leaf_to!(tree, "1.1.3");
}
add_branch_to!(tree, "1.2");
add_leaf_to!(tree, "1.2.1");
}
{
add_branch_to!(tree, "2");
add_leaf_to!(tree, "2.1");
add_leaf_to!(tree, "2.2");
}
add_leaf_to!(tree, "3");
tree
}
#[test]
fn format_output() {
let tree = example_tree();
tree.set_config_override(
TreeConfig::new()
.indent(8)
.symbols(TreeSymbols {
continued: "| |",
join_first: "|A|",
join_last: "|Z|",
join_inner: "|N|",
join_only: "|O|",
branch: "123456[NOT SHOWN]",
leaf: ")}>",
multiline_first: Some(")}MULTI>"),
multiline_continued: Some(".. CONTINUED: "),
})
.show_first_level(),
);
tree.peek_print();
assert_eq!(
tree.string(),
"\
|A|123456)}>1
| | |A|123456)}>1.1
| | | | |A|123456)}>1.1.1
| | | | |N|123456)}MULTI>1.1.2
| | | | | | .. CONTINUED: With two
| | | | | | .. CONTINUED: extra lines
| | | | |Z|123456)}>1.1.3
| | |Z|123456)}>1.2
| | |O|123456)}>1.2.1
|N|123456)}>2
| | |A|123456)}>2.1
| | |Z|123456)}>2.2
|Z|123456)}>3"
);
}
#[test]
fn format_output_thick() {
let tree = example_tree();
tree.set_config_override(
TreeConfig::new()
.symbols(TreeSymbols::with_thick())
.indent(4)
.show_first_level(),
);
tree.peek_print();
assert_eq!(
tree.string(),
"\
┣━━╼ 1
┃ ┣━━╼ 1.1
┃ ┃ ┣━━╼ 1.1.1
┃ ┃ ┣━━╼ 1.1.2
┃ ┃ ┃ With two
┃ ┃ ┃ extra lines
┃ ┃ ┗━━╼ 1.1.3
┃ ┗━━╼ 1.2
┃ ┗━━╼ 1.2.1
┣━━╼ 2
┃ ┣━━╼ 2.1
┃ ┗━━╼ 2.2
┗━━╼ 3"
);
}
#[test]
fn format_output_pipes() {
let tree = example_tree();
tree.set_config_override(
TreeConfig::new()
.symbols(TreeSymbols::with_pipes())
.indent(3)
.show_first_level(),
);
tree.peek_print();
assert_eq!(
tree.string(),
"\
╠═╼ 1
║ ╠═╼ 1.1
║ ║ ╠═╼ 1.1.1
║ ║ ╠═╼ 1.1.2
║ ║ ║ With two
║ ║ ║ extra lines
║ ║ ╚═╼ 1.1.3
║ ╚═╼ 1.2
║ ╚═╼ 1.2.1
╠═╼ 2
║ ╠═╼ 2.1
║ ╚═╼ 2.2
╚═╼ 3"
);
}
#[test]
fn format_output_dashed() {
let tree = example_tree();
tree.set_config_override(
TreeConfig::new()
.symbols(TreeSymbols::with_dashed().multiline_continued(" > "))
.indent(4)
.show_first_level(),
);
tree.peek_print();
assert_eq!(
tree.string(),
"\
┊╌╌- 1
┊ ┊╌╌- 1.1
┊ ┊ ┊╌╌- 1.1.1
┊ ┊ ┊╌╌- 1.1.2
┊ ┊ ┊ > With two
┊ ┊ ┊ > extra lines
┊ ┊ '╌╌- 1.1.3
┊ '╌╌- 1.2
┊ '╌╌- 1.2.1
┊╌╌- 2
┊ ┊╌╌- 2.1
┊ '╌╌- 2.2
'╌╌- 3"
);
}
#[test]
fn format_output_rounded() {
let tree = example_tree();
tree.set_config_override(
TreeConfig::new()
.symbols(TreeSymbols::with_rounded())
.indent(4),
);
tree.peek_print();
assert_eq!(
tree.string(),
"\
1
├──╼ 1.1
│ ├──╼ 1.1.1
│ ├──╼ 1.1.2
│ │ With two
│ │ extra lines
│ ╰──╼ 1.1.3
╰──╼ 1.2
╰──╼ 1.2.1
2
├──╼ 2.1
╰──╼ 2.2
3"
);
}
async fn wait_a_bit(tree: TreeBuilder, index: usize) {
tree.print();
add_branch_to!(tree, "inside async branch {}", index);
tree.print();
add_leaf_to!(tree, "inside async leaf {}", index);
tree.print();
}
#[tokio::test]
async fn async_barrier() {
let tree = TreeBuilder::new();
defer_peek_print!(tree);
add_branch_to!(tree, "root");
add_leaf_to!(tree, "before async");
let x2 = wait_a_bit(tree.clone(), 4);
let x1 = wait_a_bit(tree.clone(), 5);
let x3 = wait_a_bit(tree.clone(), 3);
let x4 = wait_a_bit(tree.clone(), 2);
let x5 = wait_a_bit(tree.clone(), 1);
add_leaf_to!(tree, "before join async");
join5(x1, x2, x3, x4, x5).await;
add_leaf_to!(tree, "after join async");
assert_eq!(tree.peek_string(), "after join async");
}
}

View File

@@ -1,224 +0,0 @@
use once_cell::sync::Lazy;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct TreeSymbols {
/// A vertical base of the tree (│)
pub continued: &'static str,
/// Symbol for joining the first branch in a group (├)
pub join_first: &'static str,
/// Symbol for joining the last branch in a group (└)
pub join_last: &'static str,
/// Symbol for joining a branch that is not first or last in its group (├)
pub join_inner: &'static str,
/// Symbol for joining a branch if it is the only one in its group (├)
pub join_only: &'static str,
/// A repeated branch token (─)
pub branch: &'static str,
/// End of a leaf (╼)
pub leaf: &'static str,
pub multiline_first: Option<&'static str>,
pub multiline_continued: Option<&'static str>,
}
#[derive(Debug, Clone)]
pub struct TreeConfig {
pub symbols: TreeSymbols,
/// Aside from the first branch, `indent` is equal to the number of spaces a child branch is
/// shifted from its parent.
pub indent: usize,
pub show_first_level: bool,
}
impl TreeSymbols {
pub fn new() -> Self {
Self {
continued: "",
join_first: "",
join_inner: "",
join_last: "",
join_only: "",
branch: "",
leaf: "",
multiline_first: None,
multiline_continued: None,
}
}
pub fn with_pipes() -> Self {
Self {
continued: "",
join_first: "",
join_inner: "",
join_last: "",
join_only: "",
branch: "",
leaf: "",
multiline_first: None,
multiline_continued: None,
}
}
pub fn with_thick() -> Self {
Self {
continued: "",
join_first: "",
join_inner: "",
join_last: "",
join_only: "",
branch: "",
leaf: "",
multiline_first: None,
multiline_continued: None,
}
}
pub fn with_rounded() -> Self {
Self {
continued: "",
join_first: "",
join_inner: "",
join_last: "",
join_only: "",
branch: "",
leaf: "",
multiline_first: None,
multiline_continued: None,
}
}
pub fn with_dashed() -> Self {
Self {
continued: "",
join_first: "",
join_inner: "",
join_last: "'",
join_only: "'",
branch: "",
leaf: "- ",
multiline_first: None,
multiline_continued: None,
}
}
pub fn continued(mut self, sym: &'static str) -> Self {
self.continued = sym;
self
}
pub fn join_first(mut self, sym: &'static str) -> Self {
self.join_first = sym;
self
}
pub fn join_inner(mut self, sym: &'static str) -> Self {
self.join_inner = sym;
self
}
pub fn join_last(mut self, sym: &'static str) -> Self {
self.join_last = sym;
self
}
pub fn join_only(mut self, sym: &'static str) -> Self {
self.join_only = sym;
self
}
pub fn branch(mut self, sym: &'static str) -> Self {
self.branch = sym;
self
}
pub fn leaf(mut self, sym: &'static str) -> Self {
self.leaf = sym;
self
}
pub fn multiline_first(mut self, sym: &'static str) -> Self {
self.multiline_first = Some(sym);
self
}
pub fn multiline_continued(mut self, sym: &'static str) -> Self {
self.multiline_continued = Some(sym);
self
}
}
impl TreeConfig {
pub fn new() -> Self {
Self {
symbols: TreeSymbols::new(),
indent: 2,
show_first_level: false,
}
}
pub fn with_symbols(symbols: TreeSymbols) -> Self {
Self {
symbols,
indent: 2,
show_first_level: false,
}
}
pub fn indent(mut self, x: usize) -> Self {
self.indent = x;
self
}
pub fn show_first_level(mut self) -> Self {
self.show_first_level = true;
self
}
pub fn hide_first_level(mut self) -> Self {
self.show_first_level = false;
self
}
pub fn symbols(mut self, x: TreeSymbols) -> Self {
self.symbols = x;
self
}
}
impl Default for TreeSymbols {
fn default() -> Self {
tree_config_symbols()
}
}
impl Default for TreeConfig {
fn default() -> Self {
tree_config()
}
}
static DEFAULT_CONFIG: Lazy<Arc<Mutex<TreeConfig>>> =
Lazy::new(|| -> Arc<Mutex<TreeConfig>> { Arc::new(Mutex::new(TreeConfig::new())) });
/// Set the default tree config
pub fn set_tree_config(x: TreeConfig) {
*DEFAULT_CONFIG.lock().unwrap() = x;
}
/// The default tree config
pub fn tree_config() -> TreeConfig {
DEFAULT_CONFIG.lock().unwrap().clone()
}
/// Set the default tree symbols config
pub fn set_tree_config_symbols(x: TreeSymbols) {
DEFAULT_CONFIG.lock().unwrap().symbols = x;
}
/// The default tree symbols config
pub fn tree_config_symbols() -> TreeSymbols {
DEFAULT_CONFIG.lock().unwrap().symbols.clone()
}
/// The default tree symbols config
pub fn update_tree_config<F: FnMut(&mut TreeConfig)>(mut update: F) {
let mut x = DEFAULT_CONFIG.lock().unwrap();
update(&mut x);
}
/// The default tree symbols config
pub fn update_tree_config_symbols<F: FnMut(&mut TreeSymbols)>(mut update: F) {
let mut x = DEFAULT_CONFIG.lock().unwrap();
update(&mut x.symbols);
}

View File

@@ -1 +0,0 @@
{"files":{"CHANGELOG.md":"aa4370a41f9b2860b267324f4c0a16c80747ae418e62b3d6b79d59eaa334093a","Cargo.lock":"de0bb0bd4416bb51b01be208bc3c9b57db3b243099dbbe6a9c67f6c780cb90c1","Cargo.toml":"352bd903a8be234d62cc2e0a9b58d7fd682c74c008b0a9aa689ad5df781e6842","LICENSE":"233be68ab89eaccff940b48aa638fbecbf060f4da9148837f78d0c3146b1dd30","README.md":"61899f503016729b687071d4168933d49a958120e54eaf52d4f9026cc4375668","README.tpl":"a3a1fa6df2cd9d0dca6804e578754533c4a99f6f5b42f78c55865ba339d3e097","examples/service_client.rs":"b329a8314235744a8637a2ad6bbfd36fc0a49f72549bf2af372e2034e6699d1b","src/lib.rs":"95dd484abdbb2882be1ddebc8335eb14725e835b4cede5b9305685254d803a70","src/parse.rs":"09ee2754615b2923d92489b20dd6b50f3dce2d34f6830d669e299995638aa65a","src/visit.rs":"8d3586f3414a9720a86c3308a29b18d16ba7be84fdbe263f3600e7ac70033123","tests/test.rs":"06d71ea5e81a46a9f0be84230aafbdab15c13ba61d5579d403f736574daabf7b","tests/ui/01-maybe-async.rs":"d33ef273ad5f3e5f7979c37b58a2a753fc342429025622ccfcb83104d185701e","tests/ui/02-must-be-async.rs":"0659a11158be19d22f97005a3be70081ec872d3e63091455022565fcd959a8d2","tests/ui/03-must-be-sync.rs":"5dcdc928b099c9cec17e70993bf82c475388a88a73f432bd785484a318a0632e","tests/ui/04-unit-test-util.rs":"04f3f029900e2f5d086961d753ddf10f122dbebc709618303f83fab23f6d4c29","tests/ui/05-replace-future-generic-type-with-output.rs":"481f4afb978ffc7cd756765f72e3456813dc8748ada57ac200e1a02a3d9562c3","tests/ui/06-sync_impl_async_impl.rs":"a8e8ec6db193172061eed098396ab9a0b6b03707f9854eae8a5df45c2340c809","tests/ui/test_fail/01-empty-test.rs":"d87f35ae690dcd2851fea2bb8f15745bb86aa9301efd642f30aa6018654dcebb","tests/ui/test_fail/01-empty-test.stderr":"7bb5274635582533ba5a761cdb36a211c382ff57c973352f4a7ac5a6953f08b4","tests/ui/test_fail/02-unknown-path.rs":"fc7cd52d4d7107c779980386ed5f4d133a1dcdf74261fd0ea0df53d03d5c8b1b","tests/ui/test_fail/02-unknown-path.stderr":"28374dc1c4c44f6ba7eedff576e43c3d724dc4f204040d8cec5773e5f997a44b","tests/ui/test_fail/03-async-gt2.rs":"336d628e829e6fdf41764b3d13afe5d41d1100f46bfedb61b74a4723aecb0d40","tests/ui/test_fail/03-async-gt2.stderr":"677f322ece465459410c7fdc76001f76869f9b81906096b0ece6fd97247a1f1f","tests/ui/test_fail/04-bad-sync-cond.rs":"8698ab272081cb571fcdd163a847f815b153125932ee1837829778dbe769826b","tests/ui/test_fail/04-bad-sync-cond.stderr":"bd8bdf65b1cbba7610441341dd9212da066cdd2ebee65f83a772b0921cde1e38","tests/unit-test-util.rs":"a9e09c54e14c07e5d9f33f245d583455d9324be2e826b20ce2b882f990db937e"},"package":"5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11"}

View File

@@ -1,72 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.2.10](https://github.com/fMeow/maybe-async-rs/compare/v0.2.9...v0.2.10) (2024-02-22)
### [0.2.9](https://github.com/fMeow/maybe-async-rs/compare/v0.2.8...v0.2.9) (2024-01-31)
### Features
* support `async fn` in traits ([282eb76](https://github.com/fMeow/maybe-async-rs/commit/282eb76c0be0433ade8d0a2a11646e09db2f37b7))
### [0.2.8](https://github.com/fMeow/maybe-async-rs/compare/v0.2.7...v0.2.8) (2024-01-30)
### [0.2.7](https://github.com/fMeow/maybe-async-rs/compare/v0.2.6...v0.2.7) (2023-02-01)
### Features
* allow `maybe_async` on static ([a08b112](https://github.com/fMeow/maybe-async-rs/commit/a08b11218bab0d1db304a4f68e0230c022632168))
### Bug Fixes
* applying to pub(crate) trait fail ([8cf762f](https://github.com/fMeow/maybe-async-rs/commit/8cf762fdeb1d316716fa01fb2525e5a6f5d25987))
### [0.2.6](https://github.com/guoli-lyu/maybe-async-rs/compare/v0.2.4...v0.2.6) (2021-05-28)
### Bug Fixes
* remove async test if condition not match ([0089daa](https://github.com/guoli-lyu/maybe-async-rs/commit/0089daad6e3419e11d123e8c5c87a1139880027f))
* test is removed when is_sync ([377815a](https://github.com/guoli-lyu/maybe-async-rs/commit/377815a7a81efc4a0332cc2716a7d603b350ff03))
### [0.2.5](https://github.com/guoli-lyu/maybe-async-rs/compare/v0.2.4...v0.2.5) (2021-05-28)
### Bug Fixes
* remove async test if condition not match ([0c49246](https://github.com/guoli-lyu/maybe-async-rs/commit/0c49246a3245773faff482f6b42d66522d2af208))
### [0.2.4](https://github.com/guoli-lyu/maybe-async-rs/compare/v0.2.3...v0.2.4) (2021-03-28)
### Features
* replace generic type of Future with Output ([f296cc0](https://github.com/guoli-lyu/maybe-async-rs/commit/f296cc05c90923ae3a3eeea3c5173d06d642c2ab))
* search trait bound that ends with `Future` ([3508ff2](https://github.com/guoli-lyu/maybe-async-rs/commit/3508ff2987cce61808297aa920c522e0f2012a8a))
### [0.2.3](https://github.com/guoli-lyu/maybe-async-rs/compare/v0.2.2...v0.2.3) (2021-03-27)
### Bug Fixes
* enable full feature gate for syn ([614c085](https://github.com/guoli-lyu/maybe-async-rs/commit/614c085444caf6d0d493422ca20f8ed3b86b7315))
### [0.2.2](https://github.com/guoli-lyu/maybe-async-rs/compare/v0.2.1...v0.2.2) (2020-10-19)
### Features
* avoid extra parenthesis and braces ([8d146f9](https://github.com/guoli-lyu/maybe-async-rs/commit/8d146f9a9234339de1ef6b9f7ffd44421a8d6c68))
* remove parenthesis wrap in await ([bc5f460](https://github.com/guoli-lyu/maybe-async-rs/commit/bc5f46078bfb5ccc1599570303aa72a84cc5e2d7))
* wrap await expr into block instead of paren ([5c4232a](https://github.com/guoli-lyu/maybe-async-rs/commit/5c4232a07035e9c2d4add280cc5b090a7bde471b))
### [0.2.1](https://github.com/guoli-lyu/maybe-async-rs/compare/v0.2.0...v0.2.1) (2020-10-05)
### Bug Fixes
* allow unused_paren when convert to sync ([242ded2](https://github.com/guoli-lyu/maybe-async-rs/commit/242ded2fb9f1cc3c883e0f39a081a555e7a74198))

1072
third_party/rust/maybe-async/Cargo.lock generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +0,0 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "maybe-async"
version = "0.2.10"
authors = ["Guoli Lyu <guoli-lv@hotmail.com>"]
description = "A procedure macro to unify SYNC and ASYNC implementation"
documentation = "https://docs.rs/maybe-async"
readme = "README.md"
keywords = [
"maybe",
"async",
"futures",
"macros",
"proc_macro",
]
license = "MIT"
repository = "https://github.com/fMeow/maybe-async-rs"
[lib]
path = "src/lib.rs"
proc-macro = true
[dependencies.proc-macro2]
version = "1.0"
[dependencies.quote]
version = "1.0"
[dependencies.syn]
version = "2.0"
features = [
"visit-mut",
"full",
]
[dev-dependencies.async-std]
version = "1"
features = ["attributes"]
[dev-dependencies.async-trait]
version = "0.1"
[dev-dependencies.tokio]
version = "1"
features = [
"macros",
"rt-multi-thread",
]
[dev-dependencies.trybuild]
version = "1"
features = ["diff"]
[features]
default = []
is_sync = []
[badges.maintenance]
status = "actively-developed"

View File

@@ -1,19 +0,0 @@
Copyright (c) 2020 Guoli Lyu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,295 +0,0 @@
# maybe-async
**Why bother writing similar code twice for blocking and async code?**
[![Build Status](https://github.com/fMeow/maybe-async-rs/workflows/CI%20%28Linux%29/badge.svg?branch=main)](https://github.com/fMeow/maybe-async-rs/actions)
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![Latest Version](https://img.shields.io/crates/v/maybe-async.svg)](https://crates.io/crates/maybe-async)
[![maybe-async](https://docs.rs/maybe-async/badge.svg)](https://docs.rs/maybe-async)
When implementing both sync and async versions of API in a crate, most API
of the two version are almost the same except for some async/await keyword.
`maybe-async` help unifying async and sync implementation by **procedural
macro**.
- Write async code with normal `async`, `await`, and let `maybe_async`
handles
those `async` and `await` when you need a blocking code.
- Switch between sync and async by toggling `is_sync` feature gate in
`Cargo.toml`.
- use `must_be_async` and `must_be_sync` to keep code in specified version
- use `async_impl` and `sync_impl` to only compile code block on specified
version
- A handy macro to unify unit test code is also provided.
These procedural macros can be applied to the following codes:
- trait item declaration
- trait implementation
- function definition
- struct definition
**RECOMMENDATION**: Enable **resolver ver2** in your crate, which is
introduced in Rust 1.51. If not, two crates in dependency with conflict
version (one async and another blocking) can fail compilation.
### Motivation
The async/await language feature alters the async world of rust.
Comparing with the map/and_then style, now the async code really resembles
sync version code.
In many crates, the async and sync version of crates shares the same API,
but the minor difference that all async code must be awaited prevent the
unification of async and sync code. In other words, we are forced to write
an async and a sync implementation respectively.
### Macros in Detail
`maybe-async` offers 4 set of attribute macros: `maybe_async`,
`sync_impl`/`async_impl`, `must_be_sync`/`must_be_async`, and `test`.
To use `maybe-async`, we must know which block of codes is only used on
blocking implementation, and which on async. These two implementation should
share the same function signatures except for async/await keywords, and use
`sync_impl` and `async_impl` to mark these implementation.
Use `maybe_async` macro on codes that share the same API on both async and
blocking code except for async/await keywords. And use feature gate
`is_sync` in `Cargo.toml` to toggle between async and blocking code.
- `maybe_async`
Offers a unified feature gate to provide sync and async conversion on
demand by feature gate `is_sync`, with **async first** policy.
Want to keep async code? add `maybe_async` in dependencies with default
features, which means `maybe_async` is the same as `must_be_async`:
```toml
[dependencies]
maybe_async = "0.2"
```
Want to convert async code to sync? Add `maybe_async` to dependencies with
an `is_sync` feature gate. In this way, `maybe_async` is the same as
`must_be_sync`:
```toml
[dependencies]
maybe_async = { version = "0.2", features = ["is_sync"] }
```
There are three usage variants for `maybe_async` attribute usage:
- `#[maybe_async]` or `#[maybe_async(Send)]`
In this mode, `#[async_trait::async_trait]` is added to trait declarations and trait implementations
to support async fn in traits.
- `#[maybe_async(?Send)]`
Not all async traits need futures that are `dyn Future + Send`.
In this mode, `#[async_trait::async_trait(?Send)]` is added to trait declarations and trait implementations,
to avoid having "Send" and "Sync" bounds placed on the async trait
methods.
- `#[maybe_async(AFIT)]`
AFIT is acronym for **a**sync **f**unction **i**n **t**rait, stabilized from rust 1.74
For compatibility reasons, the `async fn` in traits is supported via a verbose `AFIT` flag. This will become
the default mode for the next major release.
- `must_be_async`
**Keep async**.
There are three usage variants for `must_be_async` attribute usage:
- `#[must_be_async]` or `#[must_be_async(Send)]`
- `#[must_be_async(?Send)]`
- `#[must_be_async(AFIT)]`
- `must_be_sync`
**Convert to sync code**. Convert the async code into sync code by
removing all `async move`, `async` and `await` keyword
- `sync_impl`
A sync implementation should compile on blocking implementation and
must simply disappear when we want async version.
Although most of the API are almost the same, there definitely come to a
point when the async and sync version should differ greatly. For
example, a MongoDB client may use the same API for async and sync
version, but the code to actually send reqeust are quite different.
Here, we can use `sync_impl` to mark a synchronous implementation, and a
sync implementation should disappear when we want async version.
- `async_impl`
An async implementation should on compile on async implementation and
must simply disappear when we want sync version.
There are three usage variants for `async_impl` attribute usage:
- `#[async_impl]` or `#[async_impl(Send)]`
- `#[async_impl(?Send)]`
- `#[async_impl(AFIT)]`
- `test`
Handy macro to unify async and sync **unit and e2e test** code.
You can specify the condition to compile to sync test code
and also the conditions to compile to async test code with given test
macro, e.x. `tokio::test`, `async_std::test`, etc. When only sync
condition is specified,the test code only compiles when sync condition
is met.
```rust
# #[maybe_async::maybe_async]
# async fn async_fn() -> bool {
# true
# }
##[maybe_async::test(
feature="is_sync",
async(
all(not(feature="is_sync"), feature="async_std"),
async_std::test
),
async(
all(not(feature="is_sync"), feature="tokio"),
tokio::test
)
)]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
```
### What's Under the Hook
`maybe-async` compiles your code in different way with the `is_sync` feature
gate. It removes all `await` and `async` keywords in your code under
`maybe_async` macro and conditionally compiles codes under `async_impl` and
`sync_impl`.
Here is a detailed example on what's going on whe the `is_sync` feature
gate set or not.
```rust
#[maybe_async::maybe_async(AFIT)]
trait A {
async fn async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
struct Foo;
#[maybe_async::maybe_async(AFIT)]
impl A for Foo {
async fn async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
#[maybe_async::maybe_async]
async fn maybe_async_fn() -> Result<(), ()> {
let a = Foo::async_fn_name().await?;
let b = Foo::sync_fn_name()?;
Ok(())
}
```
When `maybe-async` feature gate `is_sync` is **NOT** set, the generated code
is async code:
```rust
// Compiled code when `is_sync` is toggled off.
trait A {
async fn maybe_async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
struct Foo;
impl A for Foo {
async fn maybe_async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
async fn maybe_async_fn() -> Result<(), ()> {
let a = Foo::maybe_async_fn_name().await?;
let b = Foo::sync_fn_name()?;
Ok(())
}
```
When `maybe-async` feature gate `is_sync` is set, all async keyword is
ignored and yields a sync version code:
```rust
// Compiled code when `is_sync` is toggled on.
trait A {
fn maybe_async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
struct Foo;
impl A for Foo {
fn maybe_async_fn_name() -> Result<(), ()> {
Ok(())
}
fn sync_fn_name() -> Result<(), ()> {
Ok(())
}
}
fn maybe_async_fn() -> Result<(), ()> {
let a = Foo::maybe_async_fn_name()?;
let b = Foo::sync_fn_name()?;
Ok(())
}
```
### Examples
#### rust client for services
When implementing rust client for any services, like awz3. The higher level
API of async and sync version is almost the same, such as creating or
deleting a bucket, retrieving an object, etc.
The example `service_client` is a proof of concept that `maybe_async` can
actually free us from writing almost the same code for sync and async. We
can toggle between a sync AWZ3 client and async one by `is_sync` feature
gate when we add `maybe-async` to dependency.
## License
MIT

View File

@@ -1,3 +0,0 @@
# {{crate}}
{{readme}}

View File

@@ -1,77 +0,0 @@
#![allow(dead_code, unused_variables)]
/// To use `maybe-async`, we must know which block of codes is only used on
/// blocking implementation, and which on async. These two implementation should
/// share the same API except for async/await keywords, and use `sync_impl` and
/// `async_impl` to mark these implementation.
type Response = String;
type Url = &'static str;
type Method = String;
/// InnerClient are used to actually send request,
/// which differ a lot between sync and async.
///
/// Use native async function in trait
#[maybe_async::maybe_async(AFIT)]
trait InnerClient {
async fn request(method: Method, url: Url, data: String) -> Response;
#[inline]
async fn post(url: Url, data: String) -> Response {
Self::request(String::from("post"), url, data).await
}
#[inline]
async fn delete(url: Url, data: String) -> Response {
Self::request(String::from("delete"), url, data).await
}
}
/// The higher level API for end user.
pub struct ServiceClient;
/// Synchronous implementation, only compiles when `is_sync` feature is off.
/// Else the compiler will complain that *request is defined multiple times* and
/// blabla.
#[maybe_async::sync_impl]
impl InnerClient for ServiceClient {
fn request(method: Method, url: Url, data: String) -> Response {
// your implementation for sync, like use
// `reqwest::blocking` to send request
String::from("pretend we have a response")
}
}
/// Asynchronous implementation, only compiles when `is_sync` feature is off.
#[maybe_async::async_impl(AFIT)]
impl InnerClient for ServiceClient {
async fn request(method: Method, url: Url, data: String) -> Response {
// your implementation for async, like use `reqwest::client`
// or `async_std` to send request
String::from("pretend we have a response")
}
}
/// Code of upstream API are almost the same for sync and async,
/// except for async/await keyword.
impl ServiceClient {
#[maybe_async::maybe_async]
async fn create_bucket(name: String) -> Response {
Self::post("http://correct_url4create", String::from("my_bucket")).await
// When `is_sync` is toggle on, this block will compiles to:
// Self::post("http://correct_url4create", String::from("my_bucket"))
}
#[maybe_async::maybe_async]
async fn delete_bucket(name: String) -> Response {
Self::delete("http://correct_url4delete", String::from("my_bucket")).await
}
// and another thousands of functions that interact with service side
}
#[maybe_async::sync_impl]
fn main() {
let _ = ServiceClient::create_bucket("bucket".to_owned());
}
#[maybe_async::async_impl]
#[tokio::main]
async fn main() {
let _ = ServiceClient::create_bucket("bucket".to_owned()).await;
}

View File

@@ -1,626 +0,0 @@
//! **Why bother writing similar code twice for blocking and async code?**
//!
//! [![Build Status](https://github.com/fMeow/maybe-async-rs/workflows/CI%20%28Linux%29/badge.svg?branch=main)](https://github.com/fMeow/maybe-async-rs/actions)
//! [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
//! [![Latest Version](https://img.shields.io/crates/v/maybe-async.svg)](https://crates.io/crates/maybe-async)
//! [![maybe-async](https://docs.rs/maybe-async/badge.svg)](https://docs.rs/maybe-async)
//!
//! When implementing both sync and async versions of API in a crate, most API
//! of the two version are almost the same except for some async/await keyword.
//!
//! `maybe-async` help unifying async and sync implementation by **procedural
//! macro**.
//! - Write async code with normal `async`, `await`, and let `maybe_async`
//! handles
//! those `async` and `await` when you need a blocking code.
//! - Switch between sync and async by toggling `is_sync` feature gate in
//! `Cargo.toml`.
//! - use `must_be_async` and `must_be_sync` to keep code in specified version
//! - use `async_impl` and `sync_impl` to only compile code block on specified
//! version
//! - A handy macro to unify unit test code is also provided.
//!
//! These procedural macros can be applied to the following codes:
//! - trait item declaration
//! - trait implementation
//! - function definition
//! - struct definition
//!
//! **RECOMMENDATION**: Enable **resolver ver2** in your crate, which is
//! introduced in Rust 1.51. If not, two crates in dependency with conflict
//! version (one async and another blocking) can fail compilation.
//!
//!
//! ## Motivation
//!
//! The async/await language feature alters the async world of rust.
//! Comparing with the map/and_then style, now the async code really resembles
//! sync version code.
//!
//! In many crates, the async and sync version of crates shares the same API,
//! but the minor difference that all async code must be awaited prevent the
//! unification of async and sync code. In other words, we are forced to write
//! an async and a sync implementation respectively.
//!
//! ## Macros in Detail
//!
//! `maybe-async` offers 4 set of attribute macros: `maybe_async`,
//! `sync_impl`/`async_impl`, `must_be_sync`/`must_be_async`, and `test`.
//!
//! To use `maybe-async`, we must know which block of codes is only used on
//! blocking implementation, and which on async. These two implementation should
//! share the same function signatures except for async/await keywords, and use
//! `sync_impl` and `async_impl` to mark these implementation.
//!
//! Use `maybe_async` macro on codes that share the same API on both async and
//! blocking code except for async/await keywords. And use feature gate
//! `is_sync` in `Cargo.toml` to toggle between async and blocking code.
//!
//! - `maybe_async`
//!
//! Offers a unified feature gate to provide sync and async conversion on
//! demand by feature gate `is_sync`, with **async first** policy.
//!
//! Want to keep async code? add `maybe_async` in dependencies with default
//! features, which means `maybe_async` is the same as `must_be_async`:
//!
//! ```toml
//! [dependencies]
//! maybe_async = "0.2"
//! ```
//!
//! Want to convert async code to sync? Add `maybe_async` to dependencies with
//! an `is_sync` feature gate. In this way, `maybe_async` is the same as
//! `must_be_sync`:
//!
//! ```toml
//! [dependencies]
//! maybe_async = { version = "0.2", features = ["is_sync"] }
//! ```
//!
//! There are three usage variants for `maybe_async` attribute usage:
//! - `#[maybe_async]` or `#[maybe_async(Send)]`
//!
//! In this mode, `#[async_trait::async_trait]` is added to trait declarations and trait implementations
//! to support async fn in traits.
//!
//! - `#[maybe_async(?Send)]`
//!
//! Not all async traits need futures that are `dyn Future + Send`.
//! In this mode, `#[async_trait::async_trait(?Send)]` is added to trait declarations and trait implementations,
//! to avoid having "Send" and "Sync" bounds placed on the async trait
//! methods.
//!
//! - `#[maybe_async(AFIT)]`
//!
//! AFIT is acronym for **a**sync **f**unction **i**n **t**rait, stabilized from rust 1.74
//!
//! For compatibility reasons, the `async fn` in traits is supported via a verbose `AFIT` flag. This will become
//! the default mode for the next major release.
//!
//! - `must_be_async`
//!
//! **Keep async**.
//!
//! There are three usage variants for `must_be_async` attribute usage:
//! - `#[must_be_async]` or `#[must_be_async(Send)]`
//! - `#[must_be_async(?Send)]`
//! - `#[must_be_async(AFIT)]`
//!
//! - `must_be_sync`
//!
//! **Convert to sync code**. Convert the async code into sync code by
//! removing all `async move`, `async` and `await` keyword
//!
//!
//! - `sync_impl`
//!
//! A sync implementation should compile on blocking implementation and
//! must simply disappear when we want async version.
//!
//! Although most of the API are almost the same, there definitely come to a
//! point when the async and sync version should differ greatly. For
//! example, a MongoDB client may use the same API for async and sync
//! version, but the code to actually send reqeust are quite different.
//!
//! Here, we can use `sync_impl` to mark a synchronous implementation, and a
//! sync implementation should disappear when we want async version.
//!
//! - `async_impl`
//!
//! An async implementation should on compile on async implementation and
//! must simply disappear when we want sync version.
//!
//! There are three usage variants for `async_impl` attribute usage:
//! - `#[async_impl]` or `#[async_impl(Send)]`
//! - `#[async_impl(?Send)]`
//! - `#[async_impl(AFIT)]`
//!
//! - `test`
//!
//! Handy macro to unify async and sync **unit and e2e test** code.
//!
//! You can specify the condition to compile to sync test code
//! and also the conditions to compile to async test code with given test
//! macro, e.x. `tokio::test`, `async_std::test`, etc. When only sync
//! condition is specified,the test code only compiles when sync condition
//! is met.
//!
//! ```rust
//! # #[maybe_async::maybe_async]
//! # async fn async_fn() -> bool {
//! # true
//! # }
//!
//! ##[maybe_async::test(
//! feature="is_sync",
//! async(
//! all(not(feature="is_sync"), feature="async_std"),
//! async_std::test
//! ),
//! async(
//! all(not(feature="is_sync"), feature="tokio"),
//! tokio::test
//! )
//! )]
//! async fn test_async_fn() {
//! let res = async_fn().await;
//! assert_eq!(res, true);
//! }
//! ```
//!
//! ## What's Under the Hook
//!
//! `maybe-async` compiles your code in different way with the `is_sync` feature
//! gate. It removes all `await` and `async` keywords in your code under
//! `maybe_async` macro and conditionally compiles codes under `async_impl` and
//! `sync_impl`.
//!
//! Here is a detailed example on what's going on whe the `is_sync` feature
//! gate set or not.
//!
//! ```rust
//! #[maybe_async::maybe_async(AFIT)]
//! trait A {
//! async fn async_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! fn sync_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! }
//!
//! struct Foo;
//!
//! #[maybe_async::maybe_async(AFIT)]
//! impl A for Foo {
//! async fn async_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! fn sync_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! }
//!
//! #[maybe_async::maybe_async]
//! async fn maybe_async_fn() -> Result<(), ()> {
//! let a = Foo::async_fn_name().await?;
//!
//! let b = Foo::sync_fn_name()?;
//! Ok(())
//! }
//! ```
//!
//! When `maybe-async` feature gate `is_sync` is **NOT** set, the generated code
//! is async code:
//!
//! ```rust
//! // Compiled code when `is_sync` is toggled off.
//! trait A {
//! async fn maybe_async_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! fn sync_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! }
//!
//! struct Foo;
//!
//! impl A for Foo {
//! async fn maybe_async_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! fn sync_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! }
//!
//! async fn maybe_async_fn() -> Result<(), ()> {
//! let a = Foo::maybe_async_fn_name().await?;
//! let b = Foo::sync_fn_name()?;
//! Ok(())
//! }
//! ```
//!
//! When `maybe-async` feature gate `is_sync` is set, all async keyword is
//! ignored and yields a sync version code:
//!
//! ```rust
//! // Compiled code when `is_sync` is toggled on.
//! trait A {
//! fn maybe_async_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! fn sync_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! }
//!
//! struct Foo;
//!
//! impl A for Foo {
//! fn maybe_async_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! fn sync_fn_name() -> Result<(), ()> {
//! Ok(())
//! }
//! }
//!
//! fn maybe_async_fn() -> Result<(), ()> {
//! let a = Foo::maybe_async_fn_name()?;
//! let b = Foo::sync_fn_name()?;
//! Ok(())
//! }
//! ```
//!
//! ## Examples
//!
//! ### rust client for services
//!
//! When implementing rust client for any services, like awz3. The higher level
//! API of async and sync version is almost the same, such as creating or
//! deleting a bucket, retrieving an object, etc.
//!
//! The example `service_client` is a proof of concept that `maybe_async` can
//! actually free us from writing almost the same code for sync and async. We
//! can toggle between a sync AWZ3 client and async one by `is_sync` feature
//! gate when we add `maybe-async` to dependency.
//!
//!
//! # License
//! MIT
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use syn::{
ext::IdentExt,
parenthesized,
parse::{ParseStream, Parser},
parse_macro_input, token, Ident, ImplItem, LitStr, Meta, Result, Token, TraitItem,
};
use quote::quote;
use crate::{parse::Item, visit::AsyncAwaitRemoval};
mod parse;
mod visit;
enum AsyncTraitMode {
Send,
NotSend,
Off,
}
fn convert_async(input: &mut Item, async_trait_mode: AsyncTraitMode) -> TokenStream2 {
match input {
Item::Trait(item) => match async_trait_mode {
AsyncTraitMode::Send => quote!(#[async_trait::async_trait]#item),
AsyncTraitMode::NotSend => quote!(#[async_trait::async_trait(?Send)]#item),
AsyncTraitMode::Off => quote!(#item),
},
Item::Impl(item) => {
let async_trait_mode = item
.trait_
.as_ref()
.map_or(AsyncTraitMode::Off, |_| async_trait_mode);
match async_trait_mode {
AsyncTraitMode::Send => quote!(#[async_trait::async_trait]#item),
AsyncTraitMode::NotSend => quote!(#[async_trait::async_trait(?Send)]#item),
AsyncTraitMode::Off => quote!(#item),
}
}
Item::Fn(item) => quote!(#item),
Item::Static(item) => quote!(#item),
}
}
fn convert_sync(input: &mut Item) -> TokenStream2 {
match input {
Item::Impl(item) => {
for inner in &mut item.items {
if let ImplItem::Fn(ref mut method) = inner {
if method.sig.asyncness.is_some() {
method.sig.asyncness = None;
}
}
}
AsyncAwaitRemoval.remove_async_await(quote!(#item))
}
Item::Trait(item) => {
for inner in &mut item.items {
if let TraitItem::Fn(ref mut method) = inner {
if method.sig.asyncness.is_some() {
method.sig.asyncness = None;
}
}
}
AsyncAwaitRemoval.remove_async_await(quote!(#item))
}
Item::Fn(item) => {
if item.sig.asyncness.is_some() {
item.sig.asyncness = None;
}
AsyncAwaitRemoval.remove_async_await(quote!(#item))
}
Item::Static(item) => AsyncAwaitRemoval.remove_async_await(quote!(#item)),
}
}
fn async_mode(arg: &str) -> Result<AsyncTraitMode> {
match arg {
"" | "Send" => Ok(AsyncTraitMode::Send),
"?Send" => Ok(AsyncTraitMode::NotSend),
// acronym for Async Function in Trait,
// TODO make AFIT as default in future release
"AFIT" => Ok(AsyncTraitMode::Off),
_ => Err(syn::Error::new(
Span::call_site(),
"Only accepts `Send`, `?Send` or `AFIT` (native async function in trait)",
)),
}
}
/// maybe_async attribute macro
///
/// Can be applied to trait item, trait impl, functions and struct impls.
#[proc_macro_attribute]
pub fn maybe_async(args: TokenStream, input: TokenStream) -> TokenStream {
let mode = match async_mode(args.to_string().replace(" ", "").as_str()) {
Ok(m) => m,
Err(e) => return e.to_compile_error().into(),
};
let mut item = parse_macro_input!(input as Item);
let token = if cfg!(feature = "is_sync") {
convert_sync(&mut item)
} else {
convert_async(&mut item, mode)
};
token.into()
}
/// convert marked async code to async code with `async-trait`
#[proc_macro_attribute]
pub fn must_be_async(args: TokenStream, input: TokenStream) -> TokenStream {
let mode = match async_mode(args.to_string().replace(" ", "").as_str()) {
Ok(m) => m,
Err(e) => return e.to_compile_error().into(),
};
let mut item = parse_macro_input!(input as Item);
convert_async(&mut item, mode).into()
}
/// convert marked async code to sync code
#[proc_macro_attribute]
pub fn must_be_sync(_args: TokenStream, input: TokenStream) -> TokenStream {
let mut item = parse_macro_input!(input as Item);
convert_sync(&mut item).into()
}
/// mark sync implementation
///
/// only compiled when `is_sync` feature gate is set.
/// When `is_sync` is not set, marked code is removed.
#[proc_macro_attribute]
pub fn sync_impl(_args: TokenStream, input: TokenStream) -> TokenStream {
let input = TokenStream2::from(input);
let token = if cfg!(feature = "is_sync") {
quote!(#input)
} else {
quote!()
};
token.into()
}
/// mark async implementation
///
/// only compiled when `is_sync` feature gate is not set.
/// When `is_sync` is set, marked code is removed.
#[proc_macro_attribute]
pub fn async_impl(args: TokenStream, _input: TokenStream) -> TokenStream {
let mode = match async_mode(args.to_string().replace(" ", "").as_str()) {
Ok(m) => m,
Err(e) => return e.to_compile_error().into(),
};
let token = if cfg!(feature = "is_sync") {
quote!()
} else {
let mut item = parse_macro_input!(_input as Item);
convert_async(&mut item, mode)
};
token.into()
}
fn parse_nested_meta_or_str(input: ParseStream) -> Result<TokenStream2> {
if let Some(s) = input.parse::<Option<LitStr>>()? {
let tokens = s.value().parse()?;
Ok(tokens)
} else {
let meta: Meta = input.parse()?;
Ok(quote!(#meta))
}
}
/// Handy macro to unify test code of sync and async code
///
/// Since the API of both sync and async code are the same,
/// with only difference that async functions must be awaited.
/// So it's tedious to write unit sync and async respectively.
///
/// This macro helps unify the sync and async unit test code.
/// Pass the condition to treat test code as sync as the first
/// argument. And specify the condition when to treat test code
/// as async and the lib to run async test, e.x. `async-std::test`,
/// `tokio::test`, or any valid attribute macro.
///
/// **ATTENTION**: do not write await inside a assert macro
///
/// - Examples
///
/// ```rust
/// #[maybe_async::maybe_async]
/// async fn async_fn() -> bool {
/// true
/// }
///
/// #[maybe_async::test(
/// // when to treat the test code as sync version
/// feature="is_sync",
/// // when to run async test
/// async(all(not(feature="is_sync"), feature="async_std"), async_std::test),
/// // you can specify multiple conditions for different async runtime
/// async(all(not(feature="is_sync"), feature="tokio"), tokio::test)
/// )]
/// async fn test_async_fn() {
/// let res = async_fn().await;
/// assert_eq!(res, true);
/// }
///
/// // Only run test in sync version
/// #[maybe_async::test(feature = "is_sync")]
/// async fn test_sync_fn() {
/// let res = async_fn().await;
/// assert_eq!(res, true);
/// }
/// ```
///
/// The above code is transcripted to the following code:
///
/// ```rust
/// # use maybe_async::{must_be_async, must_be_sync, sync_impl};
/// # #[maybe_async::maybe_async]
/// # async fn async_fn() -> bool { true }
///
/// // convert to sync version when sync condition is met, keep in async version when corresponding
/// // condition is met
/// #[cfg_attr(feature = "is_sync", must_be_sync, test)]
/// #[cfg_attr(
/// all(not(feature = "is_sync"), feature = "async_std"),
/// must_be_async,
/// async_std::test
/// )]
/// #[cfg_attr(
/// all(not(feature = "is_sync"), feature = "tokio"),
/// must_be_async,
/// tokio::test
/// )]
/// async fn test_async_fn() {
/// let res = async_fn().await;
/// assert_eq!(res, true);
/// }
///
/// // force converted to sync function, and only compile on sync condition
/// #[cfg(feature = "is_sync")]
/// #[test]
/// fn test_sync_fn() {
/// let res = async_fn();
/// assert_eq!(res, true);
/// }
/// ```
#[proc_macro_attribute]
pub fn test(args: TokenStream, input: TokenStream) -> TokenStream {
match parse_test_cfg.parse(args) {
Ok(test_cfg) => [test_cfg.into(), input].into_iter().collect(),
Err(err) => err.to_compile_error().into(),
}
}
fn parse_test_cfg(input: ParseStream) -> Result<TokenStream2> {
if input.is_empty() {
return Err(syn::Error::new(
Span::call_site(),
"Arguments cannot be empty, at least specify the condition for sync code",
));
}
// The first attributes indicates sync condition
let sync_cond = input.call(parse_nested_meta_or_str)?;
let mut ts = quote!(#[cfg_attr(#sync_cond, maybe_async::must_be_sync, test)]);
// The rest attributes indicates async condition and async test macro
// only accepts in the forms of `async(cond, test_macro)`, but `cond` and
// `test_macro` can be either meta attributes or string literal
let mut async_conditions = Vec::new();
while !input.is_empty() {
input.parse::<Token![,]>()?;
if input.is_empty() {
break;
}
if !input.peek(Ident::peek_any) {
return Err(
input.error("Must be list of metas like: `async(condition, async_test_macro)`")
);
}
let name = input.call(Ident::parse_any)?;
if name != "async" {
return Err(syn::Error::new(
name.span(),
format!("Unknown path: `{}`, must be `async`", name),
));
}
if !input.peek(token::Paren) {
return Err(
input.error("Must be list of metas like: `async(condition, async_test_macro)`")
);
}
let nested;
parenthesized!(nested in input);
let list = nested.parse_terminated(parse_nested_meta_or_str, Token![,])?;
let len = list.len();
let mut iter = list.into_iter();
let (Some(async_cond), Some(async_test), None) = (iter.next(), iter.next(), iter.next())
else {
let msg = format!(
"Must pass two metas or string literals like `async(condition, \
async_test_macro)`, you passed {len} metas.",
);
return Err(syn::Error::new(name.span(), msg));
};
let attr = quote!(
#[cfg_attr(#async_cond, maybe_async::must_be_async, #async_test)]
);
async_conditions.push(async_cond);
ts.extend(attr);
}
Ok(if !async_conditions.is_empty() {
quote! {
#[cfg(any(#sync_cond, #(#async_conditions),*))]
#ts
}
} else {
quote! {
#[cfg(#sync_cond)]
#ts
}
})
}

View File

@@ -1,43 +0,0 @@
use proc_macro2::Span;
use syn::{
parse::{discouraged::Speculative, Parse, ParseStream, Result},
Attribute, Error, ItemFn, ItemImpl, ItemStatic, ItemTrait,
};
pub enum Item {
Trait(ItemTrait),
Impl(ItemImpl),
Fn(ItemFn),
Static(ItemStatic),
}
macro_rules! fork {
($fork:ident = $input:ident) => {{
$fork = $input.fork();
&$fork
}};
}
impl Parse for Item {
fn parse(input: ParseStream) -> Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let mut fork;
let item = if let Ok(mut item) = fork!(fork = input).parse::<ItemImpl>() {
item.attrs = attrs;
Item::Impl(item)
} else if let Ok(mut item) = fork!(fork = input).parse::<ItemTrait>() {
item.attrs = attrs;
Item::Trait(item)
} else if let Ok(mut item) = fork!(fork = input).parse::<ItemFn>() {
item.attrs = attrs;
Item::Fn(item)
} else if let Ok(mut item) = fork!(fork = input).parse::<ItemStatic>() {
item.attrs = attrs;
Item::Static(item)
} else {
return Err(Error::new(Span::call_site(), "expected impl, trait or fn"));
};
input.advance_to(&fork);
Ok(item)
}
}

View File

@@ -1,187 +0,0 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
visit_mut::{self, visit_item_mut, visit_path_segment_mut, VisitMut},
Expr, ExprBlock, File, GenericArgument, GenericParam, Item, PathArguments, PathSegment, Stmt,
Type, TypeParamBound, WherePredicate,
};
pub struct ReplaceGenericType<'a> {
generic_type: &'a str,
arg_type: &'a PathSegment,
}
impl<'a> ReplaceGenericType<'a> {
pub fn new(generic_type: &'a str, arg_type: &'a PathSegment) -> Self {
Self {
generic_type,
arg_type,
}
}
pub fn replace_generic_type(item: &mut Item, generic_type: &'a str, arg_type: &'a PathSegment) {
let mut s = Self::new(generic_type, arg_type);
s.visit_item_mut(item);
}
}
impl<'a> VisitMut for ReplaceGenericType<'a> {
fn visit_item_mut(&mut self, i: &mut Item) {
if let Item::Fn(item_fn) = i {
// remove generic type from generics <T, F>
let args = item_fn
.sig
.generics
.params
.iter()
.filter_map(|param| {
if let GenericParam::Type(type_param) = &param {
if type_param.ident.to_string().eq(self.generic_type) {
None
} else {
Some(param)
}
} else {
Some(param)
}
})
.collect::<Vec<_>>();
item_fn.sig.generics.params = args.into_iter().cloned().collect();
// remove generic type from where clause
if let Some(where_clause) = &mut item_fn.sig.generics.where_clause {
let new_where_clause = where_clause
.predicates
.iter()
.filter_map(|predicate| {
if let WherePredicate::Type(predicate_type) = predicate {
if let Type::Path(p) = &predicate_type.bounded_ty {
if p.path.segments[0].ident.to_string().eq(self.generic_type) {
None
} else {
Some(predicate)
}
} else {
Some(predicate)
}
} else {
Some(predicate)
}
})
.collect::<Vec<_>>();
where_clause.predicates = new_where_clause.into_iter().cloned().collect();
};
}
visit_item_mut(self, i)
}
fn visit_path_segment_mut(&mut self, i: &mut PathSegment) {
// replace generic type with target type
if i.ident.to_string().eq(&self.generic_type) {
*i = self.arg_type.clone();
}
visit_path_segment_mut(self, i);
}
}
pub struct AsyncAwaitRemoval;
impl AsyncAwaitRemoval {
pub fn remove_async_await(&mut self, item: TokenStream) -> TokenStream {
let mut syntax_tree: File = syn::parse(item.into()).unwrap();
self.visit_file_mut(&mut syntax_tree);
quote!(#syntax_tree)
}
}
impl VisitMut for AsyncAwaitRemoval {
fn visit_expr_mut(&mut self, node: &mut Expr) {
// Delegate to the default impl to visit nested expressions.
visit_mut::visit_expr_mut(self, node);
match node {
Expr::Await(expr) => *node = (*expr.base).clone(),
Expr::Async(expr) => {
let inner = &expr.block;
let sync_expr = if let [Stmt::Expr(expr, None)] = inner.stmts.as_slice() {
// remove useless braces when there is only one statement
expr.clone()
} else {
Expr::Block(ExprBlock {
attrs: expr.attrs.clone(),
block: inner.clone(),
label: None,
})
};
*node = sync_expr;
}
_ => {}
}
}
fn visit_item_mut(&mut self, i: &mut Item) {
// find generic parameter of Future and replace it with its Output type
if let Item::Fn(item_fn) = i {
let mut inputs: Vec<(String, PathSegment)> = vec![];
// generic params: <T:Future<Output=()>, F>
for param in &item_fn.sig.generics.params {
// generic param: T:Future<Output=()>
if let GenericParam::Type(type_param) = param {
let generic_type_name = type_param.ident.to_string();
// bound: Future<Output=()>
for bound in &type_param.bounds {
inputs.extend(search_trait_bound(&generic_type_name, bound));
}
}
}
if let Some(where_clause) = &item_fn.sig.generics.where_clause {
for predicate in &where_clause.predicates {
if let WherePredicate::Type(predicate_type) = predicate {
let generic_type_name = if let Type::Path(p) = &predicate_type.bounded_ty {
p.path.segments[0].ident.to_string()
} else {
panic!("Please submit an issue");
};
for bound in &predicate_type.bounds {
inputs.extend(search_trait_bound(&generic_type_name, bound));
}
}
}
}
for (generic_type_name, path_seg) in &inputs {
ReplaceGenericType::replace_generic_type(i, generic_type_name, path_seg);
}
}
visit_item_mut(self, i);
}
}
fn search_trait_bound(
generic_type_name: &str,
bound: &TypeParamBound,
) -> Vec<(String, PathSegment)> {
let mut inputs = vec![];
if let TypeParamBound::Trait(trait_bound) = bound {
let segment = &trait_bound.path.segments[trait_bound.path.segments.len() - 1];
let name = segment.ident.to_string();
if name.eq("Future") {
// match Future<Output=Type>
if let PathArguments::AngleBracketed(args) = &segment.arguments {
// binding: Output=Type
if let GenericArgument::AssocType(binding) = &args.args[0] {
if let Type::Path(p) = &binding.ty {
inputs.push((generic_type_name.to_owned(), p.path.segments[0].clone()));
}
}
}
}
}
inputs
}

View File

@@ -1,15 +0,0 @@
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/01-maybe-async.rs");
t.pass("tests/ui/02-must-be-async.rs");
t.pass("tests/ui/03-must-be-sync.rs");
t.pass("tests/ui/04-unit-test-util.rs");
t.pass("tests/ui/05-replace-future-generic-type-with-output.rs");
t.pass("tests/ui/06-sync_impl_async_impl.rs");
t.compile_fail("tests/ui/test_fail/01-empty-test.rs");
t.compile_fail("tests/ui/test_fail/02-unknown-path.rs");
t.compile_fail("tests/ui/test_fail/03-async-gt2.rs");
t.compile_fail("tests/ui/test_fail/04-bad-sync-cond.rs");
}

View File

@@ -1,122 +0,0 @@
#![allow(dead_code)]
use maybe_async::maybe_async;
#[maybe_async(Send)]
trait Trait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async(?Send)]
pub trait PubTrait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async]
pub(crate) trait PubCrateTrait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async(AFIT)]
trait AfitTrait {
fn sync_fn_afit() {}
async fn declare_async_afit(&self);
async fn async_fn_afit(&self) {
self.declare_async_afit().await
}
}
#[maybe_async]
async fn async_fn() {}
#[maybe_async]
pub async fn pub_async_fn() {}
#[maybe_async]
pub(crate) async fn pub_crate_async_fn() {}
#[maybe_async]
unsafe fn unsafe_fn() {}
struct Struct;
#[maybe_async]
impl Struct {
fn sync_fn_inherent() {}
async fn declare_async_inherent(&self) {}
async fn async_fn_inherent(&self) {
async { self.declare_async_inherent().await }.await
}
}
#[maybe_async]
impl Trait for Struct {
fn sync_fn() {}
async fn declare_async(&self) {}
async fn async_fn(&self) {
async { self.declare_async().await }.await
}
}
#[maybe_async(AFIT)]
impl AfitTrait for Struct {
fn sync_fn_afit() {}
async fn declare_async_afit(&self) {}
async fn async_fn_afit(&self) {
async { self.declare_async_afit().await }.await
}
}
#[cfg(feature = "is_sync")]
fn main() -> std::result::Result<(), ()> {
let s = Struct;
s.declare_async_inherent();
s.async_fn_inherent();
s.declare_async();
s.async_fn();
s.declare_async_afit();
s.async_fn_afit();
async_fn();
pub_async_fn();
Ok(())
}
#[cfg(not(feature = "is_sync"))]
#[async_std::main]
async fn main() {
let s = Struct;
s.declare_async_inherent().await;
s.async_fn_inherent().await;
s.declare_async().await;
s.async_fn().await;
s.declare_async_afit().await;
s.async_fn_afit().await;
async_fn().await;
pub_async_fn().await;
}

View File

@@ -1,134 +0,0 @@
#![allow(dead_code)]
#[maybe_async::maybe_async]
trait Trait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async::maybe_async(?Send)]
trait NotSendTrait {
async fn declare_async_not_send(&self);
async fn async_fn_not_send(&self) {
self.declare_async_not_send().await
}
}
#[maybe_async::maybe_async]
pub trait PubTrait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async::maybe_async]
pub(crate) trait PubCrateTrait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async::maybe_async(AFIT)]
trait AfitTrait {
fn sync_fn_afit() {}
async fn declare_async_afit(&self);
async fn async_fn_afit(&self) {
self.declare_async_afit().await
}
}
#[cfg(not(feature = "is_sync"))]
#[maybe_async::must_be_async]
async fn async_fn() {}
#[cfg(not(feature = "is_sync"))]
#[maybe_async::must_be_async]
pub async fn pub_async_fn() {}
#[cfg(not(feature = "is_sync"))]
#[maybe_async::maybe_async]
pub(crate) async fn pub_crate_async_fn() {}
#[cfg(not(feature = "is_sync"))]
#[maybe_async::maybe_async]
unsafe fn unsafe_fn() {}
struct Struct;
#[cfg(not(feature = "is_sync"))]
#[maybe_async::must_be_async]
impl Struct {
fn sync_fn_inherent() {}
async fn declare_async_inherent(&self) {}
async fn async_fn_inherent(&self) {
async { self.declare_async_inherent().await }.await
}
}
#[cfg(not(feature = "is_sync"))]
#[maybe_async::must_be_async]
impl Trait for Struct {
fn sync_fn() {}
async fn declare_async(&self) {}
async fn async_fn(&self) {
async { self.declare_async().await }.await
}
}
#[cfg(not(feature = "is_sync"))]
#[maybe_async::must_be_async(?Send)]
impl NotSendTrait for Struct {
async fn declare_async_not_send(&self) {}
async fn async_fn_not_send(&self) {
async { self.declare_async_not_send().await }.await
}
}
#[cfg(not(feature = "is_sync"))]
#[maybe_async::must_be_async(AFIT)]
impl AfitTrait for Struct {
fn sync_fn_afit() {}
async fn declare_async_afit(&self) {}
async fn async_fn_afit(&self) {
async { self.declare_async_afit().await }.await
}
}
#[cfg(feature = "is_sync")]
fn main() {}
#[cfg(not(feature = "is_sync"))]
#[async_std::main]
async fn main() {
let s = Struct;
s.declare_async_inherent().await;
s.async_fn_inherent().await;
s.declare_async().await;
s.async_fn().await;
s.declare_async_afit().await;
s.async_fn_afit().await;
async_fn().await;
pub_async_fn().await;
}

View File

@@ -1,88 +0,0 @@
#![allow(dead_code)]
#[maybe_async::maybe_async]
trait Trait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async::maybe_async]
pub trait PubTrait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async::maybe_async]
pub(crate) trait PubCrateTrait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async::maybe_async]
async fn async_fn() {}
#[maybe_async::maybe_async]
pub async fn pub_async_fn() {}
#[maybe_async::maybe_async]
pub(crate) async fn pub_crate_async_fn() {}
#[maybe_async::maybe_async]
unsafe fn unsafe_fn() {}
struct Struct;
#[cfg(feature = "is_sync")]
#[maybe_async::must_be_sync]
impl Struct {
fn sync_fn_inherent() {}
async fn declare_async_inherent(&self) {}
async fn async_fn_inherent(&self) {
async { self.declare_async_inherent().await }.await
}
}
#[cfg(feature = "is_sync")]
#[maybe_async::must_be_sync]
impl Trait for Struct {
fn sync_fn() {}
async fn declare_async(&self) {}
async fn async_fn(&self) {
async { self.declare_async().await }.await
}
}
#[cfg(feature = "is_sync")]
fn main() -> std::result::Result<(), ()> {
let s = Struct;
s.declare_async_inherent();
s.async_fn_inherent();
s.declare_async();
s.async_fn();
async_fn();
pub_async_fn();
Ok(())
}
#[cfg(not(feature = "is_sync"))]
#[async_std::main]
async fn main() {}

View File

@@ -1,38 +0,0 @@
use maybe_async::maybe_async;
#[maybe_async]
async fn async_fn() -> bool {
true
}
#[maybe_async::test(
feature = "is_sync",
async(all(not(feature="is_sync"), feature = "async_std"), async_std::test),
async(all(not(feature="is_sync"), feature = "tokio"), tokio::test)
)]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
#[maybe_async::test(feature = "is_sync", async(not(feature = "is_sync"), async_std::test))]
async fn test_async_fn2() {
let res = async_fn().await;
assert_eq!(res, true);
}
#[maybe_async::test("feature=\"is_sync\"", async(not(feature = "is_sync"), async_std::test))]
async fn test_async_fn3() {
let res = async_fn().await;
assert_eq!(res, true);
}
#[maybe_async::test(feature = "is_sync", async("not(feature = \"is_sync\")", "async_std::test"))]
async fn test_async_fn4() {
let res = async_fn().await;
assert_eq!(res, true);
}
fn main() {
}

View File

@@ -1,34 +0,0 @@
#![allow(unused_imports)]
use std::future::Future;
#[maybe_async::maybe_async]
pub async fn with_fn<T, F: Sync + std::future::Future<Output = Result<(), ()>>>(
test: T,
) -> Result<(), ()>
where
T: FnOnce() -> F,
{
test().await
}
#[maybe_async::maybe_async]
pub async fn with_fn_where<T, F>(test: T) -> Result<(), ()>
where
T: FnOnce() -> F,
F: Sync + Future<Output = Result<(), ()>>,
{
test().await
}
#[maybe_async::sync_impl]
fn main() {
with_fn(|| Ok(())).unwrap();
with_fn_where(|| Ok(())).unwrap();
}
#[maybe_async::async_impl]
#[tokio::main]
async fn main() {
with_fn(|| async { Ok(()) }).await.unwrap();
with_fn_where(|| async { Ok(()) }).await.unwrap();
}

View File

@@ -1,44 +0,0 @@
#![allow(dead_code, unused_variables)]
/// InnerClient differ a lot between sync and async.
#[maybe_async::maybe_async]
trait Trait {
async fn maybe_async_fn();
}
/// The higher level API for end user.
pub struct Struct;
/// Synchronous implementation, only compiles when `is_sync` feature is off.
/// Else the compiler will complain that *request is defined multiple times* and
/// blabla.
#[maybe_async::sync_impl]
impl Trait for Struct {
fn maybe_async_fn() { }
}
/// Asynchronous implementation, only compiles when `is_sync` feature is off.
#[maybe_async::async_impl]
impl Trait for Struct {
async fn maybe_async_fn() { }
}
impl Struct {
#[maybe_async::maybe_async]
async fn another_maybe_async_fn() {
Self::maybe_async_fn().await
// When `is_sync` is toggle on, this block will compiles to:
// Self::maybe_async_fn()
}
}
#[maybe_async::sync_impl]
fn main() {
let _ = Struct::another_maybe_async_fn();
}
#[maybe_async::async_impl]
#[tokio::main]
async fn main() {
let _ = Struct::another_maybe_async_fn().await;
}

View File

@@ -1,17 +0,0 @@
use maybe_async::maybe_async;
#[maybe_async]
async fn async_fn() -> bool {
true
}
// at least one sync condition should be specified
#[maybe_async::test()]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
fn main() {
}

View File

@@ -1,7 +0,0 @@
error: Arguments cannot be empty, at least specify the condition for sync code
--> tests/ui/test_fail/01-empty-test.rs:9:1
|
9 | #[maybe_async::test()]
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `maybe_async::test` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@@ -1,17 +0,0 @@
use maybe_async::maybe_async;
#[maybe_async]
async fn async_fn() -> bool {
true
}
// should only accept `async`
#[maybe_async::test(feature="is_sync", unknown(not(feature="is_sync"), async_std::test))]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
fn main() {
}

View File

@@ -1,5 +0,0 @@
error: Unknown path: `unknown`, must be `async`
--> tests/ui/test_fail/02-unknown-path.rs:9:40
|
9 | #[maybe_async::test(feature="is_sync", unknown(not(feature="is_sync"), async_std::test))]
| ^^^^^^^

View File

@@ -1,16 +0,0 @@
use maybe_async::maybe_async;
#[maybe_async]
async fn async_fn() -> bool {
true
}
#[maybe_async::test(feature="is_sync", async(feature="async", async_std::test, added))]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
fn main() {
}

View File

@@ -1,5 +0,0 @@
error: Must pass two metas or string literals like `async(condition, async_test_macro)`, you passed 3 metas.
--> tests/ui/test_fail/03-async-gt2.rs:8:40
|
8 | #[maybe_async::test(feature="is_sync", async(feature="async", async_std::test, added))]
| ^^^^^

Some files were not shown because too many files have changed in this diff Show More