1
0
mirror of https://github.com/deavmi/birchwood synced 2024-09-20 11:43:22 +02:00

Merge pull request #18 from deavmi/ircv3

ircv3 support
This commit is contained in:
Tristan B. Velloza Kildaire 2023-06-21 11:03:52 +02:00 committed by GitHub
commit d5da8f8f68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 290 additions and 41 deletions

View File

@ -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",

View File

@ -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! */
@ -1019,7 +1100,7 @@ public class Client : Thread
scratch.length = bytesRead;
this.socket.receive(scratch);
/* TODO: Yield here and in other places before continue */
@ -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();
}
}

View File

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

View File

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

View File

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

View File

@ -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)
@ -181,4 +246,17 @@ 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");
}

View File

@ -6,4 +6,4 @@ module birchwood.config;
/**
* Connection information
*/
public import birchwood.config.conninfo : ConnectionInfo;
public import birchwood.config.conninfo : ConnectionInfo, setDefaults;

View File

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

View File

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