Compare commits
81 Commits
|
@ -6,9 +6,9 @@ name: D
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
branches: [ "**" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
branches: [ "**" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
@ -22,6 +22,12 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
- uses: dlang-community/setup-dlang@4c99aa991ce7d19dd3064de0a4f2f6b2f152e2d7
|
||||
|
||||
- name: Install Doveralls (code coverage tool)
|
||||
run: |
|
||||
dub fetch doveralls
|
||||
sudo apt update
|
||||
sudo apt install libcurl4-openssl-dev
|
||||
|
||||
- name: 'Build & Test'
|
||||
run: |
|
||||
# Build the project, with its main file included, without unittests
|
||||
|
@ -29,4 +35,8 @@ jobs:
|
|||
# Build and run tests, as defined by `unittest` configuration
|
||||
# In this mode, `mainSourceFile` is excluded and `version (unittest)` are included
|
||||
# See https://dub.pm/package-format-json.html#configurations
|
||||
dub test --compiler=$DC
|
||||
dub test --compiler=$DC --coverage
|
||||
|
||||
- name: Coverage upload
|
||||
run: |
|
||||
dub run doveralls -- -t ${{secrets.COVERALLS_REPO_TOKEN}}
|
||||
|
|
|
@ -13,4 +13,4 @@
|
|||
<br>
|
||||
<br>
|
||||
|
||||
[![D](https://github.com/renaissanceorg/renaissance/actions/workflows/d.yml/badge.svg)](https://github.com/renaissanceorg/renaissance/actions/workflows/d.yml)
|
||||
[![D](https://github.com/renaissanceorg/renaissance/actions/workflows/d.yml/badge.svg)](https://github.com/renaissanceorg/renaissance/actions/workflows/d.yml) [![Coverage Status](https://coveralls.io/repos/github/renaissanceorg/renaissance/badge.svg?branch=feature/queueing)](https://coveralls.io/github/renaissanceorg/renaissance?branch=feature/queueing)
|
||||
|
|
6
dub.json
6
dub.json
|
@ -4,9 +4,9 @@
|
|||
],
|
||||
"copyright": "Copyright © 2023, Tristan B. Kildaire",
|
||||
"dependencies": {
|
||||
"dante": ">=0.1.11",
|
||||
"davinci": ">=0.1.2",
|
||||
"gogga": ">=2.1.18",
|
||||
"dante": ">=0.2.2",
|
||||
"davinci": ">=0.1.11",
|
||||
"gogga": ">=2.2.1",
|
||||
"lumars": "~>1.11.0",
|
||||
"river": ">=0.3.7",
|
||||
"tristanable": ">=4.0.0-beta"
|
||||
|
|
|
@ -6,6 +6,26 @@ import renaissance.server;
|
|||
import river.core;
|
||||
import tristanable;
|
||||
import renaissance.logging;
|
||||
import renaissance.server.messagemanager : MessageManager, Message;
|
||||
|
||||
|
||||
import davinci.base.components : Validatable;
|
||||
|
||||
import davinci.c2s.auth : AuthMessage, AuthResponse;
|
||||
import davinci.c2s.generic : UnknownCommandReply;
|
||||
|
||||
import davinci.c2s.channels : ChannelEnumerateRequest, ChannelEnumerateReply, ChannelMembership, ChannelMessage;
|
||||
import davinci.c2s.test : NopMessage;
|
||||
import renaissance.server.channelmanager : ChannelManager, Channel;
|
||||
|
||||
import std.conv : to;
|
||||
|
||||
public enum LinkType
|
||||
{
|
||||
UNSET,
|
||||
USER,
|
||||
SERVER
|
||||
}
|
||||
|
||||
public class Connection : Thread
|
||||
{
|
||||
|
@ -24,6 +44,12 @@ public class Connection : Thread
|
|||
private Manager tManager;
|
||||
private Queue incomingQueue;
|
||||
|
||||
/**
|
||||
* Whether this is a user connection
|
||||
* or a server link
|
||||
*/
|
||||
private LinkType linkType;
|
||||
|
||||
private this(Server associatedServer, Stream clientStream)
|
||||
{
|
||||
this.associatedServer = associatedServer;
|
||||
|
@ -53,6 +79,11 @@ public class Connection : Thread
|
|||
this.tManager.setDefaultQueue(this.incomingQueue);
|
||||
}
|
||||
|
||||
public LinkType getLinkType()
|
||||
{
|
||||
return this.linkType;
|
||||
}
|
||||
|
||||
private void worker()
|
||||
{
|
||||
// TODO: Start tristanable manager here
|
||||
|
@ -65,8 +96,9 @@ public class Connection : Thread
|
|||
|
||||
// TODO: Well, we'd tasky I guess so I'd need to use it there I guess
|
||||
|
||||
// TODO: Add worker function here
|
||||
while(true)
|
||||
// TODO: Imp,ent nthe loop condition status (exit on error)
|
||||
bool isGood = true;
|
||||
while(isGood)
|
||||
{
|
||||
// TODO: Addn a tasky/tristanable queue managing thing with
|
||||
// ... socket here (probably just the latter)
|
||||
|
@ -87,12 +119,50 @@ public class Connection : Thread
|
|||
logger.dbg("Awoken? after dequeue()");
|
||||
|
||||
// Process the message
|
||||
handle(incomingMessage);
|
||||
TaggedMessage response = handle(incomingMessage);
|
||||
if(response !is null)
|
||||
{
|
||||
logger.dbg("There was a response, sending: ", response);
|
||||
this.tManager.sendMessage(incomingMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.dbg("There was no response, not sending anything.");
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up (TODO: Shutdown the TManager)
|
||||
|
||||
|
||||
// Clean up - notify disconnection
|
||||
this.associatedServer.onConnectionDisconnect(this);
|
||||
}
|
||||
|
||||
private void handle(TaggedMessage incomingMessage)
|
||||
// FIXME: These should be part of the auth details
|
||||
// ... associated with this user
|
||||
string myUsername = "bababooey";
|
||||
|
||||
private bool isAuthd()
|
||||
{
|
||||
return myUsername.length != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `TaggedMessage` this method will decode
|
||||
* it into a Davinci `BaseMessage`, determine the
|
||||
* payload type via this header and then handle
|
||||
* the message/command accordingly
|
||||
*
|
||||
* Params:
|
||||
* incomingMessage = the `TaggedMessage`
|
||||
* Returns: the response `TaggedMessage`, or
|
||||
* `null` if no response is to be sent
|
||||
*/
|
||||
private TaggedMessage handle(TaggedMessage incomingMessage)
|
||||
{
|
||||
// TODO: In future this decoder, surely, should be idk
|
||||
// ... in davinci as in stateful encoder/decoder
|
||||
// ... reply-generator
|
||||
logger.dbg("Examining message '"~incomingMessage.toString()~"' ...");
|
||||
|
||||
byte[] payload = incomingMessage.getPayload();
|
||||
|
@ -101,13 +171,239 @@ public class Connection : Thread
|
|||
logger.dbg("Incoming message: "~baseMessage.getCommand().toString());
|
||||
|
||||
logger.dbg("BaseMessage type: ", baseMessage.getMessageType());
|
||||
Command incomingCommand = baseMessage.getCommand();
|
||||
CommandType incomingCommandType = baseMessage.getCommandType();
|
||||
logger.dbg("Incoming CommandType: ", incomingCommandType);
|
||||
|
||||
if(baseMessage.getCommandType() == CommandType.NOP_COMMAND)
|
||||
BaseMessage response;
|
||||
MessageType mType;
|
||||
Command responseCommand;
|
||||
CommandType responseType;
|
||||
Status responseStatus;
|
||||
|
||||
/**
|
||||
* Perform validation before continueing
|
||||
*/
|
||||
if(cast(Validatable)incomingCommand)
|
||||
{
|
||||
import davinci.c2s.test;
|
||||
logger.dbg("We got a NOP");
|
||||
TestMessage nopMessage = cast(TestMessage)baseMessage.getCommand();
|
||||
Validatable validtabaleCommand = cast(Validatable)incomingCommand;
|
||||
string reason;
|
||||
if(!validtabaleCommand.validate(reason))
|
||||
{
|
||||
logger.error("Validation failed with reason: '", reason, "'");
|
||||
|
||||
|
||||
UnknownCommandReply unknownCmdReply = new UnknownCommandReply(reason);
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.UNKNOWN_COMMAND;
|
||||
responseCommand = unknownCmdReply;
|
||||
|
||||
// TODO: Can we do this without gotos?
|
||||
goto encode_n_send;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the different types of commands
|
||||
*/
|
||||
switch(incomingCommandType)
|
||||
{
|
||||
/**
|
||||
* Handle NOP commands
|
||||
*/
|
||||
case CommandType.NOP_COMMAND:
|
||||
{
|
||||
logger.dbg("We got a NOP");
|
||||
NopMessage nopMessage = cast(NopMessage)baseMessage.getCommand();
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.NOP_COMMAND;
|
||||
responseCommand = nopMessage;
|
||||
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Handle authentication request
|
||||
*/
|
||||
case CommandType.AUTH_COMMAND:
|
||||
{
|
||||
AuthMessage authMessage = cast(AuthMessage)baseMessage.getCommand();
|
||||
bool status = this.associatedServer.attemptAuth(authMessage.getUsername(), authMessage.getPassword());
|
||||
|
||||
// TODO: This is just for testing now - i intend to have a nice auth manager
|
||||
|
||||
|
||||
AuthResponse authResp = new AuthResponse();
|
||||
if(status)
|
||||
{
|
||||
authResp.good();
|
||||
|
||||
// Save username
|
||||
this.myUsername = authMessage.getUsername();
|
||||
}
|
||||
else
|
||||
{
|
||||
authResp.bad();
|
||||
}
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.AUTH_RESPONSE;
|
||||
responseCommand = authResp;
|
||||
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Handle channel list requests
|
||||
*/
|
||||
case CommandType.CHANNELS_ENUMERATE_REQ:
|
||||
{
|
||||
// FIXME: Figure out how we want to do auth checks
|
||||
if(!isAuthd())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ChannelEnumerateRequest chanEnumReq = cast(ChannelEnumerateRequest)baseMessage.getCommand();
|
||||
ubyte limit = chanEnumReq.getLimit();
|
||||
ulong offset = chanEnumReq.getOffset();
|
||||
|
||||
string[] channelNames = this.associatedServer.getChannelNames(offset, limit);
|
||||
ChannelEnumerateReply chanEnumRep = new ChannelEnumerateReply(channelNames);
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.CHANNELS_ENUMERATE_REP;
|
||||
responseCommand = chanEnumRep;
|
||||
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Handle channel joins
|
||||
*/
|
||||
case CommandType.MEMBERSHIP_JOIN:
|
||||
{
|
||||
ChannelMembership chanMemReq = cast(ChannelMembership)baseMessage.getCommand();
|
||||
string channel = chanMemReq.getChannel();
|
||||
|
||||
// Join the channel
|
||||
ChannelManager chanMan = this.associatedServer.getChannelManager();
|
||||
bool status = chanMan.membershipJoin(channel, this.myUsername); // TODO: Handle return value
|
||||
chanMemReq.replyGood();
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.MEMBERSHIP_JOIN_REP;
|
||||
responseCommand = chanMemReq;
|
||||
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Handle channel membership requests
|
||||
*/
|
||||
case CommandType.MEMBERSHIP_LIST:
|
||||
{
|
||||
ChannelMembership chanMemReq = cast(ChannelMembership)baseMessage.getCommand();
|
||||
string channel = chanMemReq.getChannel();
|
||||
|
||||
// Obtain the current members
|
||||
ChannelManager chanMan = this.associatedServer.getChannelManager();
|
||||
string[] currentMembers;
|
||||
|
||||
// TODO: Handle return value
|
||||
bool status = chanMan.membershipList(channel, currentMembers);
|
||||
logger.dbg("Current members of '"~channel~"': ", currentMembers);
|
||||
chanMemReq.listReplyGood(currentMembers);
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.MEMBERSHIP_LIST_REP;
|
||||
responseCommand = chanMemReq;
|
||||
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Handle channel leaves
|
||||
*/
|
||||
case CommandType.MEMBERSHIP_LEAVE:
|
||||
{
|
||||
ChannelMembership chanMemReq = cast(ChannelMembership)baseMessage.getCommand();
|
||||
string channel = chanMemReq.getChannel();
|
||||
|
||||
// Join the channel
|
||||
ChannelManager chanMan = this.associatedServer.getChannelManager();
|
||||
bool status = chanMan.membershipLeave(channel, this.myUsername); // TODO: Handle return value
|
||||
chanMemReq.replyGood();
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.MEMBERSHIP_LEAVE_REP;
|
||||
responseCommand = chanMemReq;
|
||||
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Handle message sending
|
||||
*/
|
||||
case CommandType.CHANNEL_SEND_MESSAGE:
|
||||
{
|
||||
ChannelMessage chanMesg = cast(ChannelMessage)baseMessage.getCommand();
|
||||
|
||||
// TODO: Get channel, lookup and do permission checks
|
||||
|
||||
// TODO: Use a messagemanager thing here
|
||||
MessageManager mesgMan = this.associatedServer.getMessageManager();
|
||||
|
||||
|
||||
// TODO: Check multiple recipients
|
||||
string[] recipients = chanMesg.getRecipients();
|
||||
foreach(string to; recipients)
|
||||
{
|
||||
Message message;
|
||||
message.setBody(chanMesg.getMessage());
|
||||
message.setFrom(this.myUsername);
|
||||
message.setDestination(to);
|
||||
|
||||
logger.dbg("Sending message: ", message);
|
||||
mesgMan.sendq(message);
|
||||
}
|
||||
|
||||
// TODO: Set this ONLY if we succeeeded in delivery
|
||||
chanMesg.messageDelivered();
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.SEND_CHANNEL_MESG_REP;
|
||||
responseCommand = chanMesg;
|
||||
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Anything else is an unknown
|
||||
* command, therefore generate
|
||||
* an error reply
|
||||
*/
|
||||
default:
|
||||
{
|
||||
logger.warn("Received unsupported message type", baseMessage);
|
||||
|
||||
UnknownCommandReply unknownCmdReply = new UnknownCommandReply("Command with type number: "~to!(string)(cast(ulong)incomingCommandType));
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.UNKNOWN_COMMAND;
|
||||
responseCommand = unknownCmdReply;
|
||||
|
||||
logger.warn("We have generated err: ", responseCommand);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
encode_n_send:
|
||||
|
||||
// Generate response
|
||||
response = new BaseMessage(mType, responseType, responseCommand);
|
||||
|
||||
// Construct a response using the same tag
|
||||
// (for matching) but a new payload (the
|
||||
// response message)
|
||||
incomingMessage.setPayload(response.encode());
|
||||
|
||||
return incomingMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,7 +19,8 @@ void main()
|
|||
|
||||
import renaissance.listeners;
|
||||
import std.socket;
|
||||
Address listenAddr = parseAddress("::1", 9091);
|
||||
// Address listenAddr = parseAddress("::1", 9091);
|
||||
Address listenAddr = new UnixAddress("/tmp/renaissance.sock");
|
||||
StreamListener streamListener = StreamListener.create(server, listenAddr);
|
||||
|
||||
server.start();
|
||||
|
|
|
@ -8,4 +8,5 @@ __gshared static this()
|
|||
{
|
||||
logger = new GoggaLogger();
|
||||
logger.enableDebug();
|
||||
logger.mode(GoggaMode.RUSTACEAN_SIMPLE);
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
module renaissance.server.channelmanager;
|
||||
|
||||
import renaissance.server.server : Server;
|
||||
import std.container.slist : SList;
|
||||
import core.sync.mutex : Mutex;
|
||||
import renaissance.logging;
|
||||
|
||||
public struct Channel
|
||||
{
|
||||
private SList!(string) members;
|
||||
private string name;
|
||||
|
||||
// TODO: Actually use this
|
||||
private Mutex channelLock;
|
||||
|
||||
this(string channelName)
|
||||
{
|
||||
this.channelLock = new Mutex();
|
||||
this.name = channelName;
|
||||
}
|
||||
|
||||
public bool hasMember(string username)
|
||||
{
|
||||
// Lock the channel
|
||||
this.channelLock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
this.channelLock.unlock();
|
||||
}
|
||||
|
||||
// Search for membership
|
||||
foreach(string member; this.members)
|
||||
{
|
||||
if(member == username)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool removeMember(string username)
|
||||
{
|
||||
// Lock the channel
|
||||
this.channelLock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
this.channelLock.unlock();
|
||||
}
|
||||
|
||||
// Only remove if we have the member
|
||||
if(hasMember(username))
|
||||
{
|
||||
// Remove ourselves from the channel
|
||||
this.members.linearRemoveElement(username);
|
||||
|
||||
return true;
|
||||
}
|
||||
// Error if not present
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool addMember(string username)
|
||||
{
|
||||
// Lock the channel
|
||||
this.channelLock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
this.channelLock.unlock();
|
||||
}
|
||||
|
||||
// Only add if we are not yet present
|
||||
if(!hasMember(username))
|
||||
{
|
||||
// Remove ourselves from the channel
|
||||
this.members.insertAfter(this.members[], username);
|
||||
|
||||
return true;
|
||||
}
|
||||
// Error if present already
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string[] getMembers()
|
||||
{
|
||||
string[] arrCopy;
|
||||
foreach(string member; this.members)
|
||||
{
|
||||
arrCopy ~= member;
|
||||
}
|
||||
return arrCopy;
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChannelManager
|
||||
{
|
||||
private Server server;
|
||||
|
||||
/**
|
||||
* Map of channel names to
|
||||
* the respective channel
|
||||
* descriptors
|
||||
*/
|
||||
private Channel[string] channels;
|
||||
private Mutex channelsLock;
|
||||
|
||||
private this()
|
||||
{
|
||||
this.channelsLock = new Mutex();
|
||||
|
||||
// TODO: Disable later, this just adds some testing channels
|
||||
// return ["#general", "#tomfoolery"];
|
||||
channelCreate("#general");
|
||||
channelCreate("#tomfoolery");
|
||||
}
|
||||
|
||||
public static ChannelManager create(Server server)
|
||||
{
|
||||
ChannelManager manager = new ChannelManager();
|
||||
manager.server = server;
|
||||
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
private Channel* getChannel(string channel)
|
||||
{
|
||||
// Lock channels map
|
||||
this.channelsLock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock channels map
|
||||
this.channelsLock.unlock();
|
||||
}
|
||||
|
||||
// Return a Channel* IF
|
||||
// a value for that key exists
|
||||
return channel in this.channels;
|
||||
}
|
||||
|
||||
private Channel* channelGet(string channel)
|
||||
{
|
||||
return getChannel(channel);
|
||||
}
|
||||
|
||||
public bool channelExists(string channel)
|
||||
{
|
||||
return getChannel(channel) !is null;
|
||||
}
|
||||
|
||||
public bool channelCreate(string channel)
|
||||
{
|
||||
// Lock channels map
|
||||
this.channelsLock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock channels map
|
||||
this.channelsLock.unlock();
|
||||
}
|
||||
|
||||
if(channelExists(channel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add a new channel descriptor
|
||||
Channel channelDesc = Channel(channel);
|
||||
this.channels[channel] = channelDesc;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string[] getChannelNames(ulong offset, ubyte limit)
|
||||
{
|
||||
// Lock channels map
|
||||
this.channelsLock.lock();
|
||||
|
||||
// TODO: Implement offset and limit
|
||||
|
||||
// Adjust offset if it overshoots available
|
||||
// items
|
||||
if(!(offset < this.channels.length))
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
ulong upperBound = offset+limit;
|
||||
logger.dbg("Upper bound (before): ", upperBound);
|
||||
|
||||
if(upperBound >= this.channels.keys().length)
|
||||
{
|
||||
upperBound = this.channels.keys().length;
|
||||
}
|
||||
|
||||
logger.dbg("Upper bound (after): ", upperBound);
|
||||
logger.dbg("Limit: ", limit);
|
||||
|
||||
// Get the channels
|
||||
string[] channels = this.channels.keys()[offset..upperBound].dup;
|
||||
|
||||
// Unlock channels map
|
||||
this.channelsLock.unlock();
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
// NOTE: In future we could lock just the channel entry?
|
||||
// (once it has been found)
|
||||
public bool membershipJoin(string channel, string username)
|
||||
{
|
||||
// Lock channels map
|
||||
this.channelsLock.lock();
|
||||
|
||||
// TODO: Move lock
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock channels map
|
||||
this.channelsLock.unlock();
|
||||
}
|
||||
|
||||
// Get the channel, check for our own membership
|
||||
Channel* channelDesc = channelGet(channel);
|
||||
|
||||
// If not found, then that's an error
|
||||
if(channelDesc is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Run any policies here
|
||||
|
||||
// If not, then add user and it is fine
|
||||
//
|
||||
// Return value is whether or not
|
||||
// adding succeeded
|
||||
return channelDesc.addMember(username);
|
||||
|
||||
// TODO: Run notification hooks here on the server
|
||||
}
|
||||
|
||||
public bool membershipList(string channel, ref string[] membersList)
|
||||
{
|
||||
// Get the channel, check for our own membership
|
||||
Channel* channelDesc = channelGet(channel);
|
||||
|
||||
// If not found, then that's an error
|
||||
if(channelDesc is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If found, get the members
|
||||
membersList = channelDesc.getMembers();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool membershipLeave(string channel, string username)
|
||||
{
|
||||
// Lock channels map
|
||||
this.channelsLock.lock();
|
||||
|
||||
// TODO: Move lock
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock channels map
|
||||
this.channelsLock.unlock();
|
||||
}
|
||||
|
||||
// Get the channel, check for our own membership
|
||||
Channel* channelDesc = channelGet(channel);
|
||||
|
||||
// If not found, then that's an error
|
||||
if(channelDesc is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Run any policies here
|
||||
|
||||
// If present, then leave
|
||||
//
|
||||
// Return value is whether or not
|
||||
// removal succeeded
|
||||
return channelDesc.removeMember(username);
|
||||
|
||||
// TODO: Run notification hooks here on the server
|
||||
}
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
ChannelManager chanMan = new ChannelManager();
|
||||
|
||||
// TODO: Add testing here
|
||||
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
module renaissance.server.messagemanager;
|
||||
|
||||
import renaissance.server.server : Server;
|
||||
import std.container.dlist : DList;
|
||||
import core.sync.mutex : Mutex;
|
||||
import renaissance.logging;
|
||||
|
||||
/**
|
||||
* An in-memory representation of
|
||||
* a message
|
||||
*/
|
||||
public struct Message
|
||||
{
|
||||
private string destination;
|
||||
private string message;
|
||||
private string from;
|
||||
|
||||
/**
|
||||
* Constructs a new message
|
||||
*
|
||||
* Params:
|
||||
* destination = the destination
|
||||
* from = the from user
|
||||
* message = the message itself
|
||||
*/
|
||||
this(string destination, string from, string message)
|
||||
{
|
||||
this.destination = destination;
|
||||
this.from = from;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message's body
|
||||
*
|
||||
* Params:
|
||||
* message = the contents
|
||||
*/
|
||||
public void setBody(string message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the from paramneter
|
||||
*
|
||||
* Params:
|
||||
* from = the username
|
||||
*/
|
||||
public void setFrom(string from)
|
||||
{
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination of this message
|
||||
*
|
||||
* Params:
|
||||
* destination = the username
|
||||
*/
|
||||
public void setDestination(string destination)
|
||||
{
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of this
|
||||
* message
|
||||
*
|
||||
* Returns: the contents
|
||||
*/
|
||||
public string getBody()
|
||||
{
|
||||
return this.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the from parameter
|
||||
*
|
||||
* Returns: the username
|
||||
*/
|
||||
public string getFrom()
|
||||
{
|
||||
return this.from;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination
|
||||
*
|
||||
* Returns: the username
|
||||
*/
|
||||
public string getDestination()
|
||||
{
|
||||
return this.destination;
|
||||
}
|
||||
}
|
||||
|
||||
public enum QUEUE_DEFAULT_SIZE = 100;
|
||||
|
||||
public enum PolicyDecision
|
||||
{
|
||||
DROP_INCOMING,
|
||||
DROP_TAIL,
|
||||
ACCEPT
|
||||
}
|
||||
|
||||
// TODO: Templatize in the future on the T element type
|
||||
public class Queue
|
||||
{
|
||||
private size_t maxSize;
|
||||
private DList!(Message) queue;
|
||||
private Mutex lock;
|
||||
|
||||
public this(size_t maxSize = QUEUE_DEFAULT_SIZE)
|
||||
{
|
||||
this.lock = new Mutex();
|
||||
}
|
||||
|
||||
public void enqueue(Message message)
|
||||
{
|
||||
// Lock the queue
|
||||
this.lock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock the queue
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
// Apply queuing policy
|
||||
PolicyDecision decision = policyCheck();
|
||||
logger.dbg("Queue decision: ", decision);
|
||||
|
||||
// If we should tail-drop
|
||||
if(decision == PolicyDecision.DROP_TAIL)
|
||||
{
|
||||
// Drop tail
|
||||
this.queue.removeBack();
|
||||
}
|
||||
// If we should drop the incoming
|
||||
else if(decision == PolicyDecision.DROP_INCOMING)
|
||||
{
|
||||
// Do not insert
|
||||
return;
|
||||
}
|
||||
// Accept
|
||||
else if(decision == PolicyDecision.ACCEPT)
|
||||
{
|
||||
// Fall through
|
||||
}
|
||||
|
||||
// Enqueue
|
||||
this.queue.insertAfter(this.queue[], message);
|
||||
}
|
||||
|
||||
private PolicyDecision policyCheck() // NOTE: In future must use lock if decision requires anlysing internal queue
|
||||
{
|
||||
// TODO: Implement me
|
||||
return PolicyDecision.ACCEPT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an interface of methods
|
||||
* which are to be called whenever
|
||||
* new messages are enqueued onto
|
||||
* a so-called "incoming" (recv-q)
|
||||
* and "outgoing" (send-q) queues
|
||||
*
|
||||
* The `MessageManager` will use
|
||||
* these as the hooks it applies
|
||||
* to its send/recv queues.
|
||||
*
|
||||
* An example usage of this is
|
||||
* to allow `Server` to get notified
|
||||
* whenever a new item appears.
|
||||
*/
|
||||
public interface MessageDeliveryTransport
|
||||
{
|
||||
/**
|
||||
* Called when a message has just been
|
||||
* enqueued to the incoming queue
|
||||
*
|
||||
* Params:
|
||||
* latest = the latest message
|
||||
* from = the queue
|
||||
* Returns: `true` if you handled
|
||||
* this without error, `false`
|
||||
* otherwise
|
||||
*/
|
||||
public bool onIncoming(Message latest, Queue from);
|
||||
|
||||
/**
|
||||
* Called when a message has just been
|
||||
* enqueued to the outgoing queue
|
||||
*
|
||||
* Params:
|
||||
* latest = the latest message
|
||||
* from = the queue
|
||||
* Returns: `true` if you handled
|
||||
* this without error, `false`
|
||||
* otherwise
|
||||
*/
|
||||
public bool onOutgoing(Message latest, Queue from);
|
||||
}
|
||||
|
||||
// TODO: Should have a thread that manages
|
||||
// ... message delivery by just calling something
|
||||
// ... in server (it must handle encoding and
|
||||
// ... so forth)
|
||||
public class MessageManager
|
||||
{
|
||||
private MessageDeliveryTransport transport;
|
||||
|
||||
private Queue sendQueue;
|
||||
private Queue receiveQueue;
|
||||
|
||||
private this()
|
||||
{
|
||||
// Initialize the queues (send+receive)
|
||||
this.sendQueue = new Queue();
|
||||
this.receiveQueue = new Queue();
|
||||
}
|
||||
|
||||
public void sendq(Message message)
|
||||
{
|
||||
logger.info("Received message for sending: ", message);
|
||||
|
||||
// Enqueue to send-q
|
||||
this.sendQueue.enqueue(message);
|
||||
}
|
||||
|
||||
public void recvq(Message message)
|
||||
{
|
||||
logger.info("Received message for reception: ", message);
|
||||
|
||||
// Enqueue to recv-q
|
||||
this.receiveQueue.enqueue(message);
|
||||
}
|
||||
|
||||
public static MessageManager create(MessageDeliveryTransport transport)
|
||||
{
|
||||
MessageManager manager = new MessageManager();
|
||||
manager.transport = transport;
|
||||
|
||||
|
||||
return manager;
|
||||
}
|
||||
}
|
|
@ -7,13 +7,16 @@ import std.algorithm : canFind;
|
|||
import renaissance.exceptions;
|
||||
import renaissance.connection;
|
||||
import renaissance.logging;
|
||||
import renaissance.server.channelmanager;
|
||||
import renaissance.server.users;
|
||||
import renaissance.server.messagemanager;
|
||||
|
||||
/**
|
||||
* Represents an instance of the daemon which manages
|
||||
* all listeners attached to it, server state and
|
||||
* message processing
|
||||
*/
|
||||
public class Server
|
||||
public class Server : MessageDeliveryTransport
|
||||
{
|
||||
// TODO: array of listeners
|
||||
private SList!(Listener) listenerQ;
|
||||
|
@ -26,12 +29,32 @@ public class Server
|
|||
// TODO: volatility
|
||||
private bool isRunning = false;
|
||||
|
||||
// TODO: Add constructor
|
||||
private ChannelManager channelManager;
|
||||
|
||||
// TODO: Some sendq/recq mechanism with messages or something
|
||||
// ... should be placed here
|
||||
|
||||
private AuthManager authManager;
|
||||
|
||||
private MessageManager messageManager;
|
||||
|
||||
/**
|
||||
* Constructs a new server
|
||||
*/
|
||||
this()
|
||||
{
|
||||
/* Initialize all mutexes */
|
||||
this.listenerQLock = new Mutex();
|
||||
this.connectionQLock = new Mutex();
|
||||
|
||||
/* Initialize the channel management sub-system */
|
||||
this.channelManager = ChannelManager.create(this);
|
||||
|
||||
/* Initialize the authentication management sub-system */
|
||||
this.authManager = AuthManager.create(this); // TODO: Set custo provder here based on argument to this constructor
|
||||
|
||||
/* Initialize the message management sub-system */
|
||||
this.messageManager = MessageManager.create(this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -174,6 +197,99 @@ public class Server
|
|||
/* Unlock the connection queue */
|
||||
connectionQLock.unlock();
|
||||
}
|
||||
|
||||
public bool attemptAuth(string username, string password)
|
||||
{
|
||||
logger.dbg("Attempting auth with user '", username, "' and password '", password, "'");
|
||||
|
||||
return this.authManager.authenticate(username, password);
|
||||
}
|
||||
|
||||
public string[] getChannelNames(ulong offset, ubyte limit)
|
||||
{
|
||||
// TODO: Implement me
|
||||
return this.channelManager.getChannelNames(offset, limit);
|
||||
}
|
||||
|
||||
public ChannelManager getChannelManager()
|
||||
{
|
||||
return this.channelManager;
|
||||
}
|
||||
|
||||
public MessageManager getMessageManager()
|
||||
{
|
||||
return this.messageManager;
|
||||
}
|
||||
|
||||
// On incoming message
|
||||
public bool onIncoming(Message latest, Queue from)
|
||||
{
|
||||
// TODO: Implement me
|
||||
logger.info("Incoming stub with latest ", latest, "from queue ", from);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// On message that must be egressed
|
||||
public bool onOutgoing(Message latest, Queue from)
|
||||
{
|
||||
// TODO: Implement me
|
||||
logger.info("Outgoing stub with latest ", latest, "from queue ", from);
|
||||
|
||||
// Lookup the user (source)
|
||||
User* fromUser = this.authManager.getUser(latest.getFrom());
|
||||
|
||||
// Lookup the user (destination)
|
||||
User* toUser = this.authManager.getUser(latest.getDestination());
|
||||
|
||||
|
||||
if(fromUser == null)
|
||||
{
|
||||
// TODO: Handle this
|
||||
logger.warn("Could not find fromUser (User* was null)");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.dbg("Found fromUser (User*)", fromUser.toString());
|
||||
}
|
||||
|
||||
if(toUser == null)
|
||||
{
|
||||
// TODO: Handle this
|
||||
logger.warn("Could not find toUser (User* was null)");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.dbg("Found toUser (User*)", toUser.toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// On connection disconnecting
|
||||
public void onConnectionDisconnect(Connection connection)
|
||||
{
|
||||
// TODO: Decide whether it is a user link or a server de-link
|
||||
|
||||
import renaissance.connection.connection : LinkType;
|
||||
LinkType type = connection.getLinkType();
|
||||
logger.dbg("Disconnecting link ", connection, " of type ", type);
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case LinkType.UNSET:
|
||||
logger.warn("Not doing anything because this link's type was never set");
|
||||
break;
|
||||
case LinkType.USER:
|
||||
// TODO: Implement me
|
||||
break;
|
||||
case LinkType.SERVER:
|
||||
// TODO: Implement me
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
version(unittest)
|
||||
|
@ -184,62 +300,64 @@ version(unittest)
|
|||
// TODO: Building a testing client with the imports below
|
||||
import std.socket;
|
||||
import tristanable.manager;
|
||||
import tristanable.queue;
|
||||
// import tristanable.queue;
|
||||
import tristanable.encoding;
|
||||
import core.thread;
|
||||
|
||||
import dante;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
/**
|
||||
* Setup a `Server` instance followed by
|
||||
* creating a single listener, after this
|
||||
* start the server
|
||||
*/
|
||||
Server server = new Server();
|
||||
// Address listenAddr = parseAddress("::1", 9091);
|
||||
Address listenAddr = new UnixAddress("/tmp/renaissance2.sock");
|
||||
StreamListener streamListener = StreamListener.create(server, listenAddr);
|
||||
server.start();
|
||||
// unittest
|
||||
// {
|
||||
// /**
|
||||
// * Setup a `Server` instance followed by
|
||||
// * creating a single listener, after this
|
||||
// * start the server
|
||||
// */
|
||||
// Server server = new Server();
|
||||
// // Address listenAddr = parseAddress("::1", 9091);
|
||||
// Address listenAddr = new UnixAddress("/tmp/renaissance2.sock");
|
||||
// StreamListener streamListener = StreamListener.create(server, listenAddr);
|
||||
// server.start();
|
||||
|
||||
scope(exit)
|
||||
{
|
||||
import std.stdio;
|
||||
remove((cast(UnixAddress)listenAddr).path().ptr);
|
||||
}
|
||||
// scope(exit)
|
||||
// {
|
||||
// import std.stdio;
|
||||
// remove((cast(UnixAddress)listenAddr).path().ptr);
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Create a few clients here (TODO: We'd need the client code)
|
||||
// */
|
||||
// for(ulong idx = 0; idx < 10; idx++)
|
||||
// {
|
||||
// Socket clientSocket = new Socket(listenAddr.addressFamily(), SocketType.STREAM);
|
||||
// clientSocket.connect(listenAddr);
|
||||
// Manager manager = new Manager(clientSocket);
|
||||
// Queue myQueue = new Queue(69);
|
||||
// manager.registerQueue(myQueue);
|
||||
// manager.start();
|
||||
// // /**
|
||||
// // * Create a few clients here (TODO: We'd need the client code)
|
||||
// // */
|
||||
// // for(ulong idx = 0; idx < 10; idx++)
|
||||
// // {
|
||||
// // Socket clientSocket = new Socket(listenAddr.addressFamily(), SocketType.STREAM);
|
||||
// // clientSocket.connect(listenAddr);
|
||||
// // Manager manager = new Manager(clientSocket);
|
||||
// // Queue myQueue = new Queue(69);
|
||||
// // manager.registerQueue(myQueue);
|
||||
// // manager.start();
|
||||
|
||||
// // Thread.sleep(dur!("seconds")(2));
|
||||
// TaggedMessage myMessage = new TaggedMessage(69, cast(byte[])"ABBA");
|
||||
// manager.sendMessage(myMessage);
|
||||
// manager.sendMessage(myMessage);
|
||||
// // Thread.sleep(dur!("seconds")(2));
|
||||
// manager.sendMessage(myMessage);
|
||||
// manager.sendMessage(myMessage);
|
||||
// }
|
||||
// // // Thread.sleep(dur!("seconds")(2));
|
||||
// // TaggedMessage myMessage = new TaggedMessage(69, cast(byte[])"ABBA");
|
||||
// // manager.sendMessage(myMessage);
|
||||
// // manager.sendMessage(myMessage);
|
||||
// // // Thread.sleep(dur!("seconds")(2));
|
||||
// // manager.sendMessage(myMessage);
|
||||
// // manager.sendMessage(myMessage);
|
||||
// // }
|
||||
|
||||
|
||||
DanteClient client = new DanteClient(new UnixAddress("/tmp/renaissance2.sock"));
|
||||
// DanteClient client = new DanteClient(new UnixAddress("/tmp/renaissance2.sock"));
|
||||
|
||||
client.start();
|
||||
// client.start();
|
||||
|
||||
client.nopRequest();
|
||||
client.nopRequest();
|
||||
// client.nopRequest();
|
||||
// client.nopRequest();
|
||||
|
||||
|
||||
// while(true)
|
||||
// Thread.sleep(dur!("seconds")(20));
|
||||
}
|
||||
// // while(true)
|
||||
// // {
|
||||
// // Thread.sleep(dur!("seconds")(20));
|
||||
// // }
|
||||
// }
|
|
@ -0,0 +1,339 @@
|
|||
module renaissance.server.users;
|
||||
|
||||
import core.sync.mutex : Mutex;
|
||||
|
||||
public struct Option
|
||||
{
|
||||
// TODO: Implement me
|
||||
private string name;
|
||||
private ubyte[] value;
|
||||
private string description;
|
||||
private Mutex lock;
|
||||
|
||||
@disable
|
||||
private this();
|
||||
|
||||
public this(string name)
|
||||
{
|
||||
this.lock = new Mutex();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public string getName()
|
||||
{
|
||||
// Lock
|
||||
this.lock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
// NOTE: String is immutable but incase of cast
|
||||
return name.dup;
|
||||
}
|
||||
|
||||
public void setName(string name)
|
||||
{
|
||||
// Lock
|
||||
this.lock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
// Copy the argument
|
||||
this.name = name.dup;
|
||||
}
|
||||
|
||||
public ubyte[] getValue()
|
||||
{
|
||||
// Lock
|
||||
this.lock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return this.value.dup;
|
||||
}
|
||||
|
||||
public void setValue(ubyte[] value)
|
||||
{
|
||||
// Lock
|
||||
this.lock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
// Copy the argument
|
||||
this.value = value.dup;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Status
|
||||
{
|
||||
ONLINE,
|
||||
OFFLINE,
|
||||
INVISIBLE,
|
||||
AWAY
|
||||
}
|
||||
|
||||
public struct User
|
||||
{
|
||||
private string username;
|
||||
private Status status;
|
||||
private Option*[string] options; // Profile key-value
|
||||
private Mutex lock;
|
||||
|
||||
@disable
|
||||
private this();
|
||||
|
||||
this(string username)
|
||||
{
|
||||
this.lock = new Mutex();
|
||||
setUsername(username);
|
||||
}
|
||||
|
||||
// TODO: Disallow parameter less construction?
|
||||
|
||||
public bool setUsername(string username)
|
||||
{
|
||||
// Username cannot be empty (TODO: Have a regex check)
|
||||
if(username.length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lock
|
||||
this.lock.lock();
|
||||
|
||||
// Set the username
|
||||
this.username = username;
|
||||
|
||||
// Unlock
|
||||
this.lock.unlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string getUsername()
|
||||
{
|
||||
string usernameCpy;
|
||||
|
||||
// Lock
|
||||
this.lock.lock();
|
||||
|
||||
// Get the username
|
||||
usernameCpy = this.username;
|
||||
|
||||
// Unlock
|
||||
this.lock.unlock();
|
||||
|
||||
return usernameCpy;
|
||||
}
|
||||
|
||||
public Status getStatus()
|
||||
{
|
||||
Status statusCpy;
|
||||
|
||||
// Lock
|
||||
this.lock.lock();
|
||||
|
||||
// Get the status
|
||||
statusCpy = this.status;
|
||||
|
||||
// Unlock
|
||||
this.lock.unlock();
|
||||
|
||||
return statusCpy;
|
||||
}
|
||||
|
||||
public void addOption(Option* option)
|
||||
{
|
||||
// Lock
|
||||
this.lock.lock();
|
||||
|
||||
// Insert the option
|
||||
this.options[option.getName()] = option;
|
||||
|
||||
// Unlock
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
public string toString()
|
||||
{
|
||||
import std.conv : to;
|
||||
return "User [username: "~getUsername()~", status: "~to!(string)(getStatus())~"]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unittest
|
||||
{
|
||||
User u = User("deavmi");
|
||||
assert(u.getUsername(), "deavmi");
|
||||
|
||||
// Change the username
|
||||
u.setUsername("gustav");
|
||||
assert(u.getUsername(), "gustav");
|
||||
|
||||
}
|
||||
|
||||
public interface AuthProvider
|
||||
{
|
||||
public bool authenticate(string username, string password);
|
||||
}
|
||||
|
||||
public class DummyProvider : AuthProvider
|
||||
{
|
||||
public bool authenticate(string username, string password)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
import renaissance.server.server : Server;
|
||||
import renaissance.logging;
|
||||
|
||||
// Should handle all users authenticated and
|
||||
// act as an information base for the current
|
||||
// users
|
||||
public class AuthManager
|
||||
{
|
||||
private Server server;
|
||||
|
||||
// TODO: Need an AuthProvider here
|
||||
private AuthProvider provider;
|
||||
|
||||
/**
|
||||
* TODO: We need to find a way to easily
|
||||
* manage User* mapped to by a string (username)
|
||||
* and how updating the username (key) would
|
||||
* work (including the allocated value)
|
||||
* then
|
||||
*
|
||||
* Update: We won't expose this User*
|
||||
* to the public API as that means
|
||||
* you can manipulate the user
|
||||
* there (that is fine) but ALSO
|
||||
* replace the entire user there
|
||||
*
|
||||
* Nah, forget the above we should discern
|
||||
* between username (never changing)
|
||||
* and nick
|
||||
*
|
||||
* UPDATE2: We will STILL need to index (somehow)
|
||||
* on that then, perhaps a seperate
|
||||
*
|
||||
* What is the point of usernames? for
|
||||
* auth but then nick is what people _should_
|
||||
* see when you `membershipList()`.
|
||||
*
|
||||
* So we would need to update
|
||||
* ChannelManager code to do that
|
||||
*
|
||||
*/
|
||||
private User*[string] users;
|
||||
private Mutex usersLock;
|
||||
|
||||
private this(AuthProvider provider)
|
||||
{
|
||||
this.usersLock = new Mutex();
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
// NOTE: Don't try de-allocate it, smart ass
|
||||
public User* getUser(string username)
|
||||
{
|
||||
// Lock
|
||||
this.usersLock.lock();
|
||||
|
||||
// On exit
|
||||
scope(exit)
|
||||
{
|
||||
// Unlock
|
||||
this.usersLock.unlock();
|
||||
}
|
||||
|
||||
// Check if such a user exists
|
||||
User** potentialUserPtrPtr = username in this.users;
|
||||
if(potentialUserPtrPtr == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *potentialUserPtrPtr;
|
||||
}
|
||||
}
|
||||
|
||||
private void addUser(string username)
|
||||
{
|
||||
// Lock
|
||||
this.usersLock.lock();
|
||||
|
||||
// Create the user and insert it
|
||||
User* newUser = new User(username);
|
||||
this.users[username] = newUser;
|
||||
|
||||
// Unlock
|
||||
this.usersLock.unlock();
|
||||
}
|
||||
|
||||
private void removeUser(string username)
|
||||
{
|
||||
// Lock
|
||||
this.usersLock.lock();
|
||||
|
||||
// Remove the user
|
||||
this.users.remove(username);
|
||||
|
||||
// Unlock
|
||||
this.usersLock.unlock();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public bool authenticate(string username, string password)
|
||||
{
|
||||
logger.dbg("Authentication request for user '"~username~"' with password '"~password~"'");
|
||||
bool status;
|
||||
|
||||
// TODO: Disallow the username from being empty
|
||||
|
||||
status = this.provider.authenticate(username, password);
|
||||
if(status)
|
||||
{
|
||||
addUser(username);
|
||||
logger.info("Authenticated user '"~username~"'");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error("Authentication failed for user '"~username~"'");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public static AuthManager create(Server server, AuthProvider provider = new DummyProvider())
|
||||
{
|
||||
AuthManager manager = new AuthManager(provider);
|
||||
manager.server = server;
|
||||
|
||||
|
||||
return manager;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue