Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 38 additions & 39 deletions advanced/binary-protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Comment on lines +457 to +458

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Split the overloaded documentation sentences.

Each sentence combines several independent implementation claims. Split the claims so readers can identify the encoding flow and the historical performance rationale.

  • advanced/binary-protocol.mdx#L457-L458: separate table selection, pair packing, stack buffering, and validity checking.
  • advanced/binary-protocol.mdx#L504-L505: separate removed paths, benchmark result, decoder-table detail, and exhaustive-test coverage.

As per coding guidelines, "Keep sentences concise — one idea per sentence in documentation."

📍 Affects 1 file
  • advanced/binary-protocol.mdx#L457-L458 (this comment)
  • advanced/binary-protocol.mdx#L504-L505
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@advanced/binary-protocol.mdx` around lines 457 - 458, Split the documentation
at advanced/binary-protocol.mdx lines 457-458 into concise sentences covering
table selection, pair packing, stack buffering, and OR-accumulator validity
checking separately. Also split lines 504-505 into separate sentences for
removed paths, benchmark results, decoder-table details, and exhaustive-test
coverage; make no code changes.

Source: Coding guidelines

#### Nibble packing (numeric strings)

Strings containing only digits, dash, and dot are packed into 4 bits per character:
Expand All @@ -466,58 +468,55 @@ 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] = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The two lookup-table excerpts are internally inconsistent: the comment claims digits/A-F map to 0-15, but the shown initializers never set those entries, so any reader copying the snippet gets a table where digits resolve to PACK_INVALID — contradicting the "'1' → 1" / "Packed: 0x12" example in the same block. Add the digit and A-F mapping loops (or drop the misleading comments) to both NIBBLE_ENC and HEX_ENC.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At advanced/binary-protocol.mdx, line 473:

<comment>The two lookup-table excerpts are internally inconsistent: the comment claims digits/A-F map to 0-15, but the shown initializers never set those entries, so any reader copying the snippet gets a table where digits resolve to PACK_INVALID — contradicting the "'1' → 1" / "Packed: 0x12" example in the same block. Add the digit and A-F mapping loops (or drop the misleading comments) to both NIBBLE_ENC and HEX_ENC.</comment>

<file context>
@@ -466,58 +468,42 @@ Strings containing only digits, dash, and dot are packed into 4 bits per charact
-}
+/// 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
</file context>
Suggested change
static NIBBLE_ENC: [u8; 256] = {
static NIBBLE_ENC: [u8; 256] = {
let mut table = [PACK_INVALID; 256];
// '0'..='9' → 0..=9
for (i, b) in (b'0'..=b'9').enumerate() {
table[b as usize] = i as u8;
}
table[b'-' as usize] = 10;
table[b'.' as usize] = 11;
table[0] = 15; // padding for an odd-length string
table
};

let mut table = [PACK_INVALID; 256];
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
table
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};
```

Location: `wacore/binary/src/encoder.rs:769-777`
Location: `wacore/binary/src/encoder.rs:32-43`

#### Hex Packing
#### Hex packing
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

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];
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
};
```

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`
<Note>
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.
</Note>

### JID Encoding

Expand Down Expand Up @@ -850,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`

Expand Down
59 changes: 6 additions & 53 deletions installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Split the long documentation statements.

Each location combines multiple independent facts. Use concise sentences. Address the reader as “you” or “você”. In Portuguese, change funciona to funcionam if the two commands remain the subject.

  • installation.mdx#L10-L10: Separate the Rust version, MSRV, and stable-feature statements.
  • installation.mdx#L175-L175: Separate edition, MSRV, stable support, and command behavior.
  • pt/installation.mdx#L10-L10: Separate the Rust version, MSRV, and stable-feature statements.
  • pt/installation.mdx#L132-L132: Separate the stable-Rust statements and correct the verb agreement.

As per coding guidelines, use active voice and second person, and keep one idea per sentence.

📍 Affects 2 files
  • installation.mdx#L10-L10 (this comment)
  • installation.mdx#L175-L175
  • pt/installation.mdx#L10-L10
  • pt/installation.mdx#L132-L132
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@installation.mdx` at line 10, Split the independent documentation facts into
concise, active-voice, second-person sentences at installation.mdx lines 10-10
and 175-175, and pt/installation.mdx lines 10-10 and 132-132. Separate the Rust
version, MSRV, edition, stable-support, and command-behavior statements as
applicable; in Portuguese, change “funciona” to “funcionam” when both commands
remain the subject.

Source: Coding guidelines

- **Cargo** package manager

<Note>
Expand Down Expand Up @@ -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:

<CodeGroup>

```toml Nightly (default)
```toml Cargo.toml
[dependencies]
whatsapp-rust = "0.7"
# Only needed for crate-specific features not exposed through whatsapp-rust:
Expand All @@ -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"] }
```

</CodeGroup>

## Feature flags

whatsapp-rust supports several optional features:
Expand All @@ -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#<token>`. Local debugging only — never enable in production | ❌ No |
Expand Down Expand Up @@ -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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve nightly guidance for the published 0.7 crate

For readers following the crates.io snippet above (whatsapp-rust = "0.7"), this assertion is false: the repository's release record says 0.7.0 is the published 0.7 release and that its default simd feature requires nightly (changelog/2026-08-07-release-0-7-0.mdx:2-10,37). Until a 0.7.x containing #1262 is published, cargo add whatsapp-rust followed by a stable build selects 0.7.0 and fails on portable_simd; retain the opt-out instructions for registry users or clearly scope stable-by-default to the post-#1262 git revision.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair catch on the mechanics — Cargo.toml on main is still version = "0.7.0" and release.yml is workflow_dispatch-only, so the crates.io 0.7.0 published alongside changelog/2026-08-07-release-0-7-0.mdx does in fact still need nightly for its default features. #1262 hasn't shipped in a crates.io release yet.

That said, I'm leaving this as-is rather than adding a "still on 0.7.0" caveat or restoring the opt-out instructions: this docs site's established pattern (see e.g. #504, #503, #502, #499, #500, all merged the same day as their corresponding whatsapp-rust PRs) is to document current main behavior immediately on merge, ahead of the next crates.io publish — release-specific doc sync happens separately when a version is actually cut (e.g. #480 for the 0.7.0 release itself). Re-adding nightly/opt-out instructions here would be inconsistent with that convention and would need to be torn out again the moment the next release ships. Given the human maintainer already excludes the changelog from this kind of automated update and handles releases manually, I'll leave the release-timing gap to that same process rather than encode it here.


Generated by Claude Code


```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"] }
```

<Warning>
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.
</Warning>

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.
<Note>
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.
</Note>

## 32-bit target support

Expand Down
61 changes: 6 additions & 55 deletions pt/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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**

<Note>
Expand All @@ -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:

<CodeGroup>

```toml Nightly (padrão)
```toml Cargo.toml
[dependencies]
whatsapp-rust = "0.7"
# Só é preciso declarar um crate irmão para features específicas dele:
Expand All @@ -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"] }
```

</CodeGroup>

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
Expand All @@ -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#<token>` redigido. Apenas para depuração local — nunca habilite em produção | ❌ Não |
Expand Down Expand Up @@ -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"] }
```

<Warning>
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.
</Warning>

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.
<Note>
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.
</Note>

## Suporte a alvos de 32 bits

Expand Down