What’s new in Pigweed: May 2026#
Highlights:
Cryptography and security: ChaCha20 cipher support in pw_crypto -
pw_cryptonow supports the ChaCha20 stream cipher.C++ data structures and utilities: - New pw_enum module
pw_enumprovides automatic stringification and tokenization of C++ enums.Async and concurrency: Notification channels and void coroutines in pw_async2 - Improved support for
voidchannels, which can be used to signal tasks without transmitting data, as well as usingco_awaitfor coroutines that returnvoid.Developer tools: Log file viewing in pw_console -
pw_consolenow directly supports opening and viewing log files (including zip archives).
Async and concurrency#
Notification channels and void coroutines in pw_async2#
Added support for void channels (such as MpmcChannelHandle<void>) in
pw_async2, which function as notification channels to signal tasks without
transmitting data. Additionally, you can now use co_await with coroutines
that return void. CLs: 1,
2
#include "pw_async2/channel.h"
#include "pw_async2/coro.h"
using namespace pw::async2;
// A coroutine that waits for a notification on a receiver
Coro<void> WaitAndNotify(Receiver<void> receiver) {
// co_awaiting a void-resolving future (like Receive() on a void receiver)
// is now supported.
co_await receiver.Receive();
co_return;
}
Coro<void> Task(Allocator& alloc) {
// Create a void (notification) channel
auto channel_opt = CreateMpmcChannel<void>(alloc, 2);
MpmcChannelHandle<void>& handle = *channel_opt;
Sender<void> sender = handle.CreateSender();
Receiver<void> receiver = handle.CreateReceiver();
// Send a notification (no data value needed)
sender.TrySend().IgnoreError();
// Run the waiter
co_await WaitAndNotify(
std::move(receiver)); // or handle.CreateReceiver() directly
}
Thread safety fixes in pw_async2#
Fixed a data race in ValueFuture<void> by ensuring the internal lock is
held during move and query operations. Also ensured channel futures hold the
lock when moving a FutureCore object to prevent data races, and corrected
channel future move behavior when the associated channel is closed. CLs: 1, 2, 3
Bluetooth#
Security fixes in pw_bluetooth_sapphire#
Fixed two security vulnerabilities in pw_bluetooth_sapphire:
Resolved a Use-After-Free in
A2dpOffloadManagerwhere a dangling callback was retained after link destruction.Added validation to the Secure Connections pairing phase to reject mirrored or negated peer public keys (preventing passkey reflection attacks).
LE Audio Isochronous Channels support in pw_bluetooth_sapphire#
Added initial support for Bluetooth LE Audio Isochronous Channels in
pw_bluetooth_sapphire. This includes implementing Connected Isochronous
Groups (CIG) parameter configuration (SetParams) with peer SCA validation,
Connected Isochronous Stream (CIS) creation, introducing the
IsoGroupManager to manage these resources, and improving the Fuchsia FIDL
IsoStreamServer lifetime management. CLs: 1,
2, 3, 4, 5, 6, 7
Passthrough support for large L2CAP PDUs in pw_bluetooth_proxy#
Updated pw_bluetooth_proxy to pass through L2CAP Protocol Data Units (PDUs)
larger than the recombination buffer limit (typically 2KB) directly to the host,
fragment by fragment. This allows supporting profiles that require large maximum
transmission units (MTUs) (like object transfer or OTA updates) without
increasing the proxy’s memory footprint. CLs: 1
C++ data structures and utilities#
New pw_enum module#
The new pw_enum module supports automatic stringification and tokenization
of C++ enums. As part of this introduction, the EnumToString helper was
moved from pw_tokenizer to pw_enum. Additionally, a code generation tool
was introduced to automatically generate pw_enum stringification helper
definitions directly from C++ header files, reducing manual boilerplate. CLs:
1, 2
#include "pw_enum/generate.h"
namespace my::app {
enum class ConnectionState : uint8_t {
kDisconnected = 0,
kConnecting,
kConnected,
};
} // namespace my::app
// Register the enum for code generation
PW_ENUM(my::app::ConnectionState, kDisconnected, kConnecting, kConnected);
load("//pw_enum:pw_cc_enum.bzl", "pw_cc_enum")
pw_cc_enum(
name = "connection_state_enum",
hdrs = ["status.h"],
)
#include "pw_enum/to_string.h"
#include "pw_log/log.h"
void LogState(my::app::ConnectionState state) {
PW_LOG_INFO("State changed to: %s", pw::EnumToString(state));
}
Lifetime bound checks for pw::FunctionRef#
Introduced PW_ATTRIBUTE_LIFETIME_BOUND to pw::FunctionRef constructors.
This allows Clang to detect at compile time when a FunctionRef is
initialized with a temporary object (such as a lambda) that does not live long
enough, preventing dangling pointers. CLs: 1
#include "pw_function/function_ref.h"
void CallTwice(pw::FunctionRef<void()> callback) {
callback();
callback();
}
void Run() {
// OK: Temporary lambda passed to a function call. The FunctionRef
// argument does not outlive the temporary lambda.
CallTwice([]() { /* ... */ });
// Warning/Error with Clang: The FunctionRef outlives the temporary lambda.
// pw::FunctionRef<void()> ref = []() {};
}
Cryptography and security#
ChaCha20 cipher support in pw_crypto#
Added support for the ChaCha20 stream cipher to pw_crypto. CLs:
1
#include "pw_crypto/chacha20.h"
#include "pw_status/status.h"
void EncryptDecrypt(pw::ConstByteSpan key,
pw::ConstByteSpan nonce,
pw::ConstByteSpan plaintext,
pw::ByteSpan ciphertext) {
// Encrypt / Decrypt (ChaCha20 is symmetric, same operation for both)
pw::Status status =
pw::crypto::chacha20::Crypt(key, nonce, plaintext, ciphertext);
if (!status.ok()) {
// Handle error
}
}
Developer tools#
Log file viewing in pw_console#
You can now open and view text files directly in pw_console, including files
compressed in .zip archives. Users can merge multiple log files into a
single chronological view or open them in separate tabs, with progress bars
showing load status. CLs: 1
# Open multiple log files in separate tabs
pw-console --open-files log1.txt log2.txt
# Open multiple log files and merge them into a single chronological view
pw-console --open-files log1.txt log2.txt --merge-open-files
# Open an Android bugreport zip file directly
pw-console --open-bugreport bugreport.zip
Downstream module creation with pw_module#
Enabled the pw module create command for downstream projects. Downstream
developers can now use this tool to generate all the required boilerplate for a
new module, including source files, tests, documentation, and build files for
GN, Bazel, and CMake, with custom prefixes. CLs: 1
# Create a new module named "app_sensors" with support for Bazel and GN
# (automatically deduces prefix "app")
pw module create --build-systems bazel,gn --languages cc app_sensors
Performance and usability improvements to pw_ide#
Compile database generation was optimized by replacing pathlib.Path with
raw string or os.path operations and batching bazel info calls,
resulting in up to a 6x speedup. Usability improvements include real-time Bazel
progress streaming, better clangd header support by no longer filtering out
.h and .hpp files, and improved symlink management to correctly handle
and replace broken symlinks in the workspace. CLs: 1, 2, 3, 4, 5
Kernel#
Vectored I/O for channel transactions in pw_kernel#
Channel transactions now support vectored I/O (scatter-gather), allowing userspace applications to pass arrays of slices for IPC transactions, reducing copying overhead. CLs: 1
use userspace::syscall;
use userspace::time::Instant;
fn TransactVectored(channel_handle: u32) -> Result<(), pw_status::Error> {
let header: &[u8] = b"MSG_TYPE_1";
let body: &[u8] = b"Payload data...";
// Scatter-gather send buffers
let send_buffers = [header, body];
let mut recv_buffer = [0u8; 128];
// channel_transact accepts arrays of slices for vectored I/O
syscall::channel_transact(
channel_handle,
&send_buffers,
&mut recv_buffer,
Instant::MAX,
)?;
Ok(())
}
Updated join and terminate syscalls#
The process and thread join syscalls (process_join and thread_join)
have been unified into a single syscall: task_join. Likewise,
process_terminate and thread_terminate were unified into
task_terminate. CLs: 1
use userspace::syscall;
fn ManageTask(task_handle: u32) -> Result<(), pw_status::Error> {
// Terminate a task (thread or process)
syscall::task_terminate(task_handle)?;
// Wait for it to exit
let _exit_status = syscall::task_join(task_handle)?;
Ok(())
}
Logging, debugging, and crash handling#
Compile-time format string concatenation in Rust pw_log#
Added support for compile-time format string concatenation using the
PW_FMT_CONCAT operator in Rust pw_log macros. This enables writing
custom wrapper macros that can prepend prefixes (like module names) to format
strings before tokenization or logging. CLs: 1
use pw_log::{log, LogLevel};
// Define a wrapper macro that prepends a module prefix
macro_rules! my_info_log {
($format_string:literal $(, $args:expr)* $(,)? ) => {
// Concatenate string literals at compile time using PW_FMT_CONCAT
log!(LogLevel::Info, "[MyModule] " PW_FMT_CONCAT $format_string $(, $args)*)
};
}
fn main() {
// Logs: [MyModule] The answer is 42.
my_info_log!("The answer is {}.", 42);
}
RPC#
Concurrency and lock reduction in pw_rpc#
When dynamic allocation is enabled, packet encoding now uses local,
stack-allocated buffers instead of a single global buffer, reducing lock
contention. Additionally, introduced the PW_RPC_LOCKLESS_CHANNEL_SEND
configuration option, which allows releasing the global RPC lock before calling
ChannelOutput::Send, further reducing lock hold times. To support this
dynamic allocation path for complex nested messages, the
PW_RPC_PWPB_SCRATCH_BUFFER_SIZE_BYTES macro was added to allow customizing
the scratch buffer size used when calculating encoded message sizes. CLs: 1, 2, 3, 4
Tokenization#
Improved collision handling and correctness in pw_tokenizer#
Updated Python and C++ detokenization logic to prevent silent, incorrect
decoding when token collisions occur. It now only detokenizes if there is an
unambiguous winner, and ok() correctly reports status when collision
resolution succeeds. Also fixed a Python regex bug that confused nested Base64
tokens with Base10/16 tokens. CLs: 1, 2, 3