waddling-errors-hash 0.7.0

Compact xxHash3-based error code hashing for network-efficient diagnostics in distributed systems
Documentation

waddling-errors-hash

WDP-compliant xxHash3 computation for diagnostic codes

A standalone hash utility crate implementing WDP (Waddling Diagnostic Protocol) Part 5 (Compact IDs) and Part 7 (Namespaces) specifications. Provides deterministic xxHash3-based hash generation for compact error code identifiers. Designed for compile-time hash embedding in procedural macros with zero runtime overhead.

Crates.io Documentation


WDP Conformance

This crate implements the Waddling Diagnostic Protocol (WDP) specifications:

Key WDP Requirements

Per WDP Part 5 §4.1:

Implementations MUST use xxHash3 (xxh3_64) as the hashing algorithm for Compact ID generation.

Per WDP Part 5 §4.3:

Alternative algorithms MUST NOT be used. This requirement ensures universal interoperability: identical error codes MUST produce identical Compact IDs across all WDP implementations.


Overview

This crate converts error codes like E.AUTH.TOKEN.001 into compact 5-character hashes using the WDP-standardized xxHash3 algorithm.

What It Does

use waddling_errors_hash::{compute_wdp_hash, compute_wdp_namespace_hash, compute_wdp_full_id};

// Hash an error code (WDP-compliant: case-insensitive, whitespace-trimmed)
let hash = compute_wdp_hash("E.Auth.Token.001");
assert_eq!(hash.len(), 5); // e.g., "V6a0B"

// These all produce the SAME hash (normalization per WDP Part 5 §3):
assert_eq!(compute_wdp_hash("E.Auth.Token.001"), compute_wdp_hash("E.AUTH.TOKEN.001"));
assert_eq!(compute_wdp_hash("E.Auth.Token.001"), compute_wdp_hash("e.auth.token.001"));

// Hash a namespace (WDP Part 7)
let ns_hash = compute_wdp_namespace_hash("auth_lib");
assert_eq!(ns_hash.len(), 5); // e.g., "05o5h"

// Combined identifier: namespace_hash-code_hash (WDP Part 7 §5)
let full_id = compute_wdp_full_id("auth_lib", "E.Auth.Token.001");
assert_eq!(full_id.len(), 11); // "05o5h-V6a0B"

Why Compact IDs?

  • 📦 Compact logging: [V6a0B] vs [E.AUTH.TOKEN.001] (~70% size reduction)
  • 🌐 Network efficiency: IoT devices send 5 bytes instead of 20+ bytes
  • 🔍 Quick lookups: Fast error correlation and catalog searches
  • 🔒 Deterministic: Same input always produces same output
  • 🌍 Cross-language: xxHash3 available in Python, JS, Go, Java, Rust, C, C++, etc.
  • 📱 URL-safe: Base62 encoding (alphanumeric only, no special characters)

Algorithm: xxHash3 Only

This crate exclusively implements xxHash3 as mandated by WDP Part 5. xxHash3 was chosen for:

  • Speed: ~30 GB/s throughput
  • 🎯 Optimized for small inputs: Perfect for 15-30 byte error codes
  • 🌐 Universal availability: Supported in all major programming languages
  • 📊 Excellent distribution: Minimal collision probability
  • 🔒 Frozen specification: Stable since 2021, guaranteed cross-platform consistency

Specification References:

Note: This crate is NOT cryptographically secure. xxHash3 is designed for speed and distribution quality, not security. For security-sensitive applications, use cryptographic verification at a higher protocol layer (TLS, JWT, etc.) as recommended in WDP Part 5 §4.4.


Quick Start

Basic Usage (WDP-Compliant)

use waddling_errors_hash::compute_wdp_hash;

let hash = compute_wdp_hash("E.AUTH.TOKEN.001");
println!("Hash: {}", hash); // e.g., "V6a0B"

// Properties:
assert_eq!(hash.len(), 5);
assert!(hash.chars().all(|c| c.is_ascii_alphanumeric()));

Algorithm: xxHash3 with "wdp-v1" seed + input normalization (uppercase, trim)

Without Normalization

use waddling_errors_hash::compute_hash;

// No normalization - input used as-is
let hash = compute_hash("E.AUTH.TOKEN.001");
println!("Hash: {}", hash);

Algorithm: xxHash3 with "wdp-v1" seed (no normalization)

Custom Seed

use waddling_errors_hash::{compute_hash_with_config, HashConfig};

// Use a custom seed for isolated hash space
let config = HashConfig::with_seed(0x1234567890ABCDEF);
let hash = compute_hash_with_config("E.AUTH.TOKEN.001", &config);

Use case: Multi-tenant isolation (different seeds produce different hashes)


Use Cases

1. WDP-Conformant (Recommended)

use waddling_errors_hash::compute_wdp_hash;

// WDP-compliant: normalization + standard seed
let hash = compute_wdp_hash("E.AUTH.TOKEN.001");

// Normalized: uppercase, trimmed (per WDP Part 5 §3)
assert_eq!(
    compute_wdp_hash("e.auth.token.001"),
    compute_wdp_hash("E.AUTH.TOKEN.001")
);

Algorithm: xxHash3 with "wdp-v1" seed (0x000031762D706477)
Normalization: Trim + Uppercase
Use for: Interoperability with WDP ecosystem

2. General Purpose (Fast)

use waddling_errors_hash::compute_hash;

// Fast hashing without normalization
let hash = compute_hash("E.AUTH.TOKEN.001");

Algorithm: xxHash3
Speed: ~30 GB/s
Use for: Projects not requiring WDP conformance

3. Multi-Tenant Isolation

use waddling_errors_hash::{compute_hash_with_config, HashConfig};

// Each tenant gets unique hashes (prevents catalog sharing)
let tenant_a_config = HashConfig::with_seed(0x1111111111111111);
let tenant_b_config = HashConfig::with_seed(0x2222222222222222);

let hash_a = compute_hash_with_config("E.QUOTA.EXCEEDED.001", &tenant_a_config);
let hash_b = compute_hash_with_config("E.QUOTA.EXCEEDED.001", &tenant_b_config);

// Same error code, different hashes!
assert_ne!(hash_a, hash_b);

Use for: SaaS, multi-tenant systems where catalog isolation is needed

4. Namespace Hashing (WDP Part 7)

use waddling_errors_hash::{compute_wdp_namespace_hash, compute_wdp_full_id};

// Hash a namespace (library, service, or application name)
let ns_hash = compute_wdp_namespace_hash("auth_lib");
assert_eq!(ns_hash.len(), 5); // e.g., "05o5h"

// Combined identifier for global uniqueness
let full_id = compute_wdp_full_id("auth_lib", "E.Auth.Token.001");
assert_eq!(full_id.len(), 11); // e.g., "05o5h-V6a0B"
assert_eq!(&full_id[5..6], "-"); // namespace_hash-code_hash format

Use for: Multi-library applications, microservices (WDP Part 7 §2)


Compile-Time Usage (Proc Macros)

The primary use case is compile-time hash generation in procedural macros:

// In waddling-errors-macros/src/diag.rs
use waddling_errors_hash::compute_hash;

// During macro expansion (compile time):
let full_code = "E.AUTH.TOKEN.001";
let hash = compute_hash(full_code);

// Generate constant in output code:
let hash_lit = syn::LitStr::new(&hash, proc_macro2::Span::call_site());
quote! {
    pub const E_AUTH_TOKEN_001_HASH: &str = #hash_lit; // e.g., "V6a0B"
}

Result: Hash is computed once at compile time and embedded as a string constant in the binary. Zero runtime cost!


WDP Seed Constants

For WDP conformance, use these seed values:

Purpose Seed String u64 Value (little-endian) WDP Reference
Error code hashes "wdp-v1" 0x000031762D706477 Part 5 §4.5
Namespace hashes "wdpns-v1" 0x31762D736E706477 Part 7 §4.2

Seed Conversion (WDP Part 5 §4.5.1):

  1. Take UTF-8 bytes of seed string (6 bytes for "wdp-v1")
  2. Zero-pad on the RIGHT to 8 bytes: [0x77, 0x64, 0x70, 0x2D, 0x76, 0x31, 0x00, 0x00]
  3. Interpret as little-endian u64: 0x000031762D706477

Cross-Language Examples

Rust:

use xxhash_rust::xxh3::xxh3_64_with_seed;

const WDP_SEED: u64 = 0x000031762D706477;
let hash = xxh3_64_with_seed(input_bytes, WDP_SEED);

// Verification: compute from bytes
let seed_bytes: [u8; 8] = [0x77, 0x64, 0x70, 0x2D, 0x76, 0x31, 0x00, 0x00];
let seed = u64::from_le_bytes(seed_bytes);
assert_eq!(seed, 0x000031762D706477);

Python:

import xxhash

WDP_SEED_U64 = 0x000031762D706477  # 13891256219767
hash_value = xxhash.xxh3_64(input_bytes, seed=WDP_SEED_U64)

# Verification
seed_bytes = b"wdp-v1" + b"\x00\x00"  # Zero-pad to 8 bytes
seed_u64 = int.from_bytes(seed_bytes, byteorder='little')
assert seed_u64 == 0x000031762D706477

TypeScript/JavaScript:

import { xxh3 } from 'xxhash-wasm';

const WDP_SEED = 0x000031762D706477n; // BigInt literal

const hash = xxh3.h64(inputBytes, WDP_SEED);

Go:

import "github.com/zeebo/xxh3"

const WDPSeedU64 uint64 = 0x000031762D706477

hash := xxh3.HashSeed(inputBytes, WDPSeedU64)

C/C++:

#include "xxhash.h"

#define WDP_SEED 0x000031762D706477ULL


XXH64_hash_t hash = XXH3_64bits_withSeed(input_bytes, input_len, WDP_SEED);

Normalization (WDP Part 5 §3.3):

All languages MUST apply the same normalization for WDP compliance:

  1. Trim leading/trailing whitespace
  2. Convert to uppercase
  3. Encode as UTF-8 bytes
# Python normalization
def normalize_wdp_input(code: str) -> str:
    return code.strip().upper()

# Usage
normalized = normalize_wdp_input("  e.auth.token.001  ")
# Result: "E.AUTH.TOKEN.001"

Security Considerations

xxHash3 is NOT Cryptographically Secure

⚠️ NOT cryptographically secure
⚠️ Attackers can craft collisions if they control input
Safe for error codes (you control the input, not attackers)
Perfect for: Catalogs, logging, compression, lookups

WDP Part 5 §4.4 states:

Compact IDs are designed for IDENTIFICATION purposes, not AUTHENTICATION.

When xxHash3 is Appropriate

// This is SAFE because error codes are controlled by developers, not users
let hash = compute_hash("E.AUTH.TOKEN.001"); // xxHash3

Security Layering (WDP Part 5 §4.4)

For security requirements, use appropriate mechanisms at the correct architectural layer:

Security Requirement Recommended Solution Layer
Transport security HTTPS/TLS Transport
Message integrity JWT/JWS signing Application
Catalog integrity Ed25519 signature Distribution
Authentication OAuth/API keys Application

Example:

{
  "error": {
    "code": "E.AUTH.TOKEN.001",
    "hash": "V6a0B",
    "message": "Token expired"
  },
  "signature": "eyJhbGc...JWT_signature_here..."
}

The Compact ID provides efficient identification while JWT provides message-level integrity.


Architecture

Why This Crate Exists

Procedural macros run in a separate compilation context and need a standalone hash implementation:

┌─────────────────────────────────┐
│  waddling-errors-macros         │
│  (proc macro - compile time)    │
│                                 │
│  Computes hash during macro     │
│  expansion and embeds as const  │
└────────────┬────────────────────┘
             │
             │ depends on
             ▼
┌─────────────────────────────────┐
│  waddling-errors-hash           │◄─────────┐
│  (pure computation)             │          │
│                                 │          │ depends on
│  • compute_hash()               │          │ (optional)
│  • compute_wdp_hash()           │          │
│  • compute_wdp_namespace_hash() │          │
│  • xxHash3 implementation       │          │
└─────────────────────────────────┘          │
             ▲                               │
             │ depends on (optional)         │
             │                               │
┌────────────┴────────────────────┐          │
│  waddling-errors                │──────────┘
│  (runtime library)              │
│                                 │
│  Can verify hashes at runtime   │
│  (if hash feature enabled)      │
└─────────────────────────────────┘
             ▲
             │ user depends on
             │
┌────────────┴────────────────────┐
│  User's Application             │
│                                 │
│  Uses error codes with embedded │
│  hash constants (zero cost!)    │
└─────────────────────────────────┘

Binary Size Impact

Compile-time (proc macro): xxHash3 implementation available, zero impact on user's binary
Runtime (optional): Only if user enables hash feature for verification

User's binary without hash feature:
  ├─ Hash constants: ~5 bytes per error code
  └─ Hash algorithm code: 0 bytes (not included!)

User's binary WITH hash feature (runtime verification):
  ├─ Hash constants: ~5 bytes per error code
  └─ xxHash3 implementation: ~10-15 KB

API Reference

WDP-Conformant Functions (Recommended)

/// Compute WDP-conformant hash (normalized: trim + uppercase)
/// Per WDP Part 5 §3
pub fn compute_wdp_hash(input: &str) -> String

/// Compute WDP-conformant namespace hash
/// Per WDP Part 7 §4
pub fn compute_wdp_namespace_hash(namespace: &str) -> String

/// Compute combined ID: "namespace_hash-code_hash"
/// Per WDP Part 7 §5
pub fn compute_wdp_full_id(namespace: &str, code: &str) -> String

/// Normalize input per WDP spec (trim + uppercase)
/// Per WDP Part 5 §3.3
pub fn normalize_wdp_input(input: &str) -> String

/// Parse full ID into (namespace_hash, code_hash)
/// Per WDP Part 7 §5.3
pub fn parse_wdp_full_id(full_id: &str) -> Option<(String, String)>

/// Verify WDP hash matches input
pub fn verify_wdp_hash(input: &str, hash: &str) -> bool

/// Verify WDP namespace hash matches input
pub fn verify_wdp_namespace_hash(namespace: &str, hash: &str) -> bool

WDP Constants

/// WDP code hash seed: "wdp-v1" as little-endian u64
/// Per WDP Part 5 §4.5
pub const WDP_SEED: u64 = 0x000031762D706477;

/// WDP seed as string
pub const WDP_SEED_STR: &str = "wdp-v1";

/// WDP code seed for xxHash3
/// Per WDP Part 5 §4.5
pub const WDP_CODE_SEED: u64 = 0x000031762D706477;

/// WDP namespace seed: "wdpns-v1" as little-endian u64
/// Per WDP Part 7 §4.2
pub const WDP_NAMESPACE_SEED: u64 = 0x31762D736E706477;

Core Functions

/// Compute hash with default config (xxHash3 + "wdp-v1")
pub fn compute_hash(input: &str) -> String

/// Compute hash with custom seed
pub fn compute_hash_with_config(input: &str, config: &HashConfig) -> String

/// Verify hash matches input (default config)
pub fn verify_hash(input: &str, hash: &str) -> bool

/// Verify hash matches input (custom config)
pub fn verify_hash_with_config(input: &str, hash: &str, config: &HashConfig) -> bool

Configuration Types

/// Hash algorithm (currently only xxHash3 per WDP Part 5 §4.1)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HashAlgorithm {
    #[default]
    XxHash3, // WDP-mandated algorithm
}

/// Hash configuration
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HashConfig {
    pub algorithm: HashAlgorithm,
    pub seed: u64,
}

impl HashConfig {
    /// Create config with algorithm and seed
    pub fn new(algorithm: HashAlgorithm, seed: u64) -> Self
    
    /// Create config with custom seed (default algorithm)
    pub fn with_seed(seed: u64) -> Self
    
    /// Create WDP-conformant config
    pub fn wdp_conformant() -> Self
}

impl Default for HashConfig {
    // Returns: XxHash3 with WDP_SEED (0x000031762D706477)
    fn default() -> Self
}

Utility Functions

/// Convert bytes to 5-character base62 string
pub fn to_base62(bytes: &[u8]) -> String

/// Convert u64 to 5-character base62 string
pub fn u64_to_base62(value: u64) -> String

/// Convert seed string to u64 (for WDP compliance)
/// Zero-pads on right to 8 bytes, interprets as little-endian
pub fn seed_to_u64(seed: &str) -> u64

Const (Compile-Time) Functions

/// Const version of compute_wdp_hash (for compile-time)
pub const fn const_wdp_hash(severity: &str, component: &str, 
                            primary: &str, sequence: &str) -> &'static str

/// Const version with raw bytes input
pub const fn const_wdp_hash_bytes(bytes: &[u8]) -> &'static str

/// Const version with pre-constructed parts
pub const fn const_wdp_hash_from_parts(severity: u8, component: &[u8],
                                        primary: &[u8], sequence: u16) -> &'static str

/// Convert u64 to base62 at compile time
pub const fn const_hash_to_base62(hash: u64) -> &'static str

/// Format sequence number at compile time
pub const fn const_format_sequence(seq: u16) -> [u8; 3]

Configuration Loading

This crate also provides configuration loading utilities for WDP projects:

/// Load global hash configuration from Cargo.toml or environment
#[cfg(feature = "std")]
pub fn load_global_config() -> HashConfig

/// Load namespace from Cargo.toml metadata
#[cfg(feature = "std")]
pub fn load_namespace() -> Option<String>

/// Load doc generation config (formats, output directory)
#[cfg(feature = "std")]
pub fn load_doc_gen_config() -> DocGenConfig

/// Apply configuration overrides
pub fn apply_overrides(mut config: HashConfig, seed_override: Option<u64>) -> HashConfig

Features

[features]

default = ["std"]

std = []  # Standard library support (enables config loading)

Note: xxHash3 implementation is always available. The std feature only affects configuration loading utilities.


no_std Support

Works in no_std environments with alloc:

[dependencies]

waddling-errors-hash = { version = "0.7", default-features = false }

Core hashing functions work in no_std, but configuration loading requires std.


FAQ

Q: Why only xxHash3?
A: WDP Part 5 §4.3 mandates a single algorithm for universal interoperability. xxHash3 was chosen for speed, distribution quality, and cross-language availability.

Q: Can I use a different algorithm?
A: No, if you want WDP conformance. WDP-compliant implementations MUST use xxHash3 to ensure identical Compact IDs across all systems.

Q: Is xxHash3 secure enough?
A: xxHash3 is NOT cryptographically secure, but that's intentional. Compact IDs are for identification, not authentication. Use cryptographic verification at the protocol layer (TLS, JWT) as recommended in WDP Part 5 §4.4.

Q: Can I use custom seeds?
A: Yes, via compute_hash_with_config(). This is useful for multi-tenant isolation. However, custom seeds break WDP conformance—use only if you don't need catalog interoperability.

Q: Does this increase my binary size?
A: No! Hashes are computed at compile time. Only the 5-character string constants are in your binary (~5 bytes each).

Q: What if I need collision resistance beyond 916M?
A: Use full structured codes (E.AUTH.TOKEN.001) in scenarios where uniqueness is critical. Compact IDs are optimized for typical projects (<5,000 error codes).

Q: How do I verify cross-language compatibility?
A: Use the test vectors in WDP Part 5 §8. All implementations should produce identical hashes for the same inputs.


Contributing

Contributions welcome! Areas of interest:

  • Performance optimizations
  • Cross-language test vectors
  • Documentation improvements
  • Bug fixes

Note: Feature requests for alternative algorithms will be declined per WDP specification requirements.


License

Licensed under either of:

at your option.


See Also


Built with 🦆 by the Waddling Errors team