1 /* 2 * Copyright (c) 2017-2018 SEL 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 * See the GNU Lesser General Public License for more details. 13 * 14 */ 15 /** 16 * Copyright: 2017-2020 sel-project 17 * License: LGPL-3.0 18 * Authors: Kripth 19 * Source: $(HTTP github.com/sel-project/sel-server/sel/server/query.d, sel/server/query.d) 20 */ 21 module sel.server.query; 22 23 import std.array : Appender; 24 import std.bitmanip : nativeToLittleEndian; 25 import std.conv : to; 26 import std.datetime.stopwatch : StopWatch; 27 import std.string : join; 28 29 import sel.server.server; 30 31 class Query { 32 33 private shared ServerInfo info; 34 35 public shared string software = "sel-server"; 36 public shared string[] plugins; 37 38 public shared string[] players; 39 40 public shared this(shared ServerInfo info) { 41 this.info = info; 42 } 43 44 public class Handler { 45 46 private immutable string game; 47 private immutable string ip; 48 private immutable ushort port; 49 50 private int challengeToken = 1; 51 private string[int] sessions; 52 private StopWatch sessionTimer; 53 54 private immutable(ubyte)[] basicQuery, fullQuery; 55 private StopWatch basicTimer, fullTimer; 56 private long lastBasic = long.min, lastFull = long.min; 57 58 public this(string game, string ip, ushort port) { 59 this.game = game; 60 this.ip = ip; 61 this.port = port; 62 this.sessionTimer.start(); 63 this.basicTimer.start(); 64 this.fullTimer.start(); 65 } 66 67 public ubyte[] handle(ubyte[] payload) { 68 if(payload.length >= 5) { 69 if(payload[0] == 0) { 70 // query request 71 if(payload.length == 13) { 72 mixin(createHandler("full", 5_000)); 73 } else { 74 mixin(createHandler("basic", 1_000)); 75 } 76 } else if(payload[0] == 9) { 77 // login 78 if(this.sessionTimer.peek().split!"msecs"().msecs > 30_000) { 79 this.sessions.clear(); 80 this.sessionTimer.reset(); 81 } 82 this.sessions[this.challengeToken] = ""; //TODO store the address 83 return ubyte(9) ~ payload[1..5] ~ cast(ubyte[])to!string(this.challengeToken++) ~ ubyte(0); 84 } 85 86 } 87 return []; 88 } 89 90 private void regenerateBasicQuery() { 91 auto appender = createAppender(); 92 appender.put(info.motd.raw); 93 appender.put(info.gametype); 94 appender.put(info.map); 95 appender.put(to!string(info.online)); 96 appender.put(to!string(info.max)); 97 appender.put(cast(ubyte[])nativeToLittleEndian(this.port)); 98 appender.put(this.ip); 99 this.basicQuery = appender.data.idup; 100 } 101 102 private void regenerateFullQuery() { 103 auto appender = createAppender(); 104 appender.put("splitnum", "\x80"); 105 appender.put("hostname", info.motd.raw); 106 appender.put("gametype", info.gametype); 107 appender.put("game_id", this.game); 108 appender.put("version", "?"); 109 appender.put("plugins", software ~ (plugins.length ? ": " ~ plugins.join("; ") : "")); 110 appender.put("map", info.map); 111 appender.put("numplayers", to!string(info.online)); 112 appender.put("maxplayers", to!string(info.max)); 113 appender.put("hostport", to!string(this.port)); 114 appender.put("hostip", this.ip); 115 appender.put("\0\1player_\0"); 116 foreach(player ; players) { 117 appender.put(player); 118 } 119 appender.appender.put(ubyte(0)); 120 this.fullQuery = appender.data.idup; 121 } 122 123 } 124 125 } 126 127 private auto createAppender() { 128 129 struct _ { 130 131 public Appender!(ubyte[]) appender; 132 133 void put(ubyte[] bytes) { 134 this.appender.put(bytes); 135 } 136 137 void put(string value) { 138 this.appender.put(cast(ubyte[])value); 139 this.appender.put(ubyte(0)); 140 } 141 142 void put(string key, string value) { 143 this.put(key); 144 this.put(value); 145 } 146 147 alias appender this; 148 149 } 150 151 return _(); 152 153 } 154 155 private string createHandler(string type, uint time) { 156 import std.string : capitalize; 157 return " 158 immutable peek = this." ~ type ~ "Timer.peek().split!`msecs`().msecs; 159 if(peek > this.last" ~ capitalize(type) ~ " + " ~ to!string(time) ~ ") { 160 this.regenerate" ~ capitalize(type) ~ "Query(); 161 this.last" ~ capitalize(type) ~ " = peek; 162 this." ~ type ~ "Timer.reset(); 163 } 164 return this." ~ type ~ "Query.dup; 165 "; 166 }