|
@@ -0,0 +1,234 @@
|
|
|
+using Invercargill;
|
|
|
+
|
|
|
+namespace Riddle {
|
|
|
+
|
|
|
+ public class Server {
|
|
|
+
|
|
|
+ public const int REGISTRATION_TIMEOUT_US = 600000000;
|
|
|
+ private SocketService service;
|
|
|
+ private Gee.HashMap<string, Invercargill.Sequence<Registration>> registrations = new Gee.HashMap<string, Invercargill.Sequence<Registration>>();
|
|
|
+ private Gee.HashMap<string, Invercargill.Sequence<Riddle>> riddles = new Gee.HashMap<string, Invercargill.Sequence<Riddle>>();
|
|
|
+ private Gee.HashMap<string, NameInfo> names = new Gee.HashMap<string, NameInfo>();
|
|
|
+
|
|
|
+
|
|
|
+ public Server(uint16 port) throws Error {
|
|
|
+ service = new SocketService ();
|
|
|
+ service.add_inet_port (port, null);
|
|
|
+ service.start ();
|
|
|
+ }
|
|
|
+
|
|
|
+ public async void run() {
|
|
|
+ while(true) {
|
|
|
+ SocketConnection connection = null;
|
|
|
+ try {
|
|
|
+ print("Waiting for connection\n");
|
|
|
+ connection = yield service.accept_async(null);
|
|
|
+ print("New connection\n");
|
|
|
+ var dis = new DataInputStream(connection.input_stream);
|
|
|
+ var dos = new DataOutputStream(connection.output_stream);
|
|
|
+
|
|
|
+ var message = yield Message.from_stream(dis);
|
|
|
+ print(@"Received $(MessageType.to_string(message.message_type)) message\n");
|
|
|
+ var reply = service_message(message, (InetSocketAddress)connection.get_remote_address());
|
|
|
+ yield reply.send(dos);
|
|
|
+ yield dos.flush_async();
|
|
|
+ yield connection.close_async();
|
|
|
+ }
|
|
|
+ catch(Error e) {
|
|
|
+ warning(@"Error servicing connection: $(e.message)");
|
|
|
+ try{
|
|
|
+ yield connection.close_async();
|
|
|
+ }
|
|
|
+ catch(Error e2) {
|
|
|
+ warning(@"Error closing connection after initial error: $(e2.message)");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private Message service_message(Message msg, InetSocketAddress origin) throws Error {
|
|
|
+
|
|
|
+ switch (msg.message_type) {
|
|
|
+ case MessageType.JOIN:
|
|
|
+ return handle_register(msg, origin);
|
|
|
+ case MessageType.LEAVE:
|
|
|
+ return handle_deregister(msg, origin);
|
|
|
+ case MessageType.PROPOGATE:
|
|
|
+ return handle_propogate(msg);
|
|
|
+ case MessageType.WHO_IN:
|
|
|
+ return handle_who_in(msg);
|
|
|
+ case MessageType.WHO_IS:
|
|
|
+ return handle_who_is(msg);
|
|
|
+ case MessageType.CHALLENGE:
|
|
|
+ return handle_challenge(msg);
|
|
|
+ default:
|
|
|
+ return new Message(MessageType.ERROR, new string[] { "unknown-command" }, new string[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private Message handle_register(Message msg, InetSocketAddress origin) throws Error {
|
|
|
+ cleanup_registrations();
|
|
|
+ var port = uint.parse(msg.arguments[1]);
|
|
|
+ add_registration(msg.arguments[0], new InetSocketAddress.from_string(origin.address.to_string(), port));
|
|
|
+
|
|
|
+ var relevent = get_group_registrations(msg.arguments[0])
|
|
|
+ .where(r => r.address.address.to_string() != origin.address.to_string() && r.address.port != port);
|
|
|
+
|
|
|
+ if(relevent.any()) {
|
|
|
+ return new Message(MessageType.SEE_ALSO, new string[0], relevent
|
|
|
+ .select<string>(r => @"$(r.address.address.to_string()) $(r.address.port)")
|
|
|
+ .to_array());
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Message(MessageType.OK, new string[0], new string[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Message handle_deregister(Message msg, InetSocketAddress origin) throws Error {
|
|
|
+ remove_registration(msg.arguments[0], new InetSocketAddress.from_string(origin.address.to_string(), uint.parse(msg.arguments[1])));
|
|
|
+ return new Message(MessageType.OK, new string[0], new string[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Message handle_propogate(Message msg) throws Error {
|
|
|
+ var name = msg.arguments[0];
|
|
|
+ var ttl = int.parse(msg.arguments[1]);
|
|
|
+ var encoded_name_info = msg.items[0];
|
|
|
+
|
|
|
+ try {
|
|
|
+ if(name.has_suffix(".rns")) {
|
|
|
+ var name_info = new DecentralisedNameInfo.from_string(encoded_name_info);
|
|
|
+ if(name_info.name != name) {
|
|
|
+ return new Message(MessageType.NOT_ACCEPTED, new string[] { "100", "name-mismatch" }, new string[0]);
|
|
|
+ }
|
|
|
+ if(!add_name_info_if_latest(name_info)) {
|
|
|
+ return new Message(MessageType.NOT_ACCEPTED, new string[] { "104", "outdated" }, new string[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // TODO, implement Certified Names
|
|
|
+ return new Message(MessageType.ERROR, new string[0], new string[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch(NameInfoError.BAD_DATA e) {
|
|
|
+ return new Message(MessageType.NOT_ACCEPTED, new string[] { "101", "malformed-information" }, new string[0]);
|
|
|
+ }
|
|
|
+ catch(NameInfoError.INVALID e) {
|
|
|
+ return new Message(MessageType.NOT_ACCEPTED, new string[] { "102", "invalid-information" }, new string[0]);
|
|
|
+ }
|
|
|
+ catch(NameInfoError.NOT_IN_DATE e) {
|
|
|
+ return new Message(MessageType.NOT_ACCEPTED, new string[] { "103", "outside-date-range" }, new string[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Message(MessageType.OK, new string[0], new string[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Message handle_who_in(Message msg) throws Error {
|
|
|
+ cleanup_registrations();
|
|
|
+ var addresses = get_group_registrations(msg.arguments[0]).select<string>(r => @"$(r.address.address.to_string()) $(r.address.port)");
|
|
|
+ return new Message(MessageType.ANSWER, new string[0], addresses.to_array());
|
|
|
+ }
|
|
|
+
|
|
|
+ private Message handle_who_is(Message msg) throws Error {
|
|
|
+ var name = get_name_info(msg.arguments[0]);
|
|
|
+ if(name == null) {
|
|
|
+ return new Message(MessageType.UNKNOWN, new string[0], new string[0]);
|
|
|
+ }
|
|
|
+ return new Message(MessageType.ANSWER, new string[0], new string[] { name.get_encoded() });
|
|
|
+ }
|
|
|
+
|
|
|
+ private Message handle_challenge(Message msg) throws Error {
|
|
|
+ var group_id = msg.arguments[0];
|
|
|
+ var riddles_to_try = Invercargill.empty<Riddle>();
|
|
|
+ lock(riddles) {
|
|
|
+ if(!riddles.has_key(group_id)) {
|
|
|
+ return new Message(MessageType.UNKNOWN, new string[0], new string[0]);
|
|
|
+ }
|
|
|
+ riddles_to_try = riddles[group_id].to_sequence();
|
|
|
+ }
|
|
|
+
|
|
|
+ var challenges = ate(msg.items).select<Challenge>(item => new Challenge.from_string(item));
|
|
|
+ var answers = challenges.select<Answer?>(c => riddles_to_try.select<Answer?>(r => r.answer_challenge(c)).first_or_default(a => a != null));
|
|
|
+
|
|
|
+ var answer = answers.first_or_default();
|
|
|
+ if(answer == null) {
|
|
|
+ return new Message(MessageType.UNKNOWN, new string[0], new string[0]);
|
|
|
+ }
|
|
|
+ return new Message(MessageType.ANSWER, answer.to_arguments(), new string[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void cleanup_registrations() {
|
|
|
+ lock(registrations) {
|
|
|
+ var copy = new Gee.HashMap<string, Invercargill.Sequence<Registration>>();
|
|
|
+ foreach (var group in registrations) {
|
|
|
+ var time = new DateTime.now_utc();
|
|
|
+ copy.set(group.key, group.value.where(r => time.difference(r.timestamp) < REGISTRATION_TIMEOUT_US).to_sequence());
|
|
|
+ }
|
|
|
+ registrations = copy;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void add_registration(string group, InetSocketAddress address) {
|
|
|
+ var reg = new Registration() {
|
|
|
+ address = address,
|
|
|
+ timestamp = new DateTime.now_utc()
|
|
|
+ };
|
|
|
+
|
|
|
+ lock(registrations) {
|
|
|
+ if(!registrations.has_key(group)) {
|
|
|
+ registrations.set(group, new Invercargill.Sequence<Registration>());
|
|
|
+ }
|
|
|
+
|
|
|
+ registrations[group].add(reg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void remove_registration(string group, InetSocketAddress address) {
|
|
|
+ lock(registrations) {
|
|
|
+ Invercargill.Sequence<Registration> group_regs;
|
|
|
+ registrations.unset(group, out group_regs);
|
|
|
+ registrations.set(group, group_regs.where(r => r.address.port != address.port || r.address.address.to_string() != address.address.to_string()).to_sequence());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Enumerable<Registration> get_group_registrations(string group) {
|
|
|
+
|
|
|
+ lock(registrations) {
|
|
|
+ var regs = Invercargill.empty<Registration>();
|
|
|
+ if(registrations.has_key(group)) {
|
|
|
+ regs = registrations[group].to_sequence();
|
|
|
+ }
|
|
|
+
|
|
|
+ return regs;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool add_name_info_if_latest(NameInfo info) {
|
|
|
+ lock(names) {
|
|
|
+ if(names.has_key(info.name) && names[info.name].effective.difference(info.effective) > 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ names.set(info.name, info);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private NameInfo? get_name_info(string name) {
|
|
|
+ lock(names) {
|
|
|
+ if(names.has_key(name)) {
|
|
|
+ return names[name];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private class Registration {
|
|
|
+
|
|
|
+ public InetSocketAddress address { get; set; }
|
|
|
+ public DateTime timestamp { get; set; }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|