feat(protocol): per-platform TS3 client_version selection

ConnectOptions previously took tsclientlib's default Version. Servers that
strictly check the announced client signature could refuse or downgrade
those sessions. Add pick_client_version() that selects a stable signed
descriptor matching the runtime OS:

  Windows  -> Version::Windows_5_0_0_beta51
  Linux    -> Version::Linux_5_0_0_beta51
  macOS    -> Version::macOS_5_0_0_beta51
  Android  -> Version::Android_3_5_0__7
  iOS      -> Version::iOS_3_5_6
  other    -> Linux fallback

All five variants are guaranteed to exist in the vendored tsproto-types
enum at compile time; build fails loudly if upstream removes one.

Wired into Connection::build(...).version(pick_client_version()) on every
connect. Emits 'selected TS3 client_version' info log line so the choice
is visible in chanora.log.
This commit is contained in:
EdisonJwa
2026-05-16 12:14:37 +08:00
parent 73066749e3
commit 8acd456af1
+65 -2
View File
@@ -28,13 +28,68 @@ use tsclientlib::data::{self, Channel, Client};
use tsclientlib::prelude::*;
use tsclientlib::{
ChannelId as TsChannelId, Connection, DisconnectOptions, Identity, MessageHandle,
OutCommandExt, StreamItem,
OutCommandExt, StreamItem, Version,
};
use tsproto_packets::packets::{InAudioBuf, OutPacket};
use crate::dto::{ChannelId, ChannelInfo, ClientId, ClientInfo, ServerSnapshot};
use crate::ProtocolError;
/// Pick the TeamSpeak `client_version`/platform/signature triple
/// (sourced from `ReSpeak/tsdeclarations/Versions.csv`, baked into
/// `tsproto-types` at vendor-time) that best matches the *runtime*
/// platform Chanora is executing on.
///
/// Per-platform selection (set by the project owner):
///
/// * Windows / Linux / macOS desktops announce as TeamSpeak **5
/// beta51** — the latest TS5 desktop signature in the vendored
/// `tsproto-types` enum. TS5 servers expect this shape; TS3
/// servers are happy to accept any well-formed signed client
/// descriptor and have no codec-version coupling.
/// * Android announces as **3.5.0__7** (the latest Android
/// signature in the vendored enum).
/// * iOS announces as **3.5.6** (latest iOS signature in the
/// vendored enum).
///
/// All five variants are guaranteed to exist in the generated
/// `Version` enum at compile time; if upstream rotates the CSV the
/// build will fail loudly here rather than silently fall back.
fn pick_client_version() -> Version {
#[cfg(target_os = "windows")]
{
Version::Windows_5_0_0_beta51
}
#[cfg(target_os = "macos")]
{
Version::macOS_5_0_0_beta51
}
#[cfg(target_os = "ios")]
{
Version::iOS_3_5_6
}
#[cfg(target_os = "android")]
{
Version::Android_3_5_0__7
}
#[cfg(target_os = "linux")]
{
Version::Linux_5_0_0_beta51
}
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "linux"
)))]
{
// Last-ditch fallback for unanticipated targets (BSDs,
// Solaris-likes). Linux signature is the closest analogue.
Version::Linux_5_0_0_beta51
}
}
/// Typed configuration for a connection attempt.
#[derive(Debug, Clone)]
pub struct ConnectConfig {
@@ -331,7 +386,15 @@ async fn connection_task(
// Pass the resolved SocketAddr directly to tsclientlib so it
// skips its own resolver entirely (tsclientlib accepts
// SocketAddr via the From<SocketAddr> for ServerAddress impl).
let mut builder = Connection::build(resolved).name(cfg.nickname.clone());
let client_version = pick_client_version();
info!(
target: "chanora_protocol",
client_version = %client_version,
"selected TS3 client_version for this platform"
);
let mut builder = Connection::build(resolved)
.name(cfg.nickname.clone())
.version(client_version);
let identity = match cfg.identity.as_deref() {
Some(s) => match Identity::new_from_str(s) {