/* net/netconnection.cc This file is part of the Osirion project and is distributed under the terms of the GNU General Public License version 2 */ #include #include #include #include "sys/sys.h" #include "core/application.h" #include "core/gameconnection.h" #include "core/netconnection.h" #include "core/player.h" #include "core/stats.h" namespace core { NetConnection::NetConnection() { connection_timeout = core::application()->time(); connection_state = Connecting; connection_timestamp = 0; receive_compressed = false; received_compressed_size = 0; compressed_size = 0; } NetConnection::~NetConnection() { disconnect(); } void NetConnection::abort() { connection_error = true; } void NetConnection::connect(std::string const &to_host, int to_port) { connection_error = false; connection_fd = -1; connection_state = Connecting; if (valid()) return; // resolve serverhostname struct hostent *serverhostent; serverhostent = gethostbyname(to_host.c_str()); if (!serverhostent) { con_warn << "Could not resolve '" << to_host.c_str() << "'" << std::endl; abort(); return; } // Get a socket file descriptor connection_fd = socket(PF_INET, SOCK_DGRAM, 0); if (connection_fd == -1) { //con_error << "Network socket() failed!" << std::endl; abort(); return; } // make the connection server_addr.sin_family = AF_INET; server_addr.sin_port = htons(to_port); // FIXME inet_addr can still fail server_addr.sin_addr.s_addr = inet_addr(inet_ntoa(*((struct in_addr *)serverhostent->h_addr))); memset(server_addr.sin_zero, '\0', sizeof server_addr.sin_zero); if (server_addr.sin_addr.s_addr == INADDR_NONE) { con_error << "Network invalid address " << to_host << "!" << std::endl; abort(); return; } connection_host = to_host; connection_port = to_port; connection_error = false; FD_ZERO(&clientset); #ifdef _WIN32 FD_SET((unsigned int) fd(), &clientset); #else FD_SET(fd(), &clientset); #endif connection_timeout = application()->time(); connection_keepalive = application()->time(); connection_state = Pending; game()->localplayer()->set_dirty(); std::stringstream str; str << "Connecting to " << inet_ntoa(*((struct in_addr *)serverhostent->h_addr)) << ":" << to_port << "..."; con_print << str.str() << std::endl; application()->notify_loader(str.str()); } void NetConnection::disconnect() { if (connection_fd != -1) { sendq.clear(); sendq.assign("disconnect\n"); transmit(); FD_ZERO(&clientset); #ifdef _WIN32 closesocket(connection_fd); #else close(connection_fd); #endif } connection_fd = -1; connection_error = false; connection_host.clear(); connection_port = 0; connection_state = Connecting; } bool NetConnection::has_messages() const { return (recvq.size() > 0); } void NetConnection::retreive(std::string & message) { if (recvq.size() > 0) { message.assign(recvq.front()); recvq.pop_front(); } else { message.clear(); } } // receive data and decode it into lines void NetConnection::receive() { // TODO: binary mode data transfer if (error() || invalid()) return; ssize_t bytes_received; memset(recvbuf, 0, BLOCKSIZE); bytes_received = ::recv(connection_fd, recvbuf, BLOCKSIZE - 1, 0); Stats::network_bytes_received += bytes_received; connection_timeout = core::application()->time(); if (bytes_received == 0) { con_print << "^BDisconnected." << std::endl; abort(); return; } else if (bytes_received < 0) { con_error << "Network receive() error!" << std::endl; //perror("recv"); abort(); return; } const char *c = recvbuf; while (bytes_received) { if (receive_compressed) { zrecvbuf[received_compressed_size] = *c; received_compressed_size++; if (compressed_size == received_compressed_size) { // uncompress char zunbuf[BLOCKSIZE*2]; memset(zunbuf, 0, sizeof(zunbuf)); uLong zunbuf_size = (uLong) BLOCKSIZE * 2 - 1; int status = uncompress((Bytef *) zunbuf, &zunbuf_size, (Bytef *) zrecvbuf, (uLong) compressed_size); if (status != Z_OK) { con_warn << "zlib error " << status << " uncompressing incoming datablock!\n"; } else { const char *zc = zunbuf; while (*zc) { if ((*zc == '\n') || (*zc == '\r')) { if (messageblock.size()) { recvq.push_back(messageblock); messageblock.clear(); } } else { if (messageblock.size() < FRAMESIZE) { messageblock += *zc; } else { con_warn << "Incoming uncompressed message exceeds " << FRAMESIZE << " bytes!\n"; messageblock.clear(); } } zc++; } } // reset receive_compressed = false; received_compressed_size = 0; compressed_size = 0; } } else if (!messageblock.size() && (bytes_received > 3) && (*c == '\xff') && (*(c + 1) == '\xff')) { receive_compressed = true; received_compressed_size = 0; compressed_size = (uLong)(*(unsigned char *)(c + 2) + (*(unsigned char *)(c + 3) << 8)); c += 3; bytes_received -= 3; memset(zrecvbuf, 0, sizeof(zrecvbuf)); if (compressed_size > BLOCKSIZE) { con_warn << "Incoming compressed datablock exceeds " << BLOCKSIZE << " bytes!\n"; receive_compressed = false; } } else if ((*c == '\n') || (*c == '\r')) { if (messageblock.size()) { recvq.push_back(messageblock); messageblock.clear(); } } else { if (messageblock.size() < FRAMESIZE) { messageblock += *c; } else { con_warn << "Incoming message exceeds " << FRAMESIZE << " bytes!\n"; messageblock.clear(); } } c++; bytes_received--; } } void NetConnection::frame() { timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; fd_set readset = clientset; int nb = select(fd() + 1, &readset, NULL, NULL, &timeout); if (nb == 0) { if (connection_timeout + NETTIMEOUT < core::application()->time()) { con_error << "Connection timeout!\n"; abort(); } return; } if (nb == -1) { con_error << "Network error on select()" << std::endl; //perror("select"); abort(); } while (FD_ISSET(this->fd(), &readset) && !error()) { receive(); while (has_messages()) { std::string message; retreive(message); parse_incoming_message(message); } nb = select(fd() + 1, &readset, NULL, NULL, &timeout); if (nb == 0) { return; } if (nb == -1) { con_error << "Network error on select()" << std::endl; //perror("select"); abort(); } } } // transmit all queued messages void NetConnection::transmit() { if (error() || invalid()) return; if (!sendq.size()) { if (connection_keepalive + NETTIMEOUT / 2 < application()->time()) { sendq.assign("ping\n"); } else { return; } } else if (sendq.size() >= BLOCKSIZE - 16) { con_warn << "Outgoing data exceeds " << BLOCKSIZE - 16 << " bytes!\n"; sendq.clear(); return; } ssize_t bytes_sent = 0; while (sendq.size()) { bytes_sent = ::sendto(connection_fd, sendq.c_str(), sendq.size(), 0, (struct sockaddr *) & server_addr, sizeof(server_addr)); if (bytes_sent <= 0) { con_error << "Network send() error!" << std::endl; //perror("send"); abort(); return; } // assert (bytes_sent <= sendbuf.size()); sendq.erase(0, bytes_sent); Stats::network_bytes_sent += bytes_sent; } connection_keepalive = application()->time(); } // queue a mmessage to the server void NetConnection::send_raw(std::string const &msg) { sendq.append(msg); } // functions for outgoing messages /** * the following outgoing messages can be send * * connect * pif * cup * cmd * say * priv */ // send a "connect" message to the server void NetConnection::send_connect() { std::ostringstream msg; msg << "connect " << PROTOCOLVERSION << "\n"; this->send_raw(msg.str()); } // send a "pif" player info message to the server void NetConnection::send_playerinfo() { localplayer()->update_info(); std::ostringstream msg; msg << "pif "; localplayer()->serialize_client_update(msg); msg << '\n'; this->send_raw(msg.str()); localplayer()->set_dirty(false); } // send a "cup" client update message to the server void NetConnection::send_client_update(Entity *entity) { // cup std::ostringstream msg; msg << "cup " << entity->id() << " "; entity->serialize_client_update(msg); msg << '\n'; this->send_raw(msg.str()); } // send a "req" entity request void NetConnection::send_entity_request(Entity *entity) { // req std::ostringstream msg; msg << "req " << entity->id() << '\n'; this->send_raw(msg.str()); } // send a "cmd" command line message to the server void NetConnection::send_command(std::string const &cmdline) { std::string msg("cmd "); msg.append(cmdline); msg += '\n'; this->send_raw(msg); } // send a "rcon" command line message to the server void NetConnection::send_rcon(std::string const &cmdline) { std::string msg("rcon "); msg.append(cmdline); msg += '\n'; this->send_raw(msg); } // send a "say" chat message message to the server void NetConnection::send_say(std::string const &text) { std::string msg("say "); msg.append(text); msg += '\n'; this->send_raw(msg); } // send a "priv" private message to the server void NetConnection::send_private_message(std::string const &text) { std::string msg("priv "); msg.append(text); msg += '\n'; this->send_raw(msg); } // send a ping reply // ping timestamp void NetConnection::send_ping_reply(unsigned long timestamp) { std::ostringstream msg; msg << "ping " << timestamp << '\n'; this->send_raw(msg.str()); } // send an info record request // inf type.label info.label void NetConnection::send_info_request(Info *info) { std::ostringstream msg; if (info->id()) { msg << "inf " << info->id() << "\n"; } else { if (!info->type()) return; msg << "inf " << info->id() << " \"" << info->type()->label() << "\" \"" << info->label() << "\"\n"; } this->send_raw(msg.str()); info->set_timestamp(timestamp()); } // parse incoming client messages /** * The following incoming messages are parsed; * * connect * disconnect * msg info * msg public * msg private * msg rcon * msg snd * die * ent * frame * sup * pif * pid * inf