commit
b698576bce
|
@ -6,6 +6,19 @@ 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 class Connection : Thread
|
||||
{
|
||||
|
@ -104,6 +117,11 @@ public class Connection : Thread
|
|||
// ... 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
|
||||
|
@ -128,6 +146,7 @@ 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);
|
||||
|
||||
|
@ -135,136 +154,221 @@ public class Connection : Thread
|
|||
MessageType mType;
|
||||
Command responseCommand;
|
||||
CommandType responseType;
|
||||
Status responseStatus;
|
||||
|
||||
if(incomingCommandType == CommandType.NOP_COMMAND)
|
||||
/**
|
||||
* Perform validation before continueing
|
||||
*/
|
||||
if(cast(Validatable)incomingCommand)
|
||||
{
|
||||
import davinci.c2s.test;
|
||||
logger.dbg("We got a NOP");
|
||||
NopMessage nopMessage = cast(NopMessage)baseMessage.getCommand();
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.NOP_COMMAND;
|
||||
responseCommand = nopMessage;
|
||||
}
|
||||
// Handle authentication request
|
||||
else if(incomingCommandType == CommandType.AUTH_COMMAND)
|
||||
{
|
||||
import davinci.c2s.auth : AuthMessage, AuthResponse;
|
||||
|
||||
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
|
||||
this.myUsername = authMessage.getUsername();
|
||||
|
||||
AuthResponse authResp = new AuthResponse();
|
||||
if(status)
|
||||
Validatable validtabaleCommand = cast(Validatable)incomingCommand;
|
||||
string reason;
|
||||
if(!validtabaleCommand.validate(reason))
|
||||
{
|
||||
authResp.good();
|
||||
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;
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the different types of commands
|
||||
*/
|
||||
switch(incomingCommandType)
|
||||
{
|
||||
/**
|
||||
* Handle NOP commands
|
||||
*/
|
||||
case CommandType.NOP_COMMAND:
|
||||
{
|
||||
authResp.bad();
|
||||
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());
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.AUTH_RESPONSE;
|
||||
responseCommand = authResp;
|
||||
}
|
||||
// Handle channel list requests
|
||||
else if(incomingCommandType == CommandType.CHANNELS_ENUMERATE_REQ)
|
||||
{
|
||||
import davinci.c2s.channels : ChannelEnumerateRequest, ChannelEnumerateReply;
|
||||
// TODO: This is just for testing now - i intend to have a nice auth manager
|
||||
|
||||
|
||||
AuthResponse authResp = new AuthResponse();
|
||||
if(status)
|
||||
{
|
||||
authResp.good();
|
||||
|
||||
ChannelEnumerateRequest chanEnumReq = cast(ChannelEnumerateRequest)baseMessage.getCommand();
|
||||
ubyte limit = chanEnumReq.getLimit();
|
||||
ulong offset = chanEnumReq.getOffset();
|
||||
// Save username
|
||||
this.myUsername = authMessage.getUsername();
|
||||
}
|
||||
else
|
||||
{
|
||||
authResp.bad();
|
||||
}
|
||||
|
||||
string[] channelNames = this.associatedServer.getChannelNames(offset, limit);
|
||||
ChannelEnumerateReply chanEnumRep = new ChannelEnumerateReply(channelNames);
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.AUTH_RESPONSE;
|
||||
responseCommand = authResp;
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.CHANNELS_ENUMERATE_REP;
|
||||
responseCommand = chanEnumRep;
|
||||
}
|
||||
// Handle channel joins
|
||||
else if(incomingCommandType == CommandType.MEMBERSHIP_JOIN)
|
||||
{
|
||||
import davinci.c2s.channels : ChannelMembership;
|
||||
import renaissance.server.channelmanager : ChannelManager, Channel;
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Handle channel list requests
|
||||
*/
|
||||
case CommandType.CHANNELS_ENUMERATE_REQ:
|
||||
{
|
||||
// FIXME: Figure out how we want to do auth checks
|
||||
if(!isAuthd())
|
||||
{
|
||||
|
||||
ChannelMembership chanMemReq = cast(ChannelMembership)baseMessage.getCommand();
|
||||
string channel = chanMemReq.getChannel();
|
||||
}
|
||||
|
||||
ChannelEnumerateRequest chanEnumReq = cast(ChannelEnumerateRequest)baseMessage.getCommand();
|
||||
ubyte limit = chanEnumReq.getLimit();
|
||||
ulong offset = chanEnumReq.getOffset();
|
||||
|
||||
// Join the channel
|
||||
ChannelManager chanMan = this.associatedServer.getChannelManager();
|
||||
bool status = chanMan.membershipJoin(channel, this.myUsername); // TODO: Handle return value
|
||||
chanMemReq.replyGood();
|
||||
string[] channelNames = this.associatedServer.getChannelNames(offset, limit);
|
||||
ChannelEnumerateReply chanEnumRep = new ChannelEnumerateReply(channelNames);
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.MEMBERSHIP_JOIN_REP;
|
||||
responseCommand = chanMemReq;
|
||||
}
|
||||
// Handle channel membership requests
|
||||
else if(incomingCommandType == CommandType.MEMBERSHIP_LIST)
|
||||
{
|
||||
import davinci.c2s.channels : ChannelMembership;
|
||||
import renaissance.server.channelmanager : ChannelManager, Channel;
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.CHANNELS_ENUMERATE_REP;
|
||||
responseCommand = chanEnumRep;
|
||||
|
||||
logger.error("HALO");
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Handle channel joins
|
||||
*/
|
||||
case CommandType.MEMBERSHIP_JOIN:
|
||||
{
|
||||
ChannelMembership chanMemReq = cast(ChannelMembership)baseMessage.getCommand();
|
||||
string channel = chanMemReq.getChannel();
|
||||
|
||||
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();
|
||||
|
||||
// Obtain the current members
|
||||
ChannelManager chanMan = this.associatedServer.getChannelManager();
|
||||
string[] currentMembers;
|
||||
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: Handle return value
|
||||
bool status = chanMan.membershipList(channel, currentMembers);
|
||||
logger.dbg("Current members of '"~channel~"': ", currentMembers);
|
||||
chanMemReq.listReplyGood(currentMembers);
|
||||
// TODO: Get channel, lookup and do permission checks
|
||||
|
||||
mType = MessageType.CLIENT_TO_SERVER;
|
||||
responseType = CommandType.MEMBERSHIP_LIST_REP;
|
||||
responseCommand = chanMemReq;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// Handle channel leaves
|
||||
else if(incomingCommandType == CommandType.MEMBERSHIP_LEAVE)
|
||||
{
|
||||
import davinci.c2s.channels : ChannelMembership;
|
||||
import renaissance.server.channelmanager : ChannelManager, Channel;
|
||||
|
||||
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;
|
||||
}
|
||||
// Unsupported type for server
|
||||
else
|
||||
{
|
||||
import davinci.c2s.generic : UnknownCommandReply;
|
||||
import std.conv : to;
|
||||
logger.warn("Received unsupported message type", baseMessage);
|
||||
|
||||
// TODO: Generate error here
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
encode_n_send:
|
||||
|
||||
// Generate response
|
||||
response = new BaseMessage(mType, responseType, responseCommand);
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
module renaissance.server.messagemanager;
|
||||
|
||||
import renaissance.server.server : Server;
|
||||
import std.container.slist : SList;
|
||||
import core.sync.mutex : Mutex;
|
||||
import renaissance.logging;
|
||||
|
||||
public struct Message
|
||||
{
|
||||
private string destination;
|
||||
private string message;
|
||||
private string from;
|
||||
|
||||
this(string destination, string from, string message)
|
||||
{
|
||||
this.destination = destination;
|
||||
this.from = from;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public void setBody(string message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public void setFrom(string from)
|
||||
{
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public void setDestination(string destination)
|
||||
{
|
||||
this.destination = destination;
|
||||
}
|
||||
}
|
||||
|
||||
public enum QUEUE_DEFAULT_SIZE = 100;
|
||||
|
||||
public class Queue
|
||||
{
|
||||
private size_t maxSize;
|
||||
private SList!(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();
|
||||
}
|
||||
|
||||
// Enqueue
|
||||
this.queue.insertAfter(this.queue[], message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface MessageDeliveryTransport
|
||||
{
|
||||
// On incoming message
|
||||
public bool onIncoming(Message latest, Queue from);
|
||||
|
||||
// On message that must be egressed
|
||||
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);
|
||||
}
|
||||
|
||||
public void recvq(Message message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static MessageManager create(MessageDeliveryTransport transport)
|
||||
{
|
||||
MessageManager manager = new MessageManager();
|
||||
manager.transport = transport;
|
||||
|
||||
|
||||
return manager;
|
||||
}
|
||||
}
|
|
@ -8,13 +8,15 @@ 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;
|
||||
|
@ -32,6 +34,10 @@ public class Server
|
|||
// TODO: Some sendq/recq mechanism with messages or something
|
||||
// ... should be placed here
|
||||
|
||||
private AuthManager authManager;
|
||||
|
||||
private MessageManager messageManager;
|
||||
|
||||
/**
|
||||
* Constructs a new server
|
||||
*/
|
||||
|
@ -43,6 +49,12 @@ public class Server
|
|||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -190,9 +202,7 @@ public class Server
|
|||
{
|
||||
logger.dbg("Attempting auth with user '", username, "' and password '", password, "'");
|
||||
|
||||
// TODO: Implement me
|
||||
|
||||
return true;
|
||||
return this.authManager.authenticate(username, password);
|
||||
}
|
||||
|
||||
public string[] getChannelNames(ulong offset, ubyte limit)
|
||||
|
@ -205,6 +215,29 @@ public class Server
|
|||
{
|
||||
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);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
version(unittest)
|
||||
|
@ -222,57 +255,57 @@ version(unittest)
|
|||
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,324 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
User* foundUser;
|
||||
|
||||
// Lock
|
||||
this.usersLock.lock();
|
||||
|
||||
foundUser = this.users[username];
|
||||
|
||||
// Unlock
|
||||
this.usersLock.unlock();
|
||||
|
||||
return foundUser;
|
||||
}
|
||||
|
||||
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