chore: rename quicproquo → quicprochat in Rust workspace

Rename all crate directories, package names, binary names, proto
package/module paths, ALPN strings, env var prefixes, config filenames,
mDNS service names, and plugin ABI symbols from quicproquo/qpq to
quicprochat/qpc.
This commit is contained in:
2026-03-07 18:24:52 +01:00
parent d8c1392587
commit a710037dde
212 changed files with 609 additions and 609 deletions

2
.gitignore vendored
View File

@@ -16,7 +16,7 @@ data/
*.convdb-shm
*.convdb-wal
*.pending.ks
qpq-server.toml
qpc-server.toml
# Internal planning docs (not for public distribution)
docs/internal/

52
Cargo.lock generated
View File

@@ -4276,7 +4276,7 @@ dependencies = [
]
[[package]]
name = "quicproquo-client"
name = "quicprochat-client"
version = "0.1.0"
dependencies = [
"anyhow",
@@ -4296,12 +4296,12 @@ dependencies = [
"opaque-ke",
"openmls_rust_crypto",
"portpicker",
"quicproquo-core",
"quicproquo-kt",
"quicproquo-p2p",
"quicproquo-proto",
"quicproquo-rpc",
"quicproquo-sdk",
"quicprochat-core",
"quicprochat-kt",
"quicprochat-p2p",
"quicprochat-proto",
"quicprochat-rpc",
"quicprochat-sdk",
"quinn",
"quinn-proto",
"rand 0.8.5",
@@ -4324,7 +4324,7 @@ dependencies = [
]
[[package]]
name = "quicproquo-core"
name = "quicprochat-core"
version = "0.1.0"
dependencies = [
"argon2",
@@ -4343,7 +4343,7 @@ dependencies = [
"openmls_rust_crypto",
"openmls_traits",
"prost",
"quicproquo-proto",
"quicprochat-proto",
"rand 0.8.5",
"serde",
"serde_json",
@@ -4356,7 +4356,7 @@ dependencies = [
]
[[package]]
name = "quicproquo-kt"
name = "quicprochat-kt"
version = "0.1.0"
dependencies = [
"bincode",
@@ -4366,14 +4366,14 @@ dependencies = [
]
[[package]]
name = "quicproquo-p2p"
name = "quicprochat-p2p"
version = "0.1.0"
dependencies = [
"anyhow",
"chacha20poly1305 0.10.1",
"hex",
"iroh",
"quicproquo-core",
"quicprochat-core",
"rand 0.8.5",
"serde",
"serde_json",
@@ -4385,11 +4385,11 @@ dependencies = [
]
[[package]]
name = "quicproquo-plugin-api"
name = "quicprochat-plugin-api"
version = "0.1.0"
[[package]]
name = "quicproquo-proto"
name = "quicprochat-proto"
version = "0.2.0"
dependencies = [
"bytes",
@@ -4402,7 +4402,7 @@ dependencies = [
]
[[package]]
name = "quicproquo-rpc"
name = "quicprochat-rpc"
version = "0.1.0"
dependencies = [
"bytes",
@@ -4410,7 +4410,7 @@ dependencies = [
"futures",
"metrics 0.22.4",
"prost",
"quicproquo-proto",
"quicprochat-proto",
"quinn",
"rcgen",
"rustls",
@@ -4423,7 +4423,7 @@ dependencies = [
]
[[package]]
name = "quicproquo-sdk"
name = "quicprochat-sdk"
version = "0.1.0"
dependencies = [
"anyhow",
@@ -4435,9 +4435,9 @@ dependencies = [
"hex",
"opaque-ke",
"prost",
"quicproquo-core",
"quicproquo-proto",
"quicproquo-rpc",
"quicprochat-core",
"quicprochat-proto",
"quicprochat-rpc",
"quinn",
"rand 0.8.5",
"rusqlite",
@@ -4453,7 +4453,7 @@ dependencies = [
]
[[package]]
name = "quicproquo-server"
name = "quicprochat-server"
version = "0.1.0"
dependencies = [
"anyhow",
@@ -4476,11 +4476,11 @@ dependencies = [
"metrics-exporter-prometheus",
"opaque-ke",
"prost",
"quicproquo-core",
"quicproquo-kt",
"quicproquo-plugin-api",
"quicproquo-proto",
"quicproquo-rpc",
"quicprochat-core",
"quicprochat-kt",
"quicprochat-plugin-api",
"quicprochat-proto",
"quicprochat-rpc",
"quinn",
"quinn-proto",
"rand 0.8.5",

View File

@@ -1,23 +1,23 @@
[workspace]
resolver = "2"
members = [
"crates/quicproquo-core",
"crates/quicproquo-proto",
"crates/quicproquo-plugin-api",
"crates/quicproquo-kt",
"crates/quicproquo-rpc",
"crates/quicproquo-sdk",
"crates/quicproquo-server",
"crates/quicproquo-client",
"crates/quicprochat-core",
"crates/quicprochat-proto",
"crates/quicprochat-plugin-api",
"crates/quicprochat-kt",
"crates/quicprochat-rpc",
"crates/quicprochat-sdk",
"crates/quicprochat-server",
"crates/quicprochat-client",
# P2P crate uses iroh (~90 extra deps). Only compiled when the `mesh`
# feature is enabled on quicproquo-client.
"crates/quicproquo-p2p",
# feature is enabled on quicprochat-client.
"crates/quicprochat-p2p",
]
[workspace.package]
edition = "2021"
rust-version = "1.75"
repository = "https://github.com/quicproquo/quicproquo"
repository = "https://github.com/quicprochat/quicprochat"
description = "End-to-end encrypted group messaging over QUIC"
keywords = ["encryption", "messaging", "quic", "mls", "post-quantum"]
categories = ["cryptography", "network-programming"]

View File

@@ -1,19 +1,19 @@
[package]
name = "quicproquo-client"
name = "quicprochat-client"
version = "0.1.0"
edition.workspace = true
description = "CLI client for quicproquo."
description = "CLI client for quicprochat."
license = "Apache-2.0 OR MIT"
repository.workspace = true
[[bin]]
name = "qpq"
name = "qpc"
path = "src/main.rs"
[dependencies]
quicproquo-core = { path = "../quicproquo-core" }
quicproquo-proto = { path = "../quicproquo-proto" }
quicproquo-kt = { path = "../quicproquo-kt" }
quicprochat-core = { path = "../quicprochat-core" }
quicprochat-proto = { path = "../quicprochat-proto" }
quicprochat-kt = { path = "../quicprochat-kt" }
openmls_rust_crypto = { workspace = true }
# Serialisation + RPC
@@ -66,7 +66,7 @@ rpassword = "5"
mdns-sd = { version = "0.12", optional = true }
# Optional P2P transport for direct node-to-node messaging.
quicproquo-p2p = { path = "../quicproquo-p2p", optional = true }
quicprochat-p2p = { path = "../quicprochat-p2p", optional = true }
# Optional TUI dependencies (Ratatui full-screen interface).
ratatui = { version = "0.29", optional = true, default-features = false, features = ["crossterm"] }
@@ -75,9 +75,9 @@ crossterm = { version = "0.28", optional = true }
# YAML playbook parsing (only compiled with --features playbook).
serde_yaml = { version = "0.9", optional = true }
# v2 SDK-based CLI (thin shell over quicproquo-sdk).
quicproquo-sdk = { path = "../quicproquo-sdk", optional = true }
quicproquo-rpc = { path = "../quicproquo-rpc", optional = true }
# v2 SDK-based CLI (thin shell over quicprochat-sdk).
quicprochat-sdk = { path = "../quicprochat-sdk", optional = true }
quicprochat-rpc = { path = "../quicprochat-rpc", optional = true }
rustyline = { workspace = true, optional = true }
[lints]
@@ -85,15 +85,15 @@ workspace = true
[features]
# Enable mesh-mode features: mDNS local peer discovery + P2P transport.
# Build: cargo build -p quicproquo-client --features mesh
mesh = ["dep:mdns-sd", "dep:quicproquo-p2p"]
# Enable full-screen Ratatui TUI: cargo build -p quicproquo-client --features tui
# Build: cargo build -p quicprochat-client --features mesh
mesh = ["dep:mdns-sd", "dep:quicprochat-p2p"]
# Enable full-screen Ratatui TUI: cargo build -p quicprochat-client --features tui
tui = ["dep:ratatui", "dep:crossterm"]
# Enable playbook (scripted command execution): YAML parser + serde derives.
# Build: cargo build -p quicproquo-client --features playbook
# Build: cargo build -p quicprochat-client --features playbook
playbook = ["dep:serde_yaml"]
# v2 CLI over SDK: cargo build -p quicproquo-client --features v2
v2 = ["dep:quicproquo-sdk", "dep:quicproquo-rpc", "dep:rustyline"]
# v2 CLI over SDK: cargo build -p quicprochat-client --features v2
v2 = ["dep:quicprochat-sdk", "dep:quicprochat-rpc", "dep:rustyline"]
[dev-dependencies]
dashmap = { workspace = true }

View File

@@ -6,7 +6,7 @@
use std::collections::HashMap;
use quicproquo_proto::node_capnp::node_service;
use quicprochat_proto::node_capnp::node_service;
use super::repl::{Input, SlashCommand, parse_input};
use super::session::SessionState;

View File

@@ -5,7 +5,7 @@ use opaque_ke::{
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialResponse, RegistrationResponse,
};
use quicproquo_core::{
use quicprochat_core::{
generate_key_package, hybrid_decrypt, hybrid_encrypt, opaque_auth::OpaqueSuite,
GroupMember, HybridKeypair, IdentityKeypair, ReceivedMessage,
};
@@ -317,7 +317,7 @@ fn derive_identity_for_login(
/// The error message contains "E018" if the user already exists.
/// Does NOT require init_auth() — OPAQUE RPCs are unauthenticated.
pub(crate) async fn opaque_register(
client: &quicproquo_proto::node_capnp::node_service::Client,
client: &quicprochat_proto::node_capnp::node_service::Client,
username: &str,
password: &str,
identity_key: Option<&[u8]>,
@@ -378,7 +378,7 @@ pub(crate) async fn opaque_register(
/// Perform OPAQUE login and return the raw session token bytes.
/// Does NOT require init_auth() — OPAQUE RPCs are unauthenticated.
pub async fn opaque_login(
client: &quicproquo_proto::node_capnp::node_service::Client,
client: &quicprochat_proto::node_capnp::node_service::Client,
username: &str,
password: &str,
identity_key: &[u8],
@@ -647,8 +647,8 @@ pub async fn cmd_fetch_key(
/// Run a two-party MLS demo against the unified server.
pub async fn cmd_demo_group(server: &str, ca_cert: &Path, server_name: &str) -> anyhow::Result<()> {
let creator_state_path = PathBuf::from("qpq-demo-creator.bin");
let joiner_state_path = PathBuf::from("qpq-demo-joiner.bin");
let creator_state_path = PathBuf::from("qpc-demo-creator.bin");
let joiner_state_path = PathBuf::from("qpc-demo-joiner.bin");
let (mut creator, creator_hybrid_opt) =
load_or_init_state(&creator_state_path, None)?.into_parts(&creator_state_path)?;
@@ -1298,7 +1298,7 @@ pub async fn cmd_chat(
///
/// `conv_db` is the path to the conversation SQLite database (`.convdb` file).
/// `conv_id_hex` is the 32-hex-character conversation ID to export.
/// `output` is the path for the `.qpqt` transcript file to write.
/// `output` is the path for the `.qpct` transcript file to write.
/// `transcript_password` is used to derive the encryption key (Argon2id).
/// `db_password` is the optional SQLCipher password for the conversation database.
pub fn cmd_export(
@@ -1308,7 +1308,7 @@ pub fn cmd_export(
transcript_password: &str,
db_password: Option<&str>,
) -> anyhow::Result<()> {
use quicproquo_core::{TranscriptRecord, TranscriptWriter};
use quicprochat_core::{TranscriptRecord, TranscriptWriter};
use super::conversation::{ConversationId, ConversationStore};
// Decode conversation ID from hex.
@@ -1367,7 +1367,7 @@ pub fn cmd_export(
conv.display_name,
output.display()
);
println!("Decrypt with: qpq export verify --input <file> --password <password>");
println!("Decrypt with: qpc export verify --input <file> --password <password>");
Ok(())
}
@@ -1376,7 +1376,7 @@ pub fn cmd_export(
///
/// Prints a summary. Does not require the encryption password (structural check only).
pub fn cmd_export_verify(input: &Path) -> anyhow::Result<()> {
use quicproquo_core::{validate_transcript_structure, ChainVerdict};
use quicprochat_core::{validate_transcript_structure, ChainVerdict};
let data = std::fs::read(input)
.with_context(|| format!("read transcript file '{}'", input.display()))?;

View File

@@ -1,6 +1,6 @@
//! mDNS-based peer discovery for Freifunk / community mesh deployments.
//!
//! Browse for `_quicproquo._udp.local.` services on the local network and
//! Browse for `_quicprochat._udp.local.` services on the local network and
//! surface them as [`DiscoveredPeer`] structs. Servers announce themselves
//! automatically on startup; this module lets clients find them without manual
//! configuration.
@@ -8,7 +8,7 @@
//! # Usage
//!
//! ```no_run
//! use quicproquo_client::client::mesh_discovery::MeshDiscovery;
//! use quicprochat_client::client::mesh_discovery::MeshDiscovery;
//!
//! let disc = MeshDiscovery::start()?;
//! // Give mDNS time to collect announcements before reading.
@@ -16,7 +16,7 @@
//! for peer in disc.peers() {
//! println!("found: {} at {}", peer.domain, peer.server_addr);
//! }
//! # Ok::<(), quicproquo_client::client::mesh_discovery::MeshDiscoveryError>(())
//! # Ok::<(), quicprochat_client::client::mesh_discovery::MeshDiscoveryError>(())
//! ```
#[cfg(feature = "mesh")]
@@ -27,7 +27,7 @@ use std::sync::{Arc, Mutex};
#[cfg(feature = "mesh")]
use std::collections::HashMap;
/// A qpq server discovered on the local network via mDNS.
/// A qpc server discovered on the local network via mDNS.
#[derive(Debug, Clone)]
pub struct DiscoveredPeer {
/// Federation domain of the remote server (e.g. `"node1.freifunk.net"`).
@@ -57,7 +57,7 @@ pub enum MeshDiscoveryError {
}
impl MeshDiscovery {
/// Start browsing for `_quicproquo._udp.local.` services.
/// Start browsing for `_quicprochat._udp.local.` services.
///
/// Returns immediately; peers are collected in the background.
/// Returns [`MeshDiscoveryError::FeatureDisabled`] when built without the
@@ -79,7 +79,7 @@ impl MeshDiscovery {
.map_err(|e| MeshDiscoveryError::DaemonError(e.to_string()))?;
let receiver = daemon
.browse("_quicproquo._udp.local.")
.browse("_quicprochat._udp.local.")
.map_err(|e| MeshDiscoveryError::BrowseError(e.to_string()))?;
let peers: Arc<Mutex<HashMap<String, DiscoveredPeer>>> =
@@ -91,7 +91,7 @@ impl MeshDiscovery {
for event in receiver {
match event {
ServiceEvent::ServiceResolved(info) => {
// Extract the qpq server address from TXT records.
// Extract the qpc server address from TXT records.
let server_addr_str = info
.get_property_val_str("server")
.map(|s| s.to_string());

View File

@@ -24,7 +24,7 @@ use std::path::Path;
use std::time::{Duration, Instant};
use anyhow::{Context, bail};
use quicproquo_proto::node_capnp::node_service;
use quicprochat_proto::node_capnp::node_service;
use serde::{Deserialize, Serialize};
use super::command_engine::{AssertCondition, CmpOp, Command, CommandRegistry};

View File

@@ -9,13 +9,13 @@ use std::sync::Arc;
use std::time::Duration;
use anyhow::Context;
use quicproquo_core::{
use quicprochat_core::{
AppMessage, DiskKeyStore, GroupMember, IdentityKeypair, ReceivedMessage,
compute_safety_number, hybrid_encrypt, parse as parse_app_msg, serialize_chat,
serialize_delete, serialize_dummy, serialize_edit, serialize_file_ref, serialize_reaction,
serialize_read_receipt, serialize_typing,
};
use quicproquo_proto::node_capnp::node_service;
use quicprochat_proto::node_capnp::node_service;
use tokio::sync::mpsc;
use tokio::time::interval;
@@ -355,10 +355,10 @@ fn derive_key_path(cert_path: &Path) -> PathBuf {
cert_path.with_file_name(key_name)
}
/// Find the `qpq-server` binary: same directory as current exe, then PATH.
/// Find the `qpc-server` binary: same directory as current exe, then PATH.
fn find_server_binary() -> Option<PathBuf> {
if let Ok(exe) = std::env::current_exe() {
let sibling = exe.with_file_name("qpq-server");
let sibling = exe.with_file_name("qpc-server");
if sibling.exists() {
return Some(sibling);
}
@@ -366,7 +366,7 @@ fn find_server_binary() -> Option<PathBuf> {
// Fall back to PATH lookup.
std::env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths)
.map(|dir| dir.join("qpq-server"))
.map(|dir| dir.join("qpc-server"))
.find(|p| p.exists())
})
}
@@ -400,13 +400,13 @@ async fn ensure_server(
if ca_cert.exists() {
// Cert exists but connection failed and no binary found.
anyhow::bail!(
"server at {server} is not reachable and qpq-server binary not found; \
start a server manually or install qpq-server"
"server at {server} is not reachable and qpc-server binary not found; \
start a server manually or install qpc-server"
);
} else {
anyhow::bail!(
"no server running and qpq-server binary not found; \
start a server manually or install qpq-server"
"no server running and qpc-server binary not found; \
start a server manually or install qpc-server"
);
}
}
@@ -445,7 +445,7 @@ async fn ensure_server(
if start.elapsed() > max_wait {
anyhow::bail!(
"auto-started qpq-server but it did not become ready within {max_wait:?}"
"auto-started qpc-server but it did not become ready within {max_wait:?}"
);
}
@@ -862,7 +862,7 @@ pub(crate) fn print_help() {
display::print_status(" /rename <name> - Rename the current conversation");
display::print_status(" /history [N] - Show last N messages (default: 20)");
display::print_status(" /whoami - Show your identity");
display::print_status(" /mesh peers - Discover nearby qpq nodes via mDNS");
display::print_status(" /mesh peers - Discover nearby qpc nodes via mDNS");
display::print_status(" /mesh server <host:port> - Show how to reconnect to a mesh node");
display::print_status(" /mesh send <peer> <msg> - Send a P2P message to a mesh peer");
display::print_status(" /mesh broadcast <topic> <m> - Broadcast an encrypted message on a topic");
@@ -1099,7 +1099,7 @@ pub(crate) async fn cmd_rotate_all_keys(
cmd_update_key(session, client).await?;
// Step 2: Generate new hybrid KEM keypair and upload.
let new_kp = quicproquo_core::HybridKeypair::generate();
let new_kp = quicprochat_core::HybridKeypair::generate();
let id_key = session.identity.public_key_bytes();
upload_hybrid_key(client, &id_key, &new_kp.public_key()).await?;
session.hybrid_kp = Some(new_kp);
@@ -1108,7 +1108,7 @@ pub(crate) async fn cmd_rotate_all_keys(
Ok(())
}
/// Discover nearby qpq servers via mDNS (requires `--features mesh` build).
/// Discover nearby qpc servers via mDNS (requires `--features mesh` build).
pub(crate) fn cmd_mesh_peers() -> anyhow::Result<()> {
use super::mesh_discovery::MeshDiscovery;
@@ -1118,19 +1118,19 @@ pub(crate) fn cmd_mesh_peers() -> anyhow::Result<()> {
return Ok(());
}
Ok(disc) => {
display::print_status("scanning for nearby qpq nodes (2s)...");
display::print_status("scanning for nearby qpc nodes (2s)...");
// Block briefly to collect mDNS announcements from the local network.
std::thread::sleep(std::time::Duration::from_secs(2));
let peers = disc.peers();
if peers.is_empty() {
display::print_status("no qpq nodes found on the local network");
display::print_status("no qpc nodes found on the local network");
} else {
display::print_status(&format!("found {} node(s):", peers.len()));
for p in &peers {
display::print_status(&format!(" {} at {}", p.domain, p.server_addr));
}
display::print_status("use: /mesh server <host:port> to note the address,");
display::print_status("then reconnect with: qpq --server <host:port>");
display::print_status("then reconnect with: qpc --server <host:port>");
}
}
}
@@ -1188,7 +1188,7 @@ pub(crate) fn cmd_mesh_route(session: &SessionState) -> anyhow::Result<()> {
{
let mesh_state_path = session.state_path.with_extension("mesh.json");
if mesh_state_path.exists() {
let id = quicproquo_p2p::identity::MeshIdentity::load(&mesh_state_path)?;
let id = quicprochat_p2p::identity::MeshIdentity::load(&mesh_state_path)?;
let peers = id.known_peers();
if peers.is_empty() {
display::print_status("no known mesh peers");
@@ -1222,7 +1222,7 @@ pub(crate) fn cmd_mesh_identity(session: &SessionState) -> anyhow::Result<()> {
{
let mesh_state_path = session.state_path.with_extension("mesh.json");
if mesh_state_path.exists() {
let id = quicproquo_p2p::identity::MeshIdentity::load(&mesh_state_path)?;
let id = quicprochat_p2p::identity::MeshIdentity::load(&mesh_state_path)?;
display::print_status(&format!("mesh public key: {}", hex::encode(id.public_key())));
display::print_status(&format!("known peers: {}", id.known_peers().len()));
} else {
@@ -2005,8 +2005,8 @@ pub(crate) async fn cmd_typing(
);
let app_payload = serialize_typing(1);
let sealed = quicproquo_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicproquo_core::padding::pad(&sealed);
let sealed = quicprochat_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicprochat_core::padding::pad(&sealed);
let ct = member
.send_message(&padded)
@@ -2082,8 +2082,8 @@ pub(crate) async fn cmd_react(
let app_payload = serialize_reaction(ref_msg_id, emoji.as_bytes())
.context("serialize reaction")?;
let sealed = quicproquo_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicproquo_core::padding::pad(&sealed);
let sealed = quicprochat_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicprochat_core::padding::pad(&sealed);
let ct = member
.send_message(&padded)
@@ -2167,8 +2167,8 @@ pub(crate) async fn cmd_edit(
let app_payload = serialize_edit(&msg_id, new_text.as_bytes())
.context("serialize edit message")?;
let sealed = quicproquo_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicproquo_core::padding::pad(&sealed);
let sealed = quicprochat_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicprochat_core::padding::pad(&sealed);
let ct = member
.send_message(&padded)
@@ -2238,8 +2238,8 @@ pub(crate) async fn cmd_delete(
);
let app_payload = serialize_delete(&msg_id);
let sealed = quicproquo_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicproquo_core::padding::pad(&sealed);
let sealed = quicprochat_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicprochat_core::padding::pad(&sealed);
let ct = member
.send_message(&padded)
@@ -2394,8 +2394,8 @@ pub(crate) async fn cmd_send_file(
"cannot send files in a local-only conversation"
);
let sealed = quicproquo_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicproquo_core::padding::pad(&sealed);
let sealed = quicprochat_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicprochat_core::padding::pad(&sealed);
let ct = member
.send_message(&padded)
@@ -2672,8 +2672,8 @@ pub(crate) async fn do_send(
.context("serialize app message")?;
// Metadata protection: seal sender identity inside payload + pad to bucket size.
let sealed = quicproquo_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicproquo_core::padding::pad(&sealed);
let sealed = quicprochat_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicprochat_core::padding::pad(&sealed);
let ct = member
.send_message(&padded)
@@ -2762,8 +2762,8 @@ async fn send_dummy_message(
}
let dummy_payload = serialize_dummy();
let sealed = quicproquo_core::sealed_sender::seal(&identity, &dummy_payload);
let padded = quicproquo_core::padding::pad(&sealed);
let sealed = quicprochat_core::sealed_sender::seal(&identity, &dummy_payload);
let padded = quicprochat_core::padding::pad(&sealed);
let ct = match member.send_message(&padded) {
Ok(ct) => ct,
@@ -2845,12 +2845,12 @@ async fn poll_messages(
// Falls back gracefully for messages from older clients.
let (sender_key, app_bytes) = {
// Step 1: try unpad
let after_unpad = quicproquo_core::padding::unpad(&plaintext)
let after_unpad = quicprochat_core::padding::unpad(&plaintext)
.unwrap_or_else(|_| plaintext.clone());
// Step 2: try unseal
if quicproquo_core::sealed_sender::is_sealed(&after_unpad) {
match quicproquo_core::sealed_sender::unseal(&after_unpad) {
if quicprochat_core::sealed_sender::is_sealed(&after_unpad) {
match quicprochat_core::sealed_sender::unseal(&after_unpad) {
Ok((sk, inner)) => (sk.to_vec(), inner),
Err(_) => (my_key.clone(), after_unpad),
}
@@ -3048,8 +3048,8 @@ async fn poll_messages(
if let Some(mid) = msg_id {
let receipt_bytes = serialize_read_receipt(mid);
let identity = Arc::clone(&session.identity);
let sealed = quicproquo_core::sealed_sender::seal(&identity, &receipt_bytes);
let padded = quicproquo_core::padding::pad(&sealed);
let sealed = quicprochat_core::sealed_sender::seal(&identity, &receipt_bytes);
let padded = quicprochat_core::padding::pad(&sealed);
if let Some(m) = session.members.get_mut(conv_id) {
if let Ok(ct) = m.send_message(&padded) {
let _ = enqueue(client, &sender_key, &ct).await;

View File

@@ -10,8 +10,8 @@ use rustls::{ClientConfig as RustlsClientConfig, RootCertStore};
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
use capnp_rpc::{rpc_twoparty_capnp::Side, twoparty, RpcSystem};
use quicproquo_core::HybridPublicKey;
use quicproquo_proto::node_capnp::{auth, node_service};
use quicprochat_core::HybridPublicKey;
use quicprochat_proto::node_capnp::{auth, node_service};
use crate::{AUTH_CONTEXT, INSECURE_SKIP_VERIFY};
@@ -440,11 +440,11 @@ pub async fn fetch_hybrid_key(
/// Decrypt a hybrid envelope. Requires a hybrid key; no fallback to plaintext MLS.
pub fn try_hybrid_decrypt(
hybrid_kp: Option<&quicproquo_core::HybridKeypair>,
hybrid_kp: Option<&quicprochat_core::HybridKeypair>,
payload: &[u8],
) -> anyhow::Result<Vec<u8>> {
let kp = hybrid_kp.ok_or_else(|| anyhow::anyhow!("hybrid key required for decryption"))?;
quicproquo_core::hybrid_decrypt(kp, payload, b"", b"").map_err(|e| anyhow::anyhow!("{e}"))
quicprochat_core::hybrid_decrypt(kp, payload, b"", b"").map_err(|e| anyhow::anyhow!("{e}"))
}
/// Peek at queued payloads without removing them.
@@ -701,9 +701,9 @@ pub async fn resolve_user(
.to_vec();
if !proof_bytes.is_empty() {
let proof = quicproquo_kt::InclusionProof::from_bytes(&proof_bytes)
let proof = quicprochat_kt::InclusionProof::from_bytes(&proof_bytes)
.context("resolve_user: inclusion proof deserialise failed")?;
quicproquo_kt::verify_inclusion(&proof, username, &key)
quicprochat_kt::verify_inclusion(&proof, username, &key)
.context("resolve_user: KT inclusion proof verification FAILED — possible key mislabelling")?;
}

View File

@@ -11,7 +11,7 @@ use std::time::Instant;
use anyhow::Context;
use zeroize::Zeroizing;
use quicproquo_core::{DiskKeyStore, GroupMember, HybridKeypair, IdentityKeypair};
use quicprochat_core::{DiskKeyStore, GroupMember, HybridKeypair, IdentityKeypair};
use super::conversation::{
now_ms, Conversation, ConversationId, ConversationKind, ConversationStore,

View File

@@ -10,7 +10,7 @@ use chacha20poly1305::{
use rand::RngCore;
use serde::{Deserialize, Serialize};
use quicproquo_core::{DiskKeyStore, GroupMember, HybridKeypair, HybridKeypairBytes, IdentityKeypair};
use quicprochat_core::{DiskKeyStore, GroupMember, HybridKeypair, HybridKeypairBytes, IdentityKeypair};
/// Magic bytes for encrypted client state files.
const STATE_MAGIC: &[u8; 4] = b"QPCE";

View File

@@ -1,4 +1,4 @@
//! Full-screen Ratatui TUI for quicproquo.
//! Full-screen Ratatui TUI for quicprochat.
//!
//! Layout:
//! ┌──────────────┬──────────────────────────────────────────┐
@@ -48,11 +48,11 @@ use super::session::SessionState;
use super::state::load_or_init_state;
use super::token_cache::{load_cached_session, save_cached_session};
use quicproquo_core::{
use quicprochat_core::{
AppMessage, DiskKeyStore, GroupMember, IdentityKeypair, ReceivedMessage,
hybrid_encrypt, parse as parse_app_msg, serialize_chat,
};
use quicproquo_proto::node_capnp::node_service;
use quicprochat_proto::node_capnp::node_service;
// ── App events ───────────────────────────────────────────────────────────────
@@ -393,11 +393,11 @@ async fn poll_task(
match member.receive_message(&mls_payload) {
Ok(ReceivedMessage::Application(plaintext)) => {
let (sender_key, app_bytes) = {
let after_unpad = quicproquo_core::padding::unpad(&plaintext)
let after_unpad = quicprochat_core::padding::unpad(&plaintext)
.unwrap_or_else(|_| plaintext.clone());
if quicproquo_core::sealed_sender::is_sealed(&after_unpad) {
match quicproquo_core::sealed_sender::unseal(&after_unpad) {
if quicprochat_core::sealed_sender::is_sealed(&after_unpad) {
match quicprochat_core::sealed_sender::unseal(&after_unpad) {
Ok((sk, inner)) => (sk.to_vec(), inner),
Err(_) => (my_key.clone(), after_unpad),
}
@@ -493,8 +493,8 @@ async fn send_message(
.context("serialize app message")?;
// Metadata protection: seal + pad.
let sealed = quicproquo_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicproquo_core::padding::pad(&sealed);
let sealed = quicprochat_core::sealed_sender::seal(&identity, &app_payload);
let padded = quicprochat_core::padding::pad(&sealed);
let ct = member.send_message(&padded).context("MLS encrypt")?;
@@ -543,7 +543,7 @@ async fn send_message(
// ── TUI entry point ───────────────────────────────────────────────────────────
/// Entry point for `qpq tui`. Sets up the terminal, runs the event loop, and
/// Entry point for `qpc tui`. Sets up the terminal, runs the event loop, and
/// restores the terminal on exit.
pub async fn run_tui(
state_path: &Path,

View File

@@ -1,10 +1,10 @@
//! v2 REPL — thin shell over `quicproquo_sdk::QpqClient`.
//! v2 REPL — thin shell over `quicprochat_sdk::QpqClient`.
//!
//! Provides an interactive command-line interface with categorized `/help`,
//! tab-completion, and a background event listener. Delegates all crypto,
//! MLS, and RPC work to the SDK.
//!
//! Build: `cargo build -p quicproquo-client --features v2`
//! Build: `cargo build -p quicprochat-client --features v2`
use std::path::PathBuf;
use std::process::{Child, Command as ProcessCommand};
@@ -12,10 +12,10 @@ use std::sync::Arc;
use std::time::Duration;
use anyhow::Context;
use quicproquo_core::{GroupMember, IdentityKeypair};
use quicproquo_sdk::client::QpqClient;
use quicproquo_sdk::conversation::{ConversationId, ConversationKind, StoredMessage};
use quicproquo_sdk::events::ClientEvent;
use quicprochat_core::{GroupMember, IdentityKeypair};
use quicprochat_sdk::client::QpqClient;
use quicprochat_sdk::conversation::{ConversationId, ConversationKind, StoredMessage};
use quicprochat_sdk::events::ClientEvent;
use rustyline::completion::{Completer, Pair};
use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter;
@@ -216,14 +216,14 @@ impl Drop for ServerGuard {
fn find_server_binary() -> Option<PathBuf> {
if let Ok(exe) = std::env::current_exe() {
let sibling = exe.with_file_name("qpq-server");
let sibling = exe.with_file_name("qpc-server");
if sibling.exists() {
return Some(sibling);
}
}
std::env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths)
.map(|dir| dir.join("qpq-server"))
.map(|dir| dir.join("qpc-server"))
.find(|p| p.exists())
})
}
@@ -235,7 +235,7 @@ async fn auto_start_server(addr: &str) -> ServerGuard {
let binary = match find_server_binary() {
Some(b) => b,
None => {
display::print_status("server not reachable and qpq-server binary not found");
display::print_status("server not reachable and qpc-server binary not found");
return ServerGuard(None);
}
};
@@ -311,7 +311,7 @@ fn show_event(event: &ClientEvent) {
// ── Help ────────────────────────────────────────────────────────────────────
fn print_help() {
println!("\n{BOLD}quicproquo v2 REPL{RESET}\n");
println!("\n{BOLD}quicprochat v2 REPL{RESET}\n");
for cat in Category::all() {
println!("{BOLD}{}{RESET}", cat.label());
for cmd in COMMANDS.iter().filter(|c| c.category == *cat) {
@@ -462,14 +462,14 @@ async fn do_login(client: &mut QpqClient, st: &mut ReplState, args: &str) -> any
// Try to load identity keypair from state file.
let state_path = &client.config_state_path();
if state_path.exists() {
match quicproquo_sdk::state::load_state(state_path, Some(pass)) {
match quicprochat_sdk::state::load_state(state_path, Some(pass)) {
Ok(stored) => {
let kp = IdentityKeypair::from_seed(stored.identity_seed);
st.identity = Some(Arc::new(kp));
}
Err(_) => {
// Try without password (unencrypted state).
if let Ok(stored) = quicproquo_sdk::state::load_state(state_path, None) {
if let Ok(stored) = quicprochat_sdk::state::load_state(state_path, None) {
let kp = IdentityKeypair::from_seed(stored.identity_seed);
st.identity = Some(Arc::new(kp));
}
@@ -493,7 +493,7 @@ async fn do_resolve(client: &QpqClient, args: &str) -> anyhow::Result<()> {
return Ok(());
}
let rpc = client.rpc().map_err(|e| anyhow::anyhow!("{e}"))?;
match quicproquo_sdk::users::resolve_user(rpc, name).await? {
match quicprochat_sdk::users::resolve_user(rpc, name).await? {
Some(key) => println!(" {name} -> {}", hex::encode(&key)),
None => display::print_error(&format!("user '{name}' not found")),
}
@@ -510,7 +510,7 @@ async fn do_safety(client: &QpqClient, st: &ReplState, args: &str) -> anyhow::Re
let my_key = identity.public_key_bytes();
let rpc = client.rpc().map_err(|e| anyhow::anyhow!("{e}"))?;
let peer_key = quicproquo_sdk::users::resolve_user(rpc, name)
let peer_key = quicprochat_sdk::users::resolve_user(rpc, name)
.await?
.ok_or_else(|| anyhow::anyhow!("user '{name}' not found"))?;
if peer_key.len() != 32 {
@@ -519,7 +519,7 @@ async fn do_safety(client: &QpqClient, st: &ReplState, args: &str) -> anyhow::Re
let mut peer_arr = [0u8; 32];
peer_arr.copy_from_slice(&peer_key);
let sn = quicproquo_core::compute_safety_number(&my_key, &peer_arr);
let sn = quicprochat_core::compute_safety_number(&my_key, &peer_arr);
println!("\n{BOLD}Safety number with {name}:{RESET}");
println!(" {sn}\n");
println!("{DIM}Compare with {name} over a trusted channel.{RESET}");
@@ -536,7 +536,7 @@ async fn do_refresh_key(client: &QpqClient, st: &ReplState) -> anyhow::Result<()
.map_err(|e| anyhow::anyhow!("generate key package: {e}"))?;
let pub_key = identity.public_key_bytes();
let fp = quicproquo_sdk::keys::upload_key_package(rpc, &pub_key, &kp_bytes).await?;
let fp = quicprochat_sdk::keys::upload_key_package(rpc, &pub_key, &kp_bytes).await?;
display::print_status(&format!(
"KeyPackage uploaded (fp: {})",
hex::encode(&fp[..8.min(fp.len())])
@@ -554,7 +554,7 @@ async fn do_dm(client: &mut QpqClient, st: &mut ReplState, args: &str) -> anyhow
let rpc = client.rpc().map_err(|e| anyhow::anyhow!("{e}"))?;
let conv_store = client.conversations().map_err(|e| anyhow::anyhow!("{e}"))?;
let peer_key = quicproquo_sdk::users::resolve_user(rpc, username)
let peer_key = quicprochat_sdk::users::resolve_user(rpc, username)
.await?
.ok_or_else(|| anyhow::anyhow!("user '{username}' not found"))?;
@@ -565,13 +565,13 @@ async fn do_dm(client: &mut QpqClient, st: &mut ReplState, args: &str) -> anyhow
return Ok(());
}
let peer_kp = quicproquo_sdk::keys::fetch_key_package(rpc, &peer_key)
let peer_kp = quicprochat_sdk::keys::fetch_key_package(rpc, &peer_key)
.await?
.ok_or_else(|| anyhow::anyhow!("peer has no available KeyPackage"))?;
let mut member = GroupMember::new(Arc::clone(&identity));
let (conv_id, was_new) = quicproquo_sdk::groups::create_dm(
let (conv_id, was_new) = quicprochat_sdk::groups::create_dm(
rpc, conv_store, &mut member, &identity,
&peer_key, &peer_kp, None, None,
).await?;
@@ -599,7 +599,7 @@ async fn do_send(client: &QpqClient, st: &ReplState, msg: &str) -> anyhow::Resul
.load_conversation(conv_id)?
.ok_or_else(|| anyhow::anyhow!("conversation not found"))?;
let mut member = quicproquo_sdk::groups::restore_mls_state(&conv, &identity)?;
let mut member = quicprochat_sdk::groups::restore_mls_state(&conv, &identity)?;
let my_pub = identity.public_key_bytes();
let recipients: Vec<Vec<u8>> = conv
@@ -614,13 +614,13 @@ async fn do_send(client: &QpqClient, st: &ReplState, msg: &str) -> anyhow::Resul
}
let hybrid_keys = vec![None; recipients.len()];
quicproquo_sdk::messaging::send_message(
quicprochat_sdk::messaging::send_message(
rpc, &mut member, &identity, msg, &recipients, &hybrid_keys, conv_id.0.as_slice(),
).await?;
quicproquo_sdk::groups::save_mls_state(conv_store, conv_id, &member)?;
quicprochat_sdk::groups::save_mls_state(conv_store, conv_id, &member)?;
let now = quicproquo_sdk::conversation::now_ms();
let now = quicprochat_sdk::conversation::now_ms();
conv_store.save_message(&StoredMessage {
conversation_id: conv_id.clone(),
message_id: None,
@@ -647,10 +647,10 @@ async fn do_recv(client: &QpqClient, st: &ReplState) -> anyhow::Result<()> {
.load_conversation(conv_id)?
.ok_or_else(|| anyhow::anyhow!("conversation not found"))?;
let mut member = quicproquo_sdk::groups::restore_mls_state(&conv, &identity)?;
let mut member = quicprochat_sdk::groups::restore_mls_state(&conv, &identity)?;
let my_pub = identity.public_key_bytes();
let messages = quicproquo_sdk::messaging::receive_messages(
let messages = quicprochat_sdk::messaging::receive_messages(
rpc, &mut member, &my_pub, None, conv_id.0.as_slice(), &[],
).await?;
@@ -659,10 +659,10 @@ async fn do_recv(client: &QpqClient, st: &ReplState) -> anyhow::Result<()> {
return Ok(());
}
quicproquo_sdk::groups::save_mls_state(conv_store, conv_id, &member)?;
quicprochat_sdk::groups::save_mls_state(conv_store, conv_id, &member)?;
for m in &messages {
let sender_name = quicproquo_sdk::users::resolve_identity(rpc, &m.sender_key)
let sender_name = quicprochat_sdk::users::resolve_identity(rpc, &m.sender_key)
.await
.ok()
.flatten();
@@ -670,13 +670,13 @@ async fn do_recv(client: &QpqClient, st: &ReplState) -> anyhow::Result<()> {
let sender = sender_name.as_deref().unwrap_or(&sender_hex);
let body = match &m.message {
quicproquo_core::AppMessage::Chat { body, .. } => {
quicprochat_core::AppMessage::Chat { body, .. } => {
String::from_utf8_lossy(body).to_string()
}
other => format!("{other:?}"),
};
let now = quicproquo_sdk::conversation::now_ms();
let now = quicprochat_sdk::conversation::now_ms();
println!("{DIM}[{}]{RESET} {CYAN}{BOLD}{sender}{RESET}: {body}", ts(now));
conv_store.save_message(&StoredMessage {
@@ -772,7 +772,7 @@ async fn do_group(client: &mut QpqClient, st: &mut ReplState, args: &str) -> any
let identity = st.require_identity()?;
let conv_store = client.conversations().map_err(|e| anyhow::anyhow!("{e}"))?;
let mut member = GroupMember::new(Arc::clone(&identity));
let conv_id = quicproquo_sdk::groups::create_group(conv_store, &mut member, name)?;
let conv_id = quicprochat_sdk::groups::create_group(conv_store, &mut member, name)?;
st.set_conversation(conv_id, format!("#{name}"));
display::print_status(&format!("group #{name} created"));
}
@@ -788,10 +788,10 @@ async fn do_group(client: &mut QpqClient, st: &mut ReplState, args: &str) -> any
let rpc = client.rpc().map_err(|e| anyhow::anyhow!("{e}"))?;
let conv_store = client.conversations().map_err(|e| anyhow::anyhow!("{e}"))?;
let peer_key = quicproquo_sdk::users::resolve_user(rpc, user)
let peer_key = quicprochat_sdk::users::resolve_user(rpc, user)
.await?
.ok_or_else(|| anyhow::anyhow!("user '{user}' not found"))?;
let peer_kp = quicproquo_sdk::keys::fetch_key_package(rpc, &peer_key)
let peer_kp = quicprochat_sdk::keys::fetch_key_package(rpc, &peer_key)
.await?
.ok_or_else(|| anyhow::anyhow!("peer has no KeyPackage"))?;
@@ -799,9 +799,9 @@ async fn do_group(client: &mut QpqClient, st: &mut ReplState, args: &str) -> any
let conv = conv_store
.load_conversation(&conv_id)?
.ok_or_else(|| anyhow::anyhow!("group '{group}' not found"))?;
let mut member = quicproquo_sdk::groups::restore_mls_state(&conv, &identity)?;
let mut member = quicprochat_sdk::groups::restore_mls_state(&conv, &identity)?;
quicproquo_sdk::groups::invite_to_group(
quicprochat_sdk::groups::invite_to_group(
rpc, conv_store, &mut member, &identity,
&conv_id, &peer_key, &peer_kp, None, None,
).await?;
@@ -816,8 +816,8 @@ async fn do_group(client: &mut QpqClient, st: &mut ReplState, args: &str) -> any
let conv = conv_store
.load_conversation(&conv_id)?
.ok_or_else(|| anyhow::anyhow!("conversation not found"))?;
let mut member = quicproquo_sdk::groups::restore_mls_state(&conv, &identity)?;
quicproquo_sdk::groups::leave_group(rpc, conv_store, &mut member, &conv_id).await?;
let mut member = quicprochat_sdk::groups::restore_mls_state(&conv, &identity)?;
quicprochat_sdk::groups::leave_group(rpc, conv_store, &mut member, &conv_id).await?;
display::print_status("left group");
}
@@ -834,7 +834,7 @@ async fn do_group(client: &mut QpqClient, st: &mut ReplState, args: &str) -> any
for key in &conv.member_keys {
let short = hex::encode(&key[..4.min(key.len())]);
if let Ok(rpc) = client.rpc() {
if let Ok(Some(n)) = quicproquo_sdk::users::resolve_identity(rpc, key).await {
if let Ok(Some(n)) = quicprochat_sdk::users::resolve_identity(rpc, key).await {
println!(" @{n} {DIM}({short}){RESET}");
continue;
}
@@ -855,14 +855,14 @@ async fn do_group(client: &mut QpqClient, st: &mut ReplState, args: &str) -> any
let rpc = client.rpc().map_err(|e| anyhow::anyhow!("{e}"))?;
let conv_store = client.conversations().map_err(|e| anyhow::anyhow!("{e}"))?;
let peer_key = quicproquo_sdk::users::resolve_user(rpc, user)
let peer_key = quicprochat_sdk::users::resolve_user(rpc, user)
.await?
.ok_or_else(|| anyhow::anyhow!("user '{user}' not found"))?;
let conv = conv_store
.load_conversation(&conv_id)?
.ok_or_else(|| anyhow::anyhow!("conversation not found"))?;
let mut member = quicproquo_sdk::groups::restore_mls_state(&conv, &identity)?;
quicproquo_sdk::groups::remove_member_from_group(
let mut member = quicprochat_sdk::groups::restore_mls_state(&conv, &identity)?;
quicprochat_sdk::groups::remove_member_from_group(
rpc, conv_store, &mut member, &conv_id, &peer_key,
).await?;
display::print_status(&format!("removed @{user} from group"));
@@ -877,7 +877,7 @@ async fn do_group(client: &mut QpqClient, st: &mut ReplState, args: &str) -> any
let conv_id = st.require_conversation()?.clone();
let rpc = client.rpc().map_err(|e| anyhow::anyhow!("{e}"))?;
let conv_store = client.conversations().map_err(|e| anyhow::anyhow!("{e}"))?;
quicproquo_sdk::groups::set_group_metadata(
quicprochat_sdk::groups::set_group_metadata(
rpc, conv_store, &conv_id, new_name, "", &[],
).await?;
st.set_conversation(conv_id, format!("#{new_name}"));
@@ -892,8 +892,8 @@ async fn do_group(client: &mut QpqClient, st: &mut ReplState, args: &str) -> any
let conv = conv_store
.load_conversation(&conv_id)?
.ok_or_else(|| anyhow::anyhow!("conversation not found"))?;
let mut member = quicproquo_sdk::groups::restore_mls_state(&conv, &identity)?;
quicproquo_sdk::groups::rotate_group_keys(rpc, conv_store, &mut member, &conv_id).await?;
let mut member = quicprochat_sdk::groups::restore_mls_state(&conv, &identity)?;
quicprochat_sdk::groups::rotate_group_keys(rpc, conv_store, &mut member, &conv_id).await?;
display::print_status("group keys rotated");
}
@@ -911,7 +911,7 @@ async fn do_devices(client: &mut QpqClient, args: &str) -> anyhow::Result<()> {
match sub {
"list" => {
let rpc = client.rpc().map_err(|e| anyhow::anyhow!("{e}"))?;
let devices = quicproquo_sdk::devices::list_devices(rpc).await?;
let devices = quicprochat_sdk::devices::list_devices(rpc).await?;
if devices.is_empty() {
display::print_status("no devices registered");
} else {
@@ -941,7 +941,7 @@ async fn do_devices(client: &mut QpqClient, args: &str) -> anyhow::Result<()> {
let mut dev_id = vec![0u8; 16];
rand::rngs::OsRng.fill_bytes(&mut dev_id);
let was_new =
quicproquo_sdk::devices::register_device(rpc, &dev_id, name).await?;
quicprochat_sdk::devices::register_device(rpc, &dev_id, name).await?;
if was_new {
display::print_status(&format!(
"device registered: {name} (id: {})",
@@ -961,7 +961,7 @@ async fn do_devices(client: &mut QpqClient, args: &str) -> anyhow::Result<()> {
let id_bytes = hex::decode(id_hex)
.map_err(|e| anyhow::anyhow!("invalid device_id hex: {e}"))?;
let rpc = client.rpc().map_err(|e| anyhow::anyhow!("{e}"))?;
let revoked = quicproquo_sdk::devices::revoke_device(rpc, &id_bytes).await?;
let revoked = quicprochat_sdk::devices::revoke_device(rpc, &id_bytes).await?;
if revoked {
display::print_status(&format!("device revoked: {id_hex}"));
} else {
@@ -1004,8 +1004,8 @@ pub async fn run_v2_repl(
// Load identity from state.
let state_path = client.config_state_path();
if state_path.exists() {
if let Ok(stored) = quicproquo_sdk::state::load_state(&state_path, Some(pass))
.or_else(|_| quicproquo_sdk::state::load_state(&state_path, None))
if let Ok(stored) = quicprochat_sdk::state::load_state(&state_path, Some(pass))
.or_else(|_| quicprochat_sdk::state::load_state(&state_path, None))
{
let kp = IdentityKeypair::from_seed(stored.identity_seed);
st.identity = Some(Arc::new(kp));
@@ -1016,7 +1016,7 @@ pub async fn run_v2_repl(
}
}
println!("\n{BOLD}quicproquo v2 REPL{RESET}");
println!("\n{BOLD}quicprochat v2 REPL{RESET}");
println!("{DIM}Type /help for commands, /quit to exit.{RESET}\n");
if let Some(u) = client.username() {
display::print_status(&format!("authenticated as {u}"));

View File

@@ -1,4 +1,4 @@
//! Full-screen Ratatui TUI for quicproquo v2, driven by the SDK event system.
//! Full-screen Ratatui TUI for quicprochat v2, driven by the SDK event system.
//!
//! Layout:
//! +-- Conversations -+-- Messages ------------------------------+
@@ -22,7 +22,7 @@
//! Feature gate: requires both `v2` and `tui` features.
//!
//! **Note:** Message display is currently local-only. Use the REPL client for
//! end-to-end encrypted delivery. See `quicproquo-sdk::messaging` for the full pipeline.
//! end-to-end encrypted delivery. See `quicprochat-sdk::messaging` for the full pipeline.
use std::time::Duration;
@@ -41,9 +41,9 @@ use ratatui::{
};
use tokio::sync::broadcast;
use quicproquo_sdk::client::QpqClient;
use quicproquo_sdk::conversation::ConversationStore;
use quicproquo_sdk::events::ClientEvent;
use quicprochat_sdk::client::QpqClient;
use quicprochat_sdk::conversation::ConversationStore;
use quicprochat_sdk::events::ClientEvent;
// ── Data Types ──────────────────────────────────────────────────────────────
@@ -540,8 +540,8 @@ async fn handle_input(app: &mut TuiApp, client: &mut QpqClient, text: &str) {
// NOTE: TUI message display is local-only. The full MLS encryption
// pipeline (sealed sender + hybrid wrap + enqueue) is implemented in
// quicproquo-sdk/src/messaging.rs but is not yet wired into the TUI.
// Use the REPL client (`qpq repl`) for end-to-end message delivery.
// quicprochat-sdk/src/messaging.rs but is not yet wired into the TUI.
// Use the REPL client (`qpc repl`) for end-to-end message delivery.
app.notification = Some("Message queued locally (TUI send not yet wired to SDK)".to_string());
}
}
@@ -864,7 +864,7 @@ fn draw_status(frame: &mut Frame, app: &TuiApp, area: Rect) {
fn draw_help(frame: &mut Frame, area: Rect) {
let help_text = vec![
Line::from(Span::styled(
" quicproquo TUI -- Help",
" quicprochat TUI -- Help",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
@@ -959,7 +959,7 @@ fn load_messages_for_selected(app: &mut TuiApp, client: &QpqClient) {
};
let sdk_conv_id =
quicproquo_sdk::conversation::ConversationId::from_slice(&conv_id);
quicprochat_sdk::conversation::ConversationId::from_slice(&conv_id);
let sdk_conv_id = match sdk_conv_id {
Some(id) => id,
None => return,

View File

@@ -1,17 +1,17 @@
//! quicproquo CLI client library.
//! quicprochat CLI client library.
//!
//! # KeyPackage expiry and refresh
//!
//! KeyPackages are single-use (consumed when someone fetches them for an invite) and the server
//! may enforce a TTL (e.g. 24 hours). To stay invitable, run `qpq refresh-keypackage`
//! may enforce a TTL (e.g. 24 hours). To stay invitable, run `qpc refresh-keypackage`
//! periodically (e.g. before the server TTL) or after your KeyPackage was consumed:
//!
//! ```bash
//! qpq refresh-keypackage --state qpq-state.bin --server 127.0.0.1:7000
//! qpc refresh-keypackage --state qpc-state.bin --server 127.0.0.1:7000
//! ```
//!
//! Use the same `--access-token` (or `QPQ_ACCESS_TOKEN`) as for other authenticated
//! commands. See the [running-the-client](https://docs.quicproquo.dev/getting-started/running-the-client)
//! commands. See the [running-the-client](https://docs.quicprochat.dev/getting-started/running-the-client)
//! docs for details.
use std::sync::RwLock;

View File

@@ -1,4 +1,4 @@
//! quicproquo CLI client.
//! quicprochat CLI client.
// ── v2 feature gate: when compiled with --features v2, use the SDK-based CLI.
#[cfg(feature = "v2")]
@@ -19,20 +19,20 @@ use anyhow::Context;
#[cfg(not(feature = "v2"))]
use clap::{Parser, Subcommand};
#[cfg(not(feature = "v2"))]
use quicproquo_client::{
use quicprochat_client::{
cmd_chat, cmd_check_key, cmd_create_group, cmd_demo_group, cmd_export, cmd_export_verify,
cmd_fetch_key, cmd_health, cmd_invite, cmd_join, cmd_login, cmd_ping, cmd_recv, cmd_register,
cmd_register_state, cmd_refresh_keypackage, cmd_register_user, cmd_send, cmd_whoami,
init_auth, run_repl, set_insecure_skip_verify, ClientAuth,
};
#[cfg(all(feature = "tui", not(feature = "v2")))]
use quicproquo_client::client::tui::run_tui;
use quicprochat_client::client::tui::run_tui;
// ── CLI ───────────────────────────────────────────────────────────────────────
#[cfg(not(feature = "v2"))]
#[derive(Debug, Parser)]
#[command(name = "qpq", about = "quicproquo CLI client", version)]
#[command(name = "qpc", about = "quicprochat CLI client", version)]
struct Args {
/// Path to the server's TLS certificate (self-signed by default).
#[arg(
@@ -82,7 +82,7 @@ struct Args {
// ── Default-repl args (used when no subcommand is given) ─────────
/// State file path (identity + MLS state). Used when running the default REPL.
#[arg(long, default_value = "qpq-state.bin", env = "QPQ_STATE")]
#[arg(long, default_value = "qpc-state.bin", env = "QPQ_STATE")]
state: PathBuf,
/// Server address (host:port). Used when running the default REPL.
@@ -97,7 +97,7 @@ struct Args {
#[arg(long, env = "QPQ_PASSWORD")]
password: Option<String>,
/// Do not auto-start a local qpq-server (useful when connecting to a remote server).
/// Do not auto-start a local qpc-server (useful when connecting to a remote server).
#[arg(long, env = "QPQ_NO_SERVER")]
no_server: bool,
@@ -144,7 +144,7 @@ enum Command {
/// State file path (identity + MLS state).
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -203,7 +203,7 @@ enum Command {
/// State file path (identity + MLS state).
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -219,7 +219,7 @@ enum Command {
/// State file path (identity + MLS state).
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -234,7 +234,7 @@ enum Command {
/// State file path (identity + MLS state).
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -252,7 +252,7 @@ enum Command {
Invite {
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -267,7 +267,7 @@ enum Command {
Join {
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -279,7 +279,7 @@ enum Command {
Send {
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -300,7 +300,7 @@ enum Command {
Recv {
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -321,7 +321,7 @@ enum Command {
Repl {
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -333,7 +333,7 @@ enum Command {
/// OPAQUE password (prompted securely if --username is set but --password is not).
#[arg(long, env = "QPQ_PASSWORD")]
password: Option<String>,
/// Do not auto-start a local qpq-server.
/// Do not auto-start a local qpc-server.
#[arg(long, env = "QPQ_NO_SERVER")]
no_server: bool,
},
@@ -344,7 +344,7 @@ enum Command {
Tui {
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -363,7 +363,7 @@ enum Command {
Chat {
#[arg(
long,
default_value = "qpq-state.bin",
default_value = "qpc-state.bin",
env = "QPQ_STATE"
)]
state: PathBuf,
@@ -380,18 +380,18 @@ enum Command {
/// Export a conversation's message history to an encrypted, tamper-evident transcript file.
///
/// The output file uses Argon2id + ChaCha20-Poly1305 encryption with a SHA-256 hash chain
/// linking every record. Use `qpq export verify` to check chain integrity without decrypting.
/// linking every record. Use `qpc export verify` to check chain integrity without decrypting.
Export {
/// Path to the conversation database (.convdb file).
#[arg(long, default_value = "qpq-convdb.sqlite", env = "QPQ_CONV_DB")]
#[arg(long, default_value = "qpc-convdb.sqlite", env = "QPQ_CONV_DB")]
conv_db: PathBuf,
/// Conversation ID to export (32 hex chars = 16 bytes).
#[arg(long)]
conv_id: String,
/// Output path for the .qpqt transcript file.
#[arg(long, default_value = "transcript.qpqt")]
/// Output path for the .qpct transcript file.
#[arg(long, default_value = "transcript.qpct")]
output: PathBuf,
/// Password used to encrypt the transcript (separate from the state/DB password).
@@ -405,7 +405,7 @@ enum Command {
/// Verify the hash-chain integrity of a transcript file without decrypting content.
ExportVerify {
/// Path to the .qpqt transcript file to verify.
/// Path to the .qpct transcript file to verify.
#[arg(long)]
input: PathBuf,
},
@@ -418,7 +418,7 @@ enum Command {
playbook: PathBuf,
/// State file path (identity + MLS state).
#[arg(long, default_value = "qpq-state.bin", env = "QPQ_STATE")]
#[arg(long, default_value = "qpc-state.bin", env = "QPQ_STATE")]
state: PathBuf,
/// Server address (host:port).
@@ -441,14 +441,14 @@ enum Command {
// ── Helpers ───────────────────────────────────────────────────────────────────
#[cfg(not(feature = "v2"))]
/// Returns `qpq-{username}.bin` when `state` is still at the default
/// (`qpq-state.bin`) and a username has been provided. Otherwise returns
/// `state` unchanged. This lets `qpq --username alice` automatically isolate
/// Returns `qpc-{username}.bin` when `state` is still at the default
/// (`qpc-state.bin`) and a username has been provided. Otherwise returns
/// `state` unchanged. This lets `qpc --username alice` automatically isolate
/// Alice's state without requiring a manual `--state` flag.
fn derive_state_path(state: PathBuf, username: Option<&str>) -> PathBuf {
if state == Path::new("qpq-state.bin") {
if state == Path::new("qpc-state.bin") {
if let Some(uname) = username {
return PathBuf::from(format!("qpq-{uname}.bin"));
return PathBuf::from(format!("qpc-{uname}.bin"));
}
}
state
@@ -470,24 +470,24 @@ async fn run_playbook(
device_id: Option<&str>,
extra_vars: &[String],
) -> anyhow::Result<()> {
use quicproquo_client::PlaybookRunner;
use quicprochat_client::PlaybookRunner;
let insecure = std::env::var("QPQ_DANGER_ACCEPT_INVALID_CERTS").is_ok();
// Connect to server.
let client =
quicproquo_client::connect_node_opt(server, ca_cert, server_name, insecure)
quicprochat_client::connect_node_opt(server, ca_cert, server_name, insecure)
.await
.context("connect to server")?;
// Build session state.
let mut session = quicproquo_client::client::session::SessionState::load(state, state_pw)
let mut session = quicprochat_client::client::session::SessionState::load(state, state_pw)
.context("load session state")?;
// If username/password provided, do OPAQUE login.
if let (Some(uname), Some(pw)) = (username, password) {
if let Err(e) =
quicproquo_client::opaque_login(&client, uname, pw, &session.identity.public_key_bytes()).await
quicprochat_client::opaque_login(&client, uname, pw, &session.identity.public_key_bytes()).await
{
eprintln!("OPAQUE login failed: {e:#}");
}

View File

@@ -1,7 +1,7 @@
//! v2 CLI command implementations — thin wrappers over the SDK.
use quicproquo_sdk::client::QpqClient;
use quicproquo_sdk::error::SdkError;
use quicprochat_sdk::client::QpqClient;
use quicprochat_sdk::error::SdkError;
/// Register a new user account via OPAQUE.
pub async fn cmd_register_user(
@@ -61,7 +61,7 @@ pub async fn cmd_health(client: &mut QpqClient) -> Result<(), SdkError> {
/// Resolve a username to its identity key.
pub async fn cmd_resolve(client: &mut QpqClient, username: &str) -> Result<(), SdkError> {
let rpc = client.rpc()?;
match quicproquo_sdk::users::resolve_user(rpc, username).await? {
match quicprochat_sdk::users::resolve_user(rpc, username).await? {
Some(key) => {
println!("{username} -> {}", hex::encode(&key));
}
@@ -75,7 +75,7 @@ pub async fn cmd_resolve(client: &mut QpqClient, username: &str) -> Result<(), S
/// List registered devices.
pub async fn cmd_devices_list(client: &mut QpqClient) -> Result<(), SdkError> {
let rpc = client.rpc()?;
let devices = quicproquo_sdk::devices::list_devices(rpc).await?;
let devices = quicprochat_sdk::devices::list_devices(rpc).await?;
if devices.is_empty() {
println!("no devices registered");
} else {
@@ -101,7 +101,7 @@ pub async fn cmd_devices_register(
let rpc = client.rpc()?;
let id_bytes = hex::decode(device_id)
.map_err(|e| SdkError::Other(anyhow::anyhow!("invalid device_id hex: {e}")))?;
let was_new = quicproquo_sdk::devices::register_device(rpc, &id_bytes, device_name).await?;
let was_new = quicprochat_sdk::devices::register_device(rpc, &id_bytes, device_name).await?;
if was_new {
println!("device registered: {device_name}");
} else {
@@ -118,7 +118,7 @@ pub async fn cmd_devices_revoke(
let rpc = client.rpc()?;
let id_bytes = hex::decode(device_id)
.map_err(|e| SdkError::Other(anyhow::anyhow!("invalid device_id hex: {e}")))?;
let revoked = quicproquo_sdk::devices::revoke_device(rpc, &id_bytes).await?;
let revoked = quicprochat_sdk::devices::revoke_device(rpc, &id_bytes).await?;
if revoked {
println!("device revoked: {device_id}");
} else {
@@ -131,12 +131,12 @@ pub async fn cmd_devices_revoke(
pub async fn cmd_recovery_setup(client: &mut QpqClient) -> Result<(), SdkError> {
// Load identity seed from state file.
let state_path = client.config_state_path();
let stored = quicproquo_sdk::state::load_state(&state_path, None)
let stored = quicprochat_sdk::state::load_state(&state_path, None)
.map_err(|e| SdkError::Crypto(format!("load identity for recovery: {e}")))?;
let rpc = client.rpc()?;
let codes =
quicproquo_sdk::recovery::setup_recovery(rpc, &stored.identity_seed, &[]).await?;
quicprochat_sdk::recovery::setup_recovery(rpc, &stored.identity_seed, &[]).await?;
println!("=== RECOVERY CODES ===");
println!("Save these codes securely. They will NOT be shown again.");
@@ -155,7 +155,7 @@ pub async fn cmd_recovery_setup(client: &mut QpqClient) -> Result<(), SdkError>
/// List pending outbox entries.
pub fn cmd_outbox_list(client: &QpqClient) -> Result<(), SdkError> {
let store = client.conversations()?;
let entries = quicproquo_sdk::outbox::list_pending(store)?;
let entries = quicprochat_sdk::outbox::list_pending(store)?;
if entries.is_empty() {
println!("outbox is empty — no pending messages");
} else {
@@ -178,7 +178,7 @@ pub fn cmd_outbox_list(client: &QpqClient) -> Result<(), SdkError> {
pub async fn cmd_outbox_retry(client: &mut QpqClient) -> Result<(), SdkError> {
let rpc = client.rpc()?;
let store = client.conversations()?;
let (sent, failed) = quicproquo_sdk::outbox::flush_outbox(rpc, store).await?;
let (sent, failed) = quicprochat_sdk::outbox::flush_outbox(rpc, store).await?;
println!("outbox flush: {sent} sent, {failed} permanently failed");
Ok(())
}
@@ -186,7 +186,7 @@ pub async fn cmd_outbox_retry(client: &mut QpqClient) -> Result<(), SdkError> {
/// Clear permanently failed outbox entries.
pub fn cmd_outbox_clear(client: &QpqClient) -> Result<(), SdkError> {
let store = client.conversations()?;
let cleared = quicproquo_sdk::outbox::clear_failed(store)?;
let cleared = quicprochat_sdk::outbox::clear_failed(store)?;
println!("cleared {cleared} failed outbox entries");
Ok(())
}
@@ -198,10 +198,10 @@ pub async fn cmd_recovery_restore(
) -> Result<(), SdkError> {
let rpc = client.rpc()?;
let (identity_seed, conversation_ids) =
quicproquo_sdk::recovery::recover_account(rpc, code).await?;
quicprochat_sdk::recovery::recover_account(rpc, code).await?;
// Restore identity.
let keypair = quicproquo_core::IdentityKeypair::from_seed(identity_seed);
let keypair = quicprochat_core::IdentityKeypair::from_seed(identity_seed);
client.set_identity_key(keypair.public_key_bytes().to_vec());
println!("account recovered successfully");
@@ -214,14 +214,14 @@ pub async fn cmd_recovery_restore(
}
// Save recovered state.
let state = quicproquo_sdk::state::StoredState {
let state = quicprochat_sdk::state::StoredState {
identity_seed,
group: None,
hybrid_key: None,
member_keys: Vec::new(),
};
let state_path = client.config_state_path();
quicproquo_sdk::state::save_state(&state_path, &state, None)?;
quicprochat_sdk::state::save_state(&state_path, &state, None)?;
println!("state saved to {}", state_path.display());
Ok(())

View File

@@ -1,4 +1,4 @@
//! v2 CLI entry point — thin shell over `quicproquo_sdk::QpqClient`.
//! v2 CLI entry point — thin shell over `quicprochat_sdk::QpqClient`.
//!
//! Activated via `--features v2`. Replaces the v1 Cap'n Proto RPC main
//! with a simplified command surface backed by the SDK.
@@ -10,15 +10,15 @@ use std::time::Duration;
use anyhow::Context;
use clap::{Parser, Subcommand};
use quicproquo_sdk::client::QpqClient;
use quicproquo_sdk::config::ClientConfig;
use quicprochat_sdk::client::QpqClient;
use quicprochat_sdk::config::ClientConfig;
use crate::v2_commands;
// ── CLI ───────────────────────────────────────────────────────────────────────
#[derive(Debug, Parser)]
#[command(name = "qpq", about = "quicproquo CLI client (v2)", version)]
#[command(name = "qpc", about = "quicprochat CLI client (v2)", version)]
struct Args {
/// Server address (host:port).
#[arg(long, global = true, default_value = "127.0.0.1:7000", env = "QPQ_SERVER")]
@@ -37,7 +37,7 @@ struct Args {
db_password: Option<String>,
/// Path to the client state file (identity key, MLS state).
#[arg(long, global = true, default_value = "qpq-state.bin", env = "QPQ_STATE")]
#[arg(long, global = true, default_value = "qpc-state.bin", env = "QPQ_STATE")]
state: PathBuf,
/// DANGER: Skip TLS certificate verification. Development only.
@@ -48,7 +48,7 @@ struct Args {
)]
danger_accept_invalid_certs: bool,
/// Do not auto-start a local qpq-server.
/// Do not auto-start a local qpc-server.
#[arg(long, global = true, env = "QPQ_NO_SERVER")]
no_server: bool,
@@ -210,17 +210,17 @@ impl Drop for ServerGuard {
}
}
/// Find the `qpq-server` binary: same directory as current exe, then PATH.
/// Find the `qpc-server` binary: same directory as current exe, then PATH.
fn find_server_binary() -> Option<PathBuf> {
if let Ok(exe) = std::env::current_exe() {
let sibling = exe.with_file_name("qpq-server");
let sibling = exe.with_file_name("qpc-server");
if sibling.exists() {
return Some(sibling);
}
}
std::env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths)
.map(|dir| dir.join("qpq-server"))
.map(|dir| dir.join("qpc-server"))
.find(|p| p.exists())
})
}
@@ -241,7 +241,7 @@ async fn probe_server(server_addr: &str) -> bool {
.is_ok()
}
/// Start a local qpq-server if one isn't already running.
/// Start a local qpc-server if one isn't already running.
/// Returns a guard that kills the child on drop (if we started one).
async fn ensure_server_running(
server_addr: &str,
@@ -258,8 +258,8 @@ async fn ensure_server_running(
let binary = find_server_binary().ok_or_else(|| {
anyhow::anyhow!(
"server at {server_addr} is not reachable and qpq-server binary not found; \
start a server manually or install qpq-server"
"server at {server_addr} is not reachable and qpc-server binary not found; \
start a server manually or install qpc-server"
)
})?;
@@ -300,7 +300,7 @@ async fn ensure_server_running(
if start.elapsed() > max_wait {
anyhow::bail!(
"auto-started qpq-server but it did not become ready within {max_wait:?}"
"auto-started qpc-server but it did not become ready within {max_wait:?}"
);
}
@@ -336,9 +336,9 @@ async fn connect_client(args: &Args) -> anyhow::Result<QpqClient> {
// Try loading identity from state file.
if args.state.exists() {
match quicproquo_sdk::state::load_state(&args.state, args.db_password.as_deref()) {
match quicprochat_sdk::state::load_state(&args.state, args.db_password.as_deref()) {
Ok(stored) => {
let keypair = quicproquo_core::IdentityKeypair::from_seed(stored.identity_seed);
let keypair = quicprochat_core::IdentityKeypair::from_seed(stored.identity_seed);
client.set_identity_key(keypair.public_key_bytes().to_vec());
}
Err(e) => {
@@ -414,13 +414,13 @@ async fn run(args: Args) -> anyhow::Result<()> {
let config = build_config(&args)?;
let mut client = QpqClient::new(config);
if args.state.exists() {
match quicproquo_sdk::state::load_state(
match quicprochat_sdk::state::load_state(
&args.state,
args.db_password.as_deref(),
) {
Ok(stored) => {
let keypair =
quicproquo_core::IdentityKeypair::from_seed(stored.identity_seed);
quicprochat_core::IdentityKeypair::from_seed(stored.identity_seed);
client.set_identity_key(keypair.public_key_bytes().to_vec());
}
Err(e) => {

View File

@@ -1,4 +1,4 @@
// cargo_bin! only works for current package's binary; we spawn qpq-server from another package.
// cargo_bin! only works for current package's binary; we spawn qpc-server from another package.
#![allow(deprecated)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::await_holding_lock)] // AUTH_LOCK intentionally held across await to serialize tests
@@ -18,12 +18,12 @@ fn ensure_rustls_provider() {
use sha2::{Sha256, Digest};
use quicproquo_client::{
use quicprochat_client::{
cmd_create_group, cmd_invite, cmd_join, cmd_login, cmd_ping, cmd_register_state,
cmd_register_user, cmd_send, connect_node, create_channel, enqueue, fetch_wait, init_auth,
opaque_login, receive_pending_plaintexts, resolve_user, ClientAuth,
};
use quicproquo_core::{GroupMember, HybridKeypair, IdentityKeypair, ReceivedMessage};
use quicprochat_core::{GroupMember, HybridKeypair, IdentityKeypair, ReceivedMessage};
/// Serialises ALL tests that call `init_auth` to prevent the global `AUTH_CONTEXT`
/// from being overwritten by concurrent tests. Every test that mutates auth state
@@ -71,7 +71,7 @@ fn spawn_server(base: &std::path::Path, extra_args: &[&str]) -> (String, PathBuf
let tls_key = base.join("server-key.der");
let data_dir = base.join("data");
let server_bin = cargo_bin("qpq-server");
let server_bin = cargo_bin("qpc-server");
let mut cmd = Command::new(server_bin);
cmd.arg("--listen")
.arg(&listen)
@@ -948,14 +948,14 @@ async fn e2e_dm_multi_message_epoch_synchronized() -> anyhow::Result<()> {
/// Helper: load a state file and reconstruct a GroupMember with its keystore.
fn load_member(state_path: &std::path::Path) -> (GroupMember, Option<HybridKeypair>) {
let bytes = std::fs::read(state_path).expect("read state");
let state: quicproquo_client::client::state::StoredState =
let state: quicprochat_client::client::state::StoredState =
bincode::deserialize(&bytes).expect("decode state");
state.into_parts(state_path).expect("into_parts")
}
/// Helper: save a GroupMember back to its state file.
fn save_member(state_path: &std::path::Path, member: &GroupMember, hybrid: Option<&HybridKeypair>) {
quicproquo_client::client::state::save_state(state_path, member, hybrid, None)
quicprochat_client::client::state::save_state(state_path, member, hybrid, None)
.expect("save state");
}
@@ -1394,7 +1394,7 @@ async fn e2e_file_upload_download() -> anyhow::Result<()> {
{
let mut p = req.get();
let mut auth = p.reborrow().init_auth();
quicproquo_client::client::rpc::set_auth(&mut auth)?;
quicprochat_client::client::rpc::set_auth(&mut auth)?;
p.set_blob_hash(&hash);
p.set_chunk(file_data);
p.set_offset(0);
@@ -1426,7 +1426,7 @@ async fn e2e_file_upload_download() -> anyhow::Result<()> {
{
let mut p = req.get();
let mut auth = p.reborrow().init_auth();
quicproquo_client::client::rpc::set_auth(&mut auth)?;
quicprochat_client::client::rpc::set_auth(&mut auth)?;
p.set_blob_id(&blob_id);
p.set_offset(0);
p.set_length(file_data.len() as u32);
@@ -1463,7 +1463,7 @@ async fn e2e_file_upload_download() -> anyhow::Result<()> {
{
let mut p = req.get();
let mut auth = p.reborrow().init_auth();
quicproquo_client::client::rpc::set_auth(&mut auth)?;
quicprochat_client::client::rpc::set_auth(&mut auth)?;
p.set_blob_id(&blob_id);
p.set_offset(100);
p.set_length(200);
@@ -1521,7 +1521,7 @@ async fn e2e_blob_hash_mismatch() -> anyhow::Result<()> {
{
let mut p = req.get();
let mut auth = p.reborrow().init_auth();
quicproquo_client::client::rpc::set_auth(&mut auth)?;
quicprochat_client::client::rpc::set_auth(&mut auth)?;
p.set_blob_hash(&wrong_hash);
p.set_chunk(&chunk_data[..]);
p.set_offset(0);
@@ -1560,7 +1560,7 @@ fn spawn_server_custom(base: &std::path::Path, args: &[&str]) -> (String, PathBu
let tls_key = base.join("server-key.der");
let data_dir = base.join("data");
let server_bin = cargo_bin("qpq-server");
let server_bin = cargo_bin("qpc-server");
let mut cmd = Command::new(server_bin);
cmd.arg("--listen")
.arg(&listen)
@@ -1888,7 +1888,7 @@ async fn e2e_keypackage_exhaustion_graceful() -> anyhow::Result<()> {
// Now try to fetch A's KeyPackage again — it should be exhausted.
let client = local.run_until(connect_node(&server, &ca_cert, "localhost")).await?;
let pkg = local
.run_until(quicproquo_client::client::rpc::fetch_key_package(&client, &a_pk))
.run_until(quicprochat_client::client::rpc::fetch_key_package(&client, &a_pk))
.await?;
// Graceful: either empty (no package available) or an error — but NOT a panic.

View File

@@ -1,8 +1,8 @@
[package]
name = "quicproquo-core"
name = "quicprochat-core"
version = "0.1.0"
edition.workspace = true
description = "Crypto primitives, MLS state machine, and hybrid post-quantum KEM for quicproquo."
description = "Crypto primitives, MLS state machine, and hybrid post-quantum KEM for quicprochat."
license = "Apache-2.0 OR MIT"
repository.workspace = true
@@ -19,7 +19,7 @@ native = [
"dep:opaque-ke",
"dep:bincode",
"dep:capnp",
"dep:quicproquo-proto",
"dep:quicprochat-proto",
"dep:tokio",
]
@@ -54,7 +54,7 @@ bincode = { workspace = true, optional = true }
# Serialisation (native only)
capnp = { workspace = true, optional = true }
quicproquo-proto = { path = "../quicproquo-proto", optional = true }
quicprochat-proto = { path = "../quicprochat-proto", optional = true }
# Async runtime (native only)
tokio = { workspace = true, optional = true }

View File

@@ -8,7 +8,7 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use quicproquo_core::{compute_safety_number, IdentityKeypair, padding};
use quicprochat_core::{compute_safety_number, IdentityKeypair, padding};
// ── Identity keypair benchmarks ──────────────────────────────────────────────
@@ -48,7 +48,7 @@ fn bench_identity_verify(c: &mut Criterion) {
// ── Sealed sender benchmarks ─────────────────────────────────────────────────
fn bench_sealed_sender(c: &mut Criterion) {
use quicproquo_core::sealed_sender::{seal, unseal};
use quicprochat_core::sealed_sender::{seal, unseal};
let sizes: &[(&str, usize)] = &[
("32B", 32),

View File

@@ -6,7 +6,7 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use quicproquo_core::{hybrid_encrypt, hybrid_decrypt, HybridKeypair};
use quicprochat_core::{hybrid_encrypt, hybrid_decrypt, HybridKeypair};
// ── Classical baseline (X25519 + ChaCha20-Poly1305) ─────────────────────────

View File

@@ -7,7 +7,7 @@
use std::sync::Arc;
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use quicproquo_core::{GroupMember, IdentityKeypair};
use quicprochat_core::{GroupMember, IdentityKeypair};
/// Create identities and a group of the given size.
/// Returns (creator, Vec<members>).

View File

@@ -11,17 +11,17 @@ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criteri
fn capnp_serialize_envelope(seq: u64, data: &[u8]) -> Vec<u8> {
let mut msg = capnp::message::Builder::new_default();
{
let mut envelope = msg.init_root::<quicproquo_proto::node_capnp::envelope::Builder>();
let mut envelope = msg.init_root::<quicprochat_proto::node_capnp::envelope::Builder>();
envelope.set_seq(seq);
envelope.set_data(data);
}
quicproquo_proto::to_bytes(&msg).unwrap()
quicprochat_proto::to_bytes(&msg).unwrap()
}
fn capnp_deserialize_envelope(bytes: &[u8]) -> (u64, Vec<u8>) {
let reader = quicproquo_proto::from_bytes(bytes).unwrap();
let reader = quicprochat_proto::from_bytes(bytes).unwrap();
let envelope = reader
.get_root::<quicproquo_proto::node_capnp::envelope::Reader>()
.get_root::<quicprochat_proto::node_capnp::envelope::Reader>()
.unwrap();
(envelope.get_seq(), envelope.get_data().unwrap().to_vec())
}

View File

@@ -1,5 +1,5 @@
syntax = "proto3";
package quicproquo.bench;
package quicprochat.bench;
// Equivalent to the Envelope struct in delivery.capnp
message Envelope {

View File

@@ -1,4 +1,4 @@
//! Error types for `quicproquo-core`.
//! Error types for `quicprochat-core`.
use thiserror::Error;

View File

@@ -14,7 +14,7 @@
//! # Wire format
//!
//! KeyPackages are TLS-encoded using `tls_codec` (same version as openmls).
//! The resulting bytes are opaque to the quicproquo transport layer.
//! The resulting bytes are opaque to the quicprochat transport layer.
use openmls::prelude::{
Ciphersuite, Credential, CredentialType, CredentialWithKey, CryptoConfig, KeyPackage,
@@ -25,7 +25,7 @@ use sha2::{Digest, Sha256};
use crate::{error::CoreError, identity::IdentityKeypair};
/// The MLS ciphersuite used throughout quicproquo (RFC 9420 §17.1).
/// The MLS ciphersuite used throughout quicprochat (RFC 9420 §17.1).
pub const ALLOWED_CIPHERSUITE: Ciphersuite =
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;

View File

@@ -1,5 +1,5 @@
//! Core cryptographic primitives, MLS group state machine, and hybrid
//! post-quantum KEM for quicproquo.
//! post-quantum KEM for quicprochat.
//!
//! # WASM support
//!

View File

@@ -5,7 +5,7 @@
use opaque_ke::CipherSuite;
/// OPAQUE cipher suite for quicproquo.
/// OPAQUE cipher suite for quicprochat.
///
/// - **OPRF**: Ristretto255 (curve25519-based, ~128-bit security)
/// - **Key exchange**: Triple-DH (3DH) over Ristretto255 with SHA-512

View File

@@ -48,7 +48,7 @@ use zeroize::Zeroizing;
use crate::error::CoreError;
/// Domain separation label for the hybrid Noise handshake.
const PROTOCOL_NAME: &[u8] = b"quicproquo-pq-noise-v1";
const PROTOCOL_NAME: &[u8] = b"quicprochat-pq-noise-v1";
/// ML-KEM-768 encapsulation key length.
const MLKEM_EK_LEN: usize = 1184;

View File

@@ -91,10 +91,10 @@ fn generate_code(rng: &mut impl RngCore) -> String {
}
/// Derive a 32-byte recovery token from a code (used for server-side lookup).
/// The token is `SHA-256("qpq-recovery-token:" || code)`.
/// The token is `SHA-256("qpc-recovery-token:" || code)`.
fn derive_recovery_token(code: &str) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(b"qpq-recovery-token:");
hasher.update(b"qpc-recovery-token:");
hasher.update(code.as_bytes());
hasher.finalize().into()
}
@@ -206,7 +206,7 @@ pub fn recover_from_bundle(
/// Compute the token hash for a recovery code (for server-side lookup).
///
/// This is `SHA-256(SHA-256("qpq-recovery-token:" || code))`.
/// This is `SHA-256(SHA-256("qpc-recovery-token:" || code))`.
pub fn recovery_token_hash(code: &str) -> Vec<u8> {
let token = derive_recovery_token(code);
Sha256::digest(token).to_vec()

View File

@@ -7,7 +7,7 @@
//! 1. Sort the keys lexicographically so the result is symmetric.
//! 2. Concatenate: `input = key_lo || key_hi` (64 bytes).
//! 3. Compute HMAC-SHA256(key=info, data=input) where
//! `info = b"quicproquo-safety-number-v1"`.
//! `info = b"quicprochat-safety-number-v1"`.
//! 4. Iterate the HMAC 5200 times: `hash = HMAC-SHA256(key=info, data=hash)`.
//! 5. Interpret the 32-byte result as 4× 64-bit big-endian integers
//! (= 256 bits → 4 groups of 64 bits). Extract 3 decimal groups per
@@ -23,7 +23,7 @@ use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
/// Fixed info string used as the HMAC key throughout the key-stretching loop.
const INFO: &[u8] = b"quicproquo-safety-number-v1";
const INFO: &[u8] = b"quicprochat-safety-number-v1";
/// Compute a 60-digit safety number from two 32-byte Ed25519 public keys.
///

View File

@@ -1,5 +1,5 @@
[package]
name = "quicproquo-kt"
name = "quicprochat-kt"
version = "0.1.0"
edition.workspace = true
description = "Key Transparency: append-only SHA-256 Merkle log for (username, identity_key) bindings."

View File

@@ -1,8 +1,8 @@
[package]
name = "quicproquo-p2p"
name = "quicprochat-p2p"
version = "0.1.0"
edition.workspace = true
description = "P2P transport layer for quicproquo using iroh."
description = "P2P transport layer for quicprochat using iroh."
license = "Apache-2.0 OR MIT"
repository.workspace = true
@@ -19,7 +19,7 @@ tracing = "0.1"
anyhow = "1"
# Mesh identity & store-and-forward
quicproquo-core = { path = "../quicproquo-core", default-features = false }
quicprochat-core = { path = "../quicprochat-core", default-features = false }
serde = { workspace = true }
serde_json = { workspace = true }
sha2 = { workspace = true }

View File

@@ -149,7 +149,7 @@ impl MeshEnvelope {
self.max_hops,
self.timestamp,
);
quicproquo_core::IdentityKeypair::verify_raw(&sender_key, &signable, &sig).is_ok()
quicprochat_core::IdentityKeypair::verify_raw(&sender_key, &signable, &sig).is_ok()
}
/// Check whether this envelope has expired (TTL elapsed since timestamp).

View File

@@ -1,4 +1,4 @@
//! Self-sovereign mesh identity backed by quicproquo-core Ed25519 keypairs.
//! Self-sovereign mesh identity backed by quicprochat-core Ed25519 keypairs.
//!
//! A [`MeshIdentity`] wraps an [`IdentityKeypair`] with a peer directory,
//! enabling P2P nodes to persist identity and track known peers across
@@ -7,7 +7,7 @@
use std::collections::HashMap;
use std::path::Path;
use quicproquo_core::IdentityKeypair;
use quicprochat_core::IdentityKeypair;
use serde::{Deserialize, Serialize};
#[cfg(unix)]
@@ -130,7 +130,7 @@ mod tests {
let msg = b"test message";
let sig = id.sign(msg);
// Verify through quicproquo_core
// Verify through quicprochat_core
let pk = id.public_key();
IdentityKeypair::verify_raw(&pk, msg, &sig).expect("valid signature");
}

View File

@@ -1,4 +1,4 @@
//! P2P transport layer for quicproquo using iroh.
//! P2P transport layer for quicprochat using iroh.
//!
//! Provides direct peer-to-peer QUIC connections with NAT traversal via iroh
//! relay servers. When both peers are online, messages bypass the central
@@ -29,10 +29,10 @@ use crate::envelope::MeshEnvelope;
use crate::identity::MeshIdentity;
use crate::store::MeshStore;
/// ALPN protocol identifier for quicproquo P2P messaging.
/// Updated from the original project name "quicnprotochat" to "quicproquo" (breaking wire change;
/// ALPN protocol identifier for quicprochat P2P messaging.
/// Updated from the original project name "quicnprotochat" to "quicprochat" (breaking wire change;
/// all peers must be on the same version to connect).
const P2P_ALPN: &[u8] = b"quicproquo/p2p/1";
const P2P_ALPN: &[u8] = b"quicprochat/p2p/1";
/// A P2P node backed by an iroh endpoint.
///

View File

@@ -30,7 +30,7 @@ pub struct MeshTrafficConfig {
impl Default for MeshTrafficConfig {
fn default() -> Self {
Self {
padding_boundary: quicproquo_core::padding::DEFAULT_PADDING_BOUNDARY,
padding_boundary: quicprochat_core::padding::DEFAULT_PADDING_BOUNDARY,
decoy_interval_ms: 5000,
}
}
@@ -38,7 +38,7 @@ impl Default for MeshTrafficConfig {
/// Pad a mesh payload to the nearest boundary before wrapping in a [`MeshEnvelope`].
pub fn pad_mesh_payload(payload: &[u8], boundary: usize) -> Vec<u8> {
quicproquo_core::padding::pad_uniform(payload, boundary)
quicprochat_core::padding::pad_uniform(payload, boundary)
}
/// Create a [`MeshEnvelope`] with a uniformly padded payload.
@@ -85,7 +85,7 @@ pub fn spawn_mesh_decoy_generator(
}
// Generate a decoy: padded empty payload with a random recipient.
let decoy_payload = quicproquo_core::padding::generate_decoy(config.padding_boundary);
let decoy_payload = quicprochat_core::padding::generate_decoy(config.padding_boundary);
let mut fake_recipient = [0u8; 32];
rand::thread_rng().fill(&mut fake_recipient);
@@ -121,7 +121,7 @@ mod tests {
let padded = pad_mesh_payload(payload, 256);
assert_eq!(padded.len() % 256, 0);
let unpadded = quicproquo_core::padding::unpad_uniform(&padded).unwrap();
let unpadded = quicprochat_core::padding::unpad_uniform(&padded).unwrap();
assert_eq!(unpadded, payload);
}
@@ -136,7 +136,7 @@ mod tests {
assert!(env.verify());
// The inner payload should unpad correctly.
let unpadded = quicproquo_core::padding::unpad_uniform(&env.payload).unwrap();
let unpadded = quicprochat_core::padding::unpad_uniform(&env.payload).unwrap();
assert_eq!(unpadded, b"short");
}
@@ -149,7 +149,7 @@ mod tests {
assert_eq!(env.payload.len() % 256, 0);
assert_eq!(env.payload.len(), 512); // 500 + 4 = 504, rounds to 512
let unpadded = quicproquo_core::padding::unpad_uniform(&env.payload).unwrap();
let unpadded = quicprochat_core::padding::unpad_uniform(&env.payload).unwrap();
assert_eq!(unpadded, payload);
}

View File

@@ -1,8 +1,8 @@
[package]
name = "quicproquo-plugin-api"
name = "quicprochat-plugin-api"
version = "0.1.0"
edition.workspace = true
description = "C-ABI vtable for quicproquo server plugins. No std dependency; usable from bare-metal plugin authors."
description = "C-ABI vtable for quicprochat server plugins. No std dependency; usable from bare-metal plugin authors."
license = "Apache-2.0 OR MIT"
repository.workspace = true

View File

@@ -1,14 +1,14 @@
//! quicproquo server plugin API — C-ABI vtable.
//! quicprochat server plugin API — C-ABI vtable.
//!
//! # Overview
//!
//! Every plugin is a `cdylib` that exports one symbol:
//!
//! ```c
//! extern "C" int32_t qpq_plugin_init(HookVTable *vtable);
//! extern "C" int32_t qpc_plugin_init(HookVTable *vtable);
//! ```
//!
//! The server passes a zeroed [`HookVTable`] to `qpq_plugin_init`. The plugin
//! The server passes a zeroed [`HookVTable`] to `qpc_plugin_init`. The plugin
//! fills in whichever function pointers it cares about and returns `0` on
//! success or a negative error code on failure. Unused slots remain null and
//! the server treats them as no-ops.
@@ -105,7 +105,7 @@ pub struct CFetchEvent {
// ── HookVTable ────────────────────────────────────────────────────────────────
/// C-ABI function-pointer table filled by [`qpq_plugin_init`].
/// C-ABI function-pointer table filled by [`qpc_plugin_init`].
///
/// All fields default to null (no-op). The server only calls a slot when its
/// pointer is non-null. The `user_data` field is passed as the first argument

View File

@@ -1,8 +1,8 @@
[package]
name = "quicproquo-proto"
name = "quicprochat-proto"
version = "0.2.0"
edition.workspace = true
description = "Protocol types for quicproquo — v1 Cap'n Proto (legacy) + v2 Protobuf (prost)"
description = "Protocol types for quicprochat — v1 Cap'n Proto (legacy) + v2 Protobuf (prost)"
license = "Apache-2.0 OR MIT"
repository.workspace = true

View File

@@ -1,8 +1,8 @@
//! Build script for quicproquo-proto.
//! Build script for quicprochat-proto.
//!
//! Runs two code generators:
//! 1. Cap'n Proto (v1 legacy) — from `schemas/*.capnp`
//! 2. Protobuf/prost (v2) — from `proto/qpq/v1/*.proto`
//! 2. Protobuf/prost (v2) — from `proto/qpc/v1/*.proto`
use std::{env, path::PathBuf};
@@ -40,20 +40,20 @@ fn main() {
let proto_dir = workspace_root.join("proto");
let proto_files = [
"qpq/v1/common.proto",
"qpq/v1/auth.proto",
"qpq/v1/delivery.proto",
"qpq/v1/keys.proto",
"qpq/v1/channel.proto",
"qpq/v1/user.proto",
"qpq/v1/blob.proto",
"qpq/v1/device.proto",
"qpq/v1/p2p.proto",
"qpq/v1/federation.proto",
"qpq/v1/push.proto",
"qpq/v1/group.proto",
"qpq/v1/moderation.proto",
"qpq/v1/recovery.proto",
"qpc/v1/common.proto",
"qpc/v1/auth.proto",
"qpc/v1/delivery.proto",
"qpc/v1/keys.proto",
"qpc/v1/channel.proto",
"qpc/v1/user.proto",
"qpc/v1/blob.proto",
"qpc/v1/device.proto",
"qpc/v1/p2p.proto",
"qpc/v1/federation.proto",
"qpc/v1/push.proto",
"qpc/v1/group.proto",
"qpc/v1/moderation.proto",
"qpc/v1/recovery.proto",
];
let full_paths: Vec<PathBuf> = proto_files.iter().map(|f| proto_dir.join(f)).collect();

View File

@@ -1,8 +1,8 @@
//! Protocol types for quicproquo.
//! Protocol types for quicprochat.
//!
//! This crate contains both:
//! - **v1 (legacy)**: Cap'n Proto generated types from `schemas/*.capnp`
//! - **v2**: Protobuf generated types from `proto/qpq/v1/*.proto`
//! - **v2**: Protobuf generated types from `proto/qpc/v1/*.proto`
//!
//! # Design constraints
//!
@@ -65,9 +65,9 @@ pub fn from_bytes_with_options(
// ════════════════════════════════════════════════════════════════════════════
/// Protobuf types for the v2 RPC protocol.
pub mod qpq {
pub mod qpc {
pub mod v1 {
include!(concat!(env!("OUT_DIR"), "/qpq.v1.rs"));
include!(concat!(env!("OUT_DIR"), "/qpc.v1.rs"));
}
}

View File

@@ -1,13 +1,13 @@
[package]
name = "quicproquo-rpc"
name = "quicprochat-rpc"
version = "0.1.0"
edition.workspace = true
description = "QUIC RPC framework for quicproquo v2 — framing, dispatch, tower middleware"
description = "QUIC RPC framework for quicprochat v2 — framing, dispatch, tower middleware"
license = "Apache-2.0 OR MIT"
repository.workspace = true
[dependencies]
quicproquo-proto = { path = "../quicproquo-proto" }
quicprochat-proto = { path = "../quicprochat-proto" }
prost = { workspace = true }
bytes = { workspace = true }
quinn = { workspace = true }

View File

@@ -1,4 +1,4 @@
//! Wire format encoding and decoding for the quicproquo v2 RPC protocol.
//! Wire format encoding and decoding for the quicprochat v2 RPC protocol.
//!
//! ## Request frame
//! ```text

View File

@@ -1,4 +1,4 @@
//! QUIC RPC framework for quicproquo v2.
//! QUIC RPC framework for quicprochat v2.
//!
//! Wire format per QUIC stream:
//! - Request: `[method_id: u16][request_id: u32][payload_len: u32][protobuf bytes]`

View File

@@ -1,15 +1,15 @@
[package]
name = "quicproquo-sdk"
name = "quicprochat-sdk"
version = "0.1.0"
edition.workspace = true
description = "Client SDK for quicproquo v2 — connect, auth, send, receive, subscribe"
description = "Client SDK for quicprochat v2 — connect, auth, send, receive, subscribe"
license = "Apache-2.0 OR MIT"
repository.workspace = true
[dependencies]
quicproquo-core = { path = "../quicproquo-core" }
quicproquo-proto = { path = "../quicproquo-proto" }
quicproquo-rpc = { path = "../quicproquo-rpc" }
quicprochat-core = { path = "../quicprochat-core" }
quicprochat-proto = { path = "../quicprochat-proto" }
quicprochat-rpc = { path = "../quicprochat-rpc" }
tokio = { workspace = true }
futures = { workspace = true }
tracing = { workspace = true }

View File

@@ -1,7 +1,7 @@
//! OPAQUE authentication — register and login via the v2 RPC protocol.
//!
//! Wraps the `opaque-ke` crate to perform the OPAQUE 3-message flow against
//! the quicproquo server using prost-encoded protobuf messages over `RpcClient::call`.
//! the quicprochat server using prost-encoded protobuf messages over `RpcClient::call`.
use bytes::Bytes;
use opaque_ke::{
@@ -9,9 +9,9 @@ use opaque_ke::{
ClientRegistrationFinishParameters, CredentialResponse, RegistrationResponse,
};
use prost::Message;
use quicproquo_core::{opaque_auth::OpaqueSuite, IdentityKeypair};
use quicproquo_proto::{method_ids, qpq::v1};
use quicproquo_rpc::client::RpcClient;
use quicprochat_core::{opaque_auth::OpaqueSuite, IdentityKeypair};
use quicprochat_proto::{method_ids, qpc::v1};
use quicprochat_rpc::client::RpcClient;
use crate::error::SdkError;

View File

@@ -1,4 +1,4 @@
//! `QpqClient` — the main entry point for the quicproquo SDK.
//! `QpqClient` — the main entry point for the quicprochat SDK.
use std::sync::Arc;
@@ -13,7 +13,7 @@ use crate::events::ClientEvent;
/// The main SDK client. All state is contained within this struct — no globals.
pub struct QpqClient {
config: ClientConfig,
rpc: Option<quicproquo_rpc::client::RpcClient>,
rpc: Option<quicprochat_rpc::client::RpcClient>,
event_tx: broadcast::Sender<ClientEvent>,
/// The authenticated username, if logged in.
username: Option<String>,
@@ -49,7 +49,7 @@ impl QpqClient {
pub async fn connect(&mut self) -> Result<(), SdkError> {
let tls_config = build_tls_config(self.config.accept_invalid_certs)?;
let rpc_config = quicproquo_rpc::client::RpcClientConfig {
let rpc_config = quicprochat_rpc::client::RpcClientConfig {
server_addr: self.config.server_addr,
server_name: self.config.server_name.clone(),
tls_config: Arc::new(tls_config),
@@ -57,7 +57,7 @@ impl QpqClient {
session_token: self.session_token.clone(),
};
let client = quicproquo_rpc::client::RpcClient::connect(rpc_config).await?;
let client = quicprochat_rpc::client::RpcClient::connect(rpc_config).await?;
self.rpc = Some(client);
// Open local conversation store.
@@ -108,7 +108,7 @@ impl QpqClient {
}
/// Get a reference to the RPC client (for direct calls).
pub fn rpc(&self) -> Result<&quicproquo_rpc::client::RpcClient, SdkError> {
pub fn rpc(&self) -> Result<&quicprochat_rpc::client::RpcClient, SdkError> {
self.rpc.as_ref().ok_or(SdkError::NotConnected)
}

View File

@@ -41,7 +41,7 @@ impl Default for ClientConfig {
db_password: None,
state_path: PathBuf::from("client-state.bin"),
accept_invalid_certs: false,
alpn: b"qpq/2".to_vec(),
alpn: b"qpc/2".to_vec(),
}
}
}

View File

@@ -1,9 +1,9 @@
//! Device management — register, list, and revoke devices.
use quicproquo_proto::bytes::Bytes;
use quicproquo_proto::prost::Message;
use quicproquo_proto::{method_ids, qpq::v1};
use quicproquo_rpc::client::RpcClient;
use quicprochat_proto::bytes::Bytes;
use quicprochat_proto::prost::Message;
use quicprochat_proto::{method_ids, qpc::v1};
use quicprochat_rpc::client::RpcClient;
use crate::error::SdkError;

View File

@@ -19,7 +19,7 @@ pub enum SdkError {
Crypto(String),
#[error("RPC error: {0}")]
Rpc(#[from] quicproquo_rpc::error::RpcError),
Rpc(#[from] quicprochat_rpc::error::RpcError),
#[error("storage error: {0}")]
Storage(String),

View File

@@ -9,16 +9,16 @@ use bytes::Bytes;
use prost::Message;
use tracing::debug;
use quicproquo_core::{
use quicprochat_core::{
hybrid_encrypt, GroupMember, HybridKeypair, HybridPublicKey, IdentityKeypair,
};
use quicproquo_proto::method_ids;
use quicproquo_proto::qpq::v1::{
use quicprochat_proto::method_ids;
use quicprochat_proto::qpc::v1::{
CreateChannelRequest, CreateChannelResponse, EnqueueRequest, EnqueueResponse,
ListGroupMembersRequest, ListGroupMembersResponse, RemoveMemberRequest, RemoveMemberResponse,
RotateKeysRequest, RotateKeysResponse, UpdateGroupMetadataRequest, UpdateGroupMetadataResponse,
};
use quicproquo_rpc::client::RpcClient;
use quicprochat_rpc::client::RpcClient;
use crate::conversation::{
now_ms, Conversation, ConversationId, ConversationKind, ConversationStore,
@@ -228,7 +228,7 @@ pub fn join_from_welcome(
// Try hybrid decryption if we have a hybrid keypair.
let decrypted;
let welcome_data = if let Some(hkp) = hybrid_kp {
match quicproquo_core::hybrid_decrypt(hkp, welcome_bytes, b"", b"") {
match quicprochat_core::hybrid_decrypt(hkp, welcome_bytes, b"", b"") {
Ok(plain) => {
decrypted = plain;
&decrypted[..]
@@ -537,7 +537,7 @@ pub fn restore_mls_state(
let mls_group = bincode::deserialize(group_blob)
.map_err(|e| SdkError::Crypto(format!("deserialize MLS group: {e}")))?;
let ks = quicproquo_core::DiskKeyStore::ephemeral();
let ks = quicprochat_core::DiskKeyStore::ephemeral();
let member = GroupMember::new_with_state(Arc::clone(identity), ks, Some(mls_group), conv.is_hybrid);
Ok(member)

View File

@@ -1,9 +1,9 @@
//! Key management — upload/fetch KeyPackages and hybrid public keys.
use quicproquo_proto::bytes::Bytes;
use quicproquo_proto::prost::Message;
use quicproquo_proto::{method_ids, qpq::v1};
use quicproquo_rpc::client::RpcClient;
use quicprochat_proto::bytes::Bytes;
use quicprochat_proto::prost::Message;
use quicprochat_proto::{method_ids, qpc::v1};
use quicprochat_rpc::client::RpcClient;
use crate::error::SdkError;

View File

@@ -1,4 +1,4 @@
//! Client SDK for quicproquo v2.
//! Client SDK for quicprochat v2.
//!
//! Provides `QpqClient` — a single entry point for connecting, authenticating,
//! sending/receiving messages, and subscribing to real-time events.

View File

@@ -9,15 +9,15 @@ use bytes::Bytes;
use prost::Message;
use tracing::debug;
use quicproquo_core::{
use quicprochat_core::{
AppMessage, GroupMember, HybridKeypair, HybridPublicKey, IdentityKeypair, ReceivedMessage,
};
use quicproquo_proto::method_ids;
use quicproquo_proto::qpq::v1::{
use quicprochat_proto::method_ids;
use quicprochat_proto::qpc::v1::{
AckRequest, AckResponse, BatchEnqueueRequest, BatchEnqueueResponse, EnqueueRequest,
EnqueueResponse, FetchRequest, FetchResponse, FetchWaitRequest, FetchWaitResponse,
};
use quicproquo_rpc::client::RpcClient;
use quicprochat_rpc::client::RpcClient;
use crate::error::SdkError;
@@ -55,10 +55,10 @@ pub async fn send_message(
channel_id: &[u8],
) -> Result<Vec<u64>, SdkError> {
// 1. Generate message ID.
let message_id = quicproquo_core::generate_message_id();
let message_id = quicprochat_core::generate_message_id();
// 2. Serialize application payload.
let serialized = quicproquo_core::serialize_chat(body.as_bytes(), Some(message_id))
let serialized = quicprochat_core::serialize_chat(body.as_bytes(), Some(message_id))
.map_err(|e| SdkError::Crypto(format!("serialize_chat: {e}")))?;
// 3. MLS encrypt.
@@ -67,7 +67,7 @@ pub async fn send_message(
.map_err(|e| SdkError::Crypto(format!("MLS encrypt: {e}")))?;
// 4. Sealed sender wrap.
let sealed = quicproquo_core::sealed_sender::seal(identity, &mls_ciphertext);
let sealed = quicprochat_core::sealed_sender::seal(identity, &mls_ciphertext);
// 5. Per-recipient hybrid wrap + enqueue.
// If all recipients can share the same payload (no hybrid keys), use batch enqueue.
@@ -84,7 +84,7 @@ pub async fn send_message(
let mut seqs = Vec::with_capacity(recipient_keys.len());
for (i, recipient_key) in recipient_keys.iter().enumerate() {
let payload = if let Some(Some(ref pk)) = hybrid_keys.get(i) {
quicproquo_core::hybrid_encrypt(pk, &sealed, b"", b"")
quicprochat_core::hybrid_encrypt(pk, &sealed, b"", b"")
.map_err(|e| SdkError::Crypto(format!("hybrid encrypt: {e}")))?
} else {
sealed.clone()
@@ -215,7 +215,7 @@ fn process_payloads(
/// raw bytes as-is.
fn try_hybrid_unwrap(hybrid_kp: Option<&HybridKeypair>, payload: &[u8]) -> Vec<u8> {
if let Some(kp) = hybrid_kp {
match quicproquo_core::hybrid_decrypt(kp, payload, b"", b"") {
match quicprochat_core::hybrid_decrypt(kp, payload, b"", b"") {
Ok(inner) => inner,
Err(_) => payload.to_vec(), // not hybrid-wrapped, use raw
}
@@ -227,7 +227,7 @@ fn try_hybrid_unwrap(hybrid_kp: Option<&HybridKeypair>, payload: &[u8]) -> Vec<u
/// Unseal (verify sender identity + Ed25519 signature) then parse the inner
/// application message. Returns None on failure (logged as debug).
fn try_unseal_and_parse(seq: u64, plaintext: &[u8]) -> Option<ReceivedPlaintext> {
let (sender_key, inner) = match quicproquo_core::sealed_sender::unseal(plaintext) {
let (sender_key, inner) = match quicprochat_core::sealed_sender::unseal(plaintext) {
Ok(pair) => pair,
Err(e) => {
debug!(seq, error = %e, "unseal failed");
@@ -235,7 +235,7 @@ fn try_unseal_and_parse(seq: u64, plaintext: &[u8]) -> Option<ReceivedPlaintext>
}
};
let (_msg_type, message) = match quicproquo_core::parse(&inner) {
let (_msg_type, message) = match quicprochat_core::parse(&inner) {
Ok(pair) => pair,
Err(e) => {
debug!(seq, error = %e, "app_message parse failed");

View File

@@ -8,9 +8,9 @@ use bytes::Bytes;
use prost::Message;
use tracing::{debug, info, warn};
use quicproquo_proto::method_ids;
use quicproquo_proto::qpq::v1::{EnqueueRequest, EnqueueResponse};
use quicproquo_rpc::client::RpcClient;
use quicprochat_proto::method_ids;
use quicprochat_proto::qpc::v1::{EnqueueRequest, EnqueueResponse};
use quicprochat_rpc::client::RpcClient;
use crate::conversation::{ConversationId, ConversationStore};
use crate::error::SdkError;

View File

@@ -1,14 +1,14 @@
//! Account recovery — setup, upload, and restore via recovery codes.
//!
//! Wraps `quicproquo_core::recovery` and the v2 RPC recovery service.
//! Wraps `quicprochat_core::recovery` and the v2 RPC recovery service.
use bytes::Bytes;
use prost::Message;
use quicproquo_core::recovery::{
use quicprochat_core::recovery::{
generate_recovery_codes, recover_from_bundle, recovery_token_hash, RecoveryBundle,
};
use quicproquo_proto::{method_ids, qpq::v1};
use quicproquo_rpc::client::RpcClient;
use quicprochat_proto::{method_ids, qpc::v1};
use quicprochat_rpc::client::RpcClient;
use crate::error::SdkError;

View File

@@ -184,7 +184,7 @@ mod tests {
#[test]
fn save_load_roundtrip() {
let dir = std::env::temp_dir().join("qpq_sdk_state_test");
let dir = std::env::temp_dir().join("qpc_sdk_state_test");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("test.state");

View File

@@ -1,13 +1,13 @@
//! Transcript archive export and verification.
//!
//! Wraps `quicproquo_core::transcript` to provide SDK-level functions for
//! Wraps `quicprochat_core::transcript` to provide SDK-level functions for
//! exporting conversation messages to an encrypted, tamper-evident archive
//! and verifying archive integrity.
use std::fs;
use std::path::Path;
use quicproquo_core::transcript::{
use quicprochat_core::transcript::{
read_transcript, validate_transcript_structure, ChainVerdict, TranscriptRecord,
TranscriptWriter,
};
@@ -142,7 +142,7 @@ mod tests {
save_msg(&store, &conv_id, "Hello!", 1000);
save_msg(&store, &conv_id, "World!", 2000);
let archive_path = dir.path().join("transcript.qpqt");
let archive_path = dir.path().join("transcript.qpct");
let count = export_transcript(&store, &conv_id, &archive_path, "test-pw").unwrap();
assert_eq!(count, 2);
@@ -163,7 +163,7 @@ mod tests {
save_conv(&store, &conv_id, "pw-test");
save_msg(&store, &conv_id, "secret", 1000);
let archive_path = dir.path().join("transcript_pw.qpqt");
let archive_path = dir.path().join("transcript_pw.qpct");
export_transcript(&store, &conv_id, &archive_path, "correct").unwrap();
let result = verify_transcript(&archive_path, Some("wrong"));
@@ -177,7 +177,7 @@ mod tests {
let conv_id = ConversationId::from_group_name("empty");
save_conv(&store, &conv_id, "empty");
let archive_path = dir.path().join("empty.qpqt");
let archive_path = dir.path().join("empty.qpct");
let count = export_transcript(&store, &conv_id, &archive_path, "pw").unwrap();
assert_eq!(count, 0);

View File

@@ -1,9 +1,9 @@
//! User resolution — username <-> identity key lookups.
use quicproquo_proto::bytes::Bytes;
use quicproquo_proto::prost::Message;
use quicproquo_proto::{method_ids, qpq::v1};
use quicproquo_rpc::client::RpcClient;
use quicprochat_proto::bytes::Bytes;
use quicprochat_proto::prost::Message;
use quicprochat_proto::{method_ids, qpc::v1};
use quicprochat_rpc::client::RpcClient;
use crate::error::SdkError;

View File

@@ -1,8 +1,8 @@
[package]
name = "quicproquo-server"
name = "quicprochat-server"
version = "0.1.0"
edition.workspace = true
description = "Delivery Service and Authentication Service for quicproquo."
description = "Delivery Service and Authentication Service for quicprochat."
license = "AGPL-3.0-only"
repository.workspace = true
@@ -11,15 +11,15 @@ traffic-resistance = []
webtransport = ["dep:h3", "dep:h3-quinn", "dep:h3-webtransport", "dep:http"]
[[bin]]
name = "qpq-server"
name = "qpc-server"
path = "src/main.rs"
[dependencies]
quicproquo-core = { path = "../quicproquo-core" }
quicproquo-proto = { path = "../quicproquo-proto" }
quicproquo-plugin-api = { path = "../quicproquo-plugin-api" }
quicproquo-kt = { path = "../quicproquo-kt" }
quicproquo-rpc = { path = "../quicproquo-rpc" }
quicprochat-core = { path = "../quicprochat-core" }
quicprochat-proto = { path = "../quicprochat-proto" }
quicprochat-plugin-api = { path = "../quicprochat-plugin-api" }
quicprochat-kt = { path = "../quicprochat-kt" }
quicprochat-rpc = { path = "../quicprochat-rpc" }
# Dynamic plugin loading
libloading = "0.8"

Some files were not shown because too many files have changed in this diff Show More