diff --git a/src/client/abstractclient.cpp b/src/client/abstractclient.cpp index 7d9baa6..6135749 100644 --- a/src/client/abstractclient.cpp +++ b/src/client/abstractclient.cpp @@ -24,6 +24,7 @@ */ #include +#include #include "abstractclient.h" @@ -70,8 +71,10 @@ using namespace NetMauMau::Client; AbstractClient::AbstractClient(const std::string &pName, const unsigned char *data, std::size_t len, const std::string &server, uint16_t port) : IPlayerPicListener(), - m_connection(pName, server, port), m_pName(pName), m_pngData(data), m_pngDataLen(len), - m_cards(), m_openCard(0L), m_disconnectNow(false) {} + m_connection(pName, server, port), m_pName(pName), m_pngData(new unsigned char[len]()), + m_pngDataLen(len), m_cards(), m_openCard(0L), m_disconnectNow(false) { + std::memcpy(m_pngData, data, len); +} AbstractClient::AbstractClient(const std::string &pName, const std::string &server, uint16_t port) : IPlayerPicListener(), m_connection(pName, server, port), m_pName(pName), m_pngData(0L), @@ -82,6 +85,7 @@ AbstractClient::~AbstractClient() { for(CARDS::const_iterator i(m_cards.begin()); i != m_cards.end(); ++i) delete *i; delete m_openCard; + delete [] m_pngData; m_connection.setInterrupted(false); } @@ -127,7 +131,11 @@ throw(NetMauMau::Common::Exception::SocketException) { void AbstractClient::play(timeval *timeout) throw(NetMauMau::Common::Exception::SocketException) { m_connection.setTimeout(timeout); - m_connection.connect(m_pngData, m_pngDataLen); + m_connection.connect(this, m_pngData, m_pngDataLen); + + delete [] m_pngData; + m_pngDataLen = 0; + m_pngData = 0L; const NetMauMau::Common::ICard *lastPlayedCard = 0L; bool initCardShown = false; @@ -193,7 +201,6 @@ void AbstractClient::play(timeval *timeout) throw(NetMauMau::Common::Exception:: &plPicPng(NetMauMau::Common::base64_decode(plPic)); endReceivePlayerPicture(msg); - playerJoined(msg, plPic == "-" ? 0L : plPicPng.data(), plPic == "-" ? 0 : plPicPng.size()); @@ -412,7 +419,8 @@ uint16_t AbstractClient::getDefaultPort() { } void AbstractClient::beginReceivePlayerPicture(const std::string &) const throw() {} - void AbstractClient::endReceivePlayerPicture(const std::string &) const throw() {} +void AbstractClient::uploadSucceded(const std::string &) const throw() {} +void AbstractClient::uploadFailed(const std::string &) const throw() {} // kate: indent-mode cstyle; indent-width 4; replace-tabs off; tab-width 4; diff --git a/src/client/clientconnection.cpp b/src/client/clientconnection.cpp index 8491646..712c70f 100644 --- a/src/client/clientconnection.cpp +++ b/src/client/clientconnection.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #ifdef HAVE_UNISTD_H @@ -101,7 +102,7 @@ throw(NetMauMau::Common::Exception::SocketException) { } #pragma GCC diagnostic pop -Connection::PLAYERINFOS Connection::playerList(const IPlayerPicListener *hdl, bool playerPNG) +Connection::PLAYERINFOS Connection::playerList(const IPlayerPicListener *hdl, bool playerPNG) throw(NetMauMau::Common::Exception::SocketException) { PLAYERINFOS plv; @@ -191,7 +192,7 @@ throw(NetMauMau::Common::Exception::SocketException) { return caps; } -void Connection::connect(const unsigned char *data, std::size_t len) +void Connection::connect(const IPlayerPicListener *l, const unsigned char *data, std::size_t len) throw(NetMauMau::Common::Exception::SocketException) { uint16_t maj = 0, min = 0; @@ -217,11 +218,28 @@ throw(NetMauMau::Common::Exception::SocketException) { if(!(data && len)) { send(m_pName.c_str(), m_pName.length(), getSocketFD()); } else { - const std::string &picPName("+" + m_pName); - const std::string &base64png(NetMauMau::Common::base64_encode(data, len)); + try { + const std::string &base64png(NetMauMau::Common::base64_encode(data, len)); - send(picPName.c_str(), picPName.length(), getSocketFD()); - send(base64png.c_str(), base64png.length(), getSocketFD()); + std::ostringstream osp; + osp << "+" << m_pName << '\0' << base64png.length() << '\0' << base64png; + + send(osp.str().c_str(), osp.str().length(), getSocketFD()); + + char ack[1024] = "0"; + recv(ack, 1023, getSocketFD()); + + if(std::strtoul(ack, NULL, 10) == base64png.length()) { + send("OK", 2, getSocketFD()); + l->uploadSucceded(m_pName); + } else { + send("NO", 2, getSocketFD()); + l->uploadFailed(m_pName); + } + + } catch(const NetMauMau::Common::Exception::SocketException &) { + l->uploadFailed(m_pName); + } } } else { throw Exception::ProtocolErrorException("Protocol error", getSocketFD()); diff --git a/src/common/abstractsocket.cpp b/src/common/abstractsocket.cpp index 1521f81..83752f2 100644 --- a/src/common/abstractsocket.cpp +++ b/src/common/abstractsocket.cpp @@ -131,11 +131,7 @@ void AbstractSocket::connect() throw(Exception::SocketException) { std::size_t AbstractSocket::recv(void *buf, std::size_t len, int fd) throw(Exception::SocketException) { - char rbuf[std::min(len, 1024)]; - char *bufP = static_cast(buf); std::size_t total = 0; - ssize_t rlen = -1; - fd_set rfds; int sret; @@ -156,16 +152,23 @@ std::size_t AbstractSocket::recv(void *buf, std::size_t len, } - while(total < len && (rlen = ::recv(fd, rbuf, std::min(len, 1024), 0)) > 0) { - total += rlen; - std::memcpy(bufP, rbuf, rlen); - bufP += rlen; + char *ptr = static_cast(buf); + + while(len > 0) { + + ssize_t i = ::recv(fd, ptr, len, 0); + + if(i < 0) throw Exception::SocketException(std::strerror(errno), fd, errno); - if(rlen <= static_cast(len)) break; + ptr += i; + + if(i < static_cast(len)) break; + + len -= i; } - } - if(rlen == -1) throw Exception::SocketException(std::strerror(errno), fd, errno); + total = (ptr - static_cast(buf)); + } return total; } @@ -174,28 +177,31 @@ std::size_t AbstractSocket::recv(void *buf, std::size_t len, std::string AbstractSocket::read(int fd, std::size_t len) throw(Exception::SocketException) { std::string ret; - char rbuf[len]; + char *rbuf = new char[len]; const std::size_t rlen = recv(rbuf, len, fd); ret.reserve(rlen); ret.append(rbuf, rlen); + delete [] rbuf; + return ret; } void AbstractSocket::send(const void *buf, std::size_t len, int fd) throw(Exception::SocketException) { - const char *bufP = static_cast(buf); - ssize_t slen = 0; - std::size_t total = 0; + const char *ptr = static_cast(buf); - while(total < len && (slen = ::send(fd, bufP, len, MSG_NOSIGNAL)) > 0) { - bufP += slen; - total += slen; - } + while(len > 0) { - if(slen == -1) throw Exception::SocketException(std::strerror(errno), fd, errno); + ssize_t i = ::send(fd, ptr, len, MSG_NOSIGNAL); + + if(i < 0) throw Exception::SocketException(std::strerror(errno), fd, errno); + + ptr += i; + len -= i; + } } void AbstractSocket::write(int fd, const std::string &msg) throw(Exception::SocketException) { diff --git a/src/include/abstractclient.h b/src/include/abstractclient.h index 1eecc19..4a4d7fa 100644 --- a/src/include/abstractclient.h +++ b/src/include/abstractclient.h @@ -212,7 +212,10 @@ class _EXPORT AbstractClient : protected IPlayerPicListener { /** * @brief Returns the list of currently registered player names * - * @param playerPNG @c true if the player images should get retieved + * @note The image data returned in @c NetMauMau::Client::AbstractClient::PLAYERLIST must + * be freed by the user @code delete [] x->pngData @endcode + * + * @param playerPNG @c true if the player images should get retrieved * @param timeout the time to wait for a connection, if @c NULL there will be no timeout * * @throw Common::Exception::SocketException if the connection failed @@ -228,6 +231,10 @@ class _EXPORT AbstractClient : protected IPlayerPicListener { throw(NetMauMau::Common::Exception::SocketException); /** + * @brief Returns the list of currently registered player names + * + * It does not retrieve the player images + * * @overload */ PLAYERLIST playerList(timeval *timeout = NULL) @@ -391,9 +398,6 @@ class _EXPORT AbstractClient : protected IPlayerPicListener { virtual void playerJoined(const std::string &player, const unsigned char *pngData, std::size_t len) const = 0; - virtual void beginReceivePlayerPicture(const std::string &player) const throw() _CONST; - virtual void endReceivePlayerPicture(const std::string &player) const throw() _CONST; - /** * @brief A player got rejected to join the game * @@ -532,6 +536,55 @@ class _EXPORT AbstractClient : protected IPlayerPicListener { // @} + /** + * @name Player image notifications + * + * The notifications can be overloaded if the client is interested in events + * regarding the player pictures. + * + * This functions all do nothing at default. + * + * @{ + */ + + /** + * @brief A download of a player image has started + * + * @param player the player the image is downloaded for + * + * @since 0.4 + */ + virtual void beginReceivePlayerPicture(const std::string &player) const throw() _CONST; + + /** + * @brief A download of a player image has ended + * + * @param player the player the image is downloaded for + * + * @since 0.4 + */ + virtual void endReceivePlayerPicture(const std::string &player) const throw() _CONST; + + /** + * @brief The upload of the player image has succeded + * + * @param player the player the image is uploaded for + * + * @since 0.4 + */ + virtual void uploadSucceded(const std::string &player) const throw() _CONST; + + /** + * @brief The upload of the player image has failed + * + * @param player the player the image is uploaded for + * + * @since 0.4 + */ + virtual void uploadFailed(const std::string &player) const throw() _CONST; + + /// @} + /** * @brief The server sent a message not understood by the client * @@ -542,7 +595,7 @@ class _EXPORT AbstractClient : protected IPlayerPicListener { private: Connection m_connection; const std::string m_pName; - const unsigned char *m_pngData; + unsigned char *m_pngData; std::size_t m_pngDataLen; CARDS m_cards; const Common::ICard *m_openCard; diff --git a/src/include/clientconnection.h b/src/include/clientconnection.h index a8cd5d2..2f4087d 100644 --- a/src/include/clientconnection.h +++ b/src/include/clientconnection.h @@ -43,12 +43,12 @@ class _EXPORT Connection : public Common::AbstractConnection { using AbstractConnection::connect; /** - * @brief tbw + * @brief Holds the name as well as the PNG data of the player image */ typedef struct { - std::string name; - const unsigned char *pngData; - std::size_t pngDataLen; + std::string name; ///< the player name + const unsigned char *pngData; ///< raw data of the player image, must be freed by the client + std::size_t pngDataLen; ///< length of the raw data of the player image } PLAYERINFO; /** @@ -64,7 +64,7 @@ class _EXPORT Connection : public Common::AbstractConnection { Connection(const std::string &pName, const std::string &server, uint16_t port); virtual ~Connection(); - virtual void connect(const unsigned char *pngData, + virtual void connect(const IPlayerPicListener *l, const unsigned char *pngData, std::size_t pngDataLen) throw(Common::Exception::SocketException); CAPABILITIES capabilities() throw(NetMauMau::Common::Exception::SocketException); PLAYERINFOS playerList(const IPlayerPicListener *hdl, diff --git a/src/include/iplayerpiclistener.h b/src/include/iplayerpiclistener.h index 0a87d6b..d81cffb 100644 --- a/src/include/iplayerpiclistener.h +++ b/src/include/iplayerpiclistener.h @@ -36,6 +36,9 @@ class IPlayerPicListener { virtual void beginReceivePlayerPicture(const std::string &player) const throw() = 0; virtual void endReceivePlayerPicture(const std::string &player) const throw() = 0; + virtual void uploadSucceded(const std::string &player) const throw() = 0; + virtual void uploadFailed(const std::string &player) const throw() = 0; + protected: IPlayerPicListener() {} }; diff --git a/src/server/serverconnection.cpp b/src/server/serverconnection.cpp index 159dd03..2f0c4a1 100644 --- a/src/server/serverconnection.cpp +++ b/src/server/serverconnection.cpp @@ -23,8 +23,9 @@ #include #include -#include #include +#include +#include #ifdef HAVE_UNISTD_H #include @@ -39,6 +40,8 @@ #include "base64.h" #include "logger.h" +#define MAXPICBYTES 1048576 + namespace { const std::string &AIDefaultIcon(NetMauMau::Common::base64_encode(ai_icon_data, @@ -164,24 +167,78 @@ Connection::ACCEPT_STATE Connection::accept(INFO &info, const uint32_t maxver = getServerVersion(); send(cver >= 4 ? "NAMP" : "NAME", 4, cfd); - info.name = read(cfd); - const std::string &sanName(info.name[0] == '+' ? info.name.substr(1) : - info.name); + std::string namePic; + namePic.reserve(MAXPICBYTES); + namePic = read(cfd, MAXPICBYTES); - if(!sanName.empty()) { - info.name = sanName; - } else { - refuse = true; - } + std::size_t left = namePic.length(); + + info.name = namePic.substr(0, namePic.find('\0')); if(cver >= minver && cver <= maxver && !refuse) { - std::string playerPic; + std::string playerPic, picLength; if(cver >= 4 && info.name[0] == '+') { - info.name = info.name.substr(1); - playerPic = read(cfd); + + try { + + left += info.name.length() + 1; + + info.name = info.name.substr(1); + + picLength = namePic.substr(namePic.find('\0') + 1); + picLength = picLength.substr(0, picLength.find('\0')); + + left += picLength.length() + 1; + + std::size_t pl; + (std::istringstream(picLength)) >> pl; + + std::size_t v = pl - left; + playerPic = namePic.substr(namePic.rfind('\0') + 1); + + while(v) { + playerPic.reserve(playerPic.size() + v); + playerPic.append(read(cfd, v)); + v = pl - playerPic.length(); + } + + char cc[8] = "0\0"; + + if(pl > MAXPICBYTES) { + + send(cc, 8, cfd); + recv(cc, 2, cfd); + logInfo("Player picture for \"" << info.name + << "\" rejected (too large)"); + std::string().swap(playerPic); + + } else { +#ifndef _WIN32 + std::snprintf(cc, 7, "%zu", playerPic.length()); +#else + std::snprintf(cc, 7, "%lu", (unsigned long)playerPic.length()); +#endif + send(cc, 8, cfd); + recv(cc, 2, cfd); + + if(!(cc[0] == 'O' && cc[1] == 'K')) { + logWarning("Player picture transmission for \"" + << info.name + << "\" failed: got " << playerPic.length() + << " bytes; expected " << pl << " bytes)"); + std::string().swap(playerPic); + } else { + logInfo("Player picture transmission for \"" << info.name + << "\" successful (" << playerPic.length() + << " bytes)"); + } + } + } catch(const NetMauMau::Common::Exception::SocketException &) { + std::string().swap(playerPic); + } } const NAMESOCKFD nsf = { info.name, playerPic, cfd, cver }; @@ -306,10 +363,13 @@ throw(NetMauMau::Common::Exception::SocketException) { for(VERSIONEDMESSAGE::const_iterator j(vm.begin()); j != vm.end(); ++j) { if(j->first && f->clientVersion >= j->first) { + std::string msg(j->second); - write(i->sockfd, msg.substr(j->second.length() - 9) == "VM_ADDPIC" ? - msg.replace(j->second.length() - 9, std::string::npos, - i->playerPic.empty() ? "-" : i->playerPic) : msg); + const bool wantPic = msg.substr(j->second.length() - 9) == "VM_ADDPIC"; + + write(i->sockfd, wantPic ? msg.replace(j->second.length() - 9, + std::string::npos, i->playerPic.empty() + ? "-" : i->playerPic) : msg); vMsg = true; break; }