Expand description
A thin client for sending and receiving messages via a Katzenpost mix network.
This crate provides a thin client library for interacting with a Katzenpost mixnet, suitable for desktop and mobile applications.
A mix network is a type of anonymous communications network. What’s a thin client library? It’s code you can use as a depencency in your application so that it can interact with services on the mix network. The Katzenpost client daemon is a multiplexing client; many applications on the same device can use their thin client libraries to connect to the daemon and interact with mixnet services concurrently.
This example can be found here: https://github.com/katzenpost/thin_client/blob/main/examples/echo_ping.rs Thin client example usage::
use std::env;
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use std::process;
use tokio::time::{timeout, Duration};
use tokio::runtime::Runtime;
use serde_cbor::Value;
use katzenpost_thin_client::{ThinClient, Config, pretty_print_pki_doc};
struct ClientState {
reply_message: Arc<Mutex<Option<BTreeMap<Value, Value>>>>,
pki_received: Arc<Mutex<bool>>,
}
impl ClientState {
fn new() -> Self {
Self {
reply_message: Arc::new(Mutex::new(None)),
pki_received: Arc::new(Mutex::new(false)),
}
}
fn save_reply(&self, reply: &BTreeMap<Value, Value>) {
let mut stored_reply = self.reply_message.lock().unwrap();
*stored_reply = Some(reply.clone());
}
fn set_pki_received(&self) {
let mut pki_flag = self.pki_received.lock().unwrap();
*pki_flag = true;
}
fn is_pki_received(&self) -> bool {
*self.pki_received.lock().unwrap()
}
fn await_message_reply(&self) -> Option<BTreeMap<Value, Value>> {
let stored_reply = self.reply_message.lock().unwrap();
stored_reply.clone()
}
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
eprintln!("Usage: {} <config_path>", args[0]);
process::exit(1);
}
let config_path = &args[1];
let rt = Runtime::new().unwrap();
rt.block_on(run_client(config_path)).unwrap();
}
async fn run_client(config_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let state = Arc::new(ClientState::new());
let state_for_reply = Arc::clone(&state);
let state_for_pki = Arc::clone(&state);
let mut cfg = Config::new(config_path)?;
cfg.on_new_pki_document = Some(Arc::new(move |_pki_doc| {
println!("✅ PKI document received.");
state_for_pki.set_pki_received();
}));
cfg.on_message_reply = Some(Arc::new(move |reply| {
println!("📩 Received a reply!");
state_for_reply.save_reply(reply);
}));
println!("🚀 Initializing ThinClient...");
let client = ThinClient::new(cfg).await?;
println!("⏳ Waiting for PKI document...");
let result = timeout(Duration::from_secs(5), async {
loop {
if state.is_pki_received() {
break;
}
tokio::task::yield_now().await;
}
})
.await;
if result.is_err() {
return Err("❌ PKI document not received in time.".into());
}
println!("✅ Pretty printing PKI document:");
let doc = client.pki_document().await;
pretty_print_pki_doc(&doc);
println!("AFTER Pretty printing PKI document");
let service_desc = client.get_service("echo").await?;
println!("got service descriptor for echo service");
let surb_id = ThinClient::new_surb_id();
let payload = b"hello".to_vec();
let (dest_node, dest_queue) = service_desc.to_destination();
println!("before calling send_message");
client.send_message(surb_id, &payload, dest_node, dest_queue).await?;
println!("after calling send_message");
println!("⏳ Waiting for message reply...");
let state_for_reply_wait = Arc::clone(&state);
let result = timeout(Duration::from_secs(5), async move {
loop {
if let Some(reply) = state_for_reply_wait.await_message_reply() {
if let Some(Value::Bytes(payload2)) = reply.get(&Value::Text("payload".to_string())) {
let payload2 = &payload2[..payload.len()];
assert_eq!(payload, payload2, "Reply does not match payload!");
println!("✅ Received valid reply, stopping client.");
return Ok::<(), Box<dyn std::error::Error>>(());
}
}
tokio::task::yield_now().await;
}
}).await;
result.map_err(|e| Box::new(e))??;
client.stop().await;
println!("✅ Client stopped successfully.");
Ok(())
}§See Also
Modules§
Structs§
- Config
- Our configuration defines some callbacks which the thin client will envoke when it receives the corresponding event from the client daemon.
- Config
File - Event
Sink Receiver - Wrapper for event sink receiver that automatically removes the drain when dropped
- Geometry
- Read
Channel Reply - Reply from ReadChannel operation, matching Go ReadChannelReply
- Service
Descriptor - ServiceDescriptor is used when we are searching the PKI document for a specific service.
- Thin
Client - This is our ThinClient type which encapsulates our thin client connection management and message processing.
- Write
Channel Reply - Reply from WriteChannel operation, matching Go WriteChannelReply
Enums§
- Read
Half - This represent the read half of our network socket.
- Write
Half - This represent the write half of our network socket.
Constants§
- THIN_
CLIENT_ ERROR_ CHANNEL_ NOT_ FOUND - ThinClientErrorChannelNotFound indicates that the specified channel does not exist or has been garbage collected.
- THIN_
CLIENT_ ERROR_ CONNECTION_ LOST - ThinClientErrorConnectionLost indicates that the connection to the daemon was lost during the operation. The client should attempt to reconnect.
- THIN_
CLIENT_ ERROR_ COURIER_ CACHE_ CORRUPTION - ThinClientErrorCourierCacheCorruption indicates that the courier’s cache has detected corruption.
- THIN_
CLIENT_ ERROR_ DUPLICATE_ CAPABILITY - ThinClientErrorDuplicateCapability indicates that the provided capability (read or write cap) has already been used and is considered a duplicate.
- THIN_
CLIENT_ ERROR_ INTERNAL_ ERROR - ThinClientErrorInternalError indicates an internal error occurred within the client daemon or thin client that prevented operation completion.
- THIN_
CLIENT_ ERROR_ INVALID_ CHANNEL - ThinClientErrorInvalidChannel indicates that the specified channel ID is invalid or malformed.
- THIN_
CLIENT_ ERROR_ INVALID_ PAYLOAD - ThinClientErrorInvalidPayload indicates that the message payload was invalid, too large, or otherwise could not be processed.
- THIN_
CLIENT_ ERROR_ INVALID_ REQUEST - ThinClientErrorInvalidRequest indicates that the request format was invalid or contained malformed data that could not be processed.
- THIN_
CLIENT_ ERROR_ MAX_ RETRIES - ThinClientErrorMaxRetries indicates that the maximum number of retry attempts was exceeded for a reliable operation (such as ARQ).
- THIN_
CLIENT_ ERROR_ PERMISSION_ DENIED - ThinClientErrorPermissionDenied indicates that the operation was denied due to insufficient permissions or capability restrictions.
- THIN_
CLIENT_ ERROR_ SERVICE_ UNAVAILABLE - ThinClientErrorServiceUnavailable indicates that the requested service or functionality is currently unavailable.
- THIN_
CLIENT_ ERROR_ TIMEOUT - ThinClientErrorTimeout indicates that the operation timed out before completion. This may occur during network operations or when waiting for responses from the mixnet.
- THIN_
CLIENT_ PROPAGATION_ ERROR - ThinClientPropagationError indicates that the request could not be propagated to replicas.
- THIN_
CLIENT_ SUCCESS - ThinClientSuccess indicates that the operation completed successfully with no errors. This is the default success state.
Functions§
- find_
services - Find a specific mixnet service if it exists.
- pretty_
print_ pki_ doc - Pretty prints a PKI document which you can gather from the client
with it’s
pki_documentmethod, documented above. - thin_
client_ error_ to_ string - Converts a thin client error code to a human-readable string. This function provides consistent error message formatting across the thin client protocol and is used for logging and error reporting.