Control messages 0.6.5
There are three types of messages with overlapping message ids:
system messages,
game messages and
control messages
This list is only covering control messages.
Control messages have a simpler layout than game or system messages.
They are always sent as a single packet. So there is no chunking and flushing.
There is also no message header just its id and payload.
Note that 0.6.5 introduced security tokens
for control messages. Here the control msg send method used to send all of them.
void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, bool UseToken, unsigned Token, int ControlMsg, const void *pExtra, int ExtraSize)
{
CNetPacketConstruct Construct;
Construct.m_Flags = NET_PACKETFLAG_CONTROL|(UseToken?NET_PACKETFLAG_TOKEN:0);
Construct.m_Ack = Ack;
Construct.m_NumChunks = 0;
Construct.m_Token = Token;
Construct.m_DataSize = 1+ExtraSize;
Construct.m_aChunkData[0] = ControlMsg;
mem_copy(&Construct.m_aChunkData[1], pExtra, ExtraSize);
// send the control message
CNetBase::SendPacket(Socket, pAddr, &Construct);
}
Note that it shares the same teeworlds packet header so it has the fields NumChunks. But it is set to 0
at all times for control messages. Also control messages are never compressed using huffman compression.
NET_CTRLMSG_KEEPALIVE
Sender: | Client Server |
---|---|
Recipient: | Client Server |
Message ID: | 0 |
Response to: | Sent after 250ms of silence |
Expected response: | None |
Argument name | Type | Note |
---|---|---|
None |
network_conn.cpp
which is shared
by client and server.
// system.c
int64 time_freq()
{
#if defined(CONF_FAMILY_UNIX)
return 1000000;
#elif defined(CONF_FAMILY_WINDOWS)
int64 t;
QueryPerformanceFrequency((PLARGE_INTEGER)&t);
return t;
#else
#error not implemented
#endif
}
// network_conn.cpp
int CNetConnection::Update()
{
int64 Now = time_get();
int64 Freq = time_freq();
// [..]
// send keep alives if nothing has happend for 250ms
if(State() == NET_CONNSTATE_ONLINE)
{
// [..]
if(Now-m_LastSendTime > Freq)
SendControl(NET_CTRLMSG_KEEPALIVE, 0, 0);
}
// [..]
return 0;
}
NET_CTRLMSG_CONNECT
Sender: | Client |
---|---|
Recipient: | Server |
Message ID: | 1 |
Response to: | TODO |
Expected response: | TODO |
Argument name | Type | Note |
---|---|---|
Null | Raw |
4 null bytes
Was added in 0.6.5! So 0.6.4 and earlier do not send any payload! |
Client Token | Raw |
Fixed size 4 byte random security token.
Was added in 0.6.5! So 0.6.4 and earlier do not send a token! |
Null | Raw |
504 null bytes filling the total payload size to be 512 plus the control message id.
Was added in 0.6.5! So 0.6.4 and earlier do not send null bytes! |
SendConnect()
wrapper.
void CNetConnection::SendConnect()
{
unsigned char aConnect[512] = {0};
uint32_to_be(&aConnect[4], m_Token);
SendControl(NET_CTRLMSG_CONNECT, aConnect, sizeof(aConnect));
}
Here a sample payload of a NET_CTRLMSG_CONNECT sent over the network.
Note this is only the payload. There has to be a teeworlds packet header
in front of it.
Token
/ \
/ \
/ \
NET_CTRLMSG_CONNECT / \
v / \
01 00 00 00 00 80 45 7f 2e 00 00 00 00 00 00 ......E........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 ..
Unpacked by the server in CNetServer::Recv()
if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT)
{
if(ClientID != -1)
{
continue; // silent ignore.. we got this client already
}
if(m_RecvUnpacker.m_Data.m_DataSize >= 1+512)
{
unsigned MyToken = GetToken(Addr);
unsigned char aConnectAccept[4];
uint32_to_be(&aConnectAccept[0], MyToken);
CNetBase::SendControlMsg(m_Socket, &Addr, 0, true, Token, NET_CTRLMSG_CONNECTACCEPT, aConnectAccept, sizeof(aConnectAccept));
if(g_Config.m_Debug)
{
dbg_msg("netserver", "got connect, sending connect+accept challenge");
}
}
// [..]
NET_CTRLMSG_CONNECTACCEPT
Sender: | Server |
---|---|
Recipient: | Client |
Message ID: | 2 |
Response to: | TODO |
Expected response: | TODO |
Argument name | Type | Note |
---|---|---|
Server Token | Raw | Fixed size 4 byte random security token |
CNetServer::Recv()
if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT)
{
if(ClientID != -1)
{
continue; // silent ignore.. we got this client already
}
if(m_RecvUnpacker.m_Data.m_DataSize >= 1+512)
{
unsigned MyToken = GetToken(Addr);
unsigned char aConnectAccept[4];
uint32_to_be(&aConnectAccept[0], MyToken);
CNetBase::SendControlMsg(m_Socket, &Addr, 0, true, Token, NET_CTRLMSG_CONNECTACCEPT, aConnectAccept, sizeof(aConnectAccept));
if(g_Config.m_Debug)
{
dbg_msg("netserver", "got connect, sending connect+accept challenge");
}
}
// [..]
Unpacked by the client in CNetConnection::Feed()
if(State() == NET_CONNSTATE_CONNECT)
{
// connection made
if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT)
{
if(pPacket->m_Flags&NET_PACKETFLAG_TOKEN)
{
if(pPacket->m_DataSize < 1+4)
{
if(g_Config.m_Debug)
{
dbg_msg("connection", "got short connect+accept, size=%d", pPacket->m_DataSize);
}
return 1;
}
m_Token = uint32_from_be(&pPacket->m_aChunkData[1]);
}
else
{
m_UseToken = false;
}
m_LastRecvTime = Now;
m_State = NET_CONNSTATE_ONLINE;
if(g_Config.m_Debug)
dbg_msg("connection", "got connect+accept, sending accept. connection online");
}
}
Here a sample payload of a NET_CTRLMSG_CONNECTACCEPT sent over the network.
Note this is only the payload. There has to be a teeworlds packet header
in front of it.
NET_CTRLMSG_CONNECTACCEPT
|
| token
| _____|___
v / \
02 5e 40 3a b8 .^@:.
NET_CTRLMSG_ACCEPT
Sender: | Client |
---|---|
Recipient: | Server |
Message ID: | 3 |
Response to: | NET_CTRLMSG_CONNECTACCEPT |
Expected response: | TODO |
Argument name | Type | Note |
---|---|---|
None |
Because 0.6.5 released while 0.7 was already in full development it also got removed in 0.7 in a seperate commit.
The client uses it to ack the servers accept. The first three messages sent when a connection is established are the following:
client -> server NET_CTRLMSG_CONNECT
server -> client NET_CTRLMSG_ACCEPTCONNECT
client -> server NET_CTRLMSG_ACCEPT (ack servers accept)
NET_CTRLMSG_CLOSE
Sender: | Client Server |
---|---|
Recipient: | Client Server |
Message ID: | 4 |
Response to: | TODO |
Expected response: | TODO |
Argument name | Type | Note |
---|---|---|
Reason | String | This argument is optional and can also be left out |
Sent in the
CNetConnection::Disconnect(const char *pReason)
method.
void CNetConnection::Disconnect(const char *pReason)
{
if(State() == NET_CONNSTATE_OFFLINE)
return;
if(m_RemoteClosed == 0)
{
if(pReason)
SendControl(NET_CTRLMSG_CLOSE, pReason, str_length(pReason)+1);
else
SendControl(NET_CTRLMSG_CLOSE, 0, 0);
if(pReason != m_ErrorString)
{
if(pReason)
str_copy(m_ErrorString, pReason, sizeof(m_ErrorString));
else
m_ErrorString[0] = 0;
}
}
Reset();
}
Unpacket in CNetConnection::Feed()
if(CtrlMsg == NET_CTRLMSG_CLOSE)
{
if(net_addr_comp(&m_PeerAddr, pAddr) == 0)
{
m_State = NET_CONNSTATE_ERROR;
m_RemoteClosed = 1;
char aStr[128] = {0};
if(pPacket->m_DataSize > 1)
{
// make sure to sanitize the error string form the other party
if(pPacket->m_DataSize < 128)
str_copy(aStr, (char *)&pPacket->m_aChunkData[1], pPacket->m_DataSize);
else
str_copy(aStr, (char *)&pPacket->m_aChunkData[1], sizeof(aStr));
str_sanitize_strong(aStr);
}
if(!m_BlockCloseMsg)
{
// set the error string
SetError(aStr);
}
if(g_Config.m_Debug)
dbg_msg("connection", "closed reason='%s'", aStr);
}
return 0;
}
If there is no reason set the payload of the teeworlds packet will only be the byte 0x04
.
If there is a reason it will be appended as a null terminated string.
NET_CTRLMSG_CLOSE
|
| All the rest is reason
| |
v v
04 53 65 72 76 65 72 20 73 68 75 .Server shu
74 64 6f 77 6e 00 tdown.
^
|
Terminating null byte