1
0
mirror of https://github.com/deavmi/birchwood synced 2024-09-20 14:23:38 +02:00

Merge branch 'deavmi:master' into master

This commit is contained in:
supremestdoggo 2023-03-17 10:20:49 -04:00 committed by GitHub
commit 622d6e509d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 604 additions and 231 deletions

View File

@ -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.

View File

@ -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 <channel>
// ... 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
}
}

View File

@ -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();

View File

@ -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 */

View File

@ -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;

View File

@ -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
}

View File

@ -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 `<params>` */
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

View File

@ -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;