/* * server/conn_session.h — Per-client connection state machine. * * State: WaitingHello → WaitingAuth → Authenticated → Disconnecting * * Design: ConnSession is a pure state machine. It receives frames via on_frame() * (called from TcpServerConn's strand) and sends via a send_fn set after construction. * The server creates the TcpServerConn first (with callbacks referencing the session), * then calls set_tcp() to give the session its send capability. */ #ifndef VOICECAT_SERVER_CONN_SESSION_H #define VOICECAT_SERVER_CONN_SESSION_H #ifdef VOICECAT_HAS_NET #include #include #include #include #include #include #include #define ASIO_STANDALONE 1 #include #include "crypto/crypto.h" #include "proto/voicecat.pb.h" namespace voicecat { class WorkerPool; } namespace voicecat::server { class Database; class SessionRegistry; class ConnSession : public std::enable_shared_from_this { public: enum class State { WaitingHello, WaitingAuth, Authenticated, Disconnecting }; using SendFn = std::function)>; using CloseFn = std::function; ConnSession(std::shared_ptr db, std::shared_ptr registry, std::shared_ptr workers, const std::array& server_fp, bool allow_guests, uint16_t udp_media_port = 0); void set_io(SendFn send_fn, CloseFn close_fn); void set_session_id(uint64_t id) { session_id_ = id; } void begin(); void on_frame(std::vector frame); void on_disconnect(); void send_envelope(const voicecat::v1::Envelope& env); void close(); // Called by SessionRegistry for kick/ban. Public so the registry can forcibly // close a session without making it a friend class. void send_disconnect_and_close(uint32_t code, const std::string& reason); // ── M2: media key injection (called from on_tls_ready) ─────────────────── void set_media_crypto(std::unique_ptr send, std::unique_ptr recv); // ── M2: UDP endpoint (set by MediaRelay on UdpBinding) ──────────────────── void set_udp_endpoint(asio::ip::udp::endpoint ep); asio::ip::udp::endpoint udp_endpoint() const; bool has_udp_endpoint() const { return has_udp_ep_.load(); } // ── M2: media crypto access (for SFU relay) ────────────────────────────── voicecat::crypto::SodiumMediaCrypto* send_crypto(); voicecat::crypto::SodiumMediaCrypto* recv_crypto(); // ── M2: UDP token (for binding) ─────────────────────────────────────────── const std::array& udp_token() const { return udp_token_; } // ── Accessors ────────────────────────────────────────────────────────────── State state() const { return state_.load(); } uint64_t session_id() const { return session_id_; } uint32_t user_id() const { return user_id_; } private: void handle_client_hello(uint64_t req_id, const voicecat::v1::ClientHello& msg); void handle_auth_request(uint64_t req_id, const voicecat::v1::AuthRequest& msg); void handle_join_channel(uint64_t req_id, const voicecat::v1::JoinChannelRequest& msg); void handle_text_message(const voicecat::v1::TextMessage& msg); void handle_ping(const voicecat::v1::Ping& msg); void handle_udp_binding(uint64_t req_id, const voicecat::v1::UdpBinding& msg); void handle_stream_announce(uint64_t req_id, const voicecat::v1::StreamAnnounce& msg); void handle_stream_stop(const voicecat::v1::StreamStop& msg); void handle_leave_channel(); // M5 handlers void handle_kick_request(uint64_t req_id, const voicecat::v1::KickRequest& msg); void handle_ban_request(uint64_t req_id, const voicecat::v1::BanRequest& msg); void handle_set_permission(uint64_t req_id, const voicecat::v1::SetPermissionRequest& msg); void handle_server_mute_request(uint64_t req_id, const voicecat::v1::ServerMuteRequest& msg); void handle_move_user(uint64_t req_id, const voicecat::v1::MoveUserRequest& msg); void handle_create_channel(uint64_t req_id, const voicecat::v1::CreateChannelRequest& msg); void handle_edit_channel(uint64_t req_id, const voicecat::v1::EditChannelRequest& msg); void handle_delete_channel(uint64_t req_id, const voicecat::v1::DeleteChannelRequest& msg); void handle_create_account(uint64_t req_id, const voicecat::v1::CreateAccountRequest& msg); void handle_reset_password(uint64_t req_id, const voicecat::v1::ResetPasswordRequest& msg); void handle_delete_account(uint64_t req_id, const voicecat::v1::DeleteAccountRequest& msg); void handle_list_accounts(uint64_t req_id, const voicecat::v1::ListAccountsRequest& msg); void finish_guest_auth(const voicecat::v1::GuestAuth& guest, uint64_t req_id); void finish_password_auth(const std::string& username, const std::string& password, uint64_t req_id); void send_auth_result_ok(uint64_t req_id, const voicecat::v1::User& user, const voicecat::v1::Permissions* perms = nullptr); void send_state_snapshot(); void broadcast_user_joined(const voicecat::v1::User& user); void send_generic_result(uint64_t req_id, bool ok, uint32_t code, const std::string& message); // Permission helpers. bool has_permission(bool (voicecat::v1::Permissions::* getter)() const) const; bool is_admin() const { return has_permission(&voicecat::v1::Permissions::is_admin); } void set_permissions(const voicecat::v1::Permissions& perms); std::shared_ptr db_; std::shared_ptr registry_; std::shared_ptr workers_; std::array server_fp_; bool allow_guests_; uint16_t udp_media_port_; SendFn send_fn_; CloseFn close_fn_; std::atomic state_{State::WaitingHello}; uint64_t session_id_{0}; std::atomic user_id_{0}; std::atomic closed_{false}; // M2 UDP / media std::array udp_token_{}; mutable std::mutex udp_ep_mu_; asio::ip::udp::endpoint udp_ep_; std::atomic has_udp_ep_{false}; mutable std::mutex crypto_mu_; std::unique_ptr send_crypto_; std::unique_ptr recv_crypto_; // M2/M3: locally-announced streams. The server assigns the stream_id (unique per // session), so a per-session counter + the set of currently-active ids is enough to // support multiple concurrent streams (MIC + SCREEN_AUDIO + AUX_DEVICE) per user. uint32_t next_stream_id_{1}; std::vector announced_stream_ids_; // M5: permissions granted at auth time (server-side authority). voicecat::v1::Permissions permissions_; }; } // namespace voicecat::server #endif // VOICECAT_HAS_NET #endif // VOICECAT_SERVER_CONN_SESSION_H