mirror of https://github.com/deavminet/dnetd-ng
Start a new
This commit is contained in:
parent
051595a377
commit
4fde6b1a6b
5
dub.json
5
dub.json
|
@ -4,9 +4,8 @@
|
|||
],
|
||||
"copyright": "Copyright © 2021, Tristan B. Kildaire",
|
||||
"dependencies": {
|
||||
"dlog": "~>0.0.5",
|
||||
"gogga": "~>0.1.3",
|
||||
"jcli": "~>0.24.0"
|
||||
"dlog": "~>0.0.6",
|
||||
"tasky": "~>1.2.0"
|
||||
},
|
||||
"description": "Official implementation of the DNET protocol for the server-side",
|
||||
"license": "AGPLv3",
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"fileVersion": 1,
|
||||
"versions": {
|
||||
"bformat": "3.1.3",
|
||||
"dlog": "0.0.6",
|
||||
"eventy": "0.2.4",
|
||||
"tasky": "1.2.0",
|
||||
"tristanable": "2.6.12"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import std.stdio;
|
||||
|
||||
void main()
|
||||
{
|
||||
writeln("Hello world");
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/**
|
||||
* Main module
|
||||
*/
|
||||
module dnetd.app;
|
||||
|
||||
import dlog;
|
||||
import dnetd.exceptions : GeneralException;
|
||||
import std.json : JSONValue;
|
||||
import dnetd.config : Configuration;
|
||||
import dnetd.server : Server;
|
||||
|
||||
public Logger logger;
|
||||
string VERSION = "0.0.1";
|
||||
|
||||
void main()
|
||||
{
|
||||
/* Setup the logger */
|
||||
logger = new DefaultLogger();
|
||||
|
||||
logger.log("Welcome to dnetd v"~VERSION);
|
||||
|
||||
/* TODO: Add jcli handling here */
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
Configuration config = Configuration.getConfig("config.json");
|
||||
|
||||
/* TODO: Server init with config here */
|
||||
Server server = new Server(config);
|
||||
|
||||
}
|
||||
catch(GeneralException e)
|
||||
{
|
||||
logger.log(e.toString());
|
||||
}
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
/**
|
||||
* Configuration sub-system
|
||||
*/
|
||||
module dnetd.config;
|
||||
|
||||
import dnetd.app : logger;
|
||||
import std.json : JSONValue, JSONException, parseJSON;
|
||||
import dnetd.exceptions;
|
||||
import std.stdio : File;
|
||||
import std.string : cmp, strip;
|
||||
|
||||
/**
|
||||
* In its instance-form this represents a read-in and parsed configuration
|
||||
* of the configuration file in an OOP format, with the ability to write back
|
||||
* any modifications made to it (TODO: Add this).
|
||||
*
|
||||
* In static use it provides the ability to translate between the JSON configuration
|
||||
* file and the instance (OOP) form.
|
||||
*/
|
||||
public final class Configuration
|
||||
{
|
||||
string configPath;
|
||||
NetworkInformation netInfo;
|
||||
|
||||
public static Configuration getConfig(string configPath)
|
||||
{
|
||||
JSONValue jsonConfig = readConfig("config.json");
|
||||
Configuration config = Configuration.fromJSON(jsonConfig);
|
||||
|
||||
/* Save the path so we can rehash later on */
|
||||
config.configPath = configPath;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads in the JSON from the given path to the configuration
|
||||
* file
|
||||
*
|
||||
* On error throws TODO
|
||||
*/
|
||||
private static JSONValue readConfig(string path)
|
||||
{
|
||||
File file;
|
||||
file.open(path); /* TODO:Check this for errors */
|
||||
/* TODO: Only open with read rights */
|
||||
|
||||
/* Allocate a buffer for the file */
|
||||
byte[] contents;
|
||||
contents.length = file.size(); /* TODO: Check size here */
|
||||
|
||||
/* TODO: Check this */
|
||||
/* TODO: Technically the below is fine */
|
||||
file.rawRead(contents);
|
||||
|
||||
JSONValue config;
|
||||
|
||||
try
|
||||
{
|
||||
config = parseJSON(cast(string)contents);
|
||||
}
|
||||
catch(JSONException e)
|
||||
{
|
||||
/* TODO: Get specific error here to show where config syntax is wrong */
|
||||
|
||||
|
||||
throw new ConfigurationError(e);
|
||||
}
|
||||
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the configuration from a JSON source, returning the
|
||||
* configuration as a Configuration object, on error, null (TODO: Throw exception rather)
|
||||
*
|
||||
* @param jsonConfig the JSONValue configuration
|
||||
*/
|
||||
private static Configuration fromJSON(JSONValue jsonConfig)
|
||||
{
|
||||
Configuration config = new Configuration();
|
||||
|
||||
try
|
||||
{
|
||||
/* TODO: Parse config here */
|
||||
|
||||
/* Retrieve the `network` block */
|
||||
JSONValue networkBlock = jsonConfig["network"];
|
||||
|
||||
config.netInfo = confNetInfo(networkBlock["info"]);
|
||||
// TODO: Log the above logger.log()
|
||||
|
||||
|
||||
/* Retrieve the `accounting` block */
|
||||
JSONValue accountingBlock = jsonConfig["accounting"];
|
||||
|
||||
/* Retrieve the `links` block */
|
||||
JSONValue linksBlock = jsonConfig["links"];
|
||||
|
||||
|
||||
}
|
||||
catch(JSONException e)
|
||||
{
|
||||
/* TODO: Throw proper error here saying which key is missing */
|
||||
throw new ConfigurationError(e);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private static NetworkInformation confNetInfo(JSONValue infoBlock)
|
||||
{
|
||||
NetworkInformation netInfo;
|
||||
|
||||
/**
|
||||
* TODO (keycheck): Make sure that `serverName` is present
|
||||
* and `networkName`
|
||||
*
|
||||
* `motd` is optional
|
||||
*/
|
||||
bool foundNetworkName;
|
||||
bool foundServerName;
|
||||
|
||||
string[] keys = infoBlock.object.keys();
|
||||
foreach(string key; keys)
|
||||
{
|
||||
if(cmp(key, "serverName") == 0)
|
||||
{
|
||||
foundServerName = true;
|
||||
netInfo.serverName = infoBlock[key].str();
|
||||
}
|
||||
else if(cmp(key, "networkName") == 0)
|
||||
{
|
||||
foundNetworkName = true;
|
||||
netInfo.networkName = infoBlock[key].str();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make sure we found the required fields
|
||||
*
|
||||
* These are: serverName, networkName, sid (TODO: Finish these)
|
||||
*/
|
||||
if(!foundServerName)
|
||||
{
|
||||
throw new ConfigurationError("Missing server name");
|
||||
}
|
||||
else if(!foundNetworkName)
|
||||
{
|
||||
throw new ConfigurationError("Missing network name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip whitespace from the beginnings and endings of
|
||||
* `serverName`, `networkName`, (TODO: Complete list)
|
||||
*/
|
||||
netInfo.serverName = strip(netInfo.serverName);
|
||||
netInfo.networkName = strip(netInfo.networkName);
|
||||
|
||||
/**
|
||||
* Make sure the required fields were set to a valid
|
||||
* value
|
||||
*/
|
||||
if(cmp(netInfo.networkName, "") == 0)
|
||||
{
|
||||
throw new ConfigurationError("Network name cannot be empty");
|
||||
}
|
||||
else if(cmp(netInfo.serverName, "") == 0)
|
||||
{
|
||||
throw new ConfigurationError("Server name cannot be empty");
|
||||
}
|
||||
|
||||
/* TODO: Add handling for motd and motdFile */
|
||||
|
||||
|
||||
return netInfo;
|
||||
}
|
||||
|
||||
private this()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Configuration error
|
||||
*/
|
||||
public final class ConfigurationError : GeneralException
|
||||
{
|
||||
this(string message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
this(JSONException e)
|
||||
{
|
||||
super("JSON configuration has a syntax error ("~e.msg~")");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Network information
|
||||
*
|
||||
* This holds information that pertains to network-wide
|
||||
* information (such as the network's name) and server-specific
|
||||
* information such as the message-of-the-day or the server's
|
||||
* name
|
||||
*/
|
||||
struct NetworkInformation
|
||||
{
|
||||
string serverName;
|
||||
string motd;
|
||||
ushort sid;
|
||||
|
||||
string networkName;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* Connection handling sub-system
|
||||
*/
|
||||
module dnetd.connection.connection;
|
||||
|
||||
import core.thread : Thread;
|
||||
import dnetd.server : Server;
|
||||
|
||||
/**
|
||||
* Represents a client's/server's connection to
|
||||
* this server
|
||||
*
|
||||
* These are normally spawned by the `serviceLoop`
|
||||
* of Listener sub-classes
|
||||
*/
|
||||
public abstract class Connection : Thread
|
||||
{
|
||||
/**
|
||||
* The server instance this Connection
|
||||
* is associated with
|
||||
*/
|
||||
private Server server;
|
||||
|
||||
this(Server server)
|
||||
{
|
||||
super(&handler);
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection handler
|
||||
*
|
||||
* This is to be implemented by sub-classes
|
||||
*/
|
||||
public abstract void handler();
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/**
|
||||
* Socket-based connection handler sub-system
|
||||
*/
|
||||
|
||||
import dnetd.connection.connection : Connection;
|
||||
import std.socket;
|
||||
import dnetd.server : Server;
|
||||
|
||||
/**
|
||||
* FIXME: When we do anything so far, I am assuming
|
||||
* a streaming socket so we shouldn't let the
|
||||
* SocketListener use any SocketType that isn't STREAM
|
||||
*/
|
||||
public final class SocketConnection : Connection
|
||||
{
|
||||
private Socket socket;
|
||||
|
||||
this(Server server, Socket socket)
|
||||
{
|
||||
super(server);
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public override void handler()
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* Exceptions sub-system
|
||||
*/
|
||||
module dnetd.exceptions;
|
||||
|
||||
import std.exception;
|
||||
|
||||
/**
|
||||
* Base class for all DNETD exceptions
|
||||
*/
|
||||
public class GeneralException : Exception
|
||||
{
|
||||
/* Name of exception type and description */
|
||||
private string errorTypeName;
|
||||
private string message;
|
||||
|
||||
/**
|
||||
* TODO: Make this take in a `string message`
|
||||
*/
|
||||
this(string message)
|
||||
{
|
||||
import std.string : split;
|
||||
string[] fragments = split(this.classinfo.name, ".");
|
||||
errorTypeName = fragments[fragments.length-1];
|
||||
this.message = message;
|
||||
super(null);
|
||||
}
|
||||
|
||||
public override string toString()
|
||||
{
|
||||
return errorTypeName~": "~message;
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* Module for listeners
|
||||
*
|
||||
* These include the base listener class responsible
|
||||
* for managing a connection with peers and some
|
||||
* concrete classes implementing this for various
|
||||
* network protocols
|
||||
*/
|
||||
module dnetd.listeners.listeners;
|
||||
|
||||
import dnetd.server : Server;
|
||||
import dnetd.exceptions : GeneralException;
|
||||
import std.exception;
|
||||
import core.thread : Thread;
|
||||
|
||||
public abstract class Listener : Thread
|
||||
{
|
||||
private Server server;
|
||||
|
||||
this(Server server)
|
||||
{
|
||||
super(&serviceLoop);
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* The connection accepting loop of which
|
||||
* is provided as the "worker" function to
|
||||
* the thread
|
||||
*/
|
||||
public abstract void serviceLoop();
|
||||
}
|
||||
|
||||
public abstract class ListenerException : GeneralException
|
||||
{
|
||||
/* TODO: Potentially remove `listener` */
|
||||
this(Listener listener, string msg)
|
||||
{
|
||||
/* TODO: Set message here */
|
||||
super(msg);
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/**
|
||||
* Socket-based listener for supporting any
|
||||
* network protocol that Linux supports, such as
|
||||
* TCP (STREAM-based) on IPv4 and IPv6 and UNIX
|
||||
* domain sockets... to name a few.
|
||||
*/
|
||||
module dnetd.listeners.socket;
|
||||
|
||||
import dnetd.listeners.listeners;
|
||||
import dnetd.server : Server;
|
||||
import std.socket;
|
||||
import std.conv : to;
|
||||
|
||||
public final class SocketListenerException : ListenerException
|
||||
{
|
||||
this(SocketListener e)
|
||||
{
|
||||
string msg = to!(string)(e.getAddress().addressFamily)
|
||||
~
|
||||
"-type socket listener error";
|
||||
|
||||
super(e, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public final class SocketListener : Listener
|
||||
{
|
||||
private Socket socket;
|
||||
private Address address;
|
||||
|
||||
this(Server server, Address address, SocketType type, ProtocolType proto)
|
||||
{
|
||||
super(server);
|
||||
this.address = address;
|
||||
|
||||
try
|
||||
{
|
||||
socket = new Socket(address.addressFamily, type, proto);
|
||||
socket.bind(address);
|
||||
}
|
||||
catch(SocketOSException e)
|
||||
{
|
||||
throw new SocketListenerException(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Socket accept-and-connection spawner
|
||||
* loop
|
||||
*/
|
||||
public override void serviceLoop()
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
Socket clientSock = socket.accept();
|
||||
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
public Address getAddress()
|
||||
{
|
||||
return address;
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/**
|
||||
* Server sub-system
|
||||
*
|
||||
* This module pertains to the code required to stand-up a
|
||||
* singular instance of a server and all the state that
|
||||
* relates to it, such as users, server links and processing/handling
|
||||
* of messages and commands.
|
||||
*/
|
||||
module dnetd.server;
|
||||
|
||||
import dnetd.app : logger;
|
||||
import dnetd.config : ConfigurationError, Configuration;
|
||||
import std.container.slist : SList;
|
||||
import dnetd.listeners.listeners : Listener;
|
||||
import dnetd.connection.connection : Connection;
|
||||
import core.sync.mutex : Mutex;
|
||||
|
||||
/**
|
||||
* Represents an instance of a dnet server
|
||||
*/
|
||||
public final class Server
|
||||
{
|
||||
/* Server Configuration */
|
||||
private Configuration config;
|
||||
|
||||
/**
|
||||
* Listeners
|
||||
*/
|
||||
private SList!(Listener) listeners;
|
||||
private Mutex listenersMutex;
|
||||
|
||||
/**
|
||||
* Connected clients and servers
|
||||
*
|
||||
* Inbound and outbound
|
||||
*/
|
||||
private SList!(Connection) conns;
|
||||
private Mutex connsMutex;
|
||||
|
||||
|
||||
|
||||
|
||||
this(Configuration config)
|
||||
{
|
||||
logger.log("Server instance '"~"PUT ID HERE"~"' starting up...");
|
||||
this.config = config;
|
||||
|
||||
/**
|
||||
* Initialize all locks for data
|
||||
* structures
|
||||
*/
|
||||
connsMutex = new Mutex();
|
||||
listeners = new Mutex();
|
||||
}
|
||||
|
||||
/* Rehash server configuration */
|
||||
public void rehash()
|
||||
{
|
||||
try
|
||||
{
|
||||
config = Configuration.getConfig(config.configPath);
|
||||
}
|
||||
catch(ConfigurationError e)
|
||||
{
|
||||
/* TODO: Handle the error here by sending a server message */
|
||||
logger.log("Error whilst rehashing the configuration");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue