(Client -> Server)
denotes packets from client to server.(Client <- Server)
denotes packets from server to client.- All datatypes are sent in network order (Big Endian) unless otherwise specified.
- Datatypes are declared with a prefixing
u
ori
for unsigned and signed and a number for the bitlength.
For exampleu8
would be the C equivalent ofuint8
orunsigned char
- Arrays are represented by the underlying datatype in square brackets,
additionally if the length is known it is added in the brackets, separated by
a semicolon. Eg:
[u8]
,[i32; 16]
- Array ranges (parts of an array) are specified in square brackets with the
included lower bound, two points (
..
) and the excluded upper bound. Eg:[0..10]
- The packets are build in a fixed scheme, though have differences depending in which direction.
- Every column here represents 1 byte.
- The entire packet size must be at max 500 bytes.
+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//----------+
| MAC | PId | CId |PT| Data |
+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//----------+
| \ Meta |
\ Header /
Name | Size | Datatype | Explanation |
---|---|---|---|
MAC | 8 bytes | [u8] | EAX Message Authentication Code |
PId | 2 bytes | u16 | Packet Id |
CId | 2 bytes | u16 | Client Id |
PT | 1 byte | u8 | Packet Type + Flags |
Data | ≤487 bytes | [u8] | The packet payload |
+--+--+--+--+--+--+--+--+--+--+--+------------//-------------+
| MAC | PId |PT| Data |
+--+--+--+--+--+--+--+--+--+--+--+------------//-------------+
| \ Meta |
\ Header /
Name | Size | Datatype | Explanation |
---|---|---|---|
MAC | 8 bytes | [u8] | EAX Message Authentication Code |
PId | 2 bytes | u16 | Packet Id |
PT | 1 byte | u8 | Packet Type + Flags |
Data | ≤489 bytes | [u8] | The packet payload |
0x00
Voice0x01
VoiceWhisper0x02
Command0x03
CommandLow0x04
Ping0x05
Pong0x06
Ack0x07
AckLow0x08
Init1
The final byte then looks like this
MSB LSB
+--+--+--+--+--+--+--+--+
|UE|CP|NP|FR| Type |
+--+--+--+--+--+--+--+--+
Name | Size | Hex | Explanation |
---|---|---|---|
UE | 1 bit | 0x80 | Unencrypted |
CP | 1 bit | 0x40 | Compressed |
NP | 1 bit | 0x20 | Newprotocol |
FR | 1 bit | 0x10 | Fragmented |
Type | 4 bit | 0-8 | The packet type |
To reduce packet size, the data can be compressed.
When the data is compressed the Compressed
flag must be set.
The algorithm "QuickLZ" with Level 1 is used for compression.
When the packet payload exceeds the maximum datablock size the data can be
split up across multiple packets.
The current protocol only allows Command
and CommandLow
to be split and/or
compressed.
When splitting occurs, the Fragmented
flag must be set on the first and
the last packet.
The Unencrypted
and Compressed
flag, if set in the original
packet, are only set on the first packet. (Though commands always have to be
encrypted)
The Newprotocol
flag has to be set on all commands and therefore also has to
be applied on all splitted command packets.
The data can additionally be compressed before splitting.
Example:
The packet to split has the following flags:
[__|CP|NP|__] Packet Id: 42
it must be split into:
[__|CP|NP|FR] Packet Id: 42
[__|__|NP|__] Packet Id: 43
[__|__|NP|FR] Packet Id: 44
When a packet is not encrypted the Unencrypted
flag is set. For encrypted
packets the flag gets cleared.
Packets get encrypted with EAX mode (AES_128_CTR with OMAC).
The en/decryption parameters are generated for each packet as follows
Name | Type | Explanation |
---|---|---|
PT | u8 | Packet Type |
PId | u16 | Packet Id |
PGId | u32 | Packet GenerationId (see 1.9.2) |
PD | bool | Packet Direction |
SIV | [u8; 20] | Shared IV (see 3.2) |
The temporary variable will have a different length depending on the result from the crypto init handshake. The old protocol SharedIV will be 20 bytes long, since it is generated with sha1, while the new protocol SharedIV will have 64 bytes, since it is generated with sha512.
let temporary: [u8; 26] OR [u8; 70]
temporary[0] = 0x30 if (Client <- Server)
0x31 if (Client -> Server)
temporary[1] = PT
temporary[2..6] = (PGId in network order)[0..4]
if SIV.length == 20
temporary[6..26] = SIV[0..20]
else
temporary[6..70] = SIV[0..64]
let keynonce: [u8; 32]
keynonce = sha256(temporary)
key: [u8; 16] = keynonce[ 0..16]
nonce: [u8; 16] = keynonce[16..32]
key[0] = key[0] xor ((PId & 0xFF00) >> 8)
key[1] = key[1] xor ((PId & 0x00FF) >> 0)
The data can now be encrypted with the key
and nonce
from (see 1.6.2) as the
EAX key and nonce and the packet Meta
as defined in (see 1.1) as the EAX
header (sometimes called "Associated Text"). The resulting EAX mac (sometimes
called "Tag") will be stored in the MAC
field as defined in (see 2.1).
When a packet is not encrypted, no MAC can be generated by EAX. In this case the SharedMac (see 3.2) will be used instead.
This stack is a reference for the execution order of the set data operations. For incoming packets the stack is executed bot to top, for outgoing packets top to bot.
Send Receive
+-----------+ +-----------+
| Data | | Λ | Data |
+-----------+ | | +-----------+
| Compress | | | | Decompress|
+-----------+ | | +-----------+
| Split | | | | Merge |
+-----------+ | | +-----------+
| Encrypt | V | | Decrypt |
+-----------+ +-----------+
The following chapter describes the data structure for different packet types.
+--+--+--+---------//---------+
| VId |C | Data |
+--+--+--+---------//---------+
Name | Type | Explanation |
---|---|---|
VId | u16 | Voice Packet Id |
C | u8 | Codec Type |
Data | var | Voice Data |
+--+--+--+--+--+---------//---------+
| VId | CId |C | Data |
+--+--+--+--+--+---------//---------+
Name | Type | Explanation |
---|---|---|
VId | u16 | Voice Packet Id |
CId | u16 | Talking Client |
C | u8 | Codec Type |
Data | var | Voice Data |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+
| VId |C |N |M | U* | T* | Data |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+
For direct user/channel targeting
The Newprotocol
Flag must be unset
Name | Type | Explanation |
---|---|---|
VId | u16 | Voice Packet Id |
C | u8 | Codec Type |
N | u8 | Count of ChannelIds to send to |
M | u8 | Count of ClientIds to send to |
U | [u64] | Targeted ChannelIds, repeated N times |
T | [u16] | Targeted ClientIds, repeated M times |
Data | var | Voice Data |
OR
+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+
| VId |C |TY|TA| U | Data |
+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+
For targeting special groups
The Newprotocol
Flag must be set
Name | Type | Explanation |
---|---|---|
VId | u16 | Voice Packet Id |
C | u8 | Codec Type |
TY | u8 | GroupWhisperType (see below) |
TA | u8 | GroupWhisperTarget (see below) |
U | u64 | the targeted channelId or groupId (0 if not applicable) |
Data | var | Voice Data |
enum GroupWhisperType : u8
{
// Targets all users in the specified server group.
ServerGroup = 0 /* U = servergroup targetId */,
// Targets all users in the specified channel group.
ChannelGroup = 1 /* U = channelgroup targetId */,
// Targets all users with channel commander.
ChannelCommander = 2, /* U = 0 (ignored) */,
// Targets all users on the server.
AllClients = 3, /* U = 0 (ignored) */,
}
enum GroupWhisperTarget : u8
{
AllChannels = 0,
CurrentChannel = 1,
ParentChannel = 2,
AllParentChannel = 3,
ChannelFamily = 4,
CompleteChannelFamily = 5,
Subchannels = 6,
}
+--+--+--+--+--+---------//---------+
| VId | CId |C | Data |
+--+--+--+--+--+---------//---------+
Name | Type | Explanation |
---|---|---|
VId | u16 | Voice Packet Id |
CId | u16 | Talking Client |
C | u8 | Codec Type |
Data | var | Voice Data |
The TeamSpeak3 Query like command string encoded in UTF-8
Empty.
+--+--+
| PId |
+--+--+
Name | Type | Explanation |
---|---|---|
PId | u16 | The packet id that is acknowledged |
- In case of
Pong
a matching ping packet id is acknowledged. - In case of
Ack
orAckLow
a matchingCommand
orCommandLow
packet id respectively is acknowledged.
(see 2.1)-(see 2.5)
Each packet type and packet direction must be maintained by an own packet id counter. This means the client has 9 different packet id counter for outgoing packets.
For each new packet the counter gets increased by 1. This also applies to split packets.
The client must also maintain packet ids for incoming packets in case of packets arriving out of order.
All Packet Ids start at 1 unless otherwise specified.
Packet Ids are stored as u16, this means they range from 0 up to 65535 included.
When the packet id overflows from 65535 to 0 at a packet, the generation counter for this packet type gets increased by 1.
Note that the new generation id immediately applies to the 'overflowing' packet.
The generation id counter is solely used for encryption (see 1.6).
In order to reliably send packets over UDP some packet types must get acknowledged when received (see 1.11).
The protocol uses selective repeat for lost packets. This means each packet has its own timeout. Already acknowledged later packets must not be resent. When a packet times out, the exact same packet should be resent until properly acknowledged by the server. If after 30 seconds no resent packet gets acknowledged the connection should be closed. Packet resend timeouts should be calculated with an exponential backoff to prevent network congestion.
Type | Acknowledged (by) | Resend | Encrypted | Splittable | Compressible |
---|---|---|---|---|---|
Voice | ✗ | ✗ | Optional | ✗ | ✗ |
VoiceWhisper | ✗ | ✗ | Optional | ✗ | ✗ |
Command | ✓ (Ack) | ✓ | ✓ | ✓ | ✓ |
CommandLow | ✓ (AckLow) | ✓ | ✓ | ✓ | ✓ |
Ping | ✓ (Pong) | ✗ | ✗ | ✗ | ✗ |
Pong | ✗ | ✗ | ✗ | ✗ | ✗ |
Ack | ✗ | ✓ | ✓ | ✗ | ✗ |
AckLow | ✗ | ✓ | ✓ | ✗ | ✗ |
Init1 | ✓ (next Init1) | ✓ | ✗ | ✗ | ✗ |
A connection is started from the client by sending the first handshake packet. The handshake process consists of 5 different init packets. This includes the so called RSA puzzle to prevent DOS attacks.
The packet header values are set as following for all packets here:
Parameter | Value |
---|---|
MAC | [u8]{ 0x54, 0x53, 0x33, 0x49, 0x4E, 0x49, 0x54, 0x31 } |
key | N/A |
nonce | N/A |
Type | Init1 |
Encrypted | ✗ |
Packet Id | u16: 101 |
Client Id | u16: 0 |
Init packets from the client contain a version field, which is the build timestamp of the client. This is a unix timestamp subtracted with 1356998400. The unix timestamp 1461588969 (date 2016-04-25) is encoded as 1461588969 - 1356998400 = 0x063bece9 = { 0x06, 0x3b, 0xec, 0xe9 }.
04 bytes : Version of the TeamSpeak client as timestamp
Example: { 0x06, 0x3b, 0xec, 0xe9 }
01 bytes : Init-packet step number
Const: 0x00
04 bytes : Current timestamp in unix format
04 bytes : Random bytes := [A0]
08 bytes : Zeros, reserved.
01 bytes : Init-packet step number
Const: 0x01
16 bytes : Server stuff := [A1]
04 bytes : The bytes from [A0] in reversed order (not always) := [A0r]
This packets usually contains the bytes from [A0] in reversed order, except when connecting from some networks for a yet unknown reason.
04 bytes : Version of the TeamSpeak client as timestamp
01 bytes : Init-packet step number
Const: 0x02
16 bytes : The bytes from [A1]
04 bytes : The bytes from [A0r]
01 bytes : Init-packet step number
Const: 0x03
64 bytes : 'x', an unsigned BigInteger
64 bytes : 'n', an unsigned BigInteger
04 bytes : 'level' a u32
100 bytes : Server stuff := [A2]
Note:
- Sometimes, instead of sending an init with step 3, the server responds with an init that contains 127 as step number. In that case, the client has to restart the connection by sending packet 0 again.
04 bytes : Version of the TeamSpeak client as timestamp
01 bytes : Init-packet step number
Const: 0x04
64 bytes : the received 'x'
64 bytes : the received 'n'
04 bytes : the received 'level'
100 bytes : The bytes from [A2]
64 bytes : 'y' which is the result of x ^ (2 ^ level) % n as an unsigned
BigInteger. Padded from the lower side with '0x00' when shorter
than 64 bytes.
Example: { 0x00, 0x00, data ... data}
var bytes : The clientinitiv command data as explained in (see 3.1)
Note:
^
in this context means 'power to'- To calculate the power of such a high number use a language integrated
function like
ModPow
or similar, when available.
If you don't have this function available you can multiply x iteratively and apply the modulo operation after each multiplication.
In this phase the client and server exchange basic information and agree on/calculate the symmetric AES encryption key with the ECDH public/private key exchange technique.
Both the client and the server will need a EC public/private key. This key is also the identity which the server uses to recognize a user again. The curve used is 'prime256v1'.
All high level packets specified in this chapter are sent as Command
Type
packets as explained in (see 2.8.3). Additionally the Newprotocol
flag
(see 2.3) must be set on all Command
, CommandLow
and Init1
packets.
All commands are specified in the Messages.txt
file.
The packet header/encryption values for (see 3.1) and (see 3.2) are as following:
Parameter | Value |
---|---|
MAC | (Generated by EAX) |
key | [u8]{0x63, 0x3A, 0x5C, 0x77, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x5C, 0x73, 0x79, 0x73, 0x74, 0x65} |
nonce | [u8]{0x6D, 0x5C, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6C, 0x6C, 0x33, 0x32, 0x2E, 0x63, 0x70, 0x6C} |
Type | Command |
Encrypted | ✓ |
Packet Id | u16: 0 |
Client Id | u16: 0 |
The acknowledgement packets use the same parameters as the commands, except with
the Type Ack
.
(Maybe add a #3.0 Prelude for required cryptographic values, if yes move the omega ASN.1 encoding here)
The first packet is sent (Client -> Server) although this is only sent for
legacy reasons since newer servers (at least 3.0.13.0?) use the data part
embedded in the last Init1
packet from the low-level handshake (see 2.5).
clientinitiv alpha={alpha} omega={omega} ot={ot} ip={ip}
-
alpha
is set tobase64(random[u8; 10])
which are 10 random bytes for entropy. -
omega
is set tobase64(publicKey[u8])
omega is an ASN.1-DER encoded public key from the ECDH parameters as following:Type Value Explanation BIT STRING 1bit, Value: 0 LibTomCrypt uses 0 for a public key INTEGER 32 The LibTomCrypt used keysize INTEGER publicKey.x The affine X-Coordinate of the public key INTEGER publicKey.y The affine Y-Coordinate of the public key -
ot
should always be1
-
ip
should be set to the final resolved ip address of the server you are actually connecting to.
Depending on the server version the server will send a different init request.
- TS3 server <3.1 will send
initivexpand
. Continue with (see 3.2.1) - TS3 server ≥3.1 will send
initivexpand2
. Continue with (see 3.2.2)
If you want to support both protocol standards you don't need to check/know the server version. The client just has to act accordingly depending on which packet the server sends.
The server responds with this command.
initivexpand alpha={alpha} beta={beta} omega={omega}
alpha
must have the same value as sent to the server in the previous step.beta
is set tobase64(random[u8; 10])
by the server.omega
is set tobase64(publicKey[u8])
with the public key from the server, encoded same as in (see 3.1)
With this information the client now must calculate the shared secret.
let sharedSecret: ECPoint
let x: [u8]
let sharedData: [u8; 32]
let SharedIV: [u8; 20]
let SharedMac: [u8; 8]
let ECDH(A, B) := (A * B).Normalize
sharedSecret = ECDH(serverPublicKey, ownPrivateKey)
x = sharedSecret.x.AsByteArray()
if x.length < 32
sharedData[ 0..(32-x.length)] = [0..0]
sharedData[(32-x.length)..32] = x[0..x.length]
elseif x.length == 32
sharedData[0..32] = x[0..32]
elseif x.length > 32
sharedData[0..32] = x[(x.length-32)..x.length]
SharedIV = sha1(sharedData)
SharedIV[0..10] = SharedIV[0..10] xor alpha.decode64()
SharedIV[10..20] = SharedIV[10..20] xor beta.decode64()
SharedMac[0..8] = sha1(SharedIV)[0..8]
The server responds with this command.
initivexpand2 l={l} beta={beta} omega={omega} ot={ot} proof={proof} tvd={tvd}
l
the server license (see 3.2.2.2)beta
is set tobase64(random[u8; 54])
by the server.omega
is abase64(publicKey[u8])
with the public key from the server, encoded same as in (see 3.1)ot
should always be1
proof
is abase64(ecdh_sign(l))
tvd
(base64, unknown; only set on servers with a license)
This step must be done to verity the integrity of the connection.
The proof
parameter is the sign of the l
parameter (not base64 encoded).
The client can verify the l
parameter with the public key of the server
which is sent in omega
.
Both proofs which are exchanged (the one you received in (see 3.2.2), and the
one sent in (see 3.2.2.5)) use 'prime256v1 with sha256, DER encoded'.
Note that the identity keys from client/server already should be keys
on 'prime256v1' as noted in (see 3).
The license has a small header continued by a list of blocks. Each block may vary in length and must be parsed sequentially therefore.
The license header:
01 bytes : License version
Const: 0x01
The block base layout:
01 bytes : Key type
Const: 0x00 (public key)
32 bytes : Block public key
01 bytes : License block type
04 bytes : Not valid before date
04 bytes : Not valid after date
var bytes : (Content from the block type)
There are currently 4 different License block type
s used.
-
00
Intermediate.
content
:04 bytes : Unknown var bytes : A null terminated string, which describes the issuer of this certificate.
-
01
Website/03
Code
content
:var bytes : A null terminated string, which describes the issuer of this certificate.
-
02
TS3 Server
content
:01 bytes : Server License Type 04 bytes : Max clients allowed on the server var bytes : A null terminated string, which describes the issuer of this certificate.
-
08
TS5 Server
content
:01 bytes : Server License Type 01 bytes : Property count var bytes : Properties, see following description on how each property is encoded Per property: 01 bytes : Length of following data 01 bytes : Property Id 01 bytes : Data Type var bytes : Content depending on data type, see below
Data Types:
00
: Null-terminated string01
/03
: 4 byte data02
/04
: 8 byte data
Property Id:
01
(type01
): Unknown02
(type00
): Issuer of the certificate03
(type01
): Max clients allowed on the server, defaults to 32 if not present
-
32
Ephemeral
content
: none
Both dates are stored in BigEndian, when read you must add 0x50e22700
to the
number and import as a unix timestamp.
Each Not valid before
and Not valid after
timespan must be within the range
of the parent license block.
The license must consist of 2 ≤ count ≤ 8
blocks.
The second to last block must be of type Server
and the last block must be of type Ephemeral
.
All elliptic curve operations for this step are done on the Curve25519.
You might find more tools looking for Ed25519 but keep in mind that Ed25519
describes an EdDsa signing/verify operation and is not the curve itself.
To calculate the shared secret each license block now must be processed sequentially the following way:
next_key = public_key * clamp(sha512(block[1..])[0..32]) + parent
Where:
public_key
is theBlock public key
taken from the current license block. This array must be imported as a compressed Curve25519 EC point.sha512(block[1..])[0..32]
is the sha512 of the current license block.
Note that the first byte (Key type
) is skipped for the sha calculation.
For the result only the first 32 bytes are used.
This resulting array must be imported as a Curve25519 private key.parent
which is the resultingnext_key
from the previous block. This is a compressed Curve25519 EC point.clamp(num)
is a function describingabs(num) mod B
whereB
is the base point of Curve25519. This function can usually be implemented conveniently on the number buffer like this:let buffer: [u8; 32] buffer[0] &= 0xF8 buffer[31] &= 0x3F buffer[31] |= 0x40
Since the first block has no predecessor.
A fixed 'root' key is used as parent
.
This key must be imported as a compressed Curve25519 EC point.
[u8; 32] {0xcd, 0x0d, 0xe2, 0xae, 0xd4, 0x63, 0x45, 0x50, 0x9a, 0x7e, 0x3c,
0xfd, 0x8f, 0x68, 0xb3, 0xdc, 0x75, 0x55, 0xb2, 0x9d, 0xcc, 0xec,
0x73, 0xcd, 0x18, 0x75, 0x0f, 0x99, 0x38, 0x12, 0x40, 0x8a}
The last next_key
is now used as the public key from the server
(see pseudocode below).
The client now has to create a temporary Curve25519 public/private keypair.
We will call them client_public_key
and client_private_key
.
Now the SharedIV
and SharedMac
which will be used in the encryption, just as
in the old protocol, can be calculated.
let SharedIV: [u8; 64]
let SharedMac: [u8; 8]
let sharedData: [u8; 32]
sharedData = next_key * client_private_key
SharedIV = sha512(sharedData[0..32])
SharedIV[ 0..10] = SharedIV[ 0..10] xor alpha.decode64()
SharedIV[10..64] = SharedIV[10..64] xor beta.decode64()
SharedMac[0..8] = sha1(SharedIV)[0..8]
clientek ek={ek} proof={proof}
ek
isbase64(client_public_key)
which the ephemeral (temporary) key created in (see 3.2.2.4) by the client. This should obviously be the public key part only.proof
isbase64(client_public_key + beta)
which is a sign of the client_public_key (theek
) concatenated with thebeta
parameter from theinitivexpand2
command. The sign must be done with the private key from the identity keypair.
The normal packet id counting starts with this packet. This means that
clientek
already has the packet id 1
and the next command will continue
with 2
.
- Only
SharedIV
andSharedMac
are needed. The other values can (and should) be discarded. - The crypto handshake is now completed. The normal encryption scheme (see 1.6) is from now on used.
- All
Command
,CommandLow
,Ack
andAckLow
packets must get encrypted. Voice
packets (andVoiceWhisper
when wanted) should be encrypted when the channel encryption or server wide encryption flag is set.Ping
andPong
must not be encrypted.
clientinit client_nickname client_version client_platform client_input_hardware client_output_hardware 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 hwid
client_nickname
the desired nicknameclient_version
the client versionclient_platform
the client platformclient_input_hardware
whether an input device is availableclient_output_hardware
whether an output device is availableclient_default_channel
the default channel to join. This can be a channel path or/<id>
(eg/1
) for a channel id.client_default_channel_password
the password for the join channel, prepared the following waybase64(sha1(password))
client_server_password
the password to enter the server, prepared the following waybase64(sha1(password))
client_meta_data
(can be left empty)client_version_sign
a cryptographic sign to verify the genuinity of the clientclient_key_offset
the number offset used to calculate the hashcash (see 4.1) value of the used identityclient_nickname_phonetic
the phonetic nickname for text-to-speechclient_default_token
permission token to be used when connecting to a serverhwid
hardware identification string
Notes:
- Since client signs are only generated and distributed by TeamSpeak systems,
this the recommended client triple, as it is the reference for this paper
- Version:
3.0.19.3 [Build: 1466672534]
- Platform:
Windows
- Sign:
a1OYzvM18mrmfUQBUgxYBxYz2DUU6y5k3/mEL6FurzU0y97Bd1FL7+PRpcHyPkg4R+kKAFZ1nhyzbgkGphDWDg==
- Version:
- The
hwid
usually consists of two 32 char strings concatenated with,
and looks like87056c6e1268aaf5055abf8256415e0e,408978b6d98810cc03f0aa16a4c75600
but even empty strings are accepted.
On windows the hwid seems to be generated from a registry key (HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductId
).
On linux and macOS it seems to derive from the MAC address of the primary Ethernet/Wifi adapter. - Parameters which are empty or not used must be declared but left without
value and the
=
character
The server sends the initserver
command.
Note:
- From this point on the client knows his client id, therefore it must be set in the header of each packet.
- Newer versions of the server send parts of the
clientinit
command in theinitserver
command.
The server will now send all needed information to display the entire server properly. Those notifications are in no fixed order, although they are most of the time sent in the here declared order.
The channellist
notification type will be sent multiple times as needed to
transfer the entire server structure.
After the last channellist
notification the server will send
channellistfinished
The notifycliententerview
notification will be sent multiple times as needed
for each client currently connected.
This is the same notification as when a new client connects.
To prevent client spamming (connecting to a server with many different clients) the server requires a certain hashcash level on each identity. This level has a exponentially growing calculation time with increasing level. This ensures that a user wanting to spam a certain server needs to invest some time into calculating the required level.
- The publicKey is a string encoded as in (see 3.1) the omega value.
- The key offset is a u64 number, which gets converted to a string when concatenated.
The first step is to calculate a hash as following
let data: [u8; 20] = sha1(publicKey + keyOffset)
The level can now be calculated by counting the continuous leading zero bits in the data array. The bytes in the array get counted from 0 to 20 and the bits in each byte from least significant to most significant.
To calculate the uid of an identity the public key is required. Therefore you can only calculate the uid of your own identity and the servers identity you are connecting to.
The publicKey is a string encoded as in (see 3.1) the omega value.
The uid can be calculated as following
let uid: string = base64(sha1(publicKey))
The server will regularly send ping packets to check if a client is still alive. The client must answer them with the according pong packet.
The client should also send ping packets to the server to check for connection. They will be answered with according pong packets.
Sending ping packets from the client side should not be started before the crypto handshake has been completed (see 3.3)
All passwords when sent are hashed and encoded with base64(sha1(password))
The TeamSpeak3 Client exports identities the following way:
<key_offset> + 'V' + <obfuscaded_identity>
(For an explanation of the key offset (see 4.1))
const staticObfucationKey = b"b9dfaa7bee6ac57ac7b65f1094a1c155e747327bc2fe5d51c512023fe54a280201004e90ad1daaae1075d53b7d571c30e063b5a62a4a017bb394833aa0983e6e"
let ident = obfuscaded_identity.decode64()
let sha_part = ident[20..]
let idx = sha_part.indexof('\0') // where '\0' is the null-byte
let sha = sha1(sha_part[..idx])
ident[0..20] = ident[0..20] xor sha[0..20]
let xorlen = min(ident.length, 100)
ident[0..xorlen] = ident[0..xorlen] xor staticObfucationKey[0..xorlen]
When the Opus codec is used, Voice and VoiceWhisper packets are using a sampling rate of 48 kHz. An voice packat without audio data signals the end of a stream.
permissionlist
requests the list of all permissions of the server.
notifypermissionlist
returns the list of permissions and a grouping as a list
of group_id_end
s. The end ids are excluding, so a group_id_end=6
for the first
group means the first 6 permissions (perms[0..6]
) are in this group.
Clients can subscribe channels, so they get notifications when someone enters or leaves from a subscribed channel. If a new channel is subscribed, the server usually sends a notification, except in some cases:
- If the client server groups or permissions change, it stays subscribed, even if it does not have the power anymore
- If the channel permissions change, the server sends a notification if the subscription status changed
- If a client enters a channel, it gets subscribed but there is no notification
- If a client leaves a channel, there is a notification that it unsubscribed
- If a new channel is created, clients are not automatically subscribed
- notifyconnectioninforequest
- => setconnectioninfo