diff --git a/.vscode/settings.json b/.vscode/settings.json index 09e11d8..81d62d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -72,7 +72,8 @@ "numeric": "cpp", "random": "cpp", "numbers": "cpp", - "cinttypes": "cpp" + "cinttypes": "cpp", + "queue": "cpp" }, "C_Cpp.vcFormat.indent.preserveComments": true } \ No newline at end of file diff --git a/Source/Core/FixedMemoryPool.hpp b/Source/Core/FixedMemoryPool.hpp index 49b293a..1b8b41a 100644 --- a/Source/Core/FixedMemoryPool.hpp +++ b/Source/Core/FixedMemoryPool.hpp @@ -35,11 +35,9 @@ class FixedMemoryPool ~FixedMemoryPool() { - // Release data that is not deallocated - for (size_t i = 0; i < mCapacity; ++i) + if (mCursor != 0) { - T* ptr = (T*)(mMemoryRaw + i * sizeof(T)); - ptr->~T(); //< Call the destructor + CoreLog("[FixedMemoryPool] Memory Leak Detected. Cursor: " + ValToString(mCursor)); } delete[] mMemoryRaw; @@ -53,7 +51,7 @@ class FixedMemoryPool const size_t idx = mIndices[mCursor++]; const char* ptrRaw = mMemoryRaw + (idx * sizeof(T)); - CoreLog("[FixedMemoryPool] Allocate: " + ValToStringByHex(ptrRaw) + " idx: " + ValToString(idx)); + CoreLog("[FixedMemoryPool] Allocate: " + ValToStringByHex(reinterpret_cast(ptrRaw)) + " idx: " + ValToString(idx)); return (T*)ptrRaw; } @@ -82,6 +80,11 @@ class FixedMemoryPool return mCursor == mCapacity; } + FORCEINLINE bool IsInPool(const void* ptr) const + { + return ptr >= mMemoryRaw && ptr < mMemoryRaw + mCapacity * sizeof(T); + } + private: enum { CAPACITY = MemoryPageCapacity * PAGE_SIZE / sizeof(T) }; //< Floor to the sizeof(T) diff --git a/Source/Core/SharedPtr.hpp b/Source/Core/SharedPtr.hpp index a4a0ace..26a6151 100644 --- a/Source/Core/SharedPtr.hpp +++ b/Source/Core/SharedPtr.hpp @@ -1,5 +1,8 @@ #pragma once +// DEBUG +#include + #include "Core/MacroDefines.hpp" #include "Core/AttributeDefines.hpp" #include "Core/VariableMemoryPool.hpp" @@ -24,7 +27,7 @@ namespace detail template struct ControlBlock { - ALIGNAS(ALIGNOF(T)) T data; + ALIGNAS(ALIGNOF(T)) char data[sizeof(T)]; size_t StrongRefCount; size_t WeakRefCount; @@ -37,7 +40,7 @@ struct ControlBlock { } - virtual ~ControlBlock() + FORCEINLINE ~ControlBlock() { } @@ -47,28 +50,40 @@ struct ControlBlock * Overload new and delete operator with memory pool for memory management. */ ///@{ - NODISCARD FORCEINLINE void* operator new (size_t size) - { - Assert(size == sizeof(ControlBlock)); - return reinterpret_cast(sMemoryPool.Allocate()); - } - - NODISCARD FORCEINLINE void* operator new (size_t size, void* ptr) - { - Assert(size == sizeof(ControlBlock)); - return ptr; - } + // NODISCARD FORCEINLINE void* operator new (size_t size) + // { + // Assert(size == sizeof(ControlBlock)); + // return reinterpret_cast(sMemoryPool.Allocate()); + // } + + // NODISCARD FORCEINLINE void* operator new (size_t size, void* ptr) + // { + // Assert(size == sizeof(ControlBlock)); + // return ptr; + // } - FORCEINLINE void operator delete (void* ptr) - { - sMemoryPool.Deallocate(reinterpret_cast(ptr)); - } + // FORCEINLINE void operator delete (void* ptr) + // { + // sMemoryPool.Deallocate(reinterpret_cast(ptr)); + // } + + // FORCEINLINE void operator delete (void* ptr, void* place) + // { + // Assert(false); + // (void)place; + // (void)ptr; + // } + ///@} - FORCEINLINE void operator delete (void* ptr, void* place) - { - (void)place; - (void)ptr; - } +private: + /** @name new[]/delete[] operators + * + * Use new/delete operator instead. + */ + ///@{ + UNUSED NORETURN FORCEINLINE void* operator new[] (size_t size); + + UNUSED NORETURN FORCEINLINE void operator delete[] (void* ptr); ///@} private: @@ -234,7 +249,6 @@ class SharedPtr : mControlBlock(controlBlock) { Assert(mControlBlock != NULL); - Assert(mControlBlock->bExpired == false); if (mControlBlock != NULL) { @@ -312,12 +326,10 @@ class SharedPtr { if (mControlBlock != NULL) { - CoreLog("SharedPtr Reset(): StrongRefCount: " + ValToString(mControlBlock->StrongRefCount) + " WeakRefCount: " + ValToString(mControlBlock->WeakRefCount)); Assert(mControlBlock->StrongRefCount > 0); mControlBlock->StrongRefCount -= 1; if (mControlBlock->StrongRefCount == 0) { - CoreLog("SharedPtr Reset(): Call Destructor"); T* ptrData = reinterpret_cast(&mControlBlock->data); ptrData->~T(); @@ -325,7 +337,7 @@ class SharedPtr if (mControlBlock->WeakRefCount == 0) { - CoreLog("SharedPtr Reset(): Delete ControlBlock"); + CoreLog("SharedPtr Reset(): Delete ControlBlock" + std::string(typeid(T).name())); delete mControlBlock; } } diff --git a/Source/Core/WeakPtr.hpp b/Source/Core/WeakPtr.hpp index 439d4ab..25d5e6d 100644 --- a/Source/Core/WeakPtr.hpp +++ b/Source/Core/WeakPtr.hpp @@ -112,6 +112,7 @@ class WeakPtr { if (mControlBlock->StrongRefCount == 0) { + CoreLog("WeakPtr Reset(): Delete ControlBlock" + std::string(typeid(T).name())); delete mControlBlock; } } diff --git a/Source/Server/ClientCommand/ClientCommand.hpp b/Source/Server/ClientCommand/ClientCommand.hpp index 6cb5a69..ee820d3 100644 --- a/Source/Server/ClientCommand/ClientCommand.hpp +++ b/Source/Server/ClientCommand/ClientCommand.hpp @@ -5,7 +5,12 @@ namespace IRC } // namespace irc +/** + * @def IRC_CLIENT_COMMAND_X + * @brief Macro for defining a client command. + */ #define IRC_CLIENT_COMMAND_LIST \ IRC_CLIENT_COMMAND_X(PASS) \ IRC_CLIENT_COMMAND_X(NICK) \ + IRC_CLIENT_COMMAND_X(USER) diff --git a/Source/Server/ClientCommand/NICK.cpp b/Source/Server/ClientCommand/NICK.cpp index 3ad1a61..3de4745 100644 --- a/Source/Server/ClientCommand/NICK.cpp +++ b/Source/Server/ClientCommand/NICK.cpp @@ -18,7 +18,7 @@ EIrcErrorCode Server::executeClientCommand_NICK(SharedPtr cl // No nickname given if (arguments.size() == 0) { - MakeIrcReplyMsg_ERR_NEEDMOREPARAMS(replyCode, replyMsg, mServerName, commandName); + MakeIrcReplyMsg_ERR_NONICKNAMEGIVEN(replyCode, replyMsg, mServerName); goto SEND_REPLY; } @@ -31,6 +31,9 @@ EIrcErrorCode Server::executeClientCommand_NICK(SharedPtr cl goto SEND_REPLY; } } + + // Empty nickname + Assert(arguments[0][0] != '\0'); // Nickname is already in use if (mNickToClientMap.find(arguments[0]) != mNickToClientMap.end()) @@ -39,12 +42,45 @@ EIrcErrorCode Server::executeClientCommand_NICK(SharedPtr cl goto SEND_REPLY; } - // Update nickname - mNickToClientMap.erase(client->Nickname); - mNickToClientMap.insert(std::make_pair(arguments[0], client)); - client->Nickname = arguments[0]; + // Try to register the client + if (!client->bRegistered) + { + client->Nickname = arguments[0]; + registerClient(client); + + return IRC_SUCCESS; + } + // Update the nickname in Server, Channels + else + { + const std::string oldNickname = client->Nickname; + const std::string newNickname = arguments[0]; + client->Nickname = newNickname; + mNickToClientMap.erase(oldNickname); + mNickToClientMap.insert(std::make_pair(newNickname, client)); + + for (std::map< std::string, WeakPtr< ChannelControlBlock > >::iterator it = client->Channels.begin(); it != client->Channels.end(); ++it) + { + SharedPtr channel = it->second.Lock(); + if (channel != NULL) + { + channel->Clients.erase(client->Nickname); + channel->Clients.insert(std::make_pair(client->Nickname, client)); + } + } + + // Send NICK message to all channels the client is in + const std::string nickMsgStr = ":" + oldNickname + " NICK " + newNickname; + SharedPtr nickMsg = MakeShared(nickMsgStr); + sendMsgToConnectedChannels(client, nickMsg); + + // Send NICK message to the origin client itself + sendMsgToClient(client, nickMsg); + + return IRC_SUCCESS; + } + - // Send NICK message to all channels the client is in SEND_REPLY: sendMsgToClient(client, MakeShared(replyMsg)); diff --git a/Source/Server/ClientCommand/PASS.cpp b/Source/Server/ClientCommand/PASS.cpp index 1fa04a2..79e28ae 100644 --- a/Source/Server/ClientCommand/PASS.cpp +++ b/Source/Server/ClientCommand/PASS.cpp @@ -5,16 +5,36 @@ namespace IRC EIrcErrorCode Server::executeClientCommand_PASS(SharedPtr client, const std::vector& arguments) { + const std::string commandName("PASS"); + EIrcReplyCode replyCode; + std::string replyMsg; + if (client->bExpired) { - return IRC_SUCCESS; } - // TODO: Implement + // Already registered + if (client->bRegistered) + { + MakeIrcReplyMsg_ERR_ALREADYREGISTRED(replyCode, replyMsg, mServerName); + sendMsgToClient(client, MakeShared(replyMsg)); + } + // Need more parameters + else if (arguments.size() == 0) + { + MakeIrcReplyMsg_ERR_NEEDMOREPARAMS(replyCode, replyMsg, mServerName, commandName); + sendMsgToClient(client, MakeShared(replyMsg)); + } + else + { + client->ServerPass = arguments[0]; - return IRC_SUCCESS; + // Try to register the client + registerClient(client); + } + return IRC_SUCCESS; } } diff --git a/Source/Server/ClientCommand/USER.cpp b/Source/Server/ClientCommand/USER.cpp new file mode 100644 index 0000000..f4000a3 --- /dev/null +++ b/Source/Server/ClientCommand/USER.cpp @@ -0,0 +1,67 @@ +#include +#include "Server/Server.hpp" + +namespace IRC +{ + +EIrcErrorCode Server::executeClientCommand_USER(SharedPtr client, const std::vector& arguments) +{ + const std::string commandName("USER"); + EIrcReplyCode replyCode; + std::string replyMsg; + + if (client->bExpired) + { + return IRC_SUCCESS; + } + + // Already registered + if (client->bRegistered) + { + MakeIrcReplyMsg_ERR_ALREADYREGISTRED(replyCode, replyMsg, mServerName); + goto SEND_REPLY; + } + + // Need more parameters + if (arguments.size() < 4) + { + MakeIrcReplyMsg_ERR_NEEDMOREPARAMS(replyCode, replyMsg, mServerName, commandName); + goto SEND_REPLY; + } + + // Validate names + for (size_t i = 0; i < 4; i++) + { + for (const char* p = arguments[i]; *p != '\0'; p++) + { + if (isalnum(*p) == 0 && *p != '_') + { + replyMsg = "ERROR :Invalid USER arguments"; + sendMsgToClient(client, MakeShared(replyMsg)); + disconnectClient(client); + + return IRC_SUCCESS; + } + } + + // Empty name + Assert(arguments[i][0] != '\0'); + } + + // Set user information + // (Hostname and Servername are ignored) + client->Username = arguments[0]; + client->Realname = arguments[3]; + + // Register the client + registerClient(client); + return IRC_SUCCESS; + + +SEND_REPLY: + sendMsgToClient(client, MakeShared(replyMsg)); + + return IRC_SUCCESS; +} + +} diff --git a/Source/Server/ClientControlBlock.hpp b/Source/Server/ClientControlBlock.hpp index 70f77a6..036c502 100644 --- a/Source/Server/ClientControlBlock.hpp +++ b/Source/Server/ClientControlBlock.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "Core/VariableMemoryPool.hpp" @@ -27,9 +28,10 @@ struct ClientControlBlock sockaddr_in_t Addr; std::string Nickname; - std::string Username; std::string Realname; - std::string Hostname; + std::string Username; + + std::string ServerPass; time_t LastActiveTime; @@ -57,7 +59,7 @@ struct ClientControlBlock * * @note Do not modify the message block in the queue. **/ - std::vector< SharedPtr< MsgBlock > > MsgSendingQueue; + std::queue< SharedPtr< MsgBlock > > MsgSendingQueue; /** A cursor to indicate the next offset to send in the message block at the front of the MsgSendingQueue */ size_t SendMsgBlockCursor; @@ -69,9 +71,9 @@ struct ClientControlBlock : hSocket(-1) , Addr() , Nickname() - , Username() , Realname() - , Hostname() + , Username() + , ServerPass() , LastActiveTime(0) , bRegistered(false) , bExpired(false) diff --git a/Source/Server/IrcReplies.hpp b/Source/Server/IrcReplies.hpp index bfb6965..f015f11 100644 --- a/Source/Server/IrcReplies.hpp +++ b/Source/Server/IrcReplies.hpp @@ -15,19 +15,23 @@ namespace IRC * @def IRC_REPLY_TUPLE_LIST * @brief Tuple of the IRC replies | IRC_REPLY_X (reply_code, reply_number, (arguments), (reply_string)) */ -#define IRC_REPLY_TUPLE_LIST \ - IRC_REPLY_X(ERR_NOSUCHNICK , 401, (PARM_X, const std::string nickname) , (nickname + " :No such nick/channel")) \ - IRC_REPLY_X(ERR_NOSUCHSERVER , 402, (PARM_X, const std::string server_name) , (server_name + " :No such server")) \ - IRC_REPLY_X(ERR_NOSUCHCHANNEL , 403, (PARM_X, const std::string channel_name), (channel_name + " :No such channel")) \ - IRC_REPLY_X(ERR_CANNOTSENDTOCHAN, 404, (PARM_X, const std::string channel_name), (channel_name + " :Cannot send to channel")) \ - IRC_REPLY_X(ERR_TOOMANYCHANNELS , 405, (PARM_X, const std::string channel_name), (channel_name + " :You have joined too many channels")) \ - IRC_REPLY_X(ERR_WASNOSUCHNICK , 406, (PARM_X, const std::string nickname) , (nickname + " :There was no such nickname")) \ - IRC_REPLY_X(ERR_TOOMANYTARGETS , 407, (PARM_X, const std::string target) , (target + " :Duplicate recipients. No message delivered")) \ - IRC_REPLY_X(ERR_UNKNOWNCOMMAND , 421, (PARM_X, const std::string command) , (command + " :Unknown command")) \ - IRC_REPLY_X(ERR_NICKNAMEINUSE , 433, (PARM_X, const std::string nickname) , (nickname + " :Nickname is already in use")) \ - IRC_REPLY_X(ERR_ERRONEUSNICKNAME , 432, (PARM_X, const std::string nickname) , (nickname + " :Erroneous nickname")) \ - IRC_REPLY_X(ERR_NEEDMOREPARAMS , 461, (PARM_X, const std::string command) , (command + " :Not enough parameters")) \ - IRC_REPLY_X(ERR_ALREADYREGISTRED, 462, (PARM_X) , (":You may not reregister")) \ +#define IRC_REPLY_TUPLE_LIST \ + IRC_REPLY_X(ERR_NOSUCHNICK , 401, (PARM_X, const std::string nickname) , (nickname + " :No such nick/channel")) \ + IRC_REPLY_X(ERR_NOSUCHSERVER , 402, (PARM_X, const std::string server_name) , (server_name + " :No such server")) \ + IRC_REPLY_X(ERR_NOSUCHCHANNEL , 403, (PARM_X, const std::string channel_name), (channel_name + " :No such channel")) \ + IRC_REPLY_X(ERR_CANNOTSENDTOCHAN, 404, (PARM_X, const std::string channel_name), (channel_name + " :Cannot send to channel")) \ + IRC_REPLY_X(ERR_TOOMANYCHANNELS , 405, (PARM_X, const std::string channel_name), (channel_name + " :You have joined too many channels")) \ + IRC_REPLY_X(ERR_WASNOSUCHNICK , 406, (PARM_X, const std::string nickname) , (nickname + " :There was no such nickname")) \ + IRC_REPLY_X(ERR_TOOMANYTARGETS , 407, (PARM_X, const std::string target) , (target + " :Duplicate recipients. No message delivered")) \ + IRC_REPLY_X(ERR_UNKNOWNCOMMAND , 421, (PARM_X, const std::string command) , (command + " :Unknown command")) \ + IRC_REPLY_X(ERR_NICKNAMEINUSE , 433, (PARM_X, const std::string nickname) , (nickname + " :Nickname is already in use")) \ + IRC_REPLY_X(ERR_ERRONEUSNICKNAME, 432, (PARM_X, const std::string nickname) , (nickname + " :Erroneous nickname")) \ + IRC_REPLY_X(ERR_NEEDMOREPARAMS , 461, (PARM_X, const std::string command) , (command + " :Not enough parameters")) \ + IRC_REPLY_X(ERR_ALREADYREGISTRED, 462, (PARM_X) , (":You may not reregister")) \ + IRC_REPLY_X(ERR_NONICKNAMEGIVEN , 431, (PARM_X) , (":No nickname given")) \ + IRC_REPLY_X(RPL_WELCOME , 001, (PARM_X, const std::string nickname) , (":Welcome to the " + serverName + " IRC Network " + nickname + "!")) \ + + // --------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Source/Server/Server.cpp b/Source/Server/Server.cpp index 7374943..71b505b 100644 --- a/Source/Server/Server.cpp +++ b/Source/Server/Server.cpp @@ -175,7 +175,7 @@ EIrcErrorCode Server::eventLoop() kevent_t& currEvent = observedEvents[eventIdx]; // 1. Error event - if (UNLIKELY(currEvent.flags & EV_ERROR)) + if (UNLIKELY(currEvent.flags & EV_ERROR || currEvent.flags & EV_EOF)) { // Listen socket error if (UNLIKELY(static_cast(currEvent.ident) == mhListenSocket)) @@ -195,7 +195,7 @@ EIrcErrorCode Server::eventLoop() return err; } } - } + } // 2. Read event else if (currEvent.filter == EVFILT_READ) @@ -236,7 +236,7 @@ EIrcErrorCode Server::eventLoop() kevent_t evClient; std::memset(&evClient, 0, sizeof(evClient)); evClient.ident = clientSocket; - evClient.filter = EVFILT_READ | EVFILT_WRITE; + evClient.filter = EVFILT_READ; evClient.flags = EV_ADD; evClient.udata = reinterpret_cast(newClient.GetControlBlock()); mEventRegistrationQueue.push_back(evClient); @@ -297,21 +297,69 @@ EIrcErrorCode Server::eventLoop() clientsWithRecvMsgToProcessQueue.push_back(currClient); } - } + } // if (currEvent.filter == EVFILT_READ) // 3. Write event // Send messages to the client else if (currEvent.filter == EVFILT_WRITE) { // TODO: Can a listen socket raise a write event? I'll check this later. - Assert(static_cast(currEvent.ident) == mhListenSocket); + Assert(static_cast(currEvent.ident) != mhListenSocket); // Send message to the client + SharedPtr currClient = getClientFromKeventUdata(currEvent); + Assert(currClient != NULL); + Assert(currClient->hSocket == static_cast(currEvent.ident)); + + // Send the messages in the sending queue + if (currClient->MsgSendingQueue.empty()) + { + goto CONTINUE_NEXT_EVENT_LOOP; + } + SharedPtr msg = currClient->MsgSendingQueue.front(); + Assert(msg != NULL); + + const int nSentBytes = send(currClient->hSocket, &msg->Msg[currClient->SendMsgBlockCursor], msg->MsgLen - currClient->SendMsgBlockCursor, 0); + if (UNLIKELY(nSentBytes == -1)) + { + logErrorCode(IRC_FAILED_TO_SEND_SOCKET); + EIrcErrorCode err = disconnectClient(currClient); + if (UNLIKELY(err != IRC_SUCCESS)) + { + return err; + } + goto CONTINUE_NEXT_EVENT_LOOP; + } + // Maybe the client's receive window is full. + if (nSentBytes == 0) { - // TODO: + goto CONTINUE_NEXT_EVENT_LOOP; + } + + logVerbose("Sent message to client. IP: " + InetAddrToString(currClient->Addr) + ", Nick: " + currClient->Nickname + ", Sent bytes: " + ValToString(nSentBytes)); + + // Update send cursor of the client + currClient->SendMsgBlockCursor += nSentBytes; + if (currClient->SendMsgBlockCursor >= msg->MsgLen) + { + currClient->MsgSendingQueue.pop(); + currClient->SendMsgBlockCursor = 0; + + // EVFILTER_WRITE filter should be disabled after sending all messages. + if (currClient->MsgSendingQueue.empty()) + { + kevent_t kev; + kev.ident = currClient->hSocket; + kev.filter = EVFILT_WRITE; + kev.flags = EV_DELETE; + kev.fflags = 0; + kev.data = 0; + kev.udata = reinterpret_cast(currClient.GetControlBlock()); + + mEventRegistrationQueue.push_back(kev); + } } - // TODO: If all messages are sent, EVFILTER_WRITE filter should be disabled. } CONTINUE_NEXT_EVENT_LOOP:; @@ -477,6 +525,12 @@ EIrcErrorCode Server::processClientMsg(SharedPtr client, Sha { msgArgTokens.push_back(&msg->Msg[i]); } + + // skip remaining characters of the token + for (; i < msg->MsgLen && msg->Msg[i] != ' ' && msg->Msg[i] != '\0'; i++) + { + } + i -= 1; } } msg->Msg[msg->MsgLen] = '\0'; @@ -515,31 +569,29 @@ EIrcErrorCode Server::processClientMsg(SharedPtr client, Sha } } - // Execute the command EIrcReplyCode replyCode; std::string replyMsg; + + // Unknown command name + if (pCommandExecFunc == NULL) { - // Unknown command name - if (pCommandExecFunc == NULL) + MakeIrcReplyMsg_ERR_UNKNOWNCOMMAND(replyCode, replyMsg, mServerName, msgCommandToken); + sendMsgToClient(client, MakeShared(replyMsg)); + } + // Execute the command + else + { + // DEBUG + logVerbose("processClientMsg(): Executing the command. IP: " + InetAddrToString(client->Addr) + ", Nick: " + client->Nickname + ", Command: " + msgCommandToken); + for (size_t i = 0; i < msgArgTokens.size(); i++) { - MakeIrcReplyMsg_ERR_UNKNOWNCOMMAND(replyCode, replyMsg, mServerName, msgCommandToken); - - // TODO: Send the reply message to the client + logVerbose("Args[" + ValToString(i) + "]: " + msgArgTokens[i]); } - else - { - // DEBUG - logVerbose("processClientMsg(): Executing the command. IP: " + InetAddrToString(client->Addr) + ", Nick: " + client->Nickname + ", Command: " + msgCommandToken); - for (size_t i = 0; i < msgArgTokens.size(); i++) - { - logVerbose("Args[" + ValToString(i) + "]: " + msgArgTokens[i]); - } - EIrcErrorCode err = (this->*pCommandExecFunc)(client, msgArgTokens); - if (UNLIKELY(err != IRC_SUCCESS)) - { - return err; - } + EIrcErrorCode err = (this->*pCommandExecFunc)(client, msgArgTokens); + if (UNLIKELY(err != IRC_SUCCESS)) + { + return err; } } @@ -548,22 +600,82 @@ EIrcErrorCode Server::processClientMsg(SharedPtr client, Sha EIrcErrorCode Server::disconnectClient(SharedPtr client) { + if (client == NULL) + { + return IRC_SUCCESS; + } + Assert(client->bExpired == false); - // close() on a socket will delete the corresponding kevent from the kqueue. + // Expire the client instead of memory release and remove from the client list. + // See ClientControlBlock::bExpired for details. + client->bExpired = true; + + // Remove the client from the client list + for (size_t i = 0; i < mClients.size(); i++) + { + if (mClients[i] == client) + { + // Fast remove (unordered) + mClients[i] = mClients.back(); + mClients.pop_back(); + break; + } + } + mNickToClientMap.erase(client->Nickname); + + // // Remove from the channels + for (std::map< std::string, WeakPtr< ChannelControlBlock > >::iterator it = client->Channels.begin(); it != client->Channels.end(); ++it) + { + SharedPtr channel = it->second.Lock(); + if (channel != NULL) + { + channel->Clients.erase(client->Nickname); + } + } + + // TODO: Send QUIT message to the channels the client is in. + + // // close() on a socket will delete the corresponding kevent from the kqueue. if (UNLIKELY(close(client->hSocket) == -1)) { logErrorCode(IRC_FAILED_TO_CLOSE_SOCKET); return IRC_FAILED_TO_CLOSE_SOCKET; } - // Expire the client instead of memory release and remove from the client list. - // See ClientControlBlock::bExpired for details. - client->bExpired = true; + return IRC_SUCCESS; +} - // TODO: Remove client from the channels +bool Server::registerClient(SharedPtr client) +{ + if (client == NULL) + { + return false; + } - return IRC_SUCCESS; + if (client->Username.empty() || client->Realname.empty() || client->Nickname.empty()) + { + return false; + } + + if (client->ServerPass != mServerPassword) + { + return false; + } + + client->bRegistered = true; + client->Nickname = client->Nickname; + mNickToClientMap.insert(std::make_pair(client->Nickname, client)); + + // Send the welcome message + { + EIrcReplyCode replyCode; + std::string replyMsg; + MakeIrcReplyMsg_RPL_WELCOME(replyCode, replyMsg, mServerName, client->Nickname); + sendMsgToClient(client, MakeShared(replyMsg)); + } + + return true; } void Server::sendMsgToClient(SharedPtr client, SharedPtr msg) @@ -571,11 +683,19 @@ void Server::sendMsgToClient(SharedPtr client, SharedPtrMsgLen < MESSAGE_LEN_MAX - 2); + if (client->bExpired) { return; } + // Insert CR-LF + msg->Msg[msg->MsgLen] = '\r'; + msg->Msg[msg->MsgLen + 1] = '\n'; + msg->MsgLen += 2; + // Enable the write event filter for the client socket. if (client->MsgSendingQueue.empty()) { @@ -586,21 +706,45 @@ void Server::sendMsgToClient(SharedPtr client, SharedPtr(client.GetControlBlock()); + mEventRegistrationQueue.push_back(kev); client->SendMsgBlockCursor = 0; } - client->MsgSendingQueue.push_back(msg); + client->MsgSendingQueue.push(msg); } -void Server::sendMsgToChannel(SharedPtr channel, SharedPtr msg) +void Server::sendMsgToChannel(SharedPtr channel, SharedPtr msg, SharedPtr exceptClient) { - for (std::map< std::string, SharedPtr< ClientControlBlock > >::iterator it = channel->Clients.begin(); it != channel->Clients.end(); it++) + Assert(channel != NULL); + Assert(msg != NULL); + + for (std::map< std::string, SharedPtr< ClientControlBlock > >::iterator it = channel->Clients.begin(); it != channel->Clients.end(); ++it) + { + SharedPtr dest = it->second; + if (dest != NULL && dest != exceptClient) + { + sendMsgToClient(dest, msg); + } + } +} + +void Server::sendMsgToConnectedChannels(SharedPtr client, SharedPtr msg) +{ + Assert(client != NULL); + Assert(msg != NULL); + + if (client->bExpired) + { + return; + } + + for (std::map< std::string, WeakPtr< ChannelControlBlock > >::iterator it = client->Channels.begin(); it != client->Channels.end(); ++it) { - SharedPtr client = it->second; - if (client != NULL) + SharedPtr channel = it->second.Lock(); + if (channel != NULL) { - sendMsgToClient(client, msg); + sendMsgToChannel(channel, msg, client); } } } diff --git a/Source/Server/Server.hpp b/Source/Server/Server.hpp index ddd7b5a..16436b1 100644 --- a/Source/Server/Server.hpp +++ b/Source/Server/Server.hpp @@ -195,31 +195,51 @@ namespace IRC ///@} private: - /** Disconnect the client. + /** Disconnect a client. * * Close the socket and mark the client's expired flag instead of memory release and remove from the client list. * Use Assert to debug if there is a place that references the client while the expired flag is true. */ EIrcErrorCode disconnectClient(SharedPtr client); - /** Send a message to the client. + /** Register a client to the server. + * + * @param client The client to register. + * @return Result of the registration. + */ + bool registerClient(SharedPtr client); + + /** + * @name Message send functions + * @note \li Do not modify the passed message after calling this function. + * \li No changes are made to the message other than adding CR-LF. + * \li No check permission of the client to send the message. + */ + ///@{ + /** Send a message to client. * * @param client The client to send the message. - * @param msg The message to send without CR-LF. - * - * @note Do not modify the passed message after calling this function. + * @param msg The message to send that do not contain CR-LF */ void sendMsgToClient(SharedPtr client, SharedPtr msg); - /** Send a message to the channel members. + /** Send a message to channel members. * * @param channel The channel to send the message. - * @param msg The message to send without CR-LF. - * - * @note Do not modify the passed message after calling this function. + * @param msg The message to send that do not contain CR-LF + * @param client A client to exclude from the message sending. */ - void sendMsgToChannel(SharedPtr channel, SharedPtr msg); + void sendMsgToChannel(SharedPtr channel, SharedPtr msg, SharedPtr exceptClient); + /** Send a message to channels the client is connected + * + * @param client The client to send the message. + * This client is excluded from the message sending. + * @param msg The message to send that do not contain CR-LF + */ + void sendMsgToConnectedChannels(SharedPtr client, SharedPtr msg); + ///@} + private: /** @name Log functions */ ///@{