Skip to content
Permalink
Browse files

Chat protocol rewrite (#5117)

* New TOCLIENT_CHAT_MESSAGE packet

* Rename old packet to TOCLIENT_CHAT_MESSAGE_OLD for compat
* Handle TOCLIENT_CHAT_MESSAGE new structure client side
* Client chat queue should use a specific object
* SendChatMessage: use the right packet depending on protocol version (not complete yet)
* Add chatmessage(type) objects and handle them client side (partially)
* Use ChatMessage instead of std::wstring server side

* Update with timestamp support
  • Loading branch information...
nerzhul committed Jul 16, 2017
1 parent ecbc972 commit 7ddf67aa1478813e12a5fcdfb4986b9e5adfe62f
@@ -0,0 +1,51 @@
/*
Minetest
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef MT_CHATMESSAGE_H
#define MT_CHATMESSAGE_H

#include <string>
#include <ctime>

enum ChatMessageType
{
CHATMESSAGE_TYPE_RAW = 0,
CHATMESSAGE_TYPE_NORMAL = 1,
CHATMESSAGE_TYPE_ANNOUNCE = 2,
CHATMESSAGE_TYPE_SYSTEM = 3,
CHATMESSAGE_TYPE_MAX = 4,
};

struct ChatMessage
{
ChatMessage(const std::wstring &m = L"") : message(m) {}

ChatMessage(ChatMessageType t, const std::wstring &m, const std::wstring &s = L"",
std::time_t ts = std::time(0))
: type(t), message(m), sender(s), timestamp(ts)
{
}

ChatMessageType type = CHATMESSAGE_TYPE_RAW;
std::wstring message = L"";
std::wstring sender = L"";
std::time_t timestamp = std::time(0);
};

#endif
@@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "guiscalingfilter.h"
#include "script/scripting_client.h"
#include "game.h"
#include "chatmessage.h"

extern gui::IGUIEnvironment* guienv;

@@ -1518,12 +1519,34 @@ u16 Client::getHP()
return player->hp;
}

bool Client::getChatMessage(std::wstring &message)
bool Client::getChatMessage(std::wstring &res)
{
if(m_chat_queue.size() == 0)
if (m_chat_queue.empty())
return false;
message = m_chat_queue.front();

ChatMessage *chatMessage = m_chat_queue.front();
m_chat_queue.pop();

res = L"";

switch (chatMessage->type) {
case CHATMESSAGE_TYPE_RAW:
case CHATMESSAGE_TYPE_ANNOUNCE:
case CHATMESSAGE_TYPE_SYSTEM:
res = chatMessage->message;
break;
case CHATMESSAGE_TYPE_NORMAL: {
if (!chatMessage->sender.empty())
res = L"<" + chatMessage->sender + L"> " + chatMessage->message;
else
res = chatMessage->message;
break;
}
default:
break;
}

delete chatMessage;
return true;
}

@@ -1542,14 +1565,13 @@ void Client::typeChatMessage(const std::wstring &message)
sendChatMessage(message);

// Show locally
if (message[0] != L'/')
{
if (message[0] != L'/') {
// compatibility code
if (m_proto_ver < 29) {
LocalPlayer *player = m_env.getLocalPlayer();
assert(player);
std::wstring name = narrow_to_wide(player->getName());
pushToChatQueue((std::wstring)L"<" + name + L"> " + message);
pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_NORMAL, message, name));
}
}
}
@@ -1806,7 +1828,8 @@ void Client::makeScreenshot()
} else {
sstr << "Failed to save screenshot '" << filename << "'";
}
pushToChatQueue(narrow_to_wide(sstr.str()));
pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
narrow_to_wide(sstr.str())));
infostream << sstr.str() << std::endl;
image->drop();
}
@@ -43,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f

struct MeshMakeData;
struct ChatMessage;
class MapBlockMesh;
class IWritableTextureSource;
class IWritableShaderSource;
@@ -328,7 +329,8 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
void handleCommand_BlockData(NetworkPacket* pkt);
void handleCommand_Inventory(NetworkPacket* pkt);
void handleCommand_TimeOfDay(NetworkPacket* pkt);
void handleCommand_ChatMessage(NetworkPacket* pkt);
void handleCommand_ChatMessageOld(NetworkPacket *pkt);
void handleCommand_ChatMessage(NetworkPacket *pkt);
void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt);
void handleCommand_ActiveObjectMessages(NetworkPacket* pkt);
void handleCommand_Movement(NetworkPacket* pkt);
@@ -520,9 +522,9 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef

void makeScreenshot();

inline void pushToChatQueue(const std::wstring &input)
inline void pushToChatQueue(ChatMessage *cec)
{
m_chat_queue.push(input);
m_chat_queue.push(cec);
}

ClientScripting *getScript() { return m_script; }
@@ -629,10 +631,10 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
// 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT
//s32 m_daynight_i;
//u32 m_daynight_ratio;
std::queue<std::wstring> m_chat_queue;
std::queue<std::wstring> m_out_chat_queue;
u32 m_last_chat_message_sent;
float m_chat_message_allowance = 5.0f;
std::queue<ChatMessage *> m_chat_queue;

// The authentication methods we can use to enter sudo mode (=change password)
u32 m_sudo_auth_methods;
@@ -693,6 +693,32 @@ void ClientInterface::sendToAll(NetworkPacket *pkt)
}
}

void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt,
u16 min_proto_ver)
{
MutexAutoLock clientslock(m_clients_mutex);
for (std::unordered_map<u16, RemoteClient*>::iterator i = m_clients.begin();
i != m_clients.end(); ++i) {
RemoteClient *client = i->second;
NetworkPacket *pkt_to_send = nullptr;

if (client->net_proto_version >= min_proto_ver) {
pkt_to_send = pkt;
} else if (client->net_proto_version != 0) {
pkt_to_send = legacypkt;
} else {
warningstream << "Client with unhandled version to handle: '"
<< client->net_proto_version << "'";
continue;
}

m_con->Send(client->peer_id,
clientCommandFactoryTable[pkt_to_send->getCommand()].channel,
pkt_to_send,
clientCommandFactoryTable[pkt_to_send->getCommand()].reliable);
}
}

RemoteClient* ClientInterface::getClientNoEx(u16 peer_id, ClientState state_min)
{
MutexAutoLock clientslock(m_clients_mutex);
@@ -434,6 +434,7 @@ class ClientInterface {

/* send to all clients */
void sendToAll(NetworkPacket *pkt);
void sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt, u16 min_proto_ver);

/* delete a client */
void DeleteClient(u16 peer_id);
@@ -976,8 +976,7 @@ static void updateChat(Client &client, f32 dtime, bool show_debug,
}

// Get new messages from client
std::wstring message;

std::wstring message = L"";
while (client.getChatMessage(message)) {
chat_backend.addUnparsedMessage(message);
}
@@ -71,8 +71,8 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
null_command_handler,
null_command_handler,
null_command_handler,
null_command_handler,
{ "TOCLIENT_CHAT_MESSAGE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ChatMessage }, // 0x30
{ "TOCLIENT_CHAT_MESSAGE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ChatMessage }, // 0x2F
{ "TOCLIENT_CHAT_MESSAGE_OLD", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ChatMessageOld }, // 0x30
{ "TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ActiveObjectRemoveAdd }, // 0x31
{ "TOCLIENT_ACTIVE_OBJECT_MESSAGES", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ActiveObjectMessages }, // 0x32
{ "TOCLIENT_HP", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HP }, // 0x33
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client.h"

#include "util/base64.h"
#include "chatmessage.h"
#include "clientmedia.h"
#include "log.h"
#include "map.h"
@@ -142,7 +143,9 @@ void Client::handleCommand_AcceptSudoMode(NetworkPacket* pkt)
}
void Client::handleCommand_DenySudoMode(NetworkPacket* pkt)
{
pushToChatQueue(L"Password change denied. Password NOT changed.");
ChatMessage *chatMessage = new ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
L"Password change denied. Password NOT changed.");
pushToChatQueue(chatMessage);
// reset everything and be sad
deleteAuthData();
}
@@ -395,7 +398,7 @@ void Client::handleCommand_TimeOfDay(NetworkPacket* pkt)
<< " dr=" << dr << std::endl;
}

void Client::handleCommand_ChatMessage(NetworkPacket* pkt)
void Client::handleCommand_ChatMessageOld(NetworkPacket *pkt)
{
/*
u16 command
@@ -413,8 +416,43 @@ void Client::handleCommand_ChatMessage(NetworkPacket* pkt)
}

// If chat message not consummed by client lua API
// @TODO send this to CSM using ChatMessage object
if (!moddingEnabled() || !m_script->on_receiving_message(wide_to_utf8(message))) {
pushToChatQueue(message);
pushToChatQueue(new ChatMessage(message));
}
}

void Client::handleCommand_ChatMessage(NetworkPacket *pkt)
{
/*
u8 version
u8 message_type
u16 sendername length
wstring sendername
u16 length
wstring message
*/

ChatMessage *chatMessage = new ChatMessage();
u8 version, message_type;
*pkt >> version >> message_type;

if (version != 1 || message_type >= CHATMESSAGE_TYPE_MAX) {
delete chatMessage;
return;
}

*pkt >> chatMessage->sender >> chatMessage->message >> chatMessage->timestamp;

chatMessage->type = (ChatMessageType) message_type;

// @TODO send this to CSM using ChatMessage object
if (!moddingEnabled() || !m_script->on_receiving_message(
wide_to_utf8(chatMessage->message))) {
pushToChatQueue(chatMessage);
} else {
// Message was consumed by CSM and should not handled by client, destroying
delete chatMessage;
}
}

@@ -275,6 +275,12 @@ NetworkPacket& NetworkPacket::operator<<(u64 src)
return *this;
}

NetworkPacket& NetworkPacket::operator<<(std::time_t src)
{
*this << (u64) src;
return *this;
}

NetworkPacket& NetworkPacket::operator<<(float src)
{
checkDataSize(4);
@@ -360,6 +366,16 @@ NetworkPacket& NetworkPacket::operator>>(u64& dst)
return *this;
}

NetworkPacket& NetworkPacket::operator>>(std::time_t& dst)
{
checkReadOffset(m_read_offset, 8);

dst = readU64(&m_data[m_read_offset]);

m_read_offset += 8;
return *this;
}

NetworkPacket& NetworkPacket::operator>>(float& dst)
{
checkReadOffset(m_read_offset, 4);
@@ -84,6 +84,9 @@ class NetworkPacket
NetworkPacket& operator>>(u64& dst);
NetworkPacket& operator<<(u64 src);

NetworkPacket& operator>>(std::time_t& dst);
NetworkPacket& operator<<(std::time_t src);

NetworkPacket& operator>>(float& dst);
NetworkPacket& operator<<(float src);

@@ -160,9 +160,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
instead of guessing based on the active object list.
PROTOCOL VERSION 34:
Add sound pitch
PROTOCOL VERSION 35:
Rename TOCLIENT_CHAT_MESSAGE to TOCLIENT_CHAT_MESSAGE_OLD (0x30)
Add TOCLIENT_CHAT_MESSAGE (0x2F)
This chat message is a signalisation message containing various informations:
* timestamp
* sender
* type (RAW, NORMAL, ANNOUNCE, SYSTEM)
* content
*/

#define LATEST_PROTOCOL_VERSION 34
#define LATEST_PROTOCOL_VERSION 35

// Server's supported network protocol range
#define SERVER_PROTOCOL_VERSION_MIN 24
@@ -307,7 +315,17 @@ enum ToClientCommand

// (oops, there is some gap here)

TOCLIENT_CHAT_MESSAGE = 0x30,
TOCLIENT_CHAT_MESSAGE = 0x2F,
/*
u8 version
u8 message_type
u16 sendername length
wstring sendername
u16 length
wstring message
*/

TOCLIENT_CHAT_MESSAGE_OLD = 0x30, // Deprecated by proto v35
/*
u16 length
wstring message
@@ -160,8 +160,8 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
null_command_factory,
null_command_factory,
null_command_factory,
null_command_factory,
{ "TOCLIENT_CHAT_MESSAGE", 0, true }, // 0x30
{ "TOCLIENT_CHAT_MESSAGE", 0, true }, // 0x2F
{ "TOCLIENT_CHAT_MESSAGE_OLD", 0, true }, // 0x30
{ "TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", 0, true }, // 0x31
{ "TOCLIENT_ACTIVE_OBJECT_MESSAGES", 0, true }, // 0x32 Special packet, sent by 0 (rel) and 1 (unrel) channel
{ "TOCLIENT_HP", 0, true }, // 0x33
Oops, something went wrong.

0 comments on commit 7ddf67a

Please sign in to comment.
You can’t perform that action at this time.