Add TaskBundle abstraction to fan out heavyweight ops
Generating blinded tokens and unblinding tokens are both fairly CPU-intensive, so it could be beneficial to perform those operations in parallel when operating on batches of tokens. By providing alternate implementations of TaskBundle, users can inject their preferred executor (e.g. by posting to a TaskRunner in Chromium, or using a pool of Fibers in a Scaffolding server).
PiperOrigin-RevId: 810903802
diff --git a/build/source_list.bzl b/build/source_list.bzl
index ae403e1..1474ea5 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -1748,6 +1748,8 @@
"blind_sign_auth/blind_sign_message_response.h",
"blind_sign_auth/blind_sign_tracing_hooks.h",
"blind_sign_auth/cached_blind_sign_auth.h",
+ "blind_sign_auth/direct_task_bundle.h",
+ "blind_sign_auth/task_bundle.h",
"blind_sign_auth/test_tools/mock_blind_sign_auth_interface.h",
"blind_sign_auth/test_tools/mock_blind_sign_message_interface.h",
]
@@ -1756,6 +1758,7 @@
"blind_sign_auth/blind_sign_auth_test_data.cc",
"blind_sign_auth/blind_sign_message_response.cc",
"blind_sign_auth/cached_blind_sign_auth.cc",
+ "blind_sign_auth/direct_task_bundle.cc",
]
blind_sign_auth_tests_hdrs = [
]
diff --git a/build/source_list.gni b/build/source_list.gni
index dfd4993..d388572 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -1753,6 +1753,8 @@
"src/quiche/blind_sign_auth/blind_sign_message_response.h",
"src/quiche/blind_sign_auth/blind_sign_tracing_hooks.h",
"src/quiche/blind_sign_auth/cached_blind_sign_auth.h",
+ "src/quiche/blind_sign_auth/direct_task_bundle.h",
+ "src/quiche/blind_sign_auth/task_bundle.h",
"src/quiche/blind_sign_auth/test_tools/mock_blind_sign_auth_interface.h",
"src/quiche/blind_sign_auth/test_tools/mock_blind_sign_message_interface.h",
]
@@ -1761,6 +1763,7 @@
"src/quiche/blind_sign_auth/blind_sign_auth_test_data.cc",
"src/quiche/blind_sign_auth/blind_sign_message_response.cc",
"src/quiche/blind_sign_auth/cached_blind_sign_auth.cc",
+ "src/quiche/blind_sign_auth/direct_task_bundle.cc",
]
blind_sign_auth_tests_hdrs = [
diff --git a/build/source_list.json b/build/source_list.json
index ee36c44..c2d6e90 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -1752,6 +1752,8 @@
"quiche/blind_sign_auth/blind_sign_message_response.h",
"quiche/blind_sign_auth/blind_sign_tracing_hooks.h",
"quiche/blind_sign_auth/cached_blind_sign_auth.h",
+ "quiche/blind_sign_auth/direct_task_bundle.h",
+ "quiche/blind_sign_auth/task_bundle.h",
"quiche/blind_sign_auth/test_tools/mock_blind_sign_auth_interface.h",
"quiche/blind_sign_auth/test_tools/mock_blind_sign_message_interface.h"
],
@@ -1759,7 +1761,8 @@
"quiche/blind_sign_auth/blind_sign_auth.cc",
"quiche/blind_sign_auth/blind_sign_auth_test_data.cc",
"quiche/blind_sign_auth/blind_sign_message_response.cc",
- "quiche/blind_sign_auth/cached_blind_sign_auth.cc"
+ "quiche/blind_sign_auth/cached_blind_sign_auth.cc",
+ "quiche/blind_sign_auth/direct_task_bundle.cc"
],
"blind_sign_auth_tests_hdrs": [
diff --git a/quiche/blind_sign_auth/blind_sign_auth.cc b/quiche/blind_sign_auth/blind_sign_auth.cc
index 67fe49b..bcf584f 100644
--- a/quiche/blind_sign_auth/blind_sign_auth.cc
+++ b/quiche/blind_sign_auth/blind_sign_auth.cc
@@ -263,42 +263,47 @@
// Create tokens using blinded signatures.
std::vector<BlindSignToken> tokens_vec;
+ tokens_vec.resize(sign_response.blinded_token_signature_size());
for (int i = 0; i < sign_response.blinded_token_signature_size(); i++) {
- std::string unescaped_blinded_sig;
- if (!absl::Base64Unescape(sign_response.blinded_token_signature()[i],
- &unescaped_blinded_sig)) {
- QUICHE_LOG(WARNING) << "Failed to unescape blinded signature";
- std::move(callback)(
- absl::InternalError("Failed to unescape blinded signature"));
- return;
- }
+ task_bundle_->Add([&, i]() {
+ std::string unescaped_blinded_sig;
+ if (!absl::Base64Unescape(sign_response.blinded_token_signature()[i],
+ &unescaped_blinded_sig)) {
+ QUICHE_LOG(WARNING) << "Failed to unescape blinded signature";
+ return absl::InternalError("Failed to unescape blinded signature");
+ }
- absl::StatusOr<Token> token =
- privacy_pass_clients[i]->FinalizeToken(unescaped_blinded_sig);
- if (!token.ok()) {
- QUICHE_LOG(WARNING) << "Failed to finalize token: " << token.status();
- std::move(callback)(absl::InternalError("Failed to finalize token"));
- return;
- }
+ absl::StatusOr<Token> token =
+ privacy_pass_clients[i]->FinalizeToken(unescaped_blinded_sig);
+ if (!token.ok()) {
+ QUICHE_LOG(WARNING) << "Failed to finalize token: " << token.status();
+ return absl::InternalError("Failed to finalize token");
+ }
- absl::StatusOr<std::string> marshaled_token = MarshalToken(*token);
- if (!marshaled_token.ok()) {
- QUICHE_LOG(WARNING) << "Failed to marshal token: "
- << marshaled_token.status();
- std::move(callback)(absl::InternalError("Failed to marshal token"));
- return;
- }
+ absl::StatusOr<std::string> marshaled_token = MarshalToken(*token);
+ if (!marshaled_token.ok()) {
+ QUICHE_LOG(WARNING)
+ << "Failed to marshal token: " << marshaled_token.status();
+ return absl::InternalError("Failed to marshal token");
+ }
- PrivacyPassTokenData privacy_pass_token_data;
- privacy_pass_token_data.mutable_token()->assign(
- ConvertBase64ToWebSafeBase64(absl::Base64Escape(*marshaled_token)));
- privacy_pass_token_data.mutable_encoded_extensions()->assign(
- ConvertBase64ToWebSafeBase64(
- absl::Base64Escape(pp_context.public_metadata_extensions_str)));
- privacy_pass_token_data.set_use_case_override(pp_context.use_case);
- tokens_vec.push_back(BlindSignToken{
- privacy_pass_token_data.SerializeAsString(),
- pp_context.public_metadata_expiry_time, pp_context.geo_hint});
+ PrivacyPassTokenData privacy_pass_token_data;
+ privacy_pass_token_data.mutable_token()->assign(
+ ConvertBase64ToWebSafeBase64(absl::Base64Escape(*marshaled_token)));
+ privacy_pass_token_data.mutable_encoded_extensions()->assign(
+ ConvertBase64ToWebSafeBase64(
+ absl::Base64Escape(pp_context.public_metadata_extensions_str)));
+ privacy_pass_token_data.set_use_case_override(pp_context.use_case);
+ tokens_vec[i] = BlindSignToken{
+ privacy_pass_token_data.SerializeAsString(),
+ pp_context.public_metadata_expiry_time, pp_context.geo_hint};
+ return absl::OkStatus();
+ });
+ }
+ absl::Status status = task_bundle_->Join();
+ if (!status.ok()) {
+ std::move(callback)(status);
+ return;
}
std::move(callback)(absl::Span<BlindSignToken>(tokens_vec));
@@ -496,44 +501,48 @@
// Create tokens using blinded signatures.
std::vector<BlindSignToken> tokens_vec;
+ tokens_vec.resize(sign_response.blinded_token_signatures_size());
for (int i = 0; i < sign_response.blinded_token_signatures_size(); i++) {
- std::string unescaped_blinded_sig;
- if (!absl::Base64Unescape(sign_response.blinded_token_signatures()[i],
- &unescaped_blinded_sig)) {
- QUICHE_LOG(WARNING) << "Failed to unescape blinded signature";
- std::move(callback)(
- absl::InternalError("Failed to unescape blinded signature"));
- return;
- }
+ task_bundle_->Add([&, i]() {
+ std::string unescaped_blinded_sig;
+ if (!absl::Base64Unescape(sign_response.blinded_token_signatures()[i],
+ &unescaped_blinded_sig)) {
+ QUICHE_LOG(WARNING) << "Failed to unescape blinded signature";
+ return absl::InternalError("Failed to unescape blinded signature");
+ }
- absl::StatusOr<Token> token =
- privacy_pass_clients[i]->FinalizeToken(unescaped_blinded_sig);
- if (!token.ok()) {
- QUICHE_LOG(WARNING) << "Failed to finalize token: " << token.status();
- std::move(callback)(absl::InternalError("Failed to finalize token"));
- return;
- }
+ absl::StatusOr<Token> token =
+ privacy_pass_clients[i]->FinalizeToken(unescaped_blinded_sig);
+ if (!token.ok()) {
+ QUICHE_LOG(WARNING) << "Failed to finalize token: " << token.status();
+ return absl::InternalError("Failed to finalize token");
+ }
- absl::StatusOr<std::string> marshaled_token = MarshalToken(*token);
- if (!marshaled_token.ok()) {
- QUICHE_LOG(WARNING) << "Failed to marshal token: "
- << marshaled_token.status();
- std::move(callback)(absl::InternalError("Failed to marshal token"));
- return;
- }
+ absl::StatusOr<std::string> marshaled_token = MarshalToken(*token);
+ if (!marshaled_token.ok()) {
+ QUICHE_LOG(WARNING)
+ << "Failed to marshal token: " << marshaled_token.status();
+ return absl::InternalError("Failed to marshal token");
+ }
- PrivacyPassTokenData privacy_pass_token_data;
- privacy_pass_token_data.mutable_token()->assign(
- ConvertBase64ToWebSafeBase64(absl::Base64Escape(*marshaled_token)));
- privacy_pass_token_data.mutable_encoded_extensions()->assign(
- ConvertBase64ToWebSafeBase64(
- absl::Base64Escape(pp_context.public_metadata_extensions_str)));
- privacy_pass_token_data.set_use_case_override(pp_context.use_case);
- tokens_vec.push_back(BlindSignToken{
- privacy_pass_token_data.SerializeAsString(),
- pp_context.public_metadata_expiry_time, pp_context.geo_hint});
+ PrivacyPassTokenData privacy_pass_token_data;
+ privacy_pass_token_data.mutable_token()->assign(
+ ConvertBase64ToWebSafeBase64(absl::Base64Escape(*marshaled_token)));
+ privacy_pass_token_data.mutable_encoded_extensions()->assign(
+ ConvertBase64ToWebSafeBase64(
+ absl::Base64Escape(pp_context.public_metadata_extensions_str)));
+ privacy_pass_token_data.set_use_case_override(pp_context.use_case);
+ tokens_vec[i] = BlindSignToken{
+ privacy_pass_token_data.SerializeAsString(),
+ pp_context.public_metadata_expiry_time, pp_context.geo_hint};
+ return absl::OkStatus();
+ });
}
-
+ absl::Status status = task_bundle_->Join();
+ if (!status.ok()) {
+ std::move(callback)(status);
+ return;
+ }
std::move(callback)(absl::Span<BlindSignToken>(tokens_vec));
}
@@ -641,33 +650,40 @@
absl::string_view token_challenge_str, absl::string_view token_key_id,
const anonymous_tokens::Extensions& extensions) {
GeneratedTokenRequests result;
- result.privacy_pass_clients.reserve(num_tokens);
- result.privacy_pass_blinded_tokens_b64.reserve(num_tokens);
+ result.privacy_pass_clients.resize(num_tokens);
+ result.privacy_pass_blinded_tokens_b64.resize(num_tokens);
QuicheRandom* random = QuicheRandom::GetInstance();
for (int i = 0; i < num_tokens; i++) {
- absl::StatusOr<std::unique_ptr<PrivacyPassRsaBssaPublicMetadataClient>>
- client = PrivacyPassRsaBssaPublicMetadataClient::Create(rsa_public_key);
- if (!client.ok()) {
- return absl::InternalError(
- absl::StrCat("Failed to create Privacy Pass client: ",
- client.status().ToString()));
- }
+ task_bundle_->Add([&, i]() {
+ auto client =
+ PrivacyPassRsaBssaPublicMetadataClient::Create(rsa_public_key);
+ if (!client.ok()) {
+ return absl::InternalError(
+ absl::StrCat("Failed to create Privacy Pass client: ",
+ client.status().ToString()));
+ }
- std::string nonce_rand(32, '\0');
- random->RandBytes(nonce_rand.data(), nonce_rand.size());
+ std::string nonce_rand(32, '\0');
+ random->RandBytes(nonce_rand.data(), nonce_rand.size());
- absl::StatusOr<ExtendedTokenRequest> extended_token_request =
- (*client)->CreateTokenRequest(token_challenge_str, nonce_rand,
- token_key_id, extensions);
- if (!extended_token_request.ok()) {
- return absl::InternalError(
- absl::StrCat("Failed to create ExtendedTokenRequest: ",
- extended_token_request.status().ToString()));
- }
- result.privacy_pass_clients.push_back(*std::move(client));
- result.privacy_pass_blinded_tokens_b64.push_back(absl::Base64Escape(
- extended_token_request->request.blinded_token_request));
+ absl::StatusOr<ExtendedTokenRequest> extended_token_request =
+ (*client)->CreateTokenRequest(token_challenge_str, nonce_rand,
+ token_key_id, extensions);
+ if (!extended_token_request.ok()) {
+ return absl::InternalError(
+ absl::StrCat("Failed to create ExtendedTokenRequest: ",
+ extended_token_request.status().ToString()));
+ }
+ result.privacy_pass_clients[i] = *std::move(client);
+ result.privacy_pass_blinded_tokens_b64[i] = absl::Base64Escape(
+ extended_token_request->request.blinded_token_request);
+ return absl::OkStatus();
+ });
+ }
+ absl::Status status = task_bundle_->Join();
+ if (!status.ok()) {
+ return status;
}
return result;
}
diff --git a/quiche/blind_sign_auth/blind_sign_auth.h b/quiche/blind_sign_auth/blind_sign_auth.h
index fc3ddcf..15accf7 100644
--- a/quiche/blind_sign_auth/blind_sign_auth.h
+++ b/quiche/blind_sign_auth/blind_sign_auth.h
@@ -23,6 +23,8 @@
#include "quiche/blind_sign_auth/blind_sign_message_interface.h"
#include "quiche/blind_sign_auth/blind_sign_message_response.h"
#include "quiche/blind_sign_auth/blind_sign_tracing_hooks.h"
+#include "quiche/blind_sign_auth/direct_task_bundle.h"
+#include "quiche/blind_sign_auth/task_bundle.h"
#include "quiche/common/platform/api/quiche_export.h"
namespace quiche {
@@ -31,8 +33,11 @@
class QUICHE_EXPORT BlindSignAuth : public BlindSignAuthInterface {
public:
explicit BlindSignAuth(std::unique_ptr<BlindSignMessageInterface> fetcher,
- privacy::ppn::BlindSignAuthOptions auth_options)
+ privacy::ppn::BlindSignAuthOptions auth_options,
+ std::unique_ptr<TaskBundle> task_bundle =
+ std::make_unique<DirectTaskBundle>())
: owned_fetcher_(std::move(fetcher)),
+ task_bundle_(std::move(task_bundle)),
auth_options_(std::move(auth_options)) {
fetcher_ = owned_fetcher_.get();
}
@@ -40,8 +45,12 @@
// TODO: b/441077019 - Remove once IpProtectionTokenDirectFetcher updates to
// transfer pointer ownership.
explicit BlindSignAuth(BlindSignMessageInterface* fetcher,
- privacy::ppn::BlindSignAuthOptions auth_options)
- : fetcher_(fetcher), auth_options_(std::move(auth_options)) {}
+ privacy::ppn::BlindSignAuthOptions auth_options,
+ std::unique_ptr<TaskBundle> task_bundle =
+ std::make_unique<DirectTaskBundle>())
+ : fetcher_(fetcher),
+ task_bundle_(std::move(task_bundle)),
+ auth_options_(std::move(auth_options)) {}
// Returns signed unblinded tokens, their expiration time, and their geo in a
// callback.
@@ -162,6 +171,7 @@
// IpProtectionTokenDirectFetcher updates to transfer pointer ownership.
BlindSignMessageInterface* fetcher_ = nullptr;
std::unique_ptr<BlindSignMessageInterface> owned_fetcher_;
+ std::unique_ptr<TaskBundle> task_bundle_;
privacy::ppn::BlindSignAuthOptions auth_options_;
};
diff --git a/quiche/blind_sign_auth/direct_task_bundle.cc b/quiche/blind_sign_auth/direct_task_bundle.cc
new file mode 100644
index 0000000..7452b12
--- /dev/null
+++ b/quiche/blind_sign_auth/direct_task_bundle.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2025 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/blind_sign_auth/direct_task_bundle.h"
+
+#include <utility>
+
+#include "absl/status/status.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_callbacks.h"
+
+namespace quiche {
+
+DirectTaskBundle::~DirectTaskBundle() {
+ QUICHE_DCHECK(tasks_.empty())
+ << "DirectTaskBundle destroyed with pending tasks";
+};
+
+void DirectTaskBundle::Add(SingleUseCallback<absl::Status()> task) {
+ tasks_.push_back(std::move(task));
+}
+
+absl::Status DirectTaskBundle::Join() {
+ absl::Status status = absl::OkStatus();
+ for (auto& task : tasks_) {
+ status = std::move(task)();
+ if (!status.ok()) {
+ break;
+ }
+ }
+ tasks_.clear();
+ return status;
+}
+
+} // namespace quiche
diff --git a/quiche/blind_sign_auth/direct_task_bundle.h b/quiche/blind_sign_auth/direct_task_bundle.h
new file mode 100644
index 0000000..a55079d
--- /dev/null
+++ b/quiche/blind_sign_auth/direct_task_bundle.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2025 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_BLIND_SIGN_AUTH_DIRECT_TASK_BUNDLE_H_
+#define QUICHE_BLIND_SIGN_AUTH_DIRECT_TASK_BUNDLE_H_
+
+#include <vector>
+
+#include "absl/status/status.h"
+#include "quiche/blind_sign_auth/task_bundle.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/quiche_callbacks.h"
+
+namespace quiche {
+
+// A TaskBundle that executes tasks directly on the calling thread in the order
+// they were added.
+class QUICHE_EXPORT DirectTaskBundle : public TaskBundle {
+ public:
+ ~DirectTaskBundle() override;
+
+ void Add(SingleUseCallback<absl::Status()> task) override;
+ absl::Status Join() override;
+
+ private:
+ std::vector<SingleUseCallback<absl::Status()>> tasks_;
+};
+
+} // namespace quiche
+
+#endif // QUICHE_BLIND_SIGN_AUTH_DIRECT_TASK_BUNDLE_H_
diff --git a/quiche/blind_sign_auth/task_bundle.h b/quiche/blind_sign_auth/task_bundle.h
new file mode 100644
index 0000000..a5e8371
--- /dev/null
+++ b/quiche/blind_sign_auth/task_bundle.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2025 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_BLIND_SIGN_AUTH_TASK_BUNDLE_H_
+#define QUICHE_BLIND_SIGN_AUTH_TASK_BUNDLE_H_
+
+#include "absl/status/status.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/quiche_callbacks.h"
+
+namespace quiche {
+
+// Interface for executing multiple independent tasks, possibly in parallel.
+class QUICHE_EXPORT TaskBundle {
+ public:
+ virtual ~TaskBundle() = default;
+
+ // Enqueues a task to be executed.
+ virtual void Add(SingleUseCallback<absl::Status()> task) = 0;
+
+ // Blocks until all added tasks have completed.
+ // Returns OK if all tasks completed successfully, or the first error
+ // encountered otherwise.
+ virtual absl::Status Join() = 0;
+};
+
+} // namespace quiche
+
+#endif // QUICHE_BLIND_SIGN_AUTH_TASK_BUNDLE_H_