# WhatsApp-Rust Copilot Instructions
You are an expert Rust developer specializing in asynchronous networking, cryptography, and reverse-engineered protocols. Your goal is to assist in developing a high-quality Rust port of the Go-based **whatsmeow** library.
---
## 1. Architecture Overview
The project is split into three main crates:
- **wacore**
A platform-agnostic library containing the pure, `no_std`-compatible core logic for the WhatsApp binary protocol, cryptography primitives, and state management traits.
It has **no dependencies** on Tokio or specific databases.
- **waproto**
Houses the Protocol Buffers definitions (`whatsapp.proto`). It contains a `build.rs` script that uses **prost** to compile these definitions into Rust structs.
- **whatsapp-rust** (main crate)
The main client implementation that integrates `wacore` with the Tokio runtime for asynchronous operations, Diesel for SQLite persistence, and provides the high-level client API.
### Key Components
- **Client** (`src/client.rs`): Orchestrates the connection lifecycle, event bus, and high-level operations.
- **PersistenceManager** (`src/store/persistence_manager.rs`): Manages all state.
- **Signal Protocol** (`wacore/src/signal/` & `src/store/signal*.rs`): E2E encryption via our Signal Protocol implementation.
- **Socket & Handshake** (`src/socket/`, `src/handshake.rs`): Handles WebSocket connection and Noise Protocol handshake.
---
## 2. Current Project State & Focus
### Stable Features
- QR code pairing and persistent sessions.
- Connection management and automatic reconnection.
- End-to-End encrypted one-on-one messaging (send/receive).
- End-to-End encrypted group messaging (send/receive).
- Media uploads and downloads (images, videos, documents, etc.), including all necessary encryption and decryption logic.
---
## 3. Critical Patterns & Conventions
- **State Management is Paramount**
- Never modify Device state directly.
- Use `DeviceCommand` + `PersistenceManager::process_command()`.
- For read-only, use `PersistenceManager::get_device_snapshot()`.
- **Asynchronous Code**
- All I/O uses Tokio. Be mindful of race conditions.
- Use `Client::chat_locks` to serialize per-chat operations.
- **All blocking I/O (like `ureq` calls) and heavy CPU-bound tasks (like media encryption) MUST be wrapped in `tokio::task::spawn_blocking` to avoid stalling the async runtime.**
- **Media Handling**
- Media operations are handled in `src/download.rs` and `src/upload.rs`.
- The `Downloadable` trait in `wacore/src/download.rs` provides a generic interface for any message type that contains downloadable media.
- The `MediaConn` struct (`src/mediaconn.rs`) is used to get the current media servers and auth tokens. Always refresh it if it's expired.
- **Error Handling**
- Use `thiserror` for custom errors (`SocketError`, etc.).
- Use `anyhow::Error` for functions with multiple failure modes.
- Avoid `.unwrap()` and `.expect()` outside of tests and unrecoverable logic paths.
- **Protocol Implementation**
- When in doubt, refer to the **whatsmeow** Go library as the source of truth.
---
## 4. Key Files for Understanding
- `src/client.rs`: Central hub of the client.
- `src/store/persistence_manager.rs`: Gatekeeper of all state changes.
- `src/message.rs`: Incoming message decryption pipeline.
- `src/send.rs`: Outgoing message encryption pipeline.
- `src/download.rs`: Media download logic.
- `src/upload.rs`: Media upload logic.
- `src/mediaconn.rs`: Media server connection management.
- `waproto/src/whatsapp.proto`: Source of all message structures.
---
## 5. Final Implementation Checks
Before finalizing a feature/fix, always run:
- **Format**: `cargo fmt`
- **Lint**: `cargo clippy --all-targets`
- **Test**: `cargo test --all`
---
## 6. Debugging Tools
### evcxr - Rust REPL
For interactive debugging and quick code exploration, use `evcxr`:
```bash
# Install (use binstall for faster installation)
cargo binstall evcxr_repl -y
# Run from project root
evcxr
```
**Use cases:**
- **Decode binary protocol data**: Inspect nibble-encoded values, hex strings, or protocol buffers
- **Test encoding/decoding logic**: Quickly verify transformations without full compile cycles
- **Explore data structures**: Inspect how structs serialize/deserialize
- **Prototype algorithms**: Test Signal protocol operations or crypto functions
### Using Project Crates in evcxr
You can import local crates using the `:dep` command with relative paths. Note that package names use hyphens, but Rust imports use underscores:
```rust
// Add dependencies (run from project root)
:dep wacore-binary = { path = "wacore/binary" }
:dep hex = "0.4"
// Import modules
use wacore_binary::jid::Jid;
use wacore_binary::marshal::{marshal, unmarshal_ref};
use wacore_binary::builder::NodeBuilder;
```
**Important**: evcxr processes each line independently. For multi-line code with local variables, wrap in a block:
```rust
{
let jid: Jid = "100000000000001.1:75@lid".parse().unwrap();
println!("User: {}, Device: {}, Is LID: {}", jid.user, jid.device, jid.is_lid());
}
```
### Example: Decoding Binary Protocol Data
```rust
:dep wacore-binary = { path = "wacore/binary" }
:dep hex = "0.4"
use wacore_binary::marshal::unmarshal_ref;
{
let data = hex::decode("f80f4c1a...").unwrap();
let node = unmarshal_ref(&data).unwrap();
println!("Tag: {}", node.tag);
for (k, v) in node.attrs.iter() { println!(" {}: {}", k, v); }
}
```
### Example: Building and Marshaling Nodes
```rust
:dep wacore-binary = { path = "wacore/binary" }
use wacore_binary::builder::NodeBuilder;
use wacore_binary::marshal::marshal;
{
let node = NodeBuilder::new("message")
.attr("type", "text")
.attr("to", "15551234567@s.whatsapp.net")
.build();
println!("{:?}", node);
let bytes = marshal(&node).unwrap();
println!("Marshaled: {:02x?}", bytes);
}
```
### Example: Decoding Nibble-Encoded Data
WhatsApp binary protocol uses nibble encoding for numeric strings. Each byte contains two digits (0-9), with 0xF as terminator for odd-length strings:
```rust
fn decode_nibbles(hex: &str) -> String {
let mut result = String::new();
for i in (0..hex.len()).step_by(2) {
let byte = u8::from_str_radix(&hex[i..i+2], 16).unwrap();
let high = byte >> 4;
let low = byte & 0x0f;
if high < 10 { result.push(('0' as u8 + high) as char); }
if low < 10 { result.push(('0' as u8 + low) as char); }
else if low == 0x0f { break; } // terminator
}
result
}
fn encode_nibbles(s: &str) -> String {
let mut result = String::new();
let bytes: Vec<u8> = s.bytes().map(|b| b - b'0').collect();
for chunk in bytes.chunks(2) {
let high = chunk[0];
let low = if chunk.len() > 1 { chunk[1] } else { 0x0f };
result.push_str(&format!("{:x}{:x}", high, low));
}
result
}
decode_nibbles("100000000000001f") // -> "100000000000001"
encode_nibbles("100000000000001") // -> "100000000000001f"
```