Crates.io | ribbit-client |
lib.rs | ribbit-client |
version | 0.4.3 |
created_at | 2025-06-29 07:04:29.897982+00 |
updated_at | 2025-08-11 12:07:20.843332+00 |
description | Ribbit protocol client with signature verification for Blizzard's NGDP system |
homepage | https://github.com/wowemulation-dev/cascette-rs |
repository | https://github.com/wowemulation-dev/cascette-rs |
max_upload_size | |
id | 1730469 |
size | 304,825 |
Async TCP client for Blizzard's Ribbit protocol, used to retrieve version information, CDN configurations, and other metadata for Blizzard games.
Add to your Cargo.toml
:
[dependencies]
ribbit-client = "0.3"
tokio = { version = "1", features = ["full"] }
Basic usage:
use ribbit_client::{RibbitClient, Region};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client for the US region
let client = RibbitClient::new(Region::US);
// Request WoW version information using typed API
let versions = client.get_product_versions("wow").await?;
// Access typed fields directly
for entry in &versions.entries {
println!(
"{}: {} (build {})",
entry.region, entry.versions_name, entry.build_id
);
}
Ok(())
}
Endpoint | Description | Example |
---|---|---|
Summary |
List all available products | client.request(&Endpoint::Summary) |
ProductVersions |
Get version info for a product | Endpoint::ProductVersions("wow".to_string()) |
ProductCdns |
Get CDN server information | Endpoint::ProductCdns("wow".to_string()) |
ProductBgdl |
Background download config | Endpoint::ProductBgdl("wow".to_string()) |
Cert |
Certificate by SHA-1 hash | Endpoint::Cert(hash.to_string()) |
Ocsp |
OCSP response by hash | Endpoint::Ocsp(hash.to_string()) |
Custom |
Any custom endpoint path | Endpoint::Custom("custom/path".to_string()) |
// V2 is the default
let client = RibbitClient::new(Region::EU);
// Or explicitly use V1 if needed
use ribbit_client::ProtocolVersion;
let client = RibbitClient::new(Region::EU)
.with_protocol_version(ProtocolVersion::V1);
The crate includes several examples demonstrating different use cases:
# Basic client usage
cargo run --example ribbit_basic_usage
# Parse version data into structured format
cargo run --example parse_versions
# Query multiple WoW products
cargo run --example wow_products
# Debug MIME structure (V1 protocol)
cargo run --example mime_parsing
# Typed API showcase
cargo run --example typed_api_showcase
# Retry handling demonstration
cargo run --example retry_handling
Responses contain both raw data and parsed components:
pub struct Response {
/// Raw response bytes
pub raw: Vec<u8>,
/// Parsed data (PSV format)
pub data: Option<String>,
/// MIME parts (V1 only)
pub mime_parts: Option<MimeParts>,
}
pub struct MimeParts {
/// Main data content
pub data: String,
/// Signature bytes (if present)
pub signature: Option<Vec<u8>>,
/// Parsed signature information
pub signature_info: Option<SignatureInfo>,
/// SHA-256 checksum from epilogue
pub checksum: Option<String>,
}
The client provides a typed API that automatically parses responses into strongly-typed structs:
// Get product versions with automatic parsing
let versions = client.get_product_versions("wow").await?;
// Direct access to typed fields
for entry in &versions.entries {
println!("Region: {}", entry.region);
println!("Build: {} ({})", entry.build_id, entry.versions_name);
println!("Build Config: {}", entry.build_config);
println!("CDN Config: {}", entry.cdn_config);
}
// Convenience methods
if let Some(us_version) = versions.get_region("us") {
println!("US version: {}", us_version.versions_name);
}
// Other typed endpoints
let summary = client.get_summary().await?;
let cdns = client.get_product_cdns("wow").await?;
let bgdl = client.get_product_bgdl("wow").await?;
For custom endpoints or manual parsing, Ribbit returns data in PSV (Pipe-Separated Values) format:
// Manual parsing of raw response
let endpoint = Endpoint::ProductVersions("wow".to_string());
let response = client.request(&endpoint).await?;
if let Some(data) = response.data {
// First line contains column definitions
// Region!STRING:0|BuildConfig!HEX:16|CDNConfig!HEX:16|...
for line in data.lines().skip(1) {
if line.is_empty() || line.starts_with('#') {
continue;
}
let fields: Vec<&str> = line.split('|').collect();
let region = fields[0];
let build_config = fields[1];
let cdn_config = fields[2];
// ... process remaining fields
}
}
The client provides detailed error types for different failure scenarios:
use ribbit_client::{Error, Result};
match client.request(&endpoint).await {
Ok(response) => println!("Success!"),
Err(Error::ConnectionFailed { host, port }) => {
eprintln!("Failed to connect to {}:{}", host, port);
}
Err(Error::ChecksumMismatch) => {
eprintln!("Response failed integrity check");
}
Err(Error::MimeParseError(msg)) => {
eprintln!("Failed to parse MIME: {}", msg);
}
Err(e) => eprintln!("Error: {}", e),
}
let mut client = RibbitClient::new(Region::US);
client.set_region(Region::EU);
// Get raw bytes for custom parsing
let raw_data = client.request_raw(&endpoint).await?;
println!("Received {} bytes", raw_data.len());
use ribbit_client::ProtocolVersion;
let client = RibbitClient::new(Region::US)
.with_protocol_version(ProtocolVersion::V1);
let response = client.request(&endpoint).await?;
if let Some(mime_parts) = response.mime_parts {
// Checksum is automatically validated
println!("Checksum verified: {:?}", mime_parts.checksum);
// Access signature information
if let Some(sig_info) = mime_parts.signature_info {
println!("Signature format: {}", sig_info.format);
println!("Algorithm: {}", sig_info.algorithm);
println!("Signers: {}", sig_info.signer_count);
println!("Certificates: {}", sig_info.certificate_count);
}
}
Enable trace logging to see detailed protocol information:
use tracing_subscriber::EnvFilter;
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env()
.add_directive("ribbit_client=trace".parse()?))
.init();
The client is optimized for performance with:
Run benchmarks:
cargo bench
The crate includes comprehensive tests:
# Run all tests
cargo test
# Run with trace logging
RUST_LOG=ribbit_client=trace cargo test
# Run specific test
cargo test test_ribbit_summary_v1
This crate follows Rust best practices:
#[must_use]
attributes where appropriateContributions are welcome! Please ensure:
This project is dual-licensed under either:
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
This crate is part of the cascette-rs
project, providing tools for World of Warcraft
emulation development.