From 370960f4cf1796490532835f6da78be5da4eba71 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 23:54:06 +0000 Subject: [PATCH 1/3] docs: remove the simd feature and document table-driven packed encoding whatsapp-rust#1262 drops the `simd` Cargo feature and portable_simd entirely; default features now build on stable Rust with no toolchain workaround needed. Update installation.mdx (EN/PT) to drop the nightly prerequisite, the simd feature row, and the disable-simd instructions, and rewrite the packed nibble/hex encoding section in advanced/binary-protocol.mdx to describe the NIBBLE_ENC/HEX_ENC lookup tables that replaced the match ladders and SIMD fast path. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_018ixTnvivkbqYADYM2hoMMj --- advanced/binary-protocol.mdx | 64 ++++++++++++++---------------------- installation.mdx | 59 ++++----------------------------- pt/installation.mdx | 61 ++++------------------------------ 3 files changed, 37 insertions(+), 147 deletions(-) diff --git a/advanced/binary-protocol.mdx b/advanced/binary-protocol.mdx index ffca7abc..930f7eef 100644 --- a/advanced/binary-protocol.mdx +++ b/advanced/binary-protocol.mdx @@ -454,6 +454,8 @@ Location: `wacore/binary/src/encoder.rs:227-237` ### Packed Encoding +Nibble and hex packing share one code path, `write_packed_bytes`: an ASCII→nibble lookup table (`NIBBLE_ENC` or `HEX_ENC`, picked by `data_type`) maps each input byte to its packed nibble, pairs are packed two at a time into a stack buffer, and validity is checked once via an OR accumulator (`seen`) instead of per pair — that is what lets the pair loop unroll. + #### Nibble packing (numeric strings) Strings containing only digits, dash, and dot are packed into 4 bits per character: @@ -466,58 +468,42 @@ Strings containing only digits, dash, and dot are packed into 4 bits per charact pub const PACKED_MAX: u8 = 127; // Max length for packed/token strings -fn pack_nibble(value: u8) -> u8 { - match value { - b'-' => 10, - b'.' => 11, - 0 => 15, // Padding - c if c.is_ascii_digit() => c - b'0', - _ => panic!("Invalid nibble"), - } -} +/// ASCII to nibble for `NIBBLE_8`: digits plus the two punctuation characters +/// a phone number can carry. +static NIBBLE_ENC: [u8; 256] = { + let mut table = [PACK_INVALID; 256]; + // '0'..='9' → 0..=9 + table[b'-' as usize] = 10; + table[b'.' as usize] = 11; + table[0] = 15; // padding for an odd-length string + table +}; ``` -Location: `wacore/binary/src/encoder.rs:769-777` +Location: `wacore/binary/src/encoder.rs:32-43` #### Hex Packing -Uppercase hex strings (0-9, A-F) are packed into 4 bits per character: +Uppercase hex strings (0-9, A-F) are packed into 4 bits per character, against `HEX_ENC` instead of `NIBBLE_ENC`: ```rust // Input: "DEADBEEF" // Packed: 0xDE, 0xAD, 0xBE, 0xEF -fn pack_hex(value: u8) -> u8 { - match value { - c if c.is_ascii_digit() => c - b'0', - c if (b'A'..=b'F').contains(&c) => 10 + (c - b'A'), - 0 => 15, // Padding - _ => panic!("Invalid hex"), - } -} +/// ASCII to nibble, the inverse of the decoder's `HEX_PAIRS`. +static HEX_ENC: [u8; 256] = { + let mut table = [PACK_INVALID; 256]; + // '0'..='9' → 0..=9, 'A'..='F' → 10..=15 + table[0] = 15; // padding for an odd-length string + table +}; ``` -Location: `wacore/binary/src/encoder.rs:780-787` - -#### SIMD Optimization - -The encoder uses SIMD instructions for fast packing of long strings: +Location: `wacore/binary/src/encoder.rs:14-28` -```rust -while input_bytes.len() >= 16 { - let input = u8x16::from_slice(chunk); - let indices = input.saturating_sub(nibble_base); - let nibbles = lookup.swizzle_dyn(indices); - - let (evens, odds) = nibbles.deinterleave( - nibbles.rotate_elements_left::<1>() - ); - let packed = (evens << Simd::splat(4)) | odds; - self.write_raw_bytes(&packed.to_array()[..8])?; -} -``` - -Location: `wacore/binary/src/encoder.rs:809-824` + + Packing used to run through per-character `match` ladders (`pack_nibble`/`pack_hex`) reached via a `fn` pointer, and for a while carried a `portable_simd` fast path for long strings on top of that. Both are gone: measurement showed the vector path only ever beat the `match` ladders, not a lookup table, and lost to the table at every string length tested — `HEX_PAIRS[byte]` (the decoder's mirror-image table) is one 2-byte load, and a shuffle/interleave/store sequence doesn't beat that. The two lookup tables above replaced all three call paths; an exhaustive test (`encode_tables_match_the_ladders_they_replaced`) checks every one of the 256 byte values against the original `match` ladders so the tables can't silently drift from the encoding they replaced. + ### JID Encoding diff --git a/installation.mdx b/installation.mdx index 9ae5df34..472f2058 100644 --- a/installation.mdx +++ b/installation.mdx @@ -7,8 +7,7 @@ description: Add whatsapp-rust to your Rust project Before installing whatsapp-rust, ensure you have: -- **Rust nightly** (default) — required only by the default `simd` feature, which uses the unstable `portable_simd` API. The project pins `nightly-2026-06-16` via `rust-toolchain.toml`. -- **Rust 1.94 or newer** — the workspace MSRV. Edition 2024 itself needs no nightly, so stable works once `simd` is off. See [Using stable Rust](#using-stable-rust). +- **Rust 1.94 or newer** — the workspace MSRV, and all default features build on **stable Rust**. See [Using stable Rust](#using-stable-rust). - **Cargo** package manager @@ -55,9 +54,7 @@ tokio = { version = "1.53", features = ["macros", "rt-multi-thread"] } If you need to declare a sibling crate explicitly (for example to enable a crate-specific feature flag), you can still add it individually. The full multi-crate form: - - -```toml Nightly (default) +```toml Cargo.toml [dependencies] whatsapp-rust = "0.7" # Only needed for crate-specific features not exposed through whatsapp-rust: @@ -69,22 +66,6 @@ whatsapp-rust = "0.7" tokio = { version = "1.53", features = ["macros", "rt-multi-thread"] } ``` -```toml Stable Rust -[dependencies] -whatsapp-rust = { version = "0.7", default-features = false, features = [ - "sqlite-storage", - "tokio-transport", - "tokio-runtime", - "ureq-client", - "tokio-native", - "signal", -] } -wacore = { version = "0.7", default-features = false } -tokio = { version = "1.53", features = ["macros", "rt-multi-thread"] } -``` - - - ## Feature flags whatsapp-rust supports several optional features: @@ -96,7 +77,6 @@ whatsapp-rust supports several optional features: | `tokio-transport` | Tokio WebSocket transport | ✅ Yes | | `ureq-client` | Ureq HTTP client | ✅ Yes | | `sqlite-storage` | SQLite storage backend | ✅ Yes | -| `simd` | SIMD-optimized binary protocol encoding/decoding (**requires nightly Rust**) | ✅ Yes | | `signal` | Unix signal handling (graceful shutdown on SIGTERM/Ctrl+C) | ✅ Yes | | `tracing` | Emit `tracing` spans/events across connect, send, receive, IQ, app state, pairing, media, and session flows. See [Observability](/advanced/observability) | ❌ No | | `tracing-pii` | Render raw phone numbers in `tracing` fields instead of redacted `pn#`. Local debugging only — never enable in production | ❌ No | @@ -192,38 +172,11 @@ See [Chat Store](/api/chat-store) for the full API. ## Using stable Rust -whatsapp-rust uses Rust **edition 2024** and declares an MSRV of **1.94**, both of which stable Rust supports. The one nightly-only piece is the default `simd` feature, which uses Rust's unstable `portable_simd` API for optimized binary protocol encoding/decoding. The project itself pins `nightly-2026-06-16` via `rust-toolchain.toml`. - -To compile on **stable Rust**, disable the `simd` feature by setting `default-features = false`. You must do this on **both** `whatsapp-rust` and `wacore` — otherwise Cargo's [feature unification](https://doc.rust-lang.org/cargo/reference/features.html#feature-unification) will re-enable SIMD through the `wacore` dependency: +whatsapp-rust uses Rust **edition 2024** and declares an MSRV of **1.94**, both of which stable Rust supports, and the default feature set has no nightly-only dependency — `cargo build`/`cargo add whatsapp-rust` works with **stable Rust** out of the box, no feature flags to disable. -```toml Cargo.toml -[dependencies] -# Disable defaults (removes `simd`), then re-enable everything else -whatsapp-rust = { version = "0.7", default-features = false, features = [ - "sqlite-storage", - "tokio-transport", - "tokio-runtime", - "ureq-client", - "tokio-native", - "signal", -] } -# wacore also needs default-features = false to prevent feature unification -# from re-enabling simd -wacore = { version = "0.7", default-features = false } - -# These crates have no SIMD dependency — no changes needed -whatsapp-rust-sqlite-storage = "0.7" -whatsapp-rust-tokio-transport = "0.7" -whatsapp-rust-ureq-http-client = "0.7" -waproto = "0.7" -tokio = { version = "1.53", features = ["macros", "rt-multi-thread"] } -``` - - - Setting `default-features = false` only on `whatsapp-rust` is **not enough** if you also depend on `wacore` directly. The direct `wacore` dependency enables `simd` by default, and Cargo merges features across all dependents. Both must opt out. - - -The encoder/decoder automatically falls back to scalar code paths when SIMD is disabled. There is no functional difference — only a minor performance difference in binary protocol operations. + + The project's own `rust-toolchain.toml` still pins a nightly compiler (`nightly-2026-06-16`), but only for two build-time flags used by the workspace's own CI and Docker builds — `-Zshare-generics` and lld/ICF linking in `.cargo/config.toml` — not for any language feature the published crates need. That pin governs building the whatsapp-rust workspace itself; it has no effect on your project when you depend on whatsapp-rust from crates.io or git. + ## 32-bit target support diff --git a/pt/installation.mdx b/pt/installation.mdx index 68aa4135..de45e3fa 100644 --- a/pt/installation.mdx +++ b/pt/installation.mdx @@ -7,8 +7,7 @@ description: Adicione whatsapp-rust ao seu projeto Rust Antes de instalar whatsapp-rust, certifique-se de ter: -- **Rust nightly** (padrão) — necessário apenas pela feature padrão `simd`, que usa a API instável `portable_simd`. O projeto fixa `nightly-2026-06-16` via `rust-toolchain.toml`. -- **Rust 1.94 ou mais novo** — o MSRV do workspace. A edição 2024 em si não exige nightly, então o stable funciona assim que o `simd` é desabilitado. Veja [Usando Rust stable](#usando-rust-stable). +- **Rust 1.94 ou mais novo** — o MSRV do workspace, e todas as features padrão compilam com **Rust stable**. Veja [Usando Rust stable](#usando-rust-stable). - Gerenciador de pacotes **Cargo** @@ -19,9 +18,7 @@ Antes de instalar whatsapp-rust, certifique-se de ter: `whatsapp-rust` reexporta a pilha inteira (`wacore`, `wacore_binary`, `waproto` e todas as implementações incluídas), então **uma linha de dependência basta** para a maioria dos projetos: - - -```toml Nightly (padrão) +```toml Cargo.toml [dependencies] whatsapp-rust = "0.7" # Só é preciso declarar um crate irmão para features específicas dele: @@ -33,24 +30,6 @@ whatsapp-rust = "0.7" tokio = { version = "1.53", features = ["macros", "rt-multi-thread"] } ``` -```toml Rust Stable -[dependencies] -whatsapp-rust = { version = "0.7", default-features = false, features = [ - "sqlite-storage", - "tokio-transport", - "tokio-runtime", - "ureq-client", - "tokio-native", - "signal", -] } -# wacore também precisa de default-features = false para a unificação de -# features não reativar o simd -wacore = { version = "0.7", default-features = false } -tokio = { version = "1.53", features = ["macros", "rt-multi-thread"] } -``` - - - Todo caminho de sub-crate é alcançável pelo crate principal — `whatsapp_rust::waproto::whatsapp` (apelidado como `wa` no `prelude`), `whatsapp_rust::wacore`, `whatsapp_rust::wacore_binary`, `whatsapp_rust::store::SqliteStore`, `whatsapp_rust::http::UreqHttpClient` e `whatsapp_rust::transport::TokioWebSocketTransportFactory`. O mesmo vale para os crates de terceiros cujos tipos aparecem na API pública (`anyhow`, `async_trait`, `bytes`, `chrono`, `futures`, `serde`, `serde_json`, `async_channel`, `buffa`): você nunca precisa adicioná-los nem fixar suas versões. ## Feature flags @@ -64,7 +43,6 @@ whatsapp-rust suporta diversas features opcionais: | `tokio-transport` | Transporte WebSocket Tokio | ✅ Sim | | `ureq-client` | Cliente HTTP Ureq | ✅ Sim | | `sqlite-storage` | Backend de armazenamento SQLite | ✅ Sim | -| `simd` | Codificação/decodificação do protocolo binário otimizada com SIMD (**requer Rust nightly**) | ✅ Sim | | `signal` | Manipulação de sinais Unix (desligamento gracioso em SIGTERM/Ctrl+C) | ✅ Sim | | `tracing` | Emite spans/eventos `tracing` em connect, send, receive, IQ, app state, pareamento, mídia e fluxos de sessão. Veja [Observabilidade](/advanced/observability) | ❌ Não | | `tracing-pii` | Mostra números de telefone crus nos campos do `tracing` em vez do `pn#` redigido. Apenas para depuração local — nunca habilite em produção | ❌ Não | @@ -151,38 +129,11 @@ Veja [Chat Store](/api/chat-store) para a API completa. ## Usando Rust stable -whatsapp-rust usa a **edição Rust 2024** e declara um MSRV de **1.94** — ambos suportados pelo Rust stable. A única parte exclusiva do nightly é a feature padrão `simd`, que usa a API instável `portable_simd` do Rust para codificação/decodificação otimizada do protocolo binário. O projeto em si fixa `nightly-2026-06-16` via `rust-toolchain.toml`. - -Para compilar com **Rust stable**, desabilite a feature `simd` definindo `default-features = false`. Você deve fazer isso em **ambos** `whatsapp-rust` e `wacore` — caso contrário, a [unificação de features](https://doc.rust-lang.org/cargo/reference/features.html#feature-unification) do Cargo irá reabilitar SIMD através da dependência `wacore`: +whatsapp-rust usa a **edição Rust 2024** e declara um MSRV de **1.94**, ambos suportados pelo Rust stable, e o conjunto de features padrão não tem nenhuma dependência exclusiva do nightly — `cargo build`/`cargo add whatsapp-rust` funciona com **Rust stable** de fábrica, sem precisar desabilitar nenhuma feature. -```toml Cargo.toml -[dependencies] -# Desabilita os padrões (remove `simd`), depois reabilita o resto -whatsapp-rust = { version = "0.7", default-features = false, features = [ - "sqlite-storage", - "tokio-transport", - "tokio-runtime", - "ureq-client", - "tokio-native", - "signal", -] } -# wacore também precisa de default-features = false para evitar que -# a unificação de features reabilite simd -wacore = { version = "0.7", default-features = false } - -# Estes crates não dependem de SIMD — nenhuma mudança necessária -whatsapp-rust-sqlite-storage = "0.7" -whatsapp-rust-tokio-transport = "0.7" -whatsapp-rust-ureq-http-client = "0.7" -waproto = "0.7" -tokio = { version = "1.53", features = ["macros", "rt-multi-thread"] } -``` - - - Definir `default-features = false` somente em `whatsapp-rust` **não é suficiente** se você também depende de `wacore` diretamente. A dependência direta de `wacore` habilita `simd` por padrão, e o Cargo mescla features entre todos os dependentes. Ambos precisam optar por sair. - - -O codificador/decodificador faz fallback automaticamente para caminhos escalares quando o SIMD está desabilitado. Não há diferença funcional — apenas uma pequena diferença de desempenho nas operações do protocolo binário. + + O `rust-toolchain.toml` do próprio projeto ainda fixa um compilador nightly (`nightly-2026-06-16`), mas apenas para duas flags de build usadas pelo CI e pelas imagens Docker do próprio workspace — `-Zshare-generics` e linking lld/ICF em `.cargo/config.toml` — não para nenhuma feature de linguagem que os crates publicados precisem. Essa fixação rege a compilação do workspace whatsapp-rust em si; ela não afeta seu projeto quando você depende de whatsapp-rust via crates.io ou git. + ## Suporte a alvos de 32 bits From 54f20eb9b14d3d5628eed3752fad7164bc7851a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 23:58:11 +0000 Subject: [PATCH 2/3] docs: fix packed-encoding table snippets and nightly-flag scope Address review feedback on #505: - NIBBLE_ENC/HEX_ENC excerpts were missing the digit-range loops they documented, so copying them verbatim produced tables that reject all normal input. Show the real initializer code. - The nightly-toolchain note named only two of the three nightly-only build flags the docs describe elsewhere (missed -Zbuild-std, used by the Docker image build); scope the claim to match. - Sentence-case the "Hex packing" heading to match its sibling. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_018ixTnvivkbqYADYM2hoMMj --- advanced/binary-protocol.mdx | 19 ++++++++++++++++--- installation.mdx | 2 +- pt/installation.mdx | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/advanced/binary-protocol.mdx b/advanced/binary-protocol.mdx index 930f7eef..138b3608 100644 --- a/advanced/binary-protocol.mdx +++ b/advanced/binary-protocol.mdx @@ -472,7 +472,11 @@ pub const PACKED_MAX: u8 = 127; // Max length for packed/token strings /// a phone number can carry. static NIBBLE_ENC: [u8; 256] = { let mut table = [PACK_INVALID; 256]; - // '0'..='9' → 0..=9 + let mut c = b'0'; + while c <= b'9' { + table[c as usize] = c - b'0'; + c += 1; + } table[b'-' as usize] = 10; table[b'.' as usize] = 11; table[0] = 15; // padding for an odd-length string @@ -482,7 +486,7 @@ static NIBBLE_ENC: [u8; 256] = { Location: `wacore/binary/src/encoder.rs:32-43` -#### Hex Packing +#### Hex packing Uppercase hex strings (0-9, A-F) are packed into 4 bits per character, against `HEX_ENC` instead of `NIBBLE_ENC`: @@ -493,7 +497,16 @@ Uppercase hex strings (0-9, A-F) are packed into 4 bits per character, against ` /// ASCII to nibble, the inverse of the decoder's `HEX_PAIRS`. static HEX_ENC: [u8; 256] = { let mut table = [PACK_INVALID; 256]; - // '0'..='9' → 0..=9, 'A'..='F' → 10..=15 + let mut c = b'0'; + while c <= b'9' { + table[c as usize] = c - b'0'; + c += 1; + } + let mut c = b'A'; + while c <= b'F' { + table[c as usize] = 10 + (c - b'A'); + c += 1; + } table[0] = 15; // padding for an odd-length string table }; diff --git a/installation.mdx b/installation.mdx index 472f2058..8def907d 100644 --- a/installation.mdx +++ b/installation.mdx @@ -175,7 +175,7 @@ See [Chat Store](/api/chat-store) for the full API. whatsapp-rust uses Rust **edition 2024** and declares an MSRV of **1.94**, both of which stable Rust supports, and the default feature set has no nightly-only dependency — `cargo build`/`cargo add whatsapp-rust` works with **stable Rust** out of the box, no feature flags to disable. - The project's own `rust-toolchain.toml` still pins a nightly compiler (`nightly-2026-06-16`), but only for two build-time flags used by the workspace's own CI and Docker builds — `-Zshare-generics` and lld/ICF linking in `.cargo/config.toml` — not for any language feature the published crates need. That pin governs building the whatsapp-rust workspace itself; it has no effect on your project when you depend on whatsapp-rust from crates.io or git. + The project's own `rust-toolchain.toml` still pins a nightly compiler (`nightly-2026-06-16`), but only for internal, binary-size-focused build flags — `-Zshare-generics` and lld/ICF linking, set workspace-wide in `.cargo/config.toml`, plus `-Zbuild-std` for the Docker image build only (see [Docker deployment](#docker-deployment) below) — not for any language feature the published crates need. That pin governs building the whatsapp-rust workspace itself; it has no effect on your project when you depend on whatsapp-rust from crates.io or git. ## 32-bit target support diff --git a/pt/installation.mdx b/pt/installation.mdx index de45e3fa..0f3b2d72 100644 --- a/pt/installation.mdx +++ b/pt/installation.mdx @@ -132,7 +132,7 @@ Veja [Chat Store](/api/chat-store) para a API completa. whatsapp-rust usa a **edição Rust 2024** e declara um MSRV de **1.94**, ambos suportados pelo Rust stable, e o conjunto de features padrão não tem nenhuma dependência exclusiva do nightly — `cargo build`/`cargo add whatsapp-rust` funciona com **Rust stable** de fábrica, sem precisar desabilitar nenhuma feature. - O `rust-toolchain.toml` do próprio projeto ainda fixa um compilador nightly (`nightly-2026-06-16`), mas apenas para duas flags de build usadas pelo CI e pelas imagens Docker do próprio workspace — `-Zshare-generics` e linking lld/ICF em `.cargo/config.toml` — não para nenhuma feature de linguagem que os crates publicados precisem. Essa fixação rege a compilação do workspace whatsapp-rust em si; ela não afeta seu projeto quando você depende de whatsapp-rust via crates.io ou git. + O `rust-toolchain.toml` do próprio projeto ainda fixa um compilador nightly (`nightly-2026-06-16`), mas apenas para flags de build internas focadas em tamanho de binário — `-Zshare-generics` e linking lld/ICF, definidas para todo o workspace em `.cargo/config.toml`, mais `-Zbuild-std` somente para o build da imagem Docker (veja a seção de implantação com Docker abaixo) — não para nenhuma feature de linguagem que os crates publicados precisem. Essa fixação rege a compilação do workspace whatsapp-rust em si; ela não afeta seu projeto quando você depende de whatsapp-rust via crates.io ou git. ## Suporte a alvos de 32 bits From de0d70cfdd73f274d3a3100ffb1293ac59df0e23 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 00:03:26 +0000 Subject: [PATCH 3/3] docs(binary-protocol): fix stale pack_hex reference in unpacking section The Unpacking section still pointed readers at pack_hex, the match-ladder function the Packed Encoding rewrite above it replaced with HEX_ENC. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_018ixTnvivkbqYADYM2hoMMj --- advanced/binary-protocol.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/advanced/binary-protocol.mdx b/advanced/binary-protocol.mdx index 138b3608..f389a9fe 100644 --- a/advanced/binary-protocol.mdx +++ b/advanced/binary-protocol.mdx @@ -849,7 +849,7 @@ fn unpack_nibble(packed: u8, position: u8) -> u8 { } ``` -Unpacking a run of packed values dispatches through a 256-entry byte-pair table — one lookup and a 2-byte store per input byte, rather than the two-shift/two-lookup scalar walk this snippet shows. This is an implementation detail of `read_packed`; the value mapping above still describes what each nibble decodes to for `NIBBLE_8`. `HEX_8` unpacking is the mirror of [`pack_hex`](#hex-packing) shown earlier — 0–9 to digits, 10–15 to `A`–`F` — not the nibble table above. +Unpacking a run of packed values dispatches through a 256-entry byte-pair table — one lookup and a 2-byte store per input byte, rather than the two-shift/two-lookup scalar walk this snippet shows. This is an implementation detail of `read_packed`; the value mapping above still describes what each nibble decodes to for `NIBBLE_8`. `HEX_8` unpacking is the mirror of [`HEX_ENC`](#hex-packing) shown earlier — 0–9 to digits, 10–15 to `A`–`F` — not the nibble table above. Location: `wacore/binary/src/decoder.rs:400-450`