Merge pull request #154 from Mitch528/query

Cleanup QueryProtocol
This commit is contained in:
Drew DeVault 2015-06-21 12:22:47 -04:00
commit fb65c410e8

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -19,12 +20,11 @@ namespace TrueCraft
private IMultiplayerServer Server; private IMultiplayerServer Server;
private CancellationTokenSource CToken; private CancellationTokenSource CToken;
private readonly byte[] ProtocolVersion = new byte[] { 0xFE, 0xFD }; private readonly byte[] ProtocolVersion = { 0xFE, 0xFD };
private readonly byte Type_Handshake = 0x09; private readonly byte Type_Handshake = 0x09;
private readonly byte Type_Stat = 0x00; private readonly byte Type_Stat = 0x00;
private Dictionary<IPEndPoint, QueryUser> UserList; private ConcurrentDictionary<IPEndPoint, QueryUser> UserList;
private object UserLock = new object();
public QueryProtocol(IMultiplayerServer server) public QueryProtocol(IMultiplayerServer server)
{ {
@ -35,6 +35,7 @@ namespace TrueCraft
{ {
Port = Program.ServerConfiguration.QueryPort; Port = Program.ServerConfiguration.QueryPort;
Udp = new UdpClient(Port); Udp = new UdpClient(Port);
UserList = new ConcurrentDictionary<IPEndPoint, QueryUser>();
Timer = new Timer(ResetUserList, null, 0, 30000); Timer = new Timer(ResetUserList, null, 0, 30000);
CToken = new CancellationTokenSource(); CToken = new CancellationTokenSource();
Udp.BeginReceive(HandleReceive, null); Udp.BeginReceive(HandleReceive, null);
@ -43,7 +44,7 @@ namespace TrueCraft
private void HandleReceive(IAsyncResult ar) private void HandleReceive(IAsyncResult ar)
{ {
if (CToken.IsCancellationRequested) return; if (CToken.IsCancellationRequested) return;
try try
{ {
var clientEP = new IPEndPoint(IPAddress.Any, Port); var clientEP = new IPEndPoint(IPAddress.Any, Port);
@ -72,75 +73,108 @@ namespace TrueCraft
private void HandleHandshake(byte[] buffer, IPEndPoint clientEP) private void HandleHandshake(byte[] buffer, IPEndPoint clientEP)
{ {
var stream = GetStream(buffer); using (var ms = new MemoryStream(buffer))
int sessionId = GetSessionId(stream);
var user = new QueryUser { SessionId = sessionId, ChallengeToken = Rnd.Next() };
lock (UserLock)
{ {
if (UserList.ContainsKey(clientEP)) using (var stream = new BinaryReader(ms))
UserList.Remove(clientEP); {
UserList.Add(clientEP, user); int sessionId = GetSessionId(stream);
var user = new QueryUser { SessionId = sessionId, ChallengeToken = Rnd.Next() };
if (UserList.ContainsKey(clientEP))
{
QueryUser u;
while (!UserList.TryRemove(clientEP, out u))
Thread.Sleep(1);
}
UserList[clientEP] = user;
using (var response = new MemoryStream())
{
using (var writer = new BinaryWriter(response))
{
WriteHead(Type_Handshake, user, writer);
WriteStringToStream(user.ChallengeToken.ToString(), response);
SendResponse(response.ToArray(), clientEP);
}
}
}
} }
var response = GetStream();
WriteHead(Type_Handshake, user, response);
WriteStringToStream(user.ChallengeToken.ToString(), response.BaseStream);
SendResponse(response, clientEP);
} }
private void HandleBasicStat(byte[] buffer, IPEndPoint clientEP) private void HandleBasicStat(byte[] buffer, IPEndPoint clientEP)
{ {
var stream = GetStream(buffer); using (var ms = new MemoryStream(buffer))
int sessionId = GetSessionId(stream); {
int token = GetToken(stream); using (var stream = new BinaryReader(ms))
{
int sessionId = GetSessionId(stream);
int token = GetToken(stream);
var user = GetUser(clientEP); var user = GetUser(clientEP);
if (user.ChallengeToken != token || user.SessionId != sessionId) throw new Exception("Invalid credentials"); if (user.ChallengeToken != token || user.SessionId != sessionId) throw new Exception("Invalid credentials");
var stats = GetStats(); var stats = GetStats();
var response = GetStream(); using (var response = new MemoryStream())
WriteHead(Type_Stat, user, response); {
WriteStringToStream(stats["hostname"], response.BaseStream); using (var writer = new BinaryWriter(response))
WriteStringToStream(stats["gametype"], response.BaseStream); {
WriteStringToStream(stats["numplayers"], response.BaseStream); WriteHead(Type_Stat, user, writer);
WriteStringToStream(stats["maxplayers"], response.BaseStream); WriteStringToStream(stats["hostname"], response);
byte[] hostport = BitConverter.GetBytes(UInt16.Parse(stats["hostport"])); WriteStringToStream(stats["gametype"], response);
Array.Reverse(hostport);//The specification needs little endian short WriteStringToStream(stats["numplayers"], response);
response.Write(hostport); WriteStringToStream(stats["maxplayers"], response);
WriteStringToStream(stats["hostip"], response.BaseStream); byte[] hostport = BitConverter.GetBytes(ushort.Parse(stats["hostport"]));
Array.Reverse(hostport);//The specification needs little endian short
writer.Write(hostport);
WriteStringToStream(stats["hostip"], response);
SendResponse(response, clientEP); SendResponse(response.ToArray(), clientEP);
}
}
}
}
} }
private void HandleFullStat(byte[] buffer, IPEndPoint clientEP) private void HandleFullStat(byte[] buffer, IPEndPoint clientEP)
{ {
var stream = GetStream(buffer); using (var stream = new MemoryStream(buffer))
int sessionId = GetSessionId(stream);
int token = GetToken(stream);
var user = GetUser(clientEP);
if (user.ChallengeToken != token || user.SessionId != sessionId) throw new Exception("Invalid credentials");
var stats = GetStats();
var response = GetStream();
WriteHead(Type_Stat, user, response);
WriteStringToStream("SPLITNUM\0\0", response.BaseStream);
foreach (var pair in stats)
{ {
WriteStringToStream(pair.Key, response.BaseStream); using (var reader = new BinaryReader(stream))
WriteStringToStream(pair.Value, response.BaseStream); {
} int sessionId = GetSessionId(reader);
response.Write((byte)0x00); int token = GetToken(reader);
response.Write((byte)0x01);
WriteStringToStream("player_\0", response.BaseStream);
var players = GetPlayers();
foreach (string player in players)
WriteStringToStream(player, response.BaseStream);
response.Write((byte)0x00);
SendResponse(response, clientEP); var user = GetUser(clientEP);
if (user.ChallengeToken != token || user.SessionId != sessionId) throw new Exception("Invalid credentials");
var stats = GetStats();
using (var response = new MemoryStream())
{
using (var writer = new BinaryWriter(response))
{
WriteHead(Type_Stat, user, writer);
WriteStringToStream("SPLITNUM\0\0", response);
foreach (var pair in stats)
{
WriteStringToStream(pair.Key, response);
WriteStringToStream(pair.Value, response);
}
writer.Write((byte)0x00);
writer.Write((byte)0x01);
WriteStringToStream("player_\0", response);
var players = GetPlayers();
foreach (string player in players)
WriteStringToStream(player, response);
writer.Write((byte)0x00);
SendResponse(response.ToArray(), clientEP);
}
}
}
}
} }
private bool CheckVersion(byte[] ver) private bool CheckVersion(byte[] ver)
@ -157,40 +191,39 @@ namespace TrueCraft
stream.BaseStream.Position = 7; stream.BaseStream.Position = 7;
return stream.ReadInt32(); return stream.ReadInt32();
} }
private BinaryReader GetStream(byte[] buffer)
{ return new BinaryReader(new MemoryStream(buffer)); }
private BinaryWriter GetStream()
{ return new BinaryWriter(new MemoryStream()); }
private void WriteHead(byte type, QueryUser user, BinaryWriter stream) private void WriteHead(byte type, QueryUser user, BinaryWriter stream)
{ {
stream.Write(type); stream.Write(type);
stream.Write(user.SessionId); stream.Write(user.SessionId);
} }
private void SendResponse(BinaryWriter res, IPEndPoint destination)
private void SendResponse(byte[] res, IPEndPoint destination)
{ {
byte[] data = ((MemoryStream)res.BaseStream).ToArray(); Udp.Send(res, res.Length, destination);
Udp.Send(data, data.Length, destination);
} }
private QueryUser GetUser(IPEndPoint ep) private QueryUser GetUser(IPEndPoint ipe)
{ {
QueryUser user; if (!UserList.ContainsKey(ipe))
lock (UserLock) throw new Exception("Undefined user");
if (!UserList.TryGetValue(ep, out user)) throw new Exception("Undefined user");
return user; return UserList[ipe];
} }
private Dictionary<string, string> GetStats() private Dictionary<string, string> GetStats()
{ {
var stats = new Dictionary<string, string>(); var stats = new Dictionary<string, string>
stats.Add("hostname", Program.ServerConfiguration.MOTD); {
stats.Add("gametype", "SMP"); {"hostname", Program.ServerConfiguration.MOTD},
stats.Add("game_id", "TRUECRAFT"); {"gametype", "SMP"},
stats.Add("version", "1.0"); {"game_id", "TRUECRAFT"},
stats.Add("plugins", "TrueCraft"); {"version", "1.0"},
stats.Add("map", Server.Worlds.First().Name); {"plugins", "TrueCraft"},
stats.Add("numplayers", Server.Clients.Count.ToString()); {"map", Server.Worlds.First().Name},
stats.Add("maxplayers", "64"); {"numplayers", Server.Clients.Count.ToString()},
stats.Add("hostport", Program.ServerConfiguration.ServerPort.ToString()); {"maxplayers", "64"},
stats.Add("hostip", Program.ServerConfiguration.ServerAddress); {"hostport", Program.ServerConfiguration.ServerPort.ToString()},
{"hostip", Program.ServerConfiguration.ServerAddress}
};
return stats; return stats;
} }
private List<string> GetPlayers() private List<string> GetPlayers()
@ -223,14 +256,14 @@ namespace TrueCraft
public void Stop() public void Stop()
{ {
Timer.Dispose();
CToken.Cancel(); CToken.Cancel();
Udp.Close(); Udp.Close();
} }
private void ResetUserList(object state) private void ResetUserList(object state)
{ {
lock (UserLock) UserList.Clear();
UserList = new Dictionary<IPEndPoint, QueryUser>();
} }
struct QueryUser struct QueryUser