2019-07-17 19:37:18 +02:00
# include <memory>
2020-09-24 23:00:58 +02:00
# include <PermissionManager.h>
2019-07-17 19:37:18 +02:00
# include <misc/endianness.h>
# include <log/LogUtils.h>
# include <ThreadPool/Timer.h>
# include <regex>
# include <src/build.h>
# include <Properties.h>
2020-09-24 22:57:10 +02:00
# include <src/client/command_handler/helpers.h>
2019-07-17 19:37:18 +02:00
# include "src/channel/ClientChannelView.h"
# include "SpeakingClient.h"
# include "src/InstanceHandler.h"
# include "StringVariable.h"
# include "misc/timer.h"
2020-06-28 14:01:14 +02:00
# include "../manager/ActionLogger.h"
2020-11-28 11:09:25 +01:00
# include "./voice/VoiceClient.h"
2021-01-04 20:32:29 +01:00
# include "../rtc/imports.h"
2019-07-17 19:37:18 +02:00
using namespace std : : chrono ;
using namespace ts ;
using namespace ts : : server ;
using namespace ts : : protocol ;
//#define PKT_LOG_VOICE
//#define PKT_LOG_WHISPER
2020-11-28 11:09:25 +01:00
SpeakingClient : : SpeakingClient ( sql : : SqlManager * a , const std : : shared_ptr < VirtualServer > & b ) : ConnectedClient ( a , b ) , whisper_handler_ { this } {
2020-11-07 13:17:51 +01:00
speak_begin = std : : chrono : : system_clock : : now ( ) ;
speak_last_packet = std : : chrono : : system_clock : : now ( ) ;
} ;
SpeakingClient : : ~ SpeakingClient ( ) {
if ( auto server { this - > server } ; this - > rtc_client_id > 0 & & server ) {
server - > rtc_server ( ) . destroy_client ( this - > rtc_client_id ) ;
}
}
2019-07-17 19:37:18 +02:00
bool SpeakingClient : : shouldReceiveVoice ( const std : : shared_ptr < ConnectedClient > & sender ) {
2020-01-24 02:57:58 +01:00
//if(this->properties()[property::CLIENT_AWAY].as<bool>()) return false;
if ( ! this - > properties ( ) [ property : : CLIENT_OUTPUT_HARDWARE ] . as < bool > ( ) ) return false ;
if ( this - > properties ( ) [ property : : CLIENT_OUTPUT_MUTED ] . as < bool > ( ) ) return false ;
{
shared_lock client_lock ( this - > channel_lock ) ;
for ( const auto & entry : this - > mutedClients )
if ( entry . lock ( ) = = sender )
return false ;
}
return true ;
2019-07-17 19:37:18 +02:00
}
bool SpeakingClient : : shouldReceiveVoiceWhisper ( const std : : shared_ptr < ConnectedClient > & sender ) {
2020-01-24 02:57:58 +01:00
if ( ! this - > shouldReceiveVoice ( sender ) )
return false ;
2019-07-17 19:37:18 +02:00
2020-02-28 11:24:07 +01:00
return permission : : v2 : : permission_granted ( this - > cpmerission_needed_whisper_power , sender - > cpmerission_whisper_power , false ) ;
2019-07-17 19:37:18 +02:00
}
2020-11-07 13:17:51 +01:00
bool SpeakingClient : : should_handle_voice_packet ( size_t ) {
2019-07-17 19:37:18 +02:00
auto current_channel = this - > currentChannel ;
2020-11-07 13:17:51 +01:00
if ( ! current_channel ) { return false ; }
if ( ! this - > allowedToTalk ) { return false ; }
2020-01-24 02:57:58 +01:00
this - > updateSpeak ( false , system_clock : : now ( ) ) ;
this - > resetIdleTime ( ) ;
2019-07-17 19:37:18 +02:00
2020-11-07 13:17:51 +01:00
return true ;
2019-07-17 19:37:18 +02:00
}
2020-02-28 11:24:07 +01:00
inline bool update_whisper_error ( std : : chrono : : system_clock : : time_point & last ) {
auto now = std : : chrono : : system_clock : : now ( ) ;
if ( last + std : : chrono : : milliseconds { 500 } < now ) {
last = now ;
return true ;
}
return false ;
}
2019-07-17 19:37:18 +02:00
auto regex_wildcard = std : : regex ( " .* " ) ;
# define S(x) #x
# define HWID_REGEX(name, pattern) \
2020-05-10 16:23:02 +02:00
auto regex_hwid_ # # name = [ ] ( ) noexcept { \
2020-01-24 02:57:58 +01:00
try { \
return std : : regex ( pattern ) ; \
} catch ( std : : exception & ex ) { \
logError ( 0 , " Failed to parse regex for " S ( name ) ) ; \
} \
return regex_wildcard ; \
2019-07-17 19:37:18 +02:00
} ( ) ;
HWID_REGEX ( windows , " ^[a-z0-9]{32},[a-z0-9]{32}$ " ) ;
HWID_REGEX ( unix , " ^[a-z0-9]{32}$ " ) ;
HWID_REGEX ( android , " ^[a-z0-9]{16}$ " ) ;
HWID_REGEX ( ios , " ^[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}$ " ) ;
2020-01-25 23:42:37 +01:00
command_result SpeakingClient : : handleCommandClientInit ( Command & cmd ) {
2020-01-24 02:57:58 +01:00
TIMING_START ( timings ) ;
2020-01-24 02:40:30 +01:00
{
lock_guard < threads : : Mutex > lock ( this - > server - > join_attempts_lock ) ;
auto inetAddr = this - > getPeerIp ( ) ;
if ( config : : voice : : clientConnectLimit > 0 & & this - > server - > join_attempts [ inetAddr ] + 1 > config : : voice : : clientConnectLimit )
2020-01-25 23:42:37 +01:00
return command_result { error : : client_join_rate_limit_reached } ;
2020-01-24 02:40:30 +01:00
if ( config : : voice : : connectLimit > 0 & & this - > server - > join_attempts [ " _ " ] + 1 > config : : voice : : connectLimit )
2020-01-25 23:42:37 +01:00
return command_result { error : : server_join_rate_limit_reached } ;
2020-01-24 02:40:30 +01:00
this - > server - > join_attempts [ inetAddr ] + + ;
this - > server - > join_attempts [ " _ " ] + + ;
}
TIMING_STEP ( timings , " join atmp c " ) ;
2020-01-25 23:42:37 +01:00
if ( ! DatabaseHelper : : assignDatabaseId ( this - > server - > getSql ( ) , this - > server - > getServerId ( ) , _this . lock ( ) ) )
return command_result { error : : vs_critical , " Could not assign database id! " } ;
2020-01-24 02:57:58 +01:00
TIMING_STEP ( timings , " db assign " ) ;
this - > server - > getGroupManager ( ) - > enableCache ( this - > getClientDatabaseId ( ) ) ;
TIMING_STEP ( timings , " gr cache " ) ;
2019-07-17 19:37:18 +02:00
2020-01-24 02:57:58 +01:00
const static vector < string > available_parameters = {
" client_nickname " ,
2019-07-17 19:37:18 +02:00
" client_version " ,
2020-01-24 02:57:58 +01:00
" client_platform " ,
" client_input_muted " ,
" client_input_hardware " ,
" client_output_hardware " ,
" client_output_muted " ,
" client_default_channel " ,
" client_default_channel_password " ,
" client_server_password " ,
" client_meta_data " ,
" client_version_sign " ,
" client_key_offset " ,
" client_nickname_phonetic " ,
" client_default_token " ,
" client_badges=badges " ,
" client_badges " ,
" client_myteamspeak_id " ,
" client_integrations " ,
" client_active_integrations_info " ,
" client_browser_engine " ,
" client_away " ,
" client_away_message " ,
" hwid " ,
" myTeamspeakId " ,
" acTime " ,
" userPubKey " ,
" authSign " ,
" pubSign " ,
" pubSignCert "
} ;
for ( const auto & key : cmd [ 0 ] . keys ( ) ) {
if ( key = = " return_code " ) continue ;
bool parm_allowed = false ;
for ( const auto & _allowed_key : available_parameters ) {
if ( _allowed_key = = key ) {
parm_allowed = true ;
break ;
}
}
if ( ! parm_allowed ) {
debugMessage ( this - > getServerId ( ) , " {} Tried to insert a not allowed parameter within clientinit (Key: {}, Value: {}) " , CLIENT_STR_LOG_PREFIX , key , cmd [ key ] . string ( ) ) ;
continue ;
}
if ( key = = " myTeamspeakId " ) {
this - > properties ( ) [ property : : CLIENT_MYTEAMSPEAK_ID ] = cmd [ key ] . string ( ) ;
continue ;
} else if ( key = = " acTime " ) continue ;
else if ( key = = " userPubKey " ) continue ;
else if ( key = = " authSign " ) continue ;
else if ( key = = " pubSign " ) continue ;
else if ( key = = " pubSignCert " ) continue ;
else if ( key = = " client_version " | | key = = " client_platform " ) {
for ( auto & character : cmd [ key ] . string ( ) )
2020-01-25 23:42:37 +01:00
if ( ! isascii ( character ) ) {
logWarning ( this - > getServerId ( ) , " {} Tried to join within an invalid supplied '{}' ({}) " , CLIENT_STR_LOG_PREFIX , key , cmd [ key ] . string ( ) ) ;
return command_result { error : : client_hacked } ;
}
2020-07-30 11:50:31 +02:00
} else if ( key = = " client_talk_request_msg " ) {
2020-08-18 22:03:07 +02:00
if ( cmd [ " client_talk_request_msg " ] . string ( ) . length ( ) > ts : : config : : server : : limits : : talk_power_request_message_length )
2020-07-30 11:50:31 +02:00
return command_result { error : : parameter_invalid_size , " client_talk_request_msg " } ;
} else if ( key = = " client_away_message " ) {
2020-08-18 22:03:07 +02:00
if ( cmd [ " client_away_message " ] . string ( ) . length ( ) > ts : : config : : server : : limits : : afk_message_length )
2020-07-30 11:50:31 +02:00
return command_result { error : : parameter_invalid_size , " client_away_message " } ;
2020-09-24 22:57:10 +02:00
} else if ( key = = " client_nickname_phonetic " ) {
2020-09-25 00:49:20 +02:00
auto name = cmd [ " client_nickname_phonetic " ] . string ( ) ;
2020-09-24 22:57:10 +02:00
if ( count_characters ( name ) > 30 ) return command_result { error : : parameter_invalid , " client_nickname_phonetic " } ;
} else if ( key = = " client_nickname " ) {
auto name = cmd [ " client_nickname " ] . string ( ) ;
if ( count_characters ( name ) < 3 ) return command_result { error : : parameter_invalid , " client_nickname " } ;
if ( count_characters ( name ) > 30 ) return command_result { error : : parameter_invalid , " client_nickname " } ;
2020-01-24 02:57:58 +01:00
}
2020-04-08 13:01:41 +02:00
const auto & info = property : : find < property : : ClientProperties > ( key ) ;
if ( info . is_undefined ( ) ) {
2019-07-17 19:37:18 +02:00
logError ( this - > getServerId ( ) , " {} Tried to pass a unknown value {}. Please report this, if you're sure that this key should be known! " , CLIENT_STR_LOG_PREFIX , key ) ;
continue ;
2020-01-24 02:57:58 +01:00
//return {findError("parameter_invalid"), "Unknown property " + key};
}
2020-04-08 13:01:41 +02:00
if ( ! info . validate_input ( cmd [ key ] . as < string > ( ) ) )
2020-01-25 23:42:37 +01:00
return command_result { error : : parameter_invalid } ;
2020-01-24 02:57:58 +01:00
this - > properties ( ) [ info ] = cmd [ key ] . as < std : : string > ( ) ;
}
debugMessage ( this - > getServerId ( ) , " {} Got client init. (HWID: {}) " , CLIENT_STR_LOG_PREFIX , this - > getHardwareId ( ) ) ;
TIMING_STEP ( timings , " props apply " ) ;
2020-01-26 14:21:34 +01:00
auto permissions_list = this - > calculate_permissions ( {
2020-01-24 02:57:58 +01:00
permission : : b_virtualserver_join_ignore_password ,
permission : : b_client_ignore_bans ,
permission : : b_client_ignore_vpn ,
permission : : i_client_max_clones_uid ,
permission : : i_client_max_clones_ip ,
permission : : i_client_max_clones_hwid ,
permission : : b_client_enforce_valid_hwid ,
permission : : b_client_use_reserved_slot
2020-01-26 14:21:34 +01:00
} , 0 ) ;
auto permissions = map < permission : : PermissionType , permission : : v2 : : PermissionFlaggedValue > ( permissions_list . begin ( ) , permissions_list . end ( ) ) ;
2020-01-24 02:57:58 +01:00
TIMING_STEP ( timings , " perm calc 1 " ) ;
2020-01-26 14:21:34 +01:00
if ( geoloc : : provider_vpn & & ! permission : : v2 : : permission_granted ( 1 , permissions [ permission : : b_client_ignore_vpn ] ) ) {
2020-01-24 02:57:58 +01:00
auto provider = this - > isAddressV4 ( ) ? geoloc : : provider_vpn - > resolveInfoV4 ( this - > getPeerIp ( ) , true ) : geoloc : : provider_vpn - > resolveInfoV6 ( this - > getPeerIp ( ) , true ) ;
if ( provider )
2020-01-25 23:42:37 +01:00
return command_result { error : : server_connect_banned , strvar : : transform ( ts : : config : : messages : : kick_vpn , strvar : : StringValue { " provider.name " , provider - > name } , strvar : : StringValue { " provider.website " , provider - > side } ) } ;
2020-01-24 02:57:58 +01:00
}
2020-01-26 14:21:34 +01:00
if ( this - > getType ( ) = = ClientType : : CLIENT_TEAMSPEAK & & permission : : v2 : : permission_granted ( 1 , permissions [ permission : : b_client_enforce_valid_hwid ] ) ) {
2020-01-24 02:57:58 +01:00
auto hwid = this - > properties ( ) [ property : : CLIENT_HARDWARE_ID ] . as < string > ( ) ;
if (
! std : : regex_match ( hwid , regex_hwid_windows ) & &
! std : : regex_match ( hwid , regex_hwid_unix ) & &
! std : : regex_match ( hwid , regex_hwid_android ) & &
! std : : regex_match ( hwid , regex_hwid_ios )
) {
2020-01-25 23:42:37 +01:00
return command_result { error : : parameter_invalid , config : : messages : : kick_invalid_hardware_id } ;
2020-01-24 02:57:58 +01:00
}
}
TIMING_STEP ( timings , " valid hw ip " ) ;
2020-01-26 14:21:34 +01:00
if ( ! permission : : v2 : : permission_granted ( 1 , permissions [ permission : : b_virtualserver_join_ignore_password ] ) )
2020-01-25 23:42:37 +01:00
if ( ! this - > server - > verifyServerPassword ( cmd [ " client_server_password " ] . string ( ) , true ) )
return command_result { error : : server_invalid_password } ;
2020-01-24 02:57:58 +01:00
2020-04-23 15:36:58 +02:00
if ( ! config : : server : : clients : : ignore_max_clone_permissions ) {
size_t clones_uid = 0 ;
size_t clones_ip = 0 ;
size_t clones_hwid = 0 ;
auto _own_hwid = this - > getHardwareId ( ) ;
this - > server - > forEachClient ( [ & ] ( const shared_ptr < ConnectedClient > & client ) {
if ( client - > getExternalType ( ) ! = CLIENT_TEAMSPEAK ) return ;
if ( client - > getUid ( ) = = this - > getUid ( ) )
clones_uid + + ;
if ( client - > getPeerIp ( ) = = this - > getPeerIp ( ) )
clones_ip + + ;
if ( ! _own_hwid . empty ( ) & & client - > getHardwareId ( ) = = _own_hwid )
clones_hwid + + ;
} ) ;
if ( clones_uid > 0 & & permissions [ permission : : i_client_max_clones_uid ] . has_value & & ! permission : : v2 : : permission_granted ( clones_uid , permissions [ permission : : i_client_max_clones_uid ] ) ) {
logMessage ( this - > getServerId ( ) , " {} Disconnecting because there are already {} uid clones connected. (Allowed: {}) " , CLIENT_STR_LOG_PREFIX , clones_uid , permissions [ permission : : i_client_max_clones_uid ] ) ;
return command_result { error : : client_too_many_clones_connected , " too many clones connected (uid) " } ;
}
2020-01-24 02:57:58 +01:00
2020-04-23 15:36:58 +02:00
if ( clones_ip > 0 & & permissions [ permission : : i_client_max_clones_ip ] . has_value & & ! permission : : v2 : : permission_granted ( clones_ip , permissions [ permission : : i_client_max_clones_ip ] ) ) {
logMessage ( this - > getServerId ( ) , " {} Disconnecting because there are already {} ip clones connected. (Allowed: {}) " , CLIENT_STR_LOG_PREFIX , clones_ip , permissions [ permission : : i_client_max_clones_ip ] ) ;
return command_result { error : : client_too_many_clones_connected , " too many clones connected (ip) " } ;
}
2020-01-24 02:57:58 +01:00
2020-04-23 15:36:58 +02:00
if ( clones_hwid > 0 & & permissions [ permission : : i_client_max_clones_hwid ] . has_value & & ! permission : : v2 : : permission_granted ( clones_hwid , permissions [ permission : : i_client_max_clones_hwid ] ) ) {
logMessage ( this - > getServerId ( ) , " {} Disconnecting because there are already {} hwid clones connected. (Allowed: {}) " , CLIENT_STR_LOG_PREFIX , clones_hwid , permissions [ permission : : i_client_max_clones_hwid ] ) ;
return command_result { error : : client_too_many_clones_connected , " too many clones connected (hwid) " } ;
}
TIMING_STEP ( timings , " max clones " ) ;
2020-01-24 02:57:58 +01:00
}
auto banEntry = this - > resolveActiveBan ( this - > getPeerIp ( ) ) ;
if ( banEntry ) {
logMessage ( this - > getServerId ( ) , " {} Disconnecting while init because of ban record. Record id {} at server {} " ,
CLIENT_STR_LOG_PREFIX ,
banEntry - > banId ,
banEntry - > serverId ) ;
serverInstance - > banManager ( ) - > trigger_ban ( banEntry , this - > getServerId ( ) , this - > getUid ( ) , this - > getHardwareId ( ) , this - > getDisplayName ( ) , this - > getPeerIp ( ) ) ;
string fullReason = string ( ) + " You are banned " + ( banEntry - > serverId = = 0 ? " globally " : " from this server " ) + " . Reason: \" " + banEntry - > reason + " \" . Ban expires " ;
string time ;
if ( banEntry - > until . time_since_epoch ( ) . count ( ) ! = 0 ) {
time + = " in " ;
auto seconds = chrono : : ceil < chrono : : seconds > ( banEntry - > until - chrono : : system_clock : : now ( ) ) . count ( ) ;
tm p { } ;
memset ( & p , 0 , sizeof ( p ) ) ;
while ( seconds > = 365 * 24 * 60 * 60 ) {
p . tm_year + + ;
seconds - = 365 * 24 * 60 * 60 ;
}
while ( seconds > = 24 * 60 * 60 ) {
p . tm_yday + + ;
seconds - = 24 * 60 * 60 ;
}
while ( seconds > = 60 * 60 ) {
p . tm_hour + + ;
seconds - = 60 * 60 ;
}
while ( seconds > = 60 ) {
p . tm_min + + ;
seconds - = 60 ;
}
p . tm_sec = ( int ) seconds ;
if ( p . tm_year > 0 )
time + = to_string ( p . tm_year ) + " years, " ;
if ( p . tm_yday > 0 )
time + = to_string ( p . tm_yday ) + " days, " ;
if ( p . tm_hour > 0 )
time + = to_string ( p . tm_hour ) + " hours, " ;
if ( p . tm_min > 0 )
time + = to_string ( p . tm_min ) + " minutes, " ;
if ( p . tm_sec > 0 )
time + = to_string ( p . tm_sec ) + " seconds, " ;
if ( time . empty ( ) ) time = " now, " ;
time = time . substr ( 0 , time . length ( ) - 2 ) ;
} else time = " never " ;
fullReason + = time + " ! " ;
2020-01-25 23:42:37 +01:00
return command_result { error : : server_connect_banned , fullReason } ;
2020-01-24 02:57:58 +01:00
}
TIMING_STEP ( timings , " ban resolve " ) ;
size_t count = 0 ;
{
for ( const auto & cl : this - > server - > getClients ( ) )
if ( ( cl - > getType ( ) = = CLIENT_TEAMSPEAK | | cl - > getType ( ) = = CLIENT_WEB | | cl - > getType ( ) = = CLIENT_TEASPEAK | | cl - > getType ( ) = = CLIENT_MUSIC ) )
if ( cl - > connectionState ( ) < = ConnectionState : : CONNECTED & & cl - > connectionState ( ) > = ConnectionState : : INIT_HIGH )
count + + ;
}
auto maxClients = this - > server - > properties ( ) [ property : : VIRTUALSERVER_MAXCLIENTS ] . as < size_t > ( ) ;
auto reserved = this - > server - > properties ( ) [ property : : VIRTUALSERVER_RESERVED_SLOTS ] . as < size_t > ( ) ;
2020-01-26 14:21:34 +01:00
bool allowReserved = permission : : v2 : : permission_granted ( 1 , permissions [ permission : : b_client_use_reserved_slot ] ) ;
2020-01-24 02:57:58 +01:00
if ( reserved > maxClients ) {
2020-01-25 23:42:37 +01:00
if ( ! allowReserved )
return command_result { error : : server_maxclients_reached } ;
2020-01-24 02:57:58 +01:00
} else if ( maxClients - ( allowReserved ? 0 : reserved ) < = count )
2020-01-25 23:42:37 +01:00
return command_result { error : : server_maxclients_reached } ;
2020-01-24 02:57:58 +01:00
TIMING_STEP ( timings , " max clients " ) ;
auto old_last_connected = this - > properties ( ) [ property : : CLIENT_LASTCONNECTED ] . as < int64_t > ( ) ;
this - > properties ( ) [ property : : CONNECTION_CLIENT_IP ] = this - > getLoggingPeerIp ( ) ;
this - > properties ( ) [ property : : CLIENT_LASTCONNECTED ] = std : : chrono : : duration_cast < std : : chrono : : seconds > ( std : : chrono : : system_clock : : now ( ) . time_since_epoch ( ) ) . count ( ) ;
this - > properties ( ) [ property : : CLIENT_TOTALCONNECTIONS ] + + ;
{
auto time_point = system_clock : : time_point ( ) + seconds ( old_last_connected ) ;
if ( time_point < build : : version ( ) - > timestamp ) {
logMessage ( this - > getServerId ( ) , " {} Client may cached a old permission list (Server is newer than the client's last join) " , CLIENT_STR_LOG_PREFIX ) ;
TIMING_STEP ( timings , " pre dummy p " ) ;
Command _dummy ( " dummy_permissionslist " ) ;
this - > handleCommandPermissionList ( _dummy ) ;
TIMING_STEP ( timings , " pst dummy p " ) ;
}
}
this - > postCommandHandler . emplace_back ( [ & ] ( ) {
auto self = dynamic_pointer_cast < SpeakingClient > ( _this . lock ( ) ) ;
std : : thread ( [ self ] ( ) {
if ( self - > state ! = ConnectionState : : INIT_HIGH ) return ;
try {
self - > processJoin ( ) ;
} catch ( std : : exception & ex ) {
logError ( self - > getServerId ( ) , " Failed to proceed client join for {}. Got exception with message {} " , CLIENT_STR_LOG_PREFIX_ ( self ) , ex . what ( ) ) ;
2020-02-01 14:32:16 +01:00
self - > close_connection ( chrono : : system_clock : : now ( ) + chrono : : seconds { 5 } ) ;
2020-01-24 02:57:58 +01:00
}
} ) . detach ( ) ;
} ) ;
debugMessage ( this - > getServerId ( ) , " {} Client init timings: {} " , CLIENT_STR_LOG_PREFIX , TIMING_FINISH ( timings ) ) ;
2020-01-25 23:42:37 +01:00
return command_result { error : : ok } ;
2019-07-17 19:37:18 +02:00
}
/* must be triggered while helding an execute lock */
//Note: Client permissions are may not really
void SpeakingClient : : processJoin ( ) {
2020-01-24 02:57:58 +01:00
TIMING_START ( timings ) ;
auto ref_server = this - > server ;
this - > resetIdleTime ( ) ;
threads : : MutexLock lock ( this - > command_lock ) ; //Don't process any commands!
if ( this - > state ! = ConnectionState : : INIT_HIGH ) {
logError ( this - > getServerId ( ) , " {} Invalid processJoin() connection state! " , CLIENT_STR_LOG_PREFIX ) ;
return ;
}
TIMING_STEP ( timings , " setup " ) ;
ref_server - > registerClient ( _this . lock ( ) ) ;
2020-11-28 11:09:25 +01:00
{
if ( this - > rtc_client_id ) {
/* in case of client reconnect */
this - > server - > rtc_server ( ) . destroy_client ( this - > rtc_client_id ) ;
}
std : : string error { } ;
this - > rtc_client_id = this - > server - > rtc_server ( ) . create_client ( dynamic_pointer_cast < SpeakingClient > ( this - > ref ( ) ) ) ;
if ( auto voice_client { dynamic_cast < VoiceClient * > ( this ) } ; voice_client ) {
if ( ! this - > server - > rtc_server ( ) . initialize_native_connection ( error , this - > rtc_client_id ) ) {
logCritical ( this - > getServerId ( ) , " {} Native connection setup failed: {} " , CLIENT_STR_LOG_PREFIX , error ) ;
}
}
if ( this - > getType ( ) = = ClientType : : CLIENT_WEB | | this - > getType ( ) = = ClientType : : CLIENT_TEASPEAK ) {
if ( ! this - > server - > rtc_server ( ) . initialize_rtc_connection ( error , this - > rtc_client_id ) ) {
logCritical ( this - > getServerId ( ) , " {} RTC connection setup failed: {} " , CLIENT_STR_LOG_PREFIX , error ) ;
} else {
this - > rtc_session_pending_describe = true ;
}
2020-11-16 14:50:54 +01:00
}
2020-11-07 13:17:51 +01:00
}
2020-01-24 02:57:58 +01:00
TIMING_STEP ( timings , " server reg " ) ;
ref_server - > getGroupManager ( ) - > cleanupAssignments ( this - > getClientDatabaseId ( ) ) ;
TIMING_STEP ( timings , " grp cleanup " ) ;
ref_server - > getGroupManager ( ) - > update_server_group_property ( _this . lock ( ) , true , nullptr ) ;
TIMING_STEP ( timings , " grp apply " ) ;
this - > properties ( ) [ property : : CLIENT_COUNTRY ] = config : : geo : : countryFlag ;
if ( geoloc : : provider ) {
auto loc = this - > isAddressV4 ( ) ? geoloc : : provider - > resolveInfoV4 ( this - > getPeerIp ( ) , false ) : geoloc : : provider - > resolveInfoV6 ( this - > getPeerIp ( ) , false ) ;
if ( loc ) {
debugMessage ( this - > getServerId ( ) , " Client " + this - > getDisplayName ( ) + " | " + this - > getLoggingPeerIp ( ) + " comes from " + loc - > name + " | " + loc - > identifier ) ;
this - > properties ( ) [ property : : CLIENT_COUNTRY ] = loc - > identifier ;
} else {
logError ( ref_server ? ref_server - > getServerId ( ) : 0 , " Could not resolve country for ip " + this - > getLoggingPeerIp ( ) + " | " + this - > getDisplayName ( ) ) ;
}
}
//this->updateChannelClientProperties(false); /* will be already updated via assignChannel */
if ( ref_server - > properties ( ) [ property : : VIRTUALSERVER_HOSTMESSAGE_MODE ] . as < int > ( ) = = 3 & & ! ref_server - > properties ( ) [ property : : VIRTUALSERVER_HOSTMESSAGE ] . as < string > ( ) . empty ( ) ) {
auto weak = this - > _this ;
threads : : Thread ( [ weak ] ( ) {
threads : : self : : sleep_for ( milliseconds ( 2000 ) ) ;
auto client = weak . lock ( ) ;
if ( ! client | | ! client - > server ) return ;
client - > disconnect ( client - > server - > properties ( ) [ property : : VIRTUALSERVER_HOSTMESSAGE ] . as < string > ( ) ) ;
} ) . detach ( ) ;
}
TIMING_STEP ( timings , " ip 2 loc as " ) ; //IP to location assignment
this - > sendServerInit ( ) ;
debugMessage ( this - > getServerId ( ) , " Client id: " + to_string ( this - > getClientId ( ) ) ) ;
TIMING_STEP ( timings , " notify sini " ) ;
if ( ! ref_server - > assignDefaultChannel ( this - > ref ( ) , false ) ) {
2020-01-25 23:42:37 +01:00
auto result = command_result { error : : vs_critical , " Could not assign default channel! " } ;
this - > notifyError ( result ) ;
2020-04-28 18:27:49 +02:00
result . release_data ( ) ;
2020-02-01 14:32:16 +01:00
this - > close_connection ( system_clock : : now ( ) + seconds ( 1 ) ) ;
2020-01-24 02:57:58 +01:00
return ;
}
TIMING_STEP ( timings , " assign chan " ) ;
this - > sendChannelList ( true ) ;
this - > state = ConnectionState : : CONNECTED ;
TIMING_STEP ( timings , " send chan t " ) ;
/* trick the join method */
auto channel = this - > currentChannel ;
this - > currentChannel = nullptr ;
{
/* enforce an update of these properties */
this - > properties ( ) [ property : : CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID ] = " 0 " ;
this - > properties ( ) [ property : : CLIENT_CHANNEL_GROUP_ID ] = " 0 " ;
this - > properties ( ) [ property : : CLIENT_TALK_POWER ] = " 0 " ;
unique_lock server_channel_lock ( this - > server - > channel_tree_lock ) ;
this - > server - > client_move ( this - > ref ( ) , channel , nullptr , " " , ViewReasonId : : VREASON_USER_ACTION , false , server_channel_lock ) ;
2020-04-10 23:29:51 +02:00
if ( this - > getType ( ) ! = ClientType : : CLIENT_TEAMSPEAK ) this - > subscribeChannel ( { this - > currentChannel } , false , true ) ; /* su "improve" the TS3 clients join speed we send the channel clients a bit later, when the TS3 client gets his own client variables */
2020-01-24 02:57:58 +01:00
}
TIMING_STEP ( timings , " join move " ) ;
this - > properties ( ) - > triggerAllModified ( ) ;
this - > notifyServerGroupList ( ) ;
this - > notifyChannelGroupList ( ) ;
TIMING_STEP ( timings , " notify grou " ) ;
logMessage ( this - > getServerId ( ) , " Voice client {}/{} ({}) from {} joined. " ,
this - > getClientDatabaseId ( ) ,
this - > getUid ( ) ,
this - > getDisplayName ( ) ,
this - > getLoggingPeerIp ( ) + " : " + to_string ( this - > getPeerPort ( ) )
) ;
this - > connectTimestamp = chrono : : system_clock : : now ( ) ;
this - > idleTimestamp = chrono : : system_clock : : now ( ) ;
2020-03-20 17:58:58 +01:00
TIMING_STEP ( timings , " welcome msg " ) ;
{
std : : string message { } ;
config : : server : : clients : : WelcomeMessageType type { config : : server : : clients : : WELCOME_MESSAGE_TYPE_NONE } ;
if ( this - > getType ( ) = = ClientType : : CLIENT_TEASPEAK ) {
message = config : : server : : clients : : extra_welcome_message_teaspeak ;
type = config : : server : : clients : : extra_welcome_message_type_teaspeak ;
} else if ( this - > getType ( ) = = ClientType : : CLIENT_TEAMSPEAK ) {
message = config : : server : : clients : : extra_welcome_message_teamspeak ;
type = config : : server : : clients : : extra_welcome_message_type_teamspeak ;
} else if ( this - > getType ( ) = = ClientType : : CLIENT_WEB ) {
message = config : : server : : clients : : extra_welcome_message_teaweb ;
type = config : : server : : clients : : extra_welcome_message_type_teaweb ;
}
if ( type = = config : : server : : clients : : WELCOME_MESSAGE_TYPE_POKE ) {
this - > notifyClientPoke ( this - > server - > serverRoot , message ) ;
} else if ( type = = config : : server : : clients : : WELCOME_MESSAGE_TYPE_CHAT ) {
this - > notifyTextMessage ( ChatMessageMode : : TEXTMODE_SERVER , this - > server - > serverRoot , 0 , 0 , std : : chrono : : system_clock : : now ( ) , message ) ;
}
}
2020-01-24 02:57:58 +01:00
debugMessage ( this - > getServerId ( ) , " {} Client join timings: {} " , CLIENT_STR_LOG_PREFIX , TIMING_FINISH ( timings ) ) ;
2020-06-28 14:01:14 +02:00
serverInstance - > action_logger ( ) - > client_channel_logger . log_client_join ( this - > getServerId ( ) , this - > ref ( ) , this - > getChannelId ( ) , this - > currentChannel - > name ( ) ) ;
2019-07-17 19:37:18 +02:00
}
void SpeakingClient : : processLeave ( ) {
2020-01-24 02:57:58 +01:00
auto ownLock = _this . lock ( ) ;
auto server = this - > getServer ( ) ;
2020-06-28 14:01:14 +02:00
auto channel = this - > currentChannel ;
2020-01-24 02:57:58 +01:00
if ( server ) {
logMessage ( this - > getServerId ( ) , " Voice client {}/{} ({}) from {} left. " , this - > getClientDatabaseId ( ) , this - > getUid ( ) , this - > getDisplayName ( ) , this - > getLoggingPeerIp ( ) + " : " + to_string ( this - > getPeerPort ( ) ) ) ;
{
unique_lock server_channel_lock ( this - > server - > channel_tree_lock ) ;
server - > unregisterClient ( ownLock , " disconnected " , server_channel_lock ) ; /* already moves client to void if needed */
}
server - > groups - > disableCache ( ownLock - > getClientDatabaseId ( ) ) ;
2020-11-07 13:17:51 +01:00
server - > music_manager_ - > cleanup_client_bots ( this - > getClientDatabaseId ( ) ) ;
2020-01-24 02:57:58 +01:00
//ref_server = nullptr; Removed caused nullptr exceptions
}
2019-07-17 19:37:18 +02:00
}
void SpeakingClient : : triggerVoiceEnd ( ) {
2020-01-24 02:57:58 +01:00
this - > properties ( ) [ property : : CLIENT_FLAG_TALKING ] = false ;
2019-07-17 19:37:18 +02:00
}
2020-11-07 13:17:51 +01:00
void SpeakingClient : : updateSpeak ( bool only_update , const std : : chrono : : system_clock : : time_point & now ) {
std : : lock_guard speak_lock { this - > speak_mutex } ;
2020-01-24 02:57:58 +01:00
if ( this - > speak_last_packet + this - > speak_accuracy < now ) {
if ( this - > speak_last_packet > this - > speak_begin ) {
2020-11-07 13:17:51 +01:00
if ( ! this - > properties ( ) [ property : : CLIENT_FLAG_TALKING ] . as < bool > ( ) ) {
2020-01-24 02:57:58 +01:00
this - > properties ( ) [ property : : CLIENT_FLAG_TALKING ] = true ;
2020-11-07 13:17:51 +01:00
}
2020-01-24 02:57:58 +01:00
this - > speak_time + = duration_cast < milliseconds > ( this - > speak_last_packet - this - > speak_begin ) ;
} else {
2020-11-07 13:17:51 +01:00
if ( this - > properties ( ) [ property : : CLIENT_FLAG_TALKING ] . as < bool > ( ) ) {
2020-01-24 02:57:58 +01:00
this - > properties ( ) [ property : : CLIENT_FLAG_TALKING ] = false ;
2020-11-07 13:17:51 +01:00
}
2020-01-24 02:57:58 +01:00
}
this - > speak_begin = now ;
this - > speak_last_packet = now ;
}
2020-11-07 13:17:51 +01:00
if ( ! only_update ) {
2020-01-24 02:57:58 +01:00
this - > speak_last_packet = now ;
2020-11-07 13:17:51 +01:00
}
2019-07-17 19:37:18 +02:00
}
void SpeakingClient : : tick ( const std : : chrono : : system_clock : : time_point & time ) {
2020-01-24 02:57:58 +01:00
ConnectedClient : : tick ( time ) ;
ALARM_TIMER ( A1 , " SpeakingClient::tick " , milliseconds ( 2 ) ) ;
this - > updateSpeak ( true , time ) ;
if ( this - > state = = ConnectionState : : CONNECTED ) {
if ( this - > max_idle_time . has_value ) {
auto max_idle = this - > max_idle_time . value ;
if ( max_idle > 0 & & this - > idleTimestamp . time_since_epoch ( ) . count ( ) > 0 & & duration_cast < seconds > ( time - this - > idleTimestamp ) . count ( ) > max_idle ) {
this - > server - > notify_client_kick ( this - > ref ( ) , this - > server - > getServerRoot ( ) , ts : : config : : messages : : idle_time_exceeded , nullptr ) ;
2020-02-01 14:32:16 +01:00
this - > close_connection ( system_clock : : now ( ) + seconds ( 1 ) ) ;
2020-01-24 02:57:58 +01:00
}
}
}
2019-07-17 19:37:18 +02:00
}
void SpeakingClient : : updateChannelClientProperties ( bool channel_lock , bool notify ) {
2020-01-24 02:57:58 +01:00
ConnectedClient : : updateChannelClientProperties ( channel_lock , notify ) ;
2020-01-26 14:21:34 +01:00
this - > max_idle_time = this - > calculate_permission ( permission : : i_client_max_idletime , this - > currentChannel ? this - > currentChannel - > channelId ( ) : 0 ) ;
2019-07-17 19:37:18 +02:00
}
2020-01-25 23:42:37 +01:00
command_result SpeakingClient : : handleCommand ( Command & command ) {
2020-01-24 02:57:58 +01:00
if ( this - > connectionState ( ) = = ConnectionState : : INIT_HIGH ) {
if ( this - > handshake . state = = HandshakeState : : BEGIN | | this - > handshake . state = = HandshakeState : : IDENTITY_PROOF ) {
2020-01-25 23:42:37 +01:00
command_result result ;
2020-11-07 13:17:51 +01:00
if ( command . command ( ) = = " handshakebegin " ) {
2020-05-07 21:28:15 +02:00
result . reset ( this - > handleCommandHandshakeBegin ( command ) ) ;
2020-11-07 13:17:51 +01:00
} else if ( command . command ( ) = = " handshakeindentityproof " ) {
2020-05-07 21:28:15 +02:00
result . reset ( this - > handleCommandHandshakeIdentityProof ( command ) ) ;
2020-11-07 13:17:51 +01:00
} else {
2020-05-07 21:28:15 +02:00
result . reset ( command_result { error : : client_not_logged_in } ) ;
2020-11-07 13:17:51 +01:00
}
2020-01-24 02:57:58 +01:00
2020-11-07 13:17:51 +01:00
if ( result . has_error ( ) ) {
2020-01-25 23:42:37 +01:00
this - > postCommandHandler . push_back ( [ & ] {
2020-02-01 14:32:16 +01:00
this - > close_connection ( system_clock : : now ( ) + seconds ( 1 ) ) ;
2020-01-25 23:42:37 +01:00
} ) ;
2020-11-07 13:17:51 +01:00
}
2020-01-24 02:57:58 +01:00
return result ;
}
2020-11-07 13:17:51 +01:00
} else if ( this - > connectionState ( ) = = ConnectionState : : CONNECTED ) {
if ( command . command ( ) = = " rtcsessiondescribe " ) {
return this - > handleCommandRtcSessionDescribe ( command ) ;
} else if ( command . command ( ) = = " rtcicecandidate " ) {
return this - > handleCommandRtcIceCandidate ( command ) ;
} else if ( command . command ( ) = = " rtcbroadcast " ) {
2021-01-04 20:32:29 +01:00
/* TODO: Remove this command once the first 1.5.0 stable is out */
2020-11-07 13:17:51 +01:00
return this - > handleCommandRtcBroadcast ( command ) ;
} else if ( command . command ( ) = = " rtcsessionreset " ) {
return this - > handleCommandRtcSessionReset ( command ) ;
2021-01-04 20:32:29 +01:00
} else if ( command . command ( ) = = " broadcastaudio " ) {
return this - > handleCommandBroadcastAudio ( command ) ;
} else if ( command . command ( ) = = " broadcastvideo " ) {
return this - > handleCommandBroadcastVideo ( command ) ;
2020-12-10 13:33:09 +01:00
} else if ( command . command ( ) = = " broadcastvideojoin " ) {
return this - > handleCommandBroadcastVideoJoin ( command ) ;
} else if ( command . command ( ) = = " broadcastvideoleave " ) {
return this - > handleCommandBroadcastVideoLeave ( command ) ;
2021-01-04 20:32:29 +01:00
} else if ( command . command ( ) = = " broadcastvideoconfig " ) {
return this - > handleCommandBroadcastVideoConfig ( command ) ;
} else if ( command . command ( ) = = " broadcastvideoconfigure " ) {
return this - > handleCommandBroadcastVideoConfigure ( command ) ;
2020-11-07 13:17:51 +01:00
}
2020-01-24 02:57:58 +01:00
}
return ConnectedClient : : handleCommand ( command ) ;
2019-07-17 19:37:18 +02:00
}
2020-11-07 13:17:51 +01:00
command_result SpeakingClient : : handleCommandRtcSessionDescribe ( Command & command ) {
CMD_REQ_SERVER ;
2020-11-15 23:30:51 +01:00
if ( this - > rtc_session_pending_describe ) {
this - > rtc_session_pending_describe = false ;
} else {
CMD_CHK_AND_INC_FLOOD_POINTS ( 15 ) ;
}
2020-11-07 13:17:51 +01:00
uint32_t mode ;
if ( command [ " mode " ] . string ( ) = = " offer " ) {
mode = 1 ;
} else if ( command [ " mode " ] . string ( ) = = " answer " ) {
mode = 2 ;
} else {
return command_result { error : : parameter_invalid , " mode " } ;
}
std : : string error { } ;
if ( ! this - > server - > rtc_server ( ) . apply_remote_description ( error , this - > rtc_client_id , mode , command [ " sdp " ] ) ) {
return command_result { error : : vs_critical , error } ;
}
if ( mode = = 1 ) {
std : : string result { } ;
if ( ! this - > server - > rtc_server ( ) . generate_local_description ( this - > rtc_client_id , result ) ) {
return command_result { error : : vs_critical , result } ;
} else {
ts : : command_builder notify { " notifyrtcsessiondescription " } ;
notify . put_unchecked ( 0 , " mode " , " answer " ) ;
notify . put_unchecked ( 0 , " sdp " , result ) ;
this - > sendCommand ( notify ) ;
}
}
return command_result { error : : ok } ;
}
command_result SpeakingClient : : handleCommandRtcSessionReset ( Command & command ) {
CMD_REQ_SERVER ;
CMD_CHK_AND_INC_FLOOD_POINTS ( 15 ) ;
this - > server - > rtc_server ( ) . reset_rtp_session ( this - > rtc_client_id ) ;
2020-12-05 21:50:02 +01:00
if ( this - > getType ( ) = = ClientType : : CLIENT_TEASPEAK ) {
/* registering the broadcast again since rtp session reset resets the broadcasts as well */
2021-01-04 20:32:29 +01:00
this - > server - > rtc_server ( ) . start_broadcast_audio ( this - > rtc_client_id , 1 ) ;
2020-12-05 21:50:02 +01:00
}
2020-11-07 13:17:51 +01:00
return command_result { error : : ok } ;
}
command_result SpeakingClient : : handleCommandRtcIceCandidate ( Command & command ) {
CMD_REQ_SERVER ;
std : : string error ;
if ( command [ 0 ] . has ( " candidate " ) ) {
auto candidate = command [ " candidate " ] . string ( ) ;
if ( ! this - > server - > rtc_server ( ) . add_ice_candidate ( error , this - > rtc_client_id , command [ " media_line " ] , candidate ) ) {
return command_result { error : : vs_critical , error } ;
}
} else {
this - > server - > rtc_server ( ) . ice_candidates_finished ( this - > rtc_client_id ) ;
}
return command_result { error : : ok } ;
}
2021-01-04 20:32:29 +01:00
ts : : command_result parse_broadcast_options ( ParameterBulk & cmd , VideoBroadcastOptions & options , bool requires_all ) {
if ( cmd . has ( " broadcast_bitrate_max " ) ) {
options . update_mask | = VideoBroadcastOptions : : kOptionBitrate ;
options . bitrate = cmd [ " broadcast_bitrate_max " ] ;
} else if ( requires_all ) {
return ts : : command_result { error : : parameter_missing , " broadcast_bitrate_max " } ;
}
if ( cmd . has ( " broadcast_keyframe_interval " ) ) {
options . update_mask | = VideoBroadcastOptions : : kOptionKeyframeInterval ;
options . keyframe_interval = cmd [ " broadcast_keyframe_interval " ] ;
} else if ( requires_all ) {
return ts : : command_result { error : : parameter_missing , " broadcast_keyframe_interval " } ;
}
return ts : : command_result { error : : ok } ;
}
void simplify_broadcast_options ( const VideoBroadcastOptions & current_options , VideoBroadcastOptions & target_options ) {
if ( target_options . bitrate = = current_options . bitrate ) {
target_options . update_mask & = ~ VideoBroadcastOptions : : kOptionBitrate ;
}
if ( target_options . keyframe_interval = = current_options . keyframe_interval ) {
target_options . update_mask & = ~ VideoBroadcastOptions : : kOptionKeyframeInterval ;
}
}
/**
* Test if the client has permissions to use the target broadcast options
* @ param client
* @ param channel_id
* @ param options
* @ return
*/
ts : : command_result test_broadcast_options ( SpeakingClient & client , ChannelId channel_id , const VideoBroadcastOptions & options ) {
if ( options . update_mask & VideoBroadcastOptions : : kOptionBitrate ) {
auto required_value = options . bitrate = = 0 ? - 1 : ( permission : : PermissionValue ) ( options . bitrate / 1000 ) ;
if ( ! permission : : v2 : : permission_granted ( required_value , client . calculate_permission ( permission : : i_video_max_kbps , channel_id ) ) ) {
return ts : : command_result { permission : : i_video_max_kbps } ;
}
}
return ts : : command_result { error : : ok } ;
}
inline command_result broadcast_start_result_to_command_result ( rtc : : BroadcastStartResult broadcast_result ) {
switch ( broadcast_result ) {
case rtc : : BroadcastStartResult : : Success :
return ts : : command_result { error : : ok } ;
case rtc : : BroadcastStartResult : : InvalidBroadcastType :
return ts : : command_result { error : : parameter_invalid , " type " } ;
case rtc : : BroadcastStartResult : : InvalidStreamId :
return ts : : command_result { error : : rtc_missing_target_channel } ;
case rtc : : BroadcastStartResult : : ClientHasNoChannel :
return ts : : command_result { error : : vs_critical , " no channel " } ;
case rtc : : BroadcastStartResult : : InvalidClient :
return ts : : command_result { error : : vs_critical , " invalid client " } ;
case rtc : : BroadcastStartResult : : UnknownError :
default :
return ts : : command_result { error : : vs_critical , " unknown error " } ;
}
}
command_result SpeakingClient : : handleCommandBroadcastAudio ( Command & command ) {
CMD_REQ_SERVER ;
CMD_CHK_AND_INC_FLOOD_POINTS ( 5 ) ;
auto ssrc = command [ 0 ] . has ( " ssrc " ) ? command [ " ssrc " ] . as < uint32_t > ( ) : ( uint32_t ) 0 ;
auto broadcast_result = this - > server - > rtc_server ( ) . start_broadcast_audio ( this - > rtc_client_id , ssrc ) ;
return broadcast_start_result_to_command_result ( broadcast_result ) ;
}
command_result SpeakingClient : : handleCommandBroadcastVideo ( Command & command ) {
CMD_REQ_SERVER ;
CMD_CHK_AND_INC_FLOOD_POINTS ( 15 ) ;
auto ssrc = command [ 0 ] . has ( " ssrc " ) ? command [ " ssrc " ] . as < uint32_t > ( ) : ( uint32_t ) 0 ;
auto type = ( rtc : : VideoBroadcastType ) command [ " type " ] . as < uint8_t > ( ) ;
switch ( type ) {
case rtc : : VideoBroadcastType : : Screen :
if ( ! permission : : v2 : : permission_granted ( 1 , this - > calculate_permission ( permission : : b_video_screen , this - > getChannelId ( ) ) , false ) ) {
return ts : : command_result { permission : : b_video_screen } ;
}
break ;
case rtc : : VideoBroadcastType : : Camera :
if ( ! permission : : v2 : : permission_granted ( 1 , this - > calculate_permission ( permission : : b_video_camera , this - > getChannelId ( ) ) , false ) ) {
return ts : : command_result { permission : : b_video_camera } ;
}
break ;
default :
return ts : : command_result { error : : parameter_invalid , " type " } ;
}
VideoBroadcastOptions options ;
memset ( & options , 0 , sizeof ( options ) ) ;
if ( ssrc ! = 0 ) {
ts : : command_result result ;
result . reset ( parse_broadcast_options ( command [ 0 ] , options , true ) ) ;
if ( result . has_error ( ) ) {
return result ;
}
result . reset ( test_broadcast_options ( * this , this - > getChannelId ( ) , options ) ) ;
if ( result . has_error ( ) ) {
return result ;
}
result . release_data ( ) ;
}
auto broadcast_result = this - > server - > rtc_server ( ) . start_broadcast_video ( this - > rtc_client_id , type , ssrc , & options ) ;
return broadcast_start_result_to_command_result ( broadcast_result ) ;
}
2020-11-07 13:17:51 +01:00
command_result SpeakingClient : : handleCommandRtcBroadcast ( Command & command ) {
CMD_REQ_SERVER ;
CMD_CHK_AND_INC_FLOOD_POINTS ( 15 ) ;
std : : vector < std : : tuple < uint8_t , uint32_t > > broadcasts { } ;
broadcasts . reserve ( command . bulkCount ( ) ) ;
2021-01-04 20:32:29 +01:00
ts : : command_result_bulk result { } ;
2020-11-07 13:17:51 +01:00
for ( size_t index { 0 } ; index < command . bulkCount ( ) ; index + + ) {
auto & bulk = command [ index ] ;
2020-12-12 20:37:14 +01:00
auto ssrc = bulk . has ( " ssrc " ) ? bulk [ " ssrc " ] . as < uint32_t > ( ) : ( uint32_t ) 0 ;
auto type = bulk [ " type " ] . as < uint8_t > ( ) ;
for ( const auto & entry : broadcasts ) {
if ( std : : get < 0 > ( entry ) = = type ) {
return ts : : command_result { error : : parameter_constraint_violation } ;
}
}
broadcasts . push_back ( std : : make_tuple ( type , ssrc ) ) ;
2021-01-04 20:32:29 +01:00
switch ( type ) {
case 1 : {
ts : : Command cmd { " " } ;
cmd [ " ssrc " ] = ssrc ;
result . insert_result ( this - > handleCommandBroadcastAudio ( cmd ) ) ;
2020-12-12 20:37:14 +01:00
break ;
2021-01-04 20:32:29 +01:00
}
case 2 : {
ts : : Command cmd { " " } ;
cmd [ " broadcast_bitrate_max " ] = 1500000 ;
cmd [ " broadcast_keyframe_interval " ] = 7 ;
cmd [ " ssrc " ] = ssrc ;
cmd [ " type " ] = ( uint8_t ) rtc : : VideoBroadcastType : : Camera ;
result . insert_result ( this - > handleCommandBroadcastVideo ( cmd ) ) ;
2020-12-12 20:37:14 +01:00
break ;
2021-01-04 20:32:29 +01:00
}
case 3 : {
ts : : Command cmd { " " } ;
cmd [ " broadcast_bitrate_max " ] = 1500000 ;
cmd [ " broadcast_keyframe_interval " ] = 7 ;
cmd [ " ssrc " ] = ssrc ;
cmd [ " type " ] = ( uint8_t ) rtc : : VideoBroadcastType : : Screen ;
result . insert_result ( this - > handleCommandBroadcastVideo ( cmd ) ) ;
2020-11-07 13:17:51 +01:00
break ;
2021-01-04 20:32:29 +01:00
}
default :
2020-11-07 13:17:51 +01:00
result . emplace_result ( error : : parameter_invalid , " type " ) ;
break ;
}
}
2021-01-04 20:32:29 +01:00
2020-11-07 13:17:51 +01:00
return ts : : command_result { std : : move ( result ) } ;
2020-12-10 13:33:09 +01:00
}
command_result SpeakingClient : : handleCommandBroadcastVideoJoin ( Command & cmd ) {
CMD_REQ_SERVER ;
auto broadcast_type = ( rtc : : VideoBroadcastType ) cmd [ " bt " ] . as < uint8_t > ( ) ;
auto broadcast_id = cmd [ " bid " ] . as < uint32_t > ( ) ;
2020-12-12 20:37:14 +01:00
if ( broadcast_id ! = this - > rtc_client_id ) {
CMD_CHK_AND_INC_FLOOD_POINTS ( 25 ) ;
/* the broadcast is is actually the rtc client id of the broadcaster */
uint32_t camera_streams , screen_streams ;
if ( ! this - > server - > rtc_server ( ) . client_video_stream_count ( this - > rtc_client_id , & camera_streams , & screen_streams ) ) {
return ts : : command_result { error : : vs_critical , " failed to count client streams " } ;
}
2020-12-17 12:00:27 +01:00
auto permission_max_streams = this - > calculate_permission ( permission : : i_video_max_streams , this - > getChannelId ( ) ) ;
if ( permission_max_streams . has_value ) {
if ( ! permission : : v2 : : permission_granted ( camera_streams + screen_streams , permission_max_streams , false ) ) {
return ts : : command_result { permission : : i_video_max_streams } ;
}
2020-12-12 20:37:14 +01:00
}
switch ( broadcast_type ) {
2020-12-17 12:00:27 +01:00
case rtc : : VideoBroadcastType : : Camera : {
const auto permission_max_camera_streams = this - > calculate_permission ( permission : : i_video_max_camera_streams , this - > getChannelId ( ) ) ;
if ( permission_max_camera_streams . has_value ) {
if ( ! permission : : v2 : : permission_granted ( camera_streams , permission_max_camera_streams , false ) ) {
return ts : : command_result { permission : : i_video_max_camera_streams } ;
}
2020-12-12 20:37:14 +01:00
}
break ;
2020-12-17 12:00:27 +01:00
}
2020-12-12 20:37:14 +01:00
2020-12-17 12:00:27 +01:00
case rtc : : VideoBroadcastType : : Screen : {
const auto permission_max_screen_streams = this - > calculate_permission ( permission : : i_video_max_camera_streams , this - > getChannelId ( ) ) ;
if ( permission_max_screen_streams . has_value ) {
if ( ! permission : : v2 : : permission_granted ( screen_streams , permission_max_screen_streams , false ) ) {
return ts : : command_result { permission : : i_video_max_screen_streams } ;
}
2020-12-12 20:37:14 +01:00
}
break ;
2020-12-17 12:00:27 +01:00
}
2020-12-12 20:37:14 +01:00
default :
return ts : : command_result { error : : broadcast_invalid_type } ;
}
} else {
CMD_CHK_AND_INC_FLOOD_POINTS ( 5 ) ;
/* The client is free to join his own broadcast */
}
2020-12-10 13:33:09 +01:00
using VideoBroadcastJoinResult = rtc : : VideoBroadcastJoinResult ;
switch ( this - > server - > rtc_server ( ) . join_video_broadcast ( this - > rtc_client_id , broadcast_id , broadcast_type ) ) {
case VideoBroadcastJoinResult : : Success :
return ts : : command_result { error : : ok } ;
case VideoBroadcastJoinResult : : InvalidBroadcast :
return ts : : command_result { error : : broadcast_invalid_id } ;
case VideoBroadcastJoinResult : : InvalidBroadcastType :
return ts : : command_result { error : : broadcast_invalid_type } ;
case VideoBroadcastJoinResult : : InvalidClient :
return ts : : command_result { error : : client_invalid_id } ;
case VideoBroadcastJoinResult : : UnknownError :
default :
return ts : : command_result { error : : vs_critical } ;
}
}
command_result SpeakingClient : : handleCommandBroadcastVideoLeave ( Command & cmd ) {
CMD_REQ_SERVER ;
auto broadcast_type = ( rtc : : VideoBroadcastType ) cmd [ " bt " ] . as < uint8_t > ( ) ;
auto broadcast_id = cmd [ " bid " ] . as < uint32_t > ( ) ;
this - > server - > rtc_server ( ) . leave_video_broadcast ( this - > rtc_client_id , broadcast_id , broadcast_type ) ;
return ts : : command_result { error : : ok } ;
2021-01-04 20:32:29 +01:00
}
command_result SpeakingClient : : handleCommandBroadcastVideoConfig ( Command & cmd ) {
CMD_REQ_SERVER ;
auto broadcast_type = ( rtc : : VideoBroadcastType ) cmd [ " bt " ] . as < uint8_t > ( ) ;
VideoBroadcastOptions options ;
auto result = this - > server - > rtc_server ( ) . client_broadcast_video_config ( this - > rtc_client_id , broadcast_type , & options ) ;
switch ( result ) {
case rtc : : VideoBroadcastConfigureResult : : Success :
break ;
case rtc : : VideoBroadcastConfigureResult : : InvalidBroadcast :
return ts : : command_result { error : : broadcast_invalid_id } ;
case rtc : : VideoBroadcastConfigureResult : : InvalidBroadcastType :
return ts : : command_result { error : : broadcast_invalid_type } ;
case rtc : : VideoBroadcastConfigureResult : : InvalidClient :
return ts : : command_result { error : : client_invalid_id } ;
case rtc : : VideoBroadcastConfigureResult : : UnknownError :
default :
return ts : : command_result { error : : vs_critical } ;
}
ts : : command_builder notify { this - > notify_response_command ( " notifybroadcastvideoconfig " ) } ;
notify . put_unchecked ( 0 , " bt " , ( uint8_t ) broadcast_type ) ;
notify . put_unchecked ( 0 , " broadcast_keyframe_interval " , options . keyframe_interval ) ;
notify . put_unchecked ( 0 , " broadcast_bitrate_max " , options . bitrate ) ;
this - > sendCommand ( notify ) ;
return ts : : command_result { error : : ok } ;
}
inline command_result broadcast_config_result_to_command_result ( rtc : : VideoBroadcastConfigureResult result ) {
switch ( result ) {
case rtc : : VideoBroadcastConfigureResult : : Success :
return ts : : command_result { error : : ok } ;
case rtc : : VideoBroadcastConfigureResult : : InvalidBroadcast :
return ts : : command_result { error : : broadcast_invalid_id } ;
case rtc : : VideoBroadcastConfigureResult : : InvalidBroadcastType :
return ts : : command_result { error : : broadcast_invalid_type } ;
case rtc : : VideoBroadcastConfigureResult : : InvalidClient :
return ts : : command_result { error : : client_invalid_id } ;
case rtc : : VideoBroadcastConfigureResult : : UnknownError :
default :
return ts : : command_result { error : : vs_critical } ;
}
}
command_result SpeakingClient : : handleCommandBroadcastVideoConfigure ( Command & cmd ) {
CMD_REQ_SERVER ;
auto broadcast_type = ( rtc : : VideoBroadcastType ) cmd [ " bt " ] . as < uint8_t > ( ) ;
VideoBroadcastOptions current_options ;
auto query_result = this - > server - > rtc_server ( ) . client_broadcast_video_config ( this - > rtc_client_id , broadcast_type , & current_options ) ;
if ( query_result ! = rtc : : VideoBroadcastConfigureResult : : Success ) {
return broadcast_config_result_to_command_result ( query_result ) ;
}
VideoBroadcastOptions options ;
memset ( & options , 0 , sizeof ( options ) ) ;
{
ts : : command_result result ;
result . reset ( parse_broadcast_options ( cmd [ 0 ] , options , false ) ) ;
if ( result . has_error ( ) ) {
return result ;
}
simplify_broadcast_options ( current_options , options ) ;
result . reset ( test_broadcast_options ( * this , this - > getChannelId ( ) , options ) ) ;
if ( result . has_error ( ) ) {
return result ;
}
result . release_data ( ) ;
}
{
auto result = test_broadcast_options ( * this , this - > getChannelId ( ) , options ) ;
if ( result . has_error ( ) ) {
return result ;
}
result . release_data ( ) ;
}
auto result = this - > server - > rtc_server ( ) . client_broadcast_video_configure ( this - > rtc_client_id , broadcast_type , & options ) ;
return broadcast_config_result_to_command_result ( result ) ;
2020-11-07 13:17:51 +01:00
}