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:
@@ -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 }
|
||||
@@ -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;
|
||||
@@ -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()))?;
|
||||
@@ -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());
|
||||
@@ -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};
|
||||
@@ -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;
|
||||
@@ -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")?;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -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";
|
||||
@@ -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,
|
||||
@@ -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}"));
|
||||
@@ -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
|
||||
//! commands. See the [running-the-client](https://docs.quicprochat.dev/getting-started/running-the-client)
|
||||
//! docs for details.
|
||||
|
||||
use std::sync::RwLock;
|
||||
@@ -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:#}");
|
||||
}
|
||||
@@ -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(())
|
||||
@@ -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) => {
|
||||
@@ -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.
|
||||
@@ -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 }
|
||||
@@ -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),
|
||||
@@ -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) ─────────────────────────
|
||||
|
||||
@@ -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>).
|
||||
@@ -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())
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
syntax = "proto3";
|
||||
package quicproquo.bench;
|
||||
package quicprochat.bench;
|
||||
|
||||
// Equivalent to the Envelope struct in delivery.capnp
|
||||
message Envelope {
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Error types for `quicproquo-core`.
|
||||
//! Error types for `quicprochat-core`.
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
//!
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
@@ -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.
|
||||
///
|
||||
@@ -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."
|
||||
@@ -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 }
|
||||
@@ -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).
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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.
|
||||
///
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
@@ -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]`
|
||||
@@ -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 }
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
@@ -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");
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user