mirror of
https://github.com/deavmi/birchwood
synced 2024-09-20 11:43:22 +02:00
commit
d5da8f8f68
2
dub.json
2
dub.json
@ -7,7 +7,7 @@
|
||||
"dependencies": {
|
||||
"dlog": "0.3.19",
|
||||
"eventy": "0.4.0",
|
||||
"libsnooze": "0.3.3"
|
||||
"libsnooze": "1.0.0-beta"
|
||||
},
|
||||
"description": "A sane IRC framework for the D language",
|
||||
"license": "LGPL-3.0",
|
||||
|
@ -8,7 +8,7 @@ import core.sync.mutex : Mutex;
|
||||
import core.thread : Thread, dur;
|
||||
import std.string;
|
||||
import eventy : EventyEvent = Event, Engine, EventType, Signal;
|
||||
import birchwood.config : ConnectionInfo;
|
||||
import birchwood.config;
|
||||
import birchwood.client.exceptions : BirchwoodException, ErrorType;
|
||||
import birchwood.protocol.messages : Message, encodeMessage, decodeMessage, isValidText;
|
||||
|
||||
@ -80,6 +80,11 @@ public class Client : Thread
|
||||
*/
|
||||
this.receiver = new ReceiverThread(this);
|
||||
this.sender = new SenderThread(this);
|
||||
|
||||
/**
|
||||
* Set defaults in db
|
||||
*/
|
||||
setDefaults(this.connInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -161,7 +166,7 @@ public class Client : Thread
|
||||
|
||||
import birchwood.protocol.constants : ReplyType;
|
||||
|
||||
if(commandReply.getReplyType() == ReplyType.RPL_BOUNCE)
|
||||
if(commandReply.getReplyType() == ReplyType.RPL_ISUPPORT)
|
||||
{
|
||||
// TODO: Testing code was here
|
||||
// logger.log();
|
||||
@ -183,7 +188,18 @@ public class Client : Thread
|
||||
// logger.log("<<<>>>");
|
||||
// logger.log();
|
||||
|
||||
import std.stdio;
|
||||
writeln("Support stuff: ", commandReply.getKVPairs());
|
||||
|
||||
/* Fetch and parse the received key-value pairs */
|
||||
string[string] receivedKV = commandReply.getKVPairs();
|
||||
foreach(string key; receivedKV.keys())
|
||||
{
|
||||
/* Update the db */
|
||||
string value = receivedKV[key];
|
||||
connInfo.updateDB(key, value);
|
||||
logger.log("Updated key in db '"~key~"' with value '"~value~"'");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -201,9 +217,21 @@ public class Client : Thread
|
||||
/* Ensure no illegal characters in nick name */
|
||||
if(isValidText(nickname))
|
||||
{
|
||||
/* Set the nick */
|
||||
Message nickMessage = new Message("", "NICK", nickname);
|
||||
sendMessage(nickMessage);
|
||||
// TODO: We could investigate this later if we want to be safer
|
||||
ulong maxNickLen = connInfo.getDB!(ulong)("MAXNICKLEN");
|
||||
|
||||
/* If the username's lenght is within the allowed bounds */
|
||||
if(nickname.length <= maxNickLen)
|
||||
{
|
||||
/* Set the nick */
|
||||
Message nickMessage = new Message("", "NICK", nickname);
|
||||
sendMessage(nickMessage);
|
||||
}
|
||||
/* If not */
|
||||
else
|
||||
{
|
||||
throw new BirchwoodException(ErrorType.NICKNAME_TOO_LONG);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -785,6 +813,9 @@ public class Client : Thread
|
||||
|
||||
/* Start the socket read-decode loop */
|
||||
this.start();
|
||||
|
||||
/* Do the /NICK and /USER handshake */
|
||||
doAuth();
|
||||
}
|
||||
catch(SocketOSException e)
|
||||
{
|
||||
@ -798,6 +829,44 @@ public class Client : Thread
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the /NICK and /USER handshake.
|
||||
*
|
||||
* This method will set the hostname to be equal to the chosen
|
||||
* username in the ConnectionInfo struct
|
||||
*
|
||||
* Params:
|
||||
* servername = the servername to use (default: bogus.net)
|
||||
*/
|
||||
private void doAuth(string servername = "bogus.net")
|
||||
{
|
||||
Thread.sleep(dur!("seconds")(2));
|
||||
nick(connInfo.nickname);
|
||||
|
||||
Thread.sleep(dur!("seconds")(2));
|
||||
// TODO: Note I am making hostname the same as username always (is this okay?)
|
||||
// TODO: Note I am making the servername always bogus.net
|
||||
user(connInfo.username, connInfo.username, servername, connInfo.realname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs user identification
|
||||
*
|
||||
* Params:
|
||||
* username = the username to identify with
|
||||
* hostname = the hostname to use
|
||||
* servername = the servername to use
|
||||
* realname = your realname
|
||||
*/
|
||||
public void user(string username, string hostname, string servername, string realname)
|
||||
{
|
||||
// TODO: Implement me properly with all required checks
|
||||
|
||||
/* User message */
|
||||
Message userMessage = new Message("", "USER", username~" "~hostname~" "~servername~" "~":"~realname);
|
||||
sendMessage(userMessage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a given message onto the receieve queue for
|
||||
@ -943,9 +1012,21 @@ public class Client : Thread
|
||||
/* Receieve at most 512 bytes (as per RFC) */
|
||||
ptrdiff_t bytesRead = socket.receive(currentData, SocketFlags.PEEK);
|
||||
|
||||
import std.stdio;
|
||||
// writeln(bytesRead);
|
||||
// writeln(currentData);
|
||||
version(unittest)
|
||||
{
|
||||
import std.stdio;
|
||||
writeln("(peek) bytesRead: '", bytesRead, "' (status var or count)");
|
||||
writeln("(peek) currentData: '", currentData, "'");
|
||||
|
||||
// On remote end closing connection
|
||||
if(bytesRead == 0)
|
||||
{
|
||||
writeln("About to do the panic!");
|
||||
*cast(byte*)0 = 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* FIXME: CHECK BYTES READ FOR SOCKET ERRORS! */
|
||||
|
||||
@ -1034,12 +1115,11 @@ public class Client : Thread
|
||||
|
||||
unittest
|
||||
{
|
||||
/* FIXME: Get domaina name resolution support */
|
||||
// ConnectionInfo connInfo = ConnectionInfo.newConnection("irc.freenode.net", 6667, "testBirchwood");
|
||||
//freenode: 149.28.246.185
|
||||
//snootnet: 178.62.125.123
|
||||
//bonobonet: fd08:8441:e254::5
|
||||
ConnectionInfo connInfo = ConnectionInfo.newConnection("worcester.community.networks.deavmi.assigned.network", 6667, "testBirchwood");
|
||||
ConnectionInfo connInfo = ConnectionInfo.newConnection("worcester.community.networks.deavmi.assigned.network", 6667, "birchwood", "doggie", "Tristan B. Kildaire");
|
||||
|
||||
// // Set the fakelag to 1 second
|
||||
// connInfo.setFakeLag(1);
|
||||
@ -1047,17 +1127,22 @@ public class Client : Thread
|
||||
// Create a new Client
|
||||
Client client = new Client(connInfo);
|
||||
|
||||
// Authenticate
|
||||
client.connect();
|
||||
|
||||
|
||||
// TODO: The below should all be automatic, maybe once IRCV3 is done
|
||||
// ... we should automate sending in NICK and USER stuff
|
||||
Thread.sleep(dur!("seconds")(2));
|
||||
// client.command(new Message("", "NICK", "birchwood")); // TODO: add nickcommand
|
||||
client.nick("birchwood");
|
||||
// Thread.sleep(dur!("seconds")(2));
|
||||
// client.nick("birchwood");
|
||||
|
||||
// Thread.sleep(dur!("seconds")(2));
|
||||
// client.command(new Message("", "USER", "doggie doggie irc.frdeenode.net :Tristan B. Kildaire"));
|
||||
// client.user("doggie", "doggie", "irc.frdeenode.net", "Tristan B. Kildaire");
|
||||
|
||||
|
||||
|
||||
|
||||
Thread.sleep(dur!("seconds")(2));
|
||||
client.command(new Message("", "USER", "doggie doggie irc.frdeenode.net :Tristan B. Kildaire"));
|
||||
|
||||
Thread.sleep(dur!("seconds")(4));
|
||||
// client.command(new Message("", "JOIN", "#birchwood"));
|
||||
@ -1165,8 +1250,29 @@ public class Client : Thread
|
||||
*/
|
||||
client.leaveChannel(["#birchwoodLeave2"]);
|
||||
|
||||
|
||||
/**
|
||||
* Definately by now we would have learnt the new MAXNICLEN
|
||||
* which on BonoboNET is 30, hence the below should work
|
||||
*/
|
||||
try
|
||||
{
|
||||
client.nick("birchwood123456789123456789123");
|
||||
assert(true);
|
||||
}
|
||||
catch(BirchwoodException e)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// TODO: Don't forget to re-enable this when done testing!
|
||||
Thread.sleep(dur!("seconds")(15));
|
||||
client.quit();
|
||||
Thread.sleep(dur!("seconds")(4));
|
||||
client.joinChannel("#birchwood");
|
||||
while(true)
|
||||
{
|
||||
Thread.sleep(dur!("seconds")(15));
|
||||
}
|
||||
|
||||
// client.quit();
|
||||
}
|
||||
}
|
@ -66,7 +66,18 @@ public enum ErrorType
|
||||
* If invalid parameters are passed
|
||||
* to any of the text formatting functions
|
||||
*/
|
||||
INVALID_FORMATTING
|
||||
INVALID_FORMATTING,
|
||||
|
||||
/**
|
||||
* If a key-lookup in the ConnInfo failed
|
||||
*/
|
||||
DB_KEY_NOT_FOUND,
|
||||
|
||||
/**
|
||||
* If the requested nickname (via /NICK) is
|
||||
* too long
|
||||
*/
|
||||
NICKNAME_TOO_LONG
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,6 +20,11 @@ import std.string : indexOf;
|
||||
import birchwood.client.events : PongEvent, IRCEvent;
|
||||
import std.string : cmp;
|
||||
|
||||
version(unittest)
|
||||
{
|
||||
import std.stdio : writeln;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the receive queue and performs
|
||||
* message parsing and event triggering
|
||||
@ -104,10 +109,6 @@ public final class ReceiverThread : Thread
|
||||
* an event depending on the type of message
|
||||
*
|
||||
* Handles PINGs along with normal messages
|
||||
*
|
||||
* TODO: Our high load average is from here
|
||||
* ... it is getting lock a lot and spinning here
|
||||
* ... we should use libsnooze to avoid this
|
||||
*/
|
||||
private void recvHandlerFunc()
|
||||
{
|
||||
@ -130,7 +131,28 @@ public final class ReceiverThread : Thread
|
||||
|
||||
// TODO: See above notes about libsnooze behaviour due
|
||||
// ... to usage in our context
|
||||
receiveEvent.wait(); // TODO: Catch any exceptions from libsnooze
|
||||
try
|
||||
{
|
||||
receiveEvent.wait();
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
version(unittest)
|
||||
{
|
||||
writeln("wait() interrupted");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
catch(FatalException e)
|
||||
{
|
||||
// TODO: This should crash and end
|
||||
version(unittest)
|
||||
{
|
||||
writeln("wait() had a FATAL error!!!!!!!!!!!");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -14,6 +14,11 @@ import libsnooze;
|
||||
|
||||
import birchwood.client;
|
||||
|
||||
version(unittest)
|
||||
{
|
||||
import std.stdio : writeln;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the send queue
|
||||
*/
|
||||
@ -90,9 +95,6 @@ public final class SenderThread : Thread
|
||||
|
||||
/**
|
||||
* The send queue worker function
|
||||
*
|
||||
* TODO: Same issue as recvHandlerFunc
|
||||
* ... we should I/O wait (sleep) here
|
||||
*/
|
||||
private void sendHandlerFunc()
|
||||
{
|
||||
@ -116,7 +118,30 @@ public final class SenderThread : Thread
|
||||
|
||||
// TODO: See above notes about libsnooze behaviour due
|
||||
// ... to usage in our context
|
||||
sendEvent.wait(); // TODO: Catch any exceptions from libsnooze
|
||||
try
|
||||
{
|
||||
sendEvent.wait();
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
version(unittest)
|
||||
{
|
||||
writeln("wait() interrupted");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
catch(FatalException e)
|
||||
{
|
||||
// TODO: This should crash and end
|
||||
version(unittest)
|
||||
{
|
||||
writeln("wait() had a FATAL error!!!!!!!!!!!");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO: After the above call have a once-off call to `ensure()` here
|
||||
// ... which then only runs once and sets a `ready` flag for the Client
|
||||
|
@ -5,6 +5,7 @@ module birchwood.config.conninfo;
|
||||
|
||||
import std.socket : SocketException, Address, getAddress;
|
||||
import birchwood.client.exceptions;
|
||||
import std.conv : to, ConvException;
|
||||
|
||||
/**
|
||||
* Represents the connection details for a server
|
||||
@ -20,7 +21,17 @@ public shared struct ConnectionInfo
|
||||
/**
|
||||
* Nickname to use
|
||||
*/
|
||||
private string nickname;
|
||||
public string nickname;
|
||||
|
||||
/**
|
||||
* Username
|
||||
*/
|
||||
public string username;
|
||||
|
||||
/**
|
||||
* Real name
|
||||
*/
|
||||
public string realname;
|
||||
|
||||
/**
|
||||
* Size to use to dequeue bytes
|
||||
@ -40,6 +51,12 @@ public shared struct ConnectionInfo
|
||||
*/
|
||||
public const string quitMessage;
|
||||
|
||||
/**
|
||||
* Key-value pairs learnt from the
|
||||
* server
|
||||
*/
|
||||
private string[string] db;
|
||||
|
||||
/* TODO: before publishing change this bulk size */
|
||||
|
||||
/**
|
||||
@ -52,11 +69,13 @@ public shared struct ConnectionInfo
|
||||
* bulkReadSize = the dequeue read size
|
||||
* quitMessage = the message to use when quitting
|
||||
*/
|
||||
private this(Address addrInfo, string nickname, ulong bulkReadSize = 20, string quitMessage = "birchwood client disconnecting...")
|
||||
private this(Address addrInfo, string nickname, string username, string realname, ulong bulkReadSize = 20, string quitMessage = "birchwood client disconnecting...")
|
||||
{
|
||||
// NOTE: Not sure if much mutable in Address anyways
|
||||
this.addrInfo = cast(shared Address)addrInfo;
|
||||
this.nickname = nickname;
|
||||
this.username = username;
|
||||
this.realname = realname;
|
||||
this.bulkReadSize = bulkReadSize;
|
||||
this.quitMessage = quitMessage;
|
||||
|
||||
@ -116,6 +135,52 @@ public shared struct ConnectionInfo
|
||||
this.fakeLag = fakeLag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a value in the key-value pair database
|
||||
*
|
||||
* Params:
|
||||
* key = the key to set
|
||||
* value = the value to set to
|
||||
*/
|
||||
public void updateDB(string key, string value)
|
||||
{
|
||||
db[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value from the key-value pair database
|
||||
*
|
||||
* Params:
|
||||
* key = the key to lookup
|
||||
* Returns: the value as type T, if not able to convert then T.init
|
||||
* Throws:
|
||||
* BirchwoodException if the key is not found
|
||||
*/
|
||||
public T getDB(T)(string key)
|
||||
{
|
||||
if(key in db)
|
||||
{
|
||||
/* Attempt conversion into T */
|
||||
try
|
||||
{
|
||||
/* Fetch and convert */
|
||||
T value = to!(T)(db[key]);
|
||||
return value;
|
||||
}
|
||||
/* If conversion to type T fails */
|
||||
catch(ConvException e)
|
||||
{
|
||||
/* Return the initial value for such a paremeter */
|
||||
return T.init;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BirchwoodException(ErrorType.DB_KEY_NOT_FOUND, "Could not find key '"~key~"'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a ConnectionInfo struct representing a client configuration which
|
||||
* can be provided to the Client class to create a new connection based on its
|
||||
@ -128,7 +193,7 @@ public shared struct ConnectionInfo
|
||||
*
|
||||
* Returns: ConnectionInfo for this server
|
||||
*/
|
||||
public static ConnectionInfo newConnection(string hostname, ushort port, string nickname)
|
||||
public static ConnectionInfo newConnection(string hostname, ushort port, string nickname, string username, string realname)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -144,7 +209,7 @@ public shared struct ConnectionInfo
|
||||
/* TODO: Add feature to choose which address to use, prefer v4 or v6 type of thing */
|
||||
Address chosenAddress = addrInfo[0];
|
||||
|
||||
return ConnectionInfo(chosenAddress, nickname);
|
||||
return ConnectionInfo(chosenAddress, nickname, username, realname);
|
||||
}
|
||||
catch(SocketException e)
|
||||
{
|
||||
@ -162,7 +227,7 @@ public shared struct ConnectionInfo
|
||||
{
|
||||
try
|
||||
{
|
||||
newConnection("1.", 21, "deavmi");
|
||||
newConnection("1.", 21, "deavmi", "thedeavmi", "Tristan Brice Birchwood Kildaire");
|
||||
assert(false);
|
||||
}
|
||||
catch(BirchwoodException e)
|
||||
@ -172,7 +237,7 @@ public shared struct ConnectionInfo
|
||||
|
||||
try
|
||||
{
|
||||
newConnection("1.1.1.1", 21, "");
|
||||
newConnection("1.1.1.1", 21, "", "thedeavmi", "Tristan Brice Birchwood Kildaire");
|
||||
assert(false);
|
||||
}
|
||||
catch(BirchwoodException e)
|
||||
@ -182,3 +247,16 @@ public shared struct ConnectionInfo
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default values as per rfc1459 in the
|
||||
* key-value pair DB
|
||||
*
|
||||
* Params:
|
||||
* connInfo = a reference to the ConnectionInfo struct to update
|
||||
*/
|
||||
public void setDefaults(ref ConnectionInfo connInfo)
|
||||
{
|
||||
/* Set the `MAXNICKLEN` to a default of 9 */
|
||||
connInfo.updateDB("MAXNICKLEN", "9");
|
||||
}
|
@ -6,4 +6,4 @@ module birchwood.config;
|
||||
/**
|
||||
* Connection information
|
||||
*/
|
||||
public import birchwood.config.conninfo : ConnectionInfo;
|
||||
public import birchwood.config.conninfo : ConnectionInfo, setDefaults;
|
@ -149,7 +149,6 @@ public enum ReplyType : ulong
|
||||
RPL_MYPORTIS = 384,
|
||||
ERR_BADCHANMASK = 476,
|
||||
|
||||
|
||||
/**
|
||||
* rfc 2812
|
||||
*/
|
||||
@ -157,9 +156,17 @@ public enum ReplyType : ulong
|
||||
RPL_YOURHOST = 2,
|
||||
RPL_CREATED = 3,
|
||||
RPL_MYINFO = 4,
|
||||
RPL_BOUNCE = 5, // TODO: We care about the key-value pairs here in RPL_BOUNCE
|
||||
RPL_BOUNCE = 10, // In ircv3 this changed from 005 to 010
|
||||
ERR_NOCHANMODES = 477,
|
||||
|
||||
/**
|
||||
* ircv3
|
||||
*/
|
||||
RPL_LOCALUSERS = 265,
|
||||
RPL_GLOBALUSERS = 266,
|
||||
RPL_WHOISCERTFP = 276,
|
||||
RPL_ISUPPORT = 5, // This overrides the old rfc2812 RPL_BOUNCE code (we can only support one of these)
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
@ -428,7 +428,7 @@ public final class Message
|
||||
bool hasTrailer;
|
||||
string[] paramsSplit = splitting(params, hasTrailer);
|
||||
|
||||
logger.debug_("ParamsSPlit direct:", paramsSplit);
|
||||
// logger.debug_("ParamsSPlit direct:", paramsSplit);
|
||||
|
||||
|
||||
|
||||
@ -440,7 +440,7 @@ public final class Message
|
||||
/* Remove it from the parameters */
|
||||
paramsSplit = paramsSplit[0..$-1];
|
||||
|
||||
logger.debug_("GOt railer ", trailing);
|
||||
// logger.debug_("GOt railer ", trailing);
|
||||
}
|
||||
|
||||
ppPairs = paramsSplit;
|
||||
@ -461,7 +461,7 @@ public final class Message
|
||||
/* Save the trailing */
|
||||
ppTrailing = trailing;
|
||||
|
||||
logger.debug_("ppTrailing: ", ppTrailing);
|
||||
// logger.debug_("ppTrailing: ", ppTrailing);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user