feat(M1): TCP/TLS control plane -- auth, channels, ephemeral text
Implements the full M1 milestone. Two clients authenticate over TLS 1.3 (guest + Argon2id password) and exchange channel + private text messages through a real server. All five ctest --preset m1-dev tests pass in ~1 s. Key components added: - vcpkg baseline + m1-dev preset (protobuf/mbedTLS/libsodium/asio/sqlite3) - FrameCodec feed+emit, encode/decode_envelope, protobuf codegen - TcpServerConn with blocking TLS handshake thread + tls_read_loop - TlsContext (mbedTLS 1.3, ECDSA-P256 self-signed cert, TOFU on client) - WorkerPool (3 threads, used for Argon2id) - Database: SQLite + libsodium Argon2id, account lifecycle, bootstrap admin - ServerIdentityManager: Ed25519 key + cert generate/persist/fingerprint - ConnSession state machine: WaitingHello -> WaitingAuth -> Authenticated - SessionRegistry: channel tree, user map, text routing, broadcast - vc_client full M1 C ABI: connect/TLS/handshake/auth/text/disconnect - voicecat-admin CLI: account add/reset/del/list - test_m1_integration: M1 exit criterion, verified green Bug fixed: double-framing in ConnSession::send_envelope -- encode_envelope was adding the [4-byte len] prefix, then TcpServerConn::send_frame added a second one, causing the client to parse [len][proto] as protobuf (silent failure). Fixed by serializing raw protobuf bytes in send_envelope and letting send_frame apply the single length prefix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,30 @@
|
||||
/*
|
||||
* client.h — the implementation type behind the opaque `vc_client*` handle.
|
||||
*
|
||||
* M0 skeleton: holds config/callbacks/state and returns VC_ERR_NOT_IMPLEMENTED for
|
||||
* everything that needs a subsystem. As subsystems land (docs/architecture.md §2), this
|
||||
* class wires them together: a net transport, a protocol state machine, a session model,
|
||||
* and an audio engine, plus the event thread that drains into vc_callbacks.on_event.
|
||||
*/
|
||||
#ifndef VOICECAT_CORE_CLIENT_H
|
||||
#define VOICECAT_CORE_CLIENT_H
|
||||
|
||||
#include "voicecat.h"
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "crypto/crypto.h"
|
||||
#include "protocol/envelope.h"
|
||||
#include "protocol/protocol.h"
|
||||
#include "session/session.h"
|
||||
#include "proto/voicecat.pb.h"
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
|
||||
struct vc_client {
|
||||
vc_client(const vc_config& cfg, vc_callbacks cb);
|
||||
~vc_client();
|
||||
@@ -39,15 +53,85 @@ struct vc_client {
|
||||
|
||||
vc_result list_devices(vc_device_kind kind, vc_device_list* out);
|
||||
|
||||
vc_connection_state state() const { return state_; }
|
||||
vc_connection_state state() const {
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
return state_net_.load(std::memory_order_acquire);
|
||||
#else
|
||||
return state_;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
// Deliver an event to the host application. Safe to call with cb_.on_event == nullptr.
|
||||
void emit(const vc_event& ev) const;
|
||||
|
||||
vc_config cfg_{};
|
||||
vc_config cfg_{};
|
||||
vc_callbacks cb_{};
|
||||
vc_connection_state state_ = VC_STATE_DISCONNECTED;
|
||||
|
||||
#ifdef VOICECAT_HAS_NET
|
||||
// ── M1: TCP/TLS control channel ─────────────────────────────────────────────
|
||||
std::atomic<vc_connection_state> state_net_{VC_STATE_DISCONNECTED};
|
||||
|
||||
// Blocking I/O thread (one per vc_client lifetime)
|
||||
std::thread io_thread_;
|
||||
std::atomic<bool> io_stop_{false};
|
||||
|
||||
// Send queue: pushed by any thread, drained by io_thread_
|
||||
std::mutex send_mutex_;
|
||||
std::condition_variable send_cv_;
|
||||
std::deque<std::vector<uint8_t>> send_queue_;
|
||||
|
||||
// TLS context — created + used exclusively on io_thread_
|
||||
std::unique_ptr<voicecat::crypto::TlsContext> tls_;
|
||||
|
||||
// Raw socket fd (stored after TCP connect; closed by disconnect())
|
||||
std::atomic<int> io_fd_{-1};
|
||||
|
||||
// Pending auth stored before ServerHello arrives
|
||||
struct PendingAuth {
|
||||
bool is_guest{true};
|
||||
std::string nick_or_user;
|
||||
std::string password;
|
||||
};
|
||||
std::mutex pending_auth_mutex_;
|
||||
std::optional<PendingAuth> pending_auth_;
|
||||
|
||||
// Self identity filled in after AuthResult
|
||||
uint32_t self_user_id_{0};
|
||||
uint64_t server_session_id_{0};
|
||||
std::atomic<uint64_t> next_req_id_{1};
|
||||
|
||||
// Client-side session model
|
||||
voicecat::session::SessionModel session_model_;
|
||||
|
||||
// ── io_thread_ entry point ──────────────────────────────────────────────────
|
||||
void run_io(std::string host, uint16_t port);
|
||||
|
||||
// ── Protocol dispatch (called on io_thread_) ────────────────────────────────
|
||||
void handle_envelope(const voicecat::v1::Envelope& env);
|
||||
void handle_server_hello(const voicecat::v1::ServerHello& msg, uint64_t req_id);
|
||||
void handle_auth_result(const voicecat::v1::AuthResult& msg);
|
||||
void handle_server_state(const voicecat::v1::ServerStateSnapshot& snap);
|
||||
void handle_user_event(const voicecat::v1::UserEvent& ue);
|
||||
void handle_text_message(const voicecat::v1::TextMessage& msg);
|
||||
void handle_disconnect(const voicecat::v1::Disconnect& msg);
|
||||
|
||||
// ── Helpers (io_thread_ and caller threads) ─────────────────────────────────
|
||||
// Queue an encoded envelope to be sent on io_thread_.
|
||||
void queue_envelope(const voicecat::v1::Envelope& env);
|
||||
|
||||
// Drain send_queue_ by doing blocking TLS writes (called on io_thread_).
|
||||
void drain_sends();
|
||||
|
||||
// Transition state + emit VC_EVENT_CONNECTION_STATE.
|
||||
void set_state(vc_connection_state s);
|
||||
|
||||
// Convenience event emitters.
|
||||
void emit_error(vc_result r, const char* text);
|
||||
void emit_disconnected(vc_result r, const char* reason);
|
||||
|
||||
#else // !VOICECAT_HAS_NET
|
||||
vc_connection_state state_{VC_STATE_DISCONNECTED};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // VOICECAT_CORE_CLIENT_H
|
||||
|
||||
Reference in New Issue
Block a user