pub mod error;
pub mod core;
pub mod pigeonhole;
pub mod persistent;
pub mod helpers;
pub mod transport;
pub use crate::core::{ThinClient, EventSinkReceiver, DirectoryAuthority};
pub use crate::error::ThinClientError;
pub use crate::helpers::{find_services, pretty_print_pki_doc};
pub use crate::pigeonhole::{TombstoneRangeResult, StartResendingResult};
pub use crate::transport::{DialConfig, DialConfigError, Dialer, TcpDialConfig, UnixDialConfig};
use std::collections::BTreeMap;
use std::sync::Arc;
use std::fs;
use serde::Deserialize;
use serde_cbor::Value;
use blake2::{Blake2b, Digest};
use generic_array::typenum::U32;
pub const THIN_CLIENT_SUCCESS: u8 = 0;
pub const THIN_CLIENT_ERROR_CONNECTION_LOST: u8 = 1;
pub const THIN_CLIENT_ERROR_TIMEOUT: u8 = 2;
pub const THIN_CLIENT_ERROR_INVALID_REQUEST: u8 = 3;
pub const THIN_CLIENT_ERROR_INTERNAL_ERROR: u8 = 4;
pub const THIN_CLIENT_ERROR_MAX_RETRIES: u8 = 5;
pub const THIN_CLIENT_ERROR_INVALID_CHANNEL: u8 = 6;
pub const THIN_CLIENT_ERROR_CHANNEL_NOT_FOUND: u8 = 7;
pub const THIN_CLIENT_ERROR_PERMISSION_DENIED: u8 = 8;
pub const THIN_CLIENT_ERROR_INVALID_PAYLOAD: u8 = 9;
pub const THIN_CLIENT_ERROR_SERVICE_UNAVAILABLE: u8 = 10;
pub const THIN_CLIENT_ERROR_DUPLICATE_CAPABILITY: u8 = 11;
pub const THIN_CLIENT_ERROR_COURIER_CACHE_CORRUPTION: u8 = 12;
pub const THIN_CLIENT_PROPAGATION_ERROR: u8 = 13;
pub const THIN_CLIENT_ERROR_INVALID_WRITE_CAPABILITY: u8 = 14;
pub const THIN_CLIENT_ERROR_INVALID_READ_CAPABILITY: u8 = 15;
pub const THIN_CLIENT_ERROR_INVALID_RESUME_WRITE_CHANNEL_REQUEST: u8 = 16;
pub const THIN_CLIENT_ERROR_INVALID_RESUME_READ_CHANNEL_REQUEST: u8 = 17;
pub const THIN_CLIENT_IMPOSSIBLE_HASH_ERROR: u8 = 18;
pub const THIN_CLIENT_IMPOSSIBLE_NEW_WRITE_CAP_ERROR: u8 = 19;
pub const THIN_CLIENT_IMPOSSIBLE_NEW_STATEFUL_WRITER_ERROR: u8 = 20;
pub const THIN_CLIENT_CAPABILITY_ALREADY_IN_USE: u8 = 21;
pub const THIN_CLIENT_ERROR_MKEM_DECRYPTION_FAILED: u8 = 22;
pub const THIN_CLIENT_ERROR_BACAP_DECRYPTION_FAILED: u8 = 23;
pub const THIN_CLIENT_ERROR_START_RESENDING_CANCELLED: u8 = 24;
pub const THIN_CLIENT_ERROR_INVALID_TOMBSTONE_SIG: u8 = 25;
pub fn thin_client_error_to_string(error_code: u8) -> &'static str {
match error_code {
THIN_CLIENT_SUCCESS => "Success",
THIN_CLIENT_ERROR_CONNECTION_LOST => "Connection lost",
THIN_CLIENT_ERROR_TIMEOUT => "Timeout",
THIN_CLIENT_ERROR_INVALID_REQUEST => "Invalid request",
THIN_CLIENT_ERROR_INTERNAL_ERROR => "Internal error",
THIN_CLIENT_ERROR_MAX_RETRIES => "Maximum retries exceeded",
THIN_CLIENT_ERROR_INVALID_CHANNEL => "Invalid channel",
THIN_CLIENT_ERROR_CHANNEL_NOT_FOUND => "Channel not found",
THIN_CLIENT_ERROR_PERMISSION_DENIED => "Permission denied",
THIN_CLIENT_ERROR_INVALID_PAYLOAD => "Invalid payload",
THIN_CLIENT_ERROR_SERVICE_UNAVAILABLE => "Service unavailable",
THIN_CLIENT_ERROR_DUPLICATE_CAPABILITY => "Duplicate capability",
THIN_CLIENT_ERROR_COURIER_CACHE_CORRUPTION => "Courier cache corruption",
THIN_CLIENT_PROPAGATION_ERROR => "Propagation error",
THIN_CLIENT_ERROR_INVALID_WRITE_CAPABILITY => "Invalid write capability",
THIN_CLIENT_ERROR_INVALID_READ_CAPABILITY => "Invalid read capability",
THIN_CLIENT_ERROR_INVALID_RESUME_WRITE_CHANNEL_REQUEST => "Invalid resume write channel request",
THIN_CLIENT_ERROR_INVALID_RESUME_READ_CHANNEL_REQUEST => "Invalid resume read channel request",
THIN_CLIENT_IMPOSSIBLE_HASH_ERROR => "Impossible hash error",
THIN_CLIENT_IMPOSSIBLE_NEW_WRITE_CAP_ERROR => "Failed to create new write capability",
THIN_CLIENT_IMPOSSIBLE_NEW_STATEFUL_WRITER_ERROR => "Failed to create new stateful writer",
THIN_CLIENT_CAPABILITY_ALREADY_IN_USE => "Capability already in use",
THIN_CLIENT_ERROR_MKEM_DECRYPTION_FAILED => "MKEM decryption failed",
THIN_CLIENT_ERROR_BACAP_DECRYPTION_FAILED => "BACAP decryption failed",
THIN_CLIENT_ERROR_START_RESENDING_CANCELLED => "Start resending cancelled",
THIN_CLIENT_ERROR_INVALID_TOMBSTONE_SIG => "Invalid tombstone signature",
_ => "Unknown thin client error code",
}
}
#[derive(Debug, Clone)]
pub struct ServiceDescriptor {
pub recipient_queue_id: Vec<u8>,
pub mix_descriptor: BTreeMap<Value, Value>,
}
impl ServiceDescriptor {
pub fn to_destination(&self) -> (Vec<u8>, Vec<u8>) {
let identity_key = self
.mix_descriptor
.get(&Value::Text("IdentityKey".to_string()))
.and_then(|v| match v {
Value::Bytes(b) => Some(b),
_ => None,
})
.cloned()
.unwrap_or_else(Vec::new);
let mut hasher = Blake2b::<U32>::new();
hasher.update(&identity_key);
let provider_id_hash = hasher.finalize().to_vec();
(provider_id_hash, self.recipient_queue_id.clone())
}
}
#[derive(Debug, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct Geometry {
#[serde(rename = "PacketLength")]
pub packet_length: usize,
#[serde(rename = "NrHops")]
pub nr_hops: usize,
#[serde(rename = "HeaderLength")]
pub header_length: usize,
#[serde(rename = "RoutingInfoLength")]
pub routing_info_length: usize,
#[serde(rename = "PerHopRoutingInfoLength")]
pub per_hop_routing_info_length: usize,
#[serde(rename = "SURBLength")]
pub surb_length: usize,
#[serde(rename = "SphinxPlaintextHeaderLength")]
pub sphinx_plaintext_header_length: usize,
#[serde(rename = "PayloadTagLength")]
pub payload_tag_length: usize,
#[serde(rename = "ForwardPayloadLength")]
pub forward_payload_length: usize,
#[serde(rename = "UserForwardPayloadLength")]
pub user_forward_payload_length: usize,
#[serde(rename = "NextNodeHopLength")]
pub next_node_hop_length: usize,
#[serde(rename = "SPRPKeyMaterialLength")]
pub sprp_key_material_length: usize,
#[serde(rename = "NIKEName")]
pub nike_name: String,
#[serde(rename = "KEMName")]
pub kem_name: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PigeonholeGeometry {
#[serde(rename = "MaxPlaintextPayloadLength")]
pub max_plaintext_payload_length: usize,
#[serde(rename = "CourierQueryReadLength")]
pub courier_query_read_length: usize,
#[serde(rename = "CourierQueryWriteLength")]
pub courier_query_write_length: usize,
#[serde(rename = "CourierQueryReplyReadLength")]
pub courier_query_reply_read_length: usize,
#[serde(rename = "CourierQueryReplyWriteLength")]
pub courier_query_reply_write_length: usize,
#[serde(rename = "NIKEName")]
pub nike_name: String,
#[serde(rename = "SignatureSchemeName")]
pub signature_scheme_name: String,
}
impl PigeonholeGeometry {
pub fn new(max_plaintext_payload_length: usize, nike_name: &str) -> Self {
Self {
max_plaintext_payload_length,
courier_query_read_length: 0,
courier_query_write_length: 0,
courier_query_reply_read_length: 0,
courier_query_reply_write_length: 0,
nike_name: nike_name.to_string(),
signature_scheme_name: "Ed25519".to_string(),
}
}
pub fn validate(&self) -> Result<(), &'static str> {
if self.max_plaintext_payload_length == 0 {
return Err("MaxPlaintextPayloadLength must be positive");
}
if self.nike_name.is_empty() {
return Err("NIKEName must be set");
}
if self.signature_scheme_name != "Ed25519" {
return Err("SignatureSchemeName must be Ed25519");
}
Ok(())
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigFile {
#[serde(rename = "Dial")]
pub dial: DialConfig,
}
impl ConfigFile {
pub fn load_from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let contents = fs::read_to_string(path)?;
let config: ConfigFile = toml::from_str(&contents)?;
config.dial.validate()?;
Ok(config)
}
}
#[derive(Clone)]
pub struct Config {
pub dial: DialConfig,
pub on_connection_status: Option<Arc<dyn Fn(&BTreeMap<Value, Value>) + Send + Sync>>,
pub on_new_pki_document: Option<Arc<dyn Fn(&BTreeMap<Value, Value>) + Send + Sync>>,
pub on_message_sent: Option<Arc<dyn Fn(&BTreeMap<Value, Value>) + Send + Sync>>,
pub on_message_reply: Option<Arc<dyn Fn(&BTreeMap<Value, Value>) + Send + Sync>>,
pub on_daemon_disconnected: Option<Arc<dyn Fn(bool, Option<String>) + Send + Sync>>,
}
impl Config {
pub fn new(filepath: &str) -> Result<Self, Box<dyn std::error::Error>> {
let contents = fs::read_to_string(filepath)?;
let parsed: ConfigFile = toml::from_str(&contents)?;
parsed.dial.validate()?;
Ok(Self {
dial: parsed.dial,
on_connection_status: None,
on_new_pki_document: None,
on_message_sent: None,
on_message_reply: None,
on_daemon_disconnected: None,
})
}
}