diff --git a/README.md b/README.md index cb507eb..e0aa557 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,10 @@ You can take a look at the `Client` API documentation on [DUB](https://birchwood ## Compatibility -- [ ] rfc1459 - * Should be more or less stable in supporting this standard +- [x] [rfc1459](https://www.rfc-editor.org/rfc/rfc1459) + * Supports all the numeric codes +- [x] [rfc2812](https://www.rfc-editor.org/rfc/rfc2812) + * Supports all the numeric codes More standards will be added within the next month or so, mostly relating to new response codes that just need to be added. diff --git a/source/birchwood/client/client.d b/source/birchwood/client/client.d index 445d225..5c79113 100644 --- a/source/birchwood/client/client.d +++ b/source/birchwood/client/client.d @@ -71,42 +71,103 @@ public class Client : Thread //TODO: Do something here, tare downs } + // TODO: Investigate public ConnectionInfo getConnInfo() { return connInfo; } - - /** - * User overridable handler functions below - */ + /** + * Called on reception of a channel message + * + * Params: + * fullMessage = the channel message in its entirety + * channel = the channel + * msgBody = the body of the message + */ public void onChannelMessage(Message fullMessage, string channel, string msgBody) { /* Default implementation */ logger.log("Channel("~channel~"): "~msgBody); } + + /** + * Called on reception of a direct message + * + * Params: + * fullMessage = the direct message in its entirety + * nickname = the sender + * msgBody = the body of the message + */ public void onDirectMessage(Message fullMessage, string nickname, string msgBody) { /* Default implementation */ logger.log("DirectMessage("~nickname~"): "~msgBody); } + + /** + * Called on generic commands + * + * Params: + * commandReply = the generic message + */ public void onGenericCommand(Message message) { /* Default implementation */ logger.log("Generic("~message.getCommand()~", "~message.getFrom()~"): "~message.getParams()); } + + + // TODO: Hook certain ones default style with an implemenation + // ... for things that the client can learn from + // TODO: comment + /** + * Called on command replies + * + * Params: + * commandReply = the command's reply + */ public void onCommandReply(Message commandReply) { + // TODO: Add numeric response check here for CERTAIN ones which add to client + // ... state + /* Default implementation */ logger.log("Response("~to!(string)(commandReply.getReplyType())~", "~commandReply.getFrom()~"): "~commandReply.toString()); + + import birchwood.protocol.constants : ReplyType; + + if(commandReply.getReplyType() == ReplyType.RPL_BOUNCE) + { + // TODO: Testing code was here + // logger.log(); + // logger.log("<<<>>>"); + + // logger.log("Take a look:\n\n"~commandReply.getParams()); + + // logger.log("And here is key-value pairs: ", commandReply.getKVPairs()); + // logger.log("And here is array: ", commandReply.getPairs()); + + // // TODO: DLog bug, this prints nothing + // logger.log("And here is trailing: ", commandReply.getTrailing()); + + // import std.stdio; + // writeln("Trailer: "~commandReply.getTrailing()); + + // writeln(cast(ubyte[])commandReply.getTrailing()); + + // logger.log("<<<>>>"); + // logger.log(); + + + + } } /** * User operations (request-response type) */ - // TODO: Add joinChannels(strung[]) - /** * Joins the requested channel * @@ -138,6 +199,75 @@ public class Client : Thread } } + /** + * Joins the requested channels + * + * Params: + * channels = the channels to join + * Throws: + * BirchwoodException on invalid channel name + */ + public void joinChannel(string[] channels) + { + /* If single channel */ + if(channels.length == 1) + { + /* Join the channel */ + joinChannel(channels[0]); + } + /* If multiple channels */ + else if(channels.length > 1) + { + string channelLine = channels[0]; + + /* Ensure valid characters in first channel */ + if(isValidText(channelLine)) + { + //TODO: Add check for # + + /* Append on a trailing `,` */ + channelLine ~= ","; + + for(ulong i = 1; i < channels.length; i++) + { + string currentChannel = channels[i]; + + /* Ensure the character channel is valid */ + if(isValidText(currentChannel)) + { + //TODO: Add check for # + + if(i == channels.length-1) + { + channelLine~=currentChannel; + } + else + { + channelLine~=currentChannel~","; + } + } + else + { + throw new BirchwoodException(BirchwoodException.ErrorType.ILLEGAL_CHARACTERS); + } + } + + /* Join multiple channels */ + Message joinMessage = new Message("", "JOIN", channelLine); + sendMessage(joinMessage); + } + else + { + throw new BirchwoodException(BirchwoodException.ErrorType.ILLEGAL_CHARACTERS); + } + } + /* If no channels provided at all (error) */ + else + { + throw new BirchwoodException(BirchwoodException.ErrorType.EMPTY_PARAMS); + } + } + /** * Parts from a list of channel(s) in one go * @@ -500,6 +630,14 @@ public class Client : Thread if(cmp(command, "PRIVMSG") == 0) { + // TODO: We will need a non kv pair thing as well to see (in the + // ... case of channel messages) the singular pair + // ... name. + // + // Then our message will be in `getTrailing()` + logger.debug_("PrivMessage parser (kv-pairs): ", ircMessage.getKVPairs()); + logger.debug_("PrivMessage parser (trailing): ", ircMessage.getTrailing()); + /* Split up into (channel/nick) and (message)*/ long firstSpaceIdx = indexOf(params, " "); //TODO: validity check; string chanNick = params[0..firstSpaceIdx]; @@ -524,6 +662,9 @@ public class Client : Thread // If the command is numeric then it is a reply of some sorts else if(ircMessage.isResponseMessage()) { + // TODO: Add numeric response check here for CERTAIN ones which add to client + // ... state + /* Call the command reply handler */ onCommandReply(ircMessage); } @@ -632,25 +773,6 @@ public class Client : Thread receiver.rq(message); } - // /** - // * Sends a message to the server by enqueuing it on - // * the client-side send queue - // * - // * Params: - // * messageOut = the message to send - // */ - // private void sendMessage(string messageOut) - // { - // // TODO: Do message splits here - - - // /* Encode the mesage */ - // ubyte[] encodedMessage = encodeMessage(messageOut); - - // /* Enqueue the message to the send queue */ - // sender.sq(encodedMessage); - // } - /** * Sends a message to the server by enqueuing it on * the client-side send queue. @@ -900,9 +1022,11 @@ public class Client : Thread client.joinChannel("#birchwood"); // TODO: Add a joinChannels(string[]) client.joinChannel("#birchwood2"); - client.joinChannel("#birchwoodLeave1"); - client.joinChannel("#birchwoodLeave2"); - client.joinChannel("#birchwoodLeave3"); + + client.joinChannel(["#birchwoodLeave1", "#birchwoodLeave2", "#birchwoodLeave3"]); + // client.joinChannel("#birchwoodLeave1"); + // client.joinChannel("#birchwoodLeave2"); + // client.joinChannel("#birchwoodLeave3"); Thread.sleep(dur!("seconds")(2)); client.command(new Message("", "NAMES", "")); // TODO: add names commdn @@ -977,5 +1101,7 @@ public class Client : Thread } + + } \ No newline at end of file diff --git a/source/birchwood/client/receiver.d b/source/birchwood/client/receiver.d index 93be2e8..2b73c16 100644 --- a/source/birchwood/client/receiver.d +++ b/source/birchwood/client/receiver.d @@ -53,6 +53,13 @@ public final class ReceiverThread : Thread } // TODO: Rename to `receiveQ` + /** + * Enqueues the raw message into the receieve queue + * for eventual processing + * + * Params: + * encodedMessage = the message to enqueue + */ public void rq(ubyte[] encodedMessage) { /* Lock queue */ @@ -117,29 +124,62 @@ public final class ReceiverThread : Thread /* Lock the receieve queue */ recvQueueLock.lock(); - /* Message being analysed */ - Message curMsg; + /* Parsed messages */ + SList!(Message) currentMessageQueue; - /* Search for a PING */ - ubyte[] pingMessage; - - ulong pos = 0; + /** + * Parse all messages and save them + * into the above array + */ foreach(ubyte[] message; recvQueue[]) { - // FIXME: Holy shit this is funny (see https://github.com/deavmi/birchwood/issues/13) - if(indexOf(cast(string)message, "PING") > -1) - { - pingMessage = message; - recvQueue.linearRemoveElement(message); + /* Decode the message */ + string decodedMessage = decodeMessage(message); - import std.stdio; - writeln("\n\nHOLY SHIT\n: "~cast(string)(message)~"\n\n"); + /* Parse the message */ + Message parsedMessage = Message.parseReceivedMessage(decodedMessage); + + /* Save it */ + currentMessageQueue.insertAfter(currentMessageQueue[], parsedMessage); + } + + + /** + * Search for any PING messages, then store it if so + * and remove it so it isn't processed again later + */ + Message pingMessage; + foreach(Message curMsg; currentMessageQueue[]) + { + import std.string : cmp; + if(cmp(curMsg.getCommand(), "PING") == 0) + { + currentMessageQueue.linearRemoveElement(curMsg); + pingMessage = curMsg; break; } - - pos++; } + /** + * If we have a PING then respond with a PONG + */ + if(pingMessage !is null) + { + logger.log("Found a ping: "~pingMessage.toString()); + + /* Extract the PING ID */ + string pingID = pingMessage.getParams(); + + /* Spawn a PONG event */ + EventyEvent pongEvent = new PongEvent(pingID); + client.engine.push(pongEvent); + } + + + + + + /** * TODO: Plan of action @@ -156,61 +196,25 @@ public final class ReceiverThread : Thread * - we can cache or remember stuff when we get 353 */ - - - - /* If we found a PING */ - if(pingMessage.length > 0) - { - /* Decode the message and parse it */ - curMsg = Message.parseReceivedMessage(decodeMessage(pingMessage)); - logger.log("Found a ping: "~curMsg.toString()); - - // string ogMessage = cast(string)pingMessage; - // long idxSigStart = indexOf(ogMessage, ":")+1; - // long idxSigEnd = lastIndexOf(ogMessage, '\r'); - - // string pingID = ogMessage[idxSigStart..idxSigEnd]; - string pingID = curMsg.getParams(); - - - // this.socket.send(encodeMessage("PONG "~pingID)); - // string messageToSend = "PONG "~pingID; - - // sendMessage(messageToSend); - - // logger.log("Ponged"); - - /* TODO: Implement */ - // TODO: Remove the Eventy push and replace with a handler call (on second thought no) - EventyEvent pongEvent = new PongEvent(pingID); - client.engine.push(pongEvent); - } - /** * Process each message remaining in the queue now * till it is empty */ - while(!recvQueue.empty()) + while(!currentMessageQueue.empty()) { - ubyte[] message = recvQueue.front(); - - /* Decode message */ - string messageNormal = decodeMessage(message); - - recvQueue.linearRemoveElement(recvQueue.front()); - - // writeln("Normal message: "~messageNormal); - - - - /* TODO: Parse message and call correct handler */ - curMsg = Message.parseReceivedMessage(messageNormal); + /* Get the frontmost Message */ + Message curMsg = currentMessageQueue.front(); // TODO: Remove the Eventy push and replace with a handler call (on second thought no) EventyEvent ircEvent = new IRCEvent(curMsg); client.engine.push(ircEvent); + + /* Remove the message from the queue */ + currentMessageQueue.linearRemoveElement(curMsg); } + + /* Clear the receive queue */ + recvQueue.clear(); /* Unlock the receive queue */ recvQueueLock.unlock(); diff --git a/source/birchwood/client/sender.d b/source/birchwood/client/sender.d index 8f22468..3e70f90 100644 --- a/source/birchwood/client/sender.d +++ b/source/birchwood/client/sender.d @@ -48,6 +48,13 @@ public final class SenderThread : Thread } // TODO: Rename to `sendQ` + /** + * Enqueues the raw message into the send queue + * for eventual sending + * + * Params: + * encodedMessage = the message to enqueue + */ public void sq(ubyte[] encodedMessage) { /* Lock queue */ diff --git a/source/birchwood/config/conninfo.d b/source/birchwood/config/conninfo.d index ce2b9be..2eebd76 100644 --- a/source/birchwood/config/conninfo.d +++ b/source/birchwood/config/conninfo.d @@ -1,8 +1,15 @@ +/** + * COnfiguration-related types + */ module birchwood.config.conninfo; import std.socket : SocketException, Address, getAddress; import birchwood.client.exceptions : BirchwoodException; +/** + * Represents the connection details for a server + * to connect to + */ public struct ConnectionInfo { /* Server address information */ @@ -36,16 +43,32 @@ public struct ConnectionInfo return this.bulkReadSize; } + /** + * Get the address of the endpoint server + * + * Returns: the server's address + */ public Address getAddr() { return addrInfo; } + /** + * Get the chosen fake lag + * + * Returns: the fake lag in seconds + */ public ulong getFakeLag() { return fakeLag; } + /** + * Sets the fake lag in seconds + * + * Params: + * fakeLag = the fake lag to use + */ public void setFakeLag(ulong fakeLag) { this.fakeLag = fakeLag; diff --git a/source/birchwood/protocol/constants.d b/source/birchwood/protocol/constants.d index e3ab7d6..b1d6d3e 100644 --- a/source/birchwood/protocol/constants.d +++ b/source/birchwood/protocol/constants.d @@ -1,148 +1,169 @@ module birchwood.protocol.constants; -/* Reply object */ - public enum ReplyType : ulong - { - /* Error replies */ - ERR_NOSUCHNICK = 401, - ERR_NOSUCHSERVER = 402, - ERR_NOSUCHCHANNEL = 403, - ERR_CANNOTSENDTOCHAN = 404, - ERR_TOOMANYCHANNELS = 405, - ERR_WASNOSUCHNICK = 406, - ERR_TOOMANYTARGETS = 407, - ERR_NOORIGIN = 409, - ERR_NORECIPIENT = 411, - ERR_NOTEXTTOSEND = 412, - ERR_NOTOPLEVEL = 413, - ERR_WILDTOPLEVEL = 414, - ERR_UNKNOWNCOMMAND = 421, - ERR_NOMOTD = 422, - ERR_NOADMININFO = 423, - ERR_FILEERROR = 424, - ERR_NONICKNAMEGIVEN = 431, - ERR_ERRONEUSNICKNAME = 432, - ERR_NICKNAMEINUSE = 433, - ERR_NICKCOLLISION = 436, - ERR_USERNOTINCHANNEL = 441, - ERR_NOTONCHANNEL = 442, - ERR_USERONCHANNEL = 443, - ERR_NOLOGIN = 444, - ERR_SUMMONDISABLED = 445, - ERR_USERSDISABLED = 446, - ERR_NOTREGISTERED = 451, - ERR_NEEDMOREPARAMS = 461, - ERR_ALREADYREGISTRED = 462, - ERR_NOPERMFORHOST = 463, - ERR_PASSWDMISMATCH = 464, - ERR_YOUREBANNEDCREEP = 465, - ERR_KEYSET = 467, - ERR_CHANNELISFULL = 471, - ERR_UNKNOWNMODE = 472, - ERR_INVITEONLYCHAN = 473, - ERR_BANNEDFROMCHAN = 474, - ERR_BADCHANNELKEY = 475, - ERR_NOPRIVILEGES = 481, - ERR_CHANOPRIVSNEEDED = 482, - ERR_CANTKILLSERVER = 483, - ERR_NOOPERHOST = 491, - ERR_UMODEUNKNOWNFLAG = 501, - ERR_USERSDONTMATCH = 502, +/** + * The type of numeric response + */ +public enum ReplyType : ulong +{ + /** + * rfc 1459 + */ - /* Command responses */ - RPL_NONE = 300, - RPL_USERHOST = 302, - RPL_ISON = 303, - RPL_AWAY = 301, - RPL_UNAWAY = 305, - RPL_NOWAWAY = 306, - RPL_WHOISUSER = 311, - RPL_WHOISSERVER = 312, - RPL_WHOISOPERATOR = 313, - RPL_WHOISIDLE = 317, - RPL_ENDOFWHOIS = 318, - RPL_WHOISCHANNELS = 319, - RPL_WHOWASUSER = 314, - RPL_ENDOFWHOWAS = 369, - RPL_LISTSTART = 321, - RPL_LIST = 322, - RPL_LISTEND = 323, - RPL_CHANNELMODEIS = 324, - RPL_NOTOPIC = 331, - RPL_TOPIC = 332, - RPL_INVITING = 341, - RPL_SUMMONING = 342, - RPL_VERSION = 351, - RPL_WHOREPLY = 352, - RPL_ENDOFWHO = 315, - RPL_NAMREPLY = 353, - RPL_ENDOFNAMES = 366, - RPL_LINKS = 364, - RPL_ENDOFLINKS = 365, - RPL_BANLIST = 367, - RPL_ENDOFBANLIST = 368, - RPL_INFO = 371, - RPL_ENDOFINFO = 374, - RPL_MOTDSTART = 375, - RPL_MOTD = 372, - RPL_ENDOFMOTD = 376, - RPL_YOUREOPER = 381, - RPL_REHASHING = 382, - RPL_TIME = 391, - RPL_USERSSTART = 392, - RPL_USERS = 393, - RPL_ENDOFUSERS = 394, - RPL_NOUSERS = 395, - RPL_TRACELINK = 200, - RPL_TRACECONNECTING = 201, - RPL_TRACEHANDSHAKE = 202, - RPL_TRACEUNKNOWN = 203, - RPL_TRACEOPERATOR = 204, - RPL_TRACEUSER = 205, - RPL_TRACESERVER = 206, - RPL_TRACENEWTYPE = 208, - RPL_TRACELOG = 261, - RPL_STATSLINKINFO = 211, - RPL_STATSCOMMANDS = 212, - RPL_STATSCLINE = 213, - RPL_STATSNLINE = 214, - RPL_STATSILINE = 215, - RPL_STATSKLINE = 216, - RPL_STATSYLINE = 218, - RPL_ENDOFSTATS = 219, - RPL_STATSLLINE = 241, - RPL_STATSUPTIME = 242, - RPL_STATSOLINE = 243, - RPL_STATSHLINE = 244, - RPL_UMODEIS = 221, - RPL_LUSERCLIENT = 251, - RPL_LUSEROP = 252, - RPL_LUSERUNKNOWN = 253, - RPL_LUSERCHANNELS = 254, - RPL_LUSERME = 255, - RPL_ADMINME = 256, - RPL_ADMINLOC1 = 257, - RPL_ADMINLOC2 = 258, - RPL_ADMINEMAIL = 259, + /* Error replies */ + ERR_NOSUCHNICK = 401, + ERR_NOSUCHSERVER = 402, + ERR_NOSUCHCHANNEL = 403, + ERR_CANNOTSENDTOCHAN = 404, + ERR_TOOMANYCHANNELS = 405, + ERR_WASNOSUCHNICK = 406, + ERR_TOOMANYTARGETS = 407, + ERR_NOORIGIN = 409, + ERR_NORECIPIENT = 411, + ERR_NOTEXTTOSEND = 412, + ERR_NOTOPLEVEL = 413, + ERR_WILDTOPLEVEL = 414, + ERR_UNKNOWNCOMMAND = 421, + ERR_NOMOTD = 422, + ERR_NOADMININFO = 423, + ERR_FILEERROR = 424, + ERR_NONICKNAMEGIVEN = 431, + ERR_ERRONEUSNICKNAME = 432, + ERR_NICKNAMEINUSE = 433, + ERR_NICKCOLLISION = 436, + ERR_USERNOTINCHANNEL = 441, + ERR_NOTONCHANNEL = 442, + ERR_USERONCHANNEL = 443, + ERR_NOLOGIN = 444, + ERR_SUMMONDISABLED = 445, + ERR_USERSDISABLED = 446, + ERR_NOTREGISTERED = 451, + ERR_NEEDMOREPARAMS = 461, + ERR_ALREADYREGISTRED = 462, + ERR_NOPERMFORHOST = 463, + ERR_PASSWDMISMATCH = 464, + ERR_YOUREBANNEDCREEP = 465, + ERR_KEYSET = 467, + ERR_CHANNELISFULL = 471, + ERR_UNKNOWNMODE = 472, + ERR_INVITEONLYCHAN = 473, + ERR_BANNEDFROMCHAN = 474, + ERR_BADCHANNELKEY = 475, + ERR_NOPRIVILEGES = 481, + ERR_CHANOPRIVSNEEDED = 482, + ERR_CANTKILLSERVER = 483, + ERR_NOOPERHOST = 491, + ERR_UMODEUNKNOWNFLAG = 501, + ERR_USERSDONTMATCH = 502, - /* Reserved Numerics (See section 6.3 in RFC 1459) */ - RPL_TRACECLASS = 209, - RPL_SERVICEINFO = 231, - RPL_SERVICE = 233, - RPL_SERVLISTEND = 235, - RPL_WHOISCHANOP = 316, - RPL_CLOSING = 362, - RPL_INFOSTART = 372, - ERR_YOUWILLBEBANNED = 466, - ERR_NOSERVICEHOST = 492, - RPL_STATSQLINE = 217, - RPL_ENDOFSERVICES = 232, - RPL_SERVLIST = 234, - RPL_KILLDONE = 361, - RPL_CLOSEEND = 363, - RPL_MYPORTIS = 384, - ERR_BADCHANMASK = 476, + /* Command responses */ + RPL_NONE = 300, + RPL_USERHOST = 302, + RPL_ISON = 303, + RPL_AWAY = 301, + RPL_UNAWAY = 305, + RPL_NOWAWAY = 306, + RPL_WHOISUSER = 311, + RPL_WHOISSERVER = 312, + RPL_WHOISOPERATOR = 313, + RPL_WHOISIDLE = 317, + RPL_ENDOFWHOIS = 318, + RPL_WHOISCHANNELS = 319, + RPL_WHOWASUSER = 314, + RPL_ENDOFWHOWAS = 369, + RPL_LISTSTART = 321, + RPL_LIST = 322, + RPL_LISTEND = 323, + RPL_CHANNELMODEIS = 324, + RPL_NOTOPIC = 331, + RPL_TOPIC = 332, + RPL_INVITING = 341, + RPL_SUMMONING = 342, + RPL_VERSION = 351, + RPL_WHOREPLY = 352, + RPL_ENDOFWHO = 315, + RPL_NAMREPLY = 353, + RPL_ENDOFNAMES = 366, + RPL_LINKS = 364, + RPL_ENDOFLINKS = 365, + RPL_BANLIST = 367, + RPL_ENDOFBANLIST = 368, + RPL_INFO = 371, + RPL_ENDOFINFO = 374, + RPL_MOTDSTART = 375, + RPL_MOTD = 372, + RPL_ENDOFMOTD = 376, + RPL_YOUREOPER = 381, + RPL_REHASHING = 382, + RPL_TIME = 391, + RPL_USERSSTART = 392, + RPL_USERS = 393, + RPL_ENDOFUSERS = 394, + RPL_NOUSERS = 395, + RPL_TRACELINK = 200, + RPL_TRACECONNECTING = 201, + RPL_TRACEHANDSHAKE = 202, + RPL_TRACEUNKNOWN = 203, + RPL_TRACEOPERATOR = 204, + RPL_TRACEUSER = 205, + RPL_TRACESERVER = 206, + RPL_TRACENEWTYPE = 208, + RPL_TRACELOG = 261, + RPL_STATSLINKINFO = 211, + RPL_STATSCOMMANDS = 212, + RPL_STATSCLINE = 213, + RPL_STATSNLINE = 214, + RPL_STATSILINE = 215, + RPL_STATSKLINE = 216, + RPL_STATSYLINE = 218, + RPL_ENDOFSTATS = 219, + RPL_STATSLLINE = 241, + RPL_STATSUPTIME = 242, + RPL_STATSOLINE = 243, + RPL_STATSHLINE = 244, + RPL_UMODEIS = 221, + RPL_LUSERCLIENT = 251, + RPL_LUSEROP = 252, + RPL_LUSERUNKNOWN = 253, + RPL_LUSERCHANNELS = 254, + RPL_LUSERME = 255, + RPL_ADMINME = 256, + RPL_ADMINLOC1 = 257, + RPL_ADMINLOC2 = 258, + RPL_ADMINEMAIL = 259, + + /* Reserved Numerics (See section 6.3 in RFC 1459) */ + RPL_TRACECLASS = 209, + RPL_SERVICEINFO = 231, + RPL_SERVICE = 233, + RPL_SERVLISTEND = 235, + RPL_WHOISCHANOP = 316, + RPL_CLOSING = 362, + RPL_INFOSTART = 372, + ERR_YOUWILLBEBANNED = 466, + ERR_NOSERVICEHOST = 492, + RPL_STATSQLINE = 217, + RPL_ENDOFSERVICES = 232, + RPL_SERVLIST = 234, + RPL_KILLDONE = 361, + RPL_CLOSEEND = 363, + RPL_MYPORTIS = 384, + ERR_BADCHANMASK = 476, - BIRCHWOOD_UNKNOWN_RESP_CODE = 0 + /** + * rfc 2812 + */ + RPL_WELCOME = 1, + RPL_YOURHOST = 2, + RPL_CREATED = 3, + RPL_MYINFO = 4, + RPL_BOUNCE = 5, // TODO: We care about the key-value pairs here in RPL_BOUNCE + ERR_NOCHANMODES = 477, + + + + /** + * If no code is matched then this is the default + */ + BIRCHWOOD_UNKNOWN_RESP_CODE = 0 } \ No newline at end of file diff --git a/source/birchwood/protocol/messages.d b/source/birchwood/protocol/messages.d index 590335c..1e29dc1 100644 --- a/source/birchwood/protocol/messages.d +++ b/source/birchwood/protocol/messages.d @@ -138,6 +138,9 @@ public final class Message logger.log(e); } } + + /* Parse the parameters into key-value pairs (if any) and trailing text (if any) */ + parameterParse(); } /* TODO: Implement encoder function */ @@ -276,6 +279,192 @@ public final class Message return params; } + /** + * Retrieves the trailing text in the paramaters + * (if any) + * + * Returns: the trailing text + */ + public string getTrailing() + { + return ppTrailing; + } + + /** + * Returns the parameters excluding the trailing text + * which are seperated by spaces but only those + * which are key-value pairs + * + * Returns: the key-value pair parameters + */ + public string[string] getKVPairs() + { + return ppKVPairs; + } + + /** + * Returns the parameters excluding the trailing text + * which are seperated by spaces + * + * Returns: the parameters + */ + public string[] getPairs() + { + return ppPairs; + } + + private string ppTrailing; + private string[string] ppKVPairs; + private string[] ppPairs; + + + version(unittest) + { + import std.stdio; + } + + unittest + { + string testInput = "A:=1 A=2 :Hello this is text"; + writeln("Input: ", testInput); + + bool hasTrailer; + string[] splitted = splitting(testInput, hasTrailer); + writeln("Input (split): ", splitted); + + + + assert(cmp(splitted[0], "A:=1") == 0); + assert(cmp(splitted[1], "A=2") == 0); + + /* Trailer test */ + assert(hasTrailer); + assert(cmp(splitted[2], "Hello this is text") == 0); + } + + unittest + { + string testInput = ":Hello this is text"; + bool hasTrailer; + string[] splitted = splitting(testInput, hasTrailer); + + /* Trailer test */ + assert(hasTrailer); + assert(cmp(splitted[0], "Hello this is text") == 0); + } + + /** + * Imagine: `A:=1 A=2 :Hello` + * + * Params: + * input = + * Returns: + */ + private static string[] splitting(string input, ref bool hasTrailer) + { + string[] splits; + + bool trailingMode; + string buildUp; + for(ulong idx = 0; idx < input.length; idx++) + { + /* Get current character */ + char curCHar = input[idx]; + + + if(trailingMode) + { + buildUp ~= curCHar; + continue; + } + + if(buildUp.length == 0) + { + if(curCHar == ':') + { + trailingMode = true; + continue; + } + } + + + if(curCHar == ' ') + { + /* Flush */ + splits ~= buildUp; + buildUp = ""; + } + else + { + buildUp ~= curCHar; + } + } + + if(buildUp.length) + { + splits ~= buildUp; + } + + hasTrailer = trailingMode; + + return splits; + } + + /** + * NOTE: This needs more work with trailing support + * we must make sure we only look for lastInex of `:` + * where it is first cyaracter after space but NOT within + * an active parameter + */ + private void parameterParse() + { + /* Only parse if there are params */ + if(params.length) + { + /* Trailing text */ + string trailing; + + /* Split the `` */ + bool hasTrailer; + string[] paramsSplit = splitting(params, hasTrailer); + + logger.debug_("ParamsSPlit direct:", paramsSplit); + + + + /* Extract the trailer as the last item in the array (if it exists) */ + if(hasTrailer) + { + trailing = paramsSplit[paramsSplit.length-1]; + + /* Remove it from the parameters */ + paramsSplit = paramsSplit[0..$-1]; + + logger.debug_("GOt railer ", trailing); + } + + ppPairs = paramsSplit; + + + /* Generate the key-value pairs */ + foreach(string pair; paramsSplit) + { + /* Only do this if we have an `=` in the current pair */ + if(indexOf(pair, "=") > -1) + { + string key = split(pair, "=")[0]; + string value = split(pair, "=")[1]; + ppKVPairs[key] = value; + } + } + + /* Save the trailing */ + ppTrailing = trailing; + + logger.debug_("ppTrailing: ", ppTrailing); + } + } + /** * Returns whether or not this message was * a numeric response diff --git a/source/birchwood/protocol/package.d b/source/birchwood/protocol/package.d index 0853c26..733ce7a 100644 --- a/source/birchwood/protocol/package.d +++ b/source/birchwood/protocol/package.d @@ -1,6 +1,7 @@ module birchwood.protocol; public import birchwood.protocol.messages : Message; +public import birchwood.protocol.constants : ReplyType; // TODO: Look how to neaten up (if any) public import birchwood.protocol.formatting; \ No newline at end of file