the pink ball in crabby 64
rer
This commit is contained in:
parent
3f51e965fc
commit
2937d799ae
13 changed files with 150 additions and 60 deletions
45
protocol.md
45
protocol.md
|
@ -211,22 +211,17 @@ Communication between the master server and clients will be done over a WebSocke
|
|||
<td colspan="2">Iterated over <i>n</i> (0 ≤ <i>i</i> ≤ <i>n - 1</i>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">2 + 4<i>i</i></td>
|
||||
<td class="center">2 + 3<i>i</i></td>
|
||||
<td>Server Id</td>
|
||||
<td>Packed Unsigned Short</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">3 + 4<i>i</i></td>
|
||||
<td class="center">3 + 3<i>i</i></td>
|
||||
<td>User Count</td>
|
||||
<td>Packed Unsigned Short</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">4 + 4<i>i</i></td>
|
||||
<td>IPv4 Address</td>
|
||||
<td>Bytes (4)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">5 + 4<i>i</i></td>
|
||||
<td class="center">4 + 3<i>i</i></td>
|
||||
<td>Port</td>
|
||||
<td>Packed Unsigned Short</td>
|
||||
</tr>
|
||||
|
@ -313,6 +308,30 @@ Communication between the master server and clients will be done over a WebSocke
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<table style="margin-right: 8px; margin-bottom: 8px;">
|
||||
<thead>
|
||||
<th colspan="100" class="center">
|
||||
ID 4: Server List Request<br />
|
||||
[Encrypted] Responder
|
||||
</th>
|
||||
</thead>
|
||||
<thead>
|
||||
<th>#</th>
|
||||
<th>Region</th>
|
||||
<th>Type</th>
|
||||
</thead>
|
||||
<tr>
|
||||
<td class="center">1</td>
|
||||
<td>Succeeded</td>
|
||||
<td>Boolean</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">2</td>
|
||||
<td>Message</td>
|
||||
<td>String</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#### Client to Master
|
||||
|
||||
<table style="margin-right: 8px; margin-bottom: 8px;">
|
||||
|
@ -387,6 +406,16 @@ Communication between the master server and clients will be done over a WebSocke
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<table style="margin-right: 8px; margin-bottom: 8px;">
|
||||
<thead>
|
||||
<th colspan="100" class="center">
|
||||
ID 4: Server List Request<br />
|
||||
[Encrypted] Requester<br />
|
||||
<i>Bodyless Packet</i>
|
||||
</th>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
## Sockstamps
|
||||
|
||||
Because epoch time is not standardized across systems, an intermediate layer of date/time transmission must be used between the client and server so as to handle time dependent interactions. Therefore, a "sockstamp" will be used in place of the context-dependent implementations of epoch time.
|
||||
|
|
|
@ -22,8 +22,6 @@ namespace SockScape {
|
|||
|
||||
class Entrypoint {
|
||||
static void Main(string[] args) {
|
||||
var db = new DAL.ScapeDb();
|
||||
|
||||
foreach(var server in Configuration.Servers) {
|
||||
var pool = new Pool<PlayerConnection> {
|
||||
InitialCount = 3,
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace Glove.INI {
|
|||
if(currentInstance != null)
|
||||
currentInstance.Push(line);
|
||||
else
|
||||
throw new FormatException("Non-section line before any define sections in '"+ path +"'");
|
||||
throw new FormatException($"Non-section line before any define sections in '{path}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,18 +41,18 @@ namespace Glove.INI {
|
|||
if(ContainsSection(name)) {
|
||||
var section = Sections[name];
|
||||
if(!rule.AllowMultiple && section.Count > 1)
|
||||
throw new FormatException("Section '"+ name +"' is not allowed to have multiple declarations in '"+ path +"'");
|
||||
throw new FormatException($"Section '{name}' is not allowed to have multiple declarations in '{path}'");
|
||||
|
||||
if(rule.RequiredFields.Length > 0) {
|
||||
foreach(var instance in section) {
|
||||
foreach(var field in rule.RequiredFields) {
|
||||
if(!instance.ContainsKey(field))
|
||||
throw new FormatException("Expected field '"+ field +"' in section '" + name + "' was not found in '" + path + "'");
|
||||
throw new FormatException($"Expected field '{field}' in section '{name}' was not found in '{path}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(rule.Required)
|
||||
throw new FormatException("Expected section '"+ name +"' was not found in '"+ path +"'");
|
||||
throw new FormatException($"Expected section '{name}' was not found in '{path}'");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ namespace Kneesocks {
|
|||
|
||||
var rawOpcode = raw[0] & 0x0F;
|
||||
if(!Enum.IsDefined(typeof(kOpcode), rawOpcode))
|
||||
throw new ArgumentException("Opcode '0x" + rawOpcode.ToString("X") + "' not understood");
|
||||
throw new ArgumentException($"Opcode '0x{rawOpcode.ToString("X")}' not understood");
|
||||
|
||||
var returnFrame = new Frame {
|
||||
IsFinal = (raw[0] & 0x80) != 0,
|
||||
|
@ -156,9 +156,9 @@ namespace Kneesocks {
|
|||
|
||||
uint expectedFrameLength = (uint)returnFrame.BodyLength + (uint)returnFrame.HeaderLength;
|
||||
if((uint)raw.Length < expectedFrameLength)
|
||||
throw new FormatException("Raw frame length ("+ (uint)raw.Length + ") is less than described size ("+ expectedFrameLength + ")");
|
||||
throw new FormatException($"Raw frame length ({(uint)raw.Length}) is less than described size ({expectedFrameLength})");
|
||||
|
||||
returnFrame.Content = new byte[returnFrame.BodyLength];
|
||||
returnFrame.Content = new byte[returnFrame.BodyLength];
|
||||
Array.Copy(raw, returnFrame.HeaderLength, returnFrame.Content, 0L, returnFrame.BodyLength);
|
||||
if(returnFrame.IsMasked)
|
||||
returnFrame.Content = returnFrame.MaskedContent;
|
||||
|
|
|
@ -30,9 +30,8 @@ namespace Kneesocks {
|
|||
Gateway_Timeout = 504
|
||||
}
|
||||
public kStatusCode? StatusCode { get; private set; } = null;
|
||||
protected string StatusCodeText {
|
||||
get => Enum.GetName(typeof(kStatusCode), StatusCode).Replace('_', ' ');
|
||||
}
|
||||
protected string StatusCodeText
|
||||
=> Enum.GetName(typeof(kStatusCode), StatusCode).Replace('_', ' ');
|
||||
|
||||
private readonly Dictionary<string, string> Headers =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
@ -103,9 +102,9 @@ namespace Kneesocks {
|
|||
SetHeader("Content-Type", "text/plain");
|
||||
}
|
||||
|
||||
var raw = "HTTP/"+ HttpVersion +" "+ (int)StatusCode + " "+ StatusCodeText +"\r\n";
|
||||
var raw = $"HTTP/{HttpVersion} {(int)StatusCode} {StatusCodeText}\r\n";
|
||||
foreach(var header in Headers)
|
||||
raw += header.Key.Trim() + ": " + header.Value.Trim() + "\r\n";
|
||||
raw += $"{header.Key.Trim()}: {header.Value.Trim()}\r\n";
|
||||
return raw + "\r\n";
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,9 @@ namespace Kneesocks {
|
|||
CreateThread(runWithNoClients: true);
|
||||
}
|
||||
|
||||
public int ConnectionCount
|
||||
=> Connections.Count;
|
||||
|
||||
public T this[UInt64 id] {
|
||||
get {
|
||||
lock (Connections) {
|
||||
|
@ -86,7 +89,7 @@ namespace Kneesocks {
|
|||
if(Disposed)
|
||||
return false;
|
||||
|
||||
if(MaxTotal != 0 && Connections.Count >= MaxTotal)
|
||||
if(MaxTotal != 0 && ConnectionCount >= MaxTotal)
|
||||
return false;
|
||||
|
||||
lock(Threads) {
|
||||
|
|
|
@ -32,9 +32,7 @@ namespace Kneesocks {
|
|||
}
|
||||
}
|
||||
|
||||
public int Count {
|
||||
get => Clients.Count;
|
||||
}
|
||||
public int Count => Clients.Count;
|
||||
|
||||
internal void StopThread() => Running = false;
|
||||
|
||||
|
|
|
@ -6,26 +6,51 @@ using System.Text;
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace SockScape {
|
||||
class ServerList {
|
||||
public static Dictionary<int, Server> Servers { get; }
|
||||
= new Dictionary<int, Server>();
|
||||
static class ServerList {
|
||||
private static ServerDictionary Servers { get; }
|
||||
= new ServerDictionary();
|
||||
|
||||
public Server this[int i] {
|
||||
get => Servers.ContainsKey(i) ? Servers[i] : null;
|
||||
set => Servers[i] = value;
|
||||
private static Dictionary<UInt16, Server> List
|
||||
=> Servers.List;
|
||||
|
||||
public static void Write(Server server) {
|
||||
lock(Servers) {
|
||||
if(HasId(server.Id) && !List[server.Id].Address.Equals(server.Address))
|
||||
Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - Server {server.Id} has changed IP addresses.");
|
||||
|
||||
List[server.Id] = server;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasId(int id)
|
||||
=> Servers.ContainsKey(id);
|
||||
public static Packet ReportPacket {
|
||||
get {
|
||||
var packet = new Packet(kInterMasterId.);
|
||||
|
||||
public void Clear()
|
||||
=> Servers.Clear();
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasId(UInt16 id)
|
||||
=> List.ContainsKey(id);
|
||||
|
||||
public static void Clear()
|
||||
=> List.Clear();
|
||||
|
||||
public class ServerDictionary {
|
||||
public Dictionary<UInt16, Server> List { get; }
|
||||
= new Dictionary<UInt16, Server>();
|
||||
|
||||
public Server this[UInt16 i] {
|
||||
get => List.ContainsKey(i) ? List[i] : null;
|
||||
set => List[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Server {
|
||||
public ushort Id { get; set; }
|
||||
public ushort UserCount { get; set; }
|
||||
public IPEndPoint Address { get; set; }
|
||||
public IPEndPoint Owner { get; set; }
|
||||
public IPAddress Address { get; set; }
|
||||
public ushort Port { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ namespace SockScape {
|
|||
class MasterConnection : Connection {
|
||||
private Key Key;
|
||||
public Cipher Encryptor { get; private set; }
|
||||
|
||||
|
||||
protected override void OnOpen() {
|
||||
Key = new Key();
|
||||
|
@ -53,7 +52,7 @@ namespace SockScape {
|
|||
break;
|
||||
}
|
||||
|
||||
Console.WriteLine(Id + " says " + data.GetString());
|
||||
Console.WriteLine($"{Id} says {data.GetString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace SockScape {
|
|||
private static Thread ListeningThread;
|
||||
private static bool IsOpen;
|
||||
|
||||
private static DateTime LastMessageOut;
|
||||
private static DateTime LastMessageOut = new DateTime(0);
|
||||
private static TimeSpan DeltaLastOut
|
||||
=> DateTime.Now - LastMessageOut;
|
||||
|
||||
|
@ -43,9 +43,6 @@ namespace SockScape {
|
|||
|
||||
public static void Listener() {
|
||||
while(IsOpen) {
|
||||
if(LastMessageIn.Ticks == 0 && DeltaLastOut.TotalSeconds > 10)
|
||||
Send(new Packet(kIntraSlaveId.InitiationAttempt, Configuration.General["Master Secret"]));
|
||||
|
||||
var endPoint = new IPEndPoint(0, 0);
|
||||
while(Sock.Available > 0) {
|
||||
var data = Sock.Receive(ref endPoint);
|
||||
|
@ -67,11 +64,11 @@ namespace SockScape {
|
|||
break;
|
||||
|
||||
case kIntraMasterId.PositiveAck:
|
||||
|
||||
Console.WriteLine($"Packet type {packet[0]} accepted by master");
|
||||
break;
|
||||
|
||||
case kIntraMasterId.NegativeAck:
|
||||
Console.WriteLine("Packet type "+ packet[0] +" declined for reason "+ packet[1]);
|
||||
Console.WriteLine($"Packet type {packet[0]} declined by master for reason {packet[1]}");
|
||||
break;
|
||||
|
||||
case kIntraMasterId.EncryptionError:
|
||||
|
@ -82,6 +79,12 @@ namespace SockScape {
|
|||
}
|
||||
}
|
||||
|
||||
if(LastMessageIn.Ticks == 0 && DeltaLastOut.TotalSeconds > 10)
|
||||
Send(new Packet(kIntraSlaveId.InitiationAttempt, Configuration.General["Master Secret"]));
|
||||
else {
|
||||
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Glove;
|
||||
using SockScape.DAL;
|
||||
using SockScape.Encryption;
|
||||
|
||||
namespace SockScape {
|
||||
static class MasterUdpServer {
|
||||
private static Dictionary<string, Client> Prospects;
|
||||
private static Dictionary<string, Client> Clients;
|
||||
private static ServerList Servers;
|
||||
|
||||
private static UdpClient Sock;
|
||||
private static Thread ListeningThread;
|
||||
|
@ -22,8 +22,8 @@ namespace SockScape {
|
|||
public static void Initialize() {
|
||||
if(IsOpen || ListeningThread != null)
|
||||
return;
|
||||
|
||||
Servers = new ServerList();
|
||||
|
||||
ServerList.Clear();
|
||||
Prospects = new Dictionary<string, Client>();
|
||||
Clients = new Dictionary<string, Client>();
|
||||
|
||||
|
@ -50,7 +50,7 @@ namespace SockScape {
|
|||
: Packet.FromBytes(encryptor.Parse(data));
|
||||
|
||||
if(packet == null)
|
||||
break;
|
||||
continue;
|
||||
|
||||
switch((kIntraSlaveId)packet.Id) {
|
||||
case kIntraSlaveId.InitiationAttempt:
|
||||
|
@ -86,7 +86,30 @@ namespace SockScape {
|
|||
if(!IsClientConnected(client) || packet.RegionCount < 1)
|
||||
break;
|
||||
|
||||
|
||||
if(packet.CheckRegions(0, 1)) {
|
||||
NegativeAck(endPoint, kIntraSlaveId.StatusUpdate, "Server count is malformed.");
|
||||
break;
|
||||
}
|
||||
|
||||
byte serverCount = packet[0].Raw[0];
|
||||
if(packet.RegionCount != 1 + 3 * serverCount) {
|
||||
NegativeAck(endPoint, kIntraSlaveId.StatusUpdate, "Region count does not match server count");
|
||||
break;
|
||||
}
|
||||
|
||||
for(byte i = 0; i < serverCount; ++i) {
|
||||
if(!packet.CheckRegions(2 + 3 * i, 2, 2, 2))
|
||||
continue;
|
||||
|
||||
ServerList.Write(new Server {
|
||||
Id = packet[2 + 3 * i].Raw.UnpackUInt16(),
|
||||
UserCount = packet[3 + 3 * i].Raw.UnpackUInt16(),
|
||||
Address = endPoint.Address,
|
||||
Port = packet[4 + 3 * i].Raw.UnpackUInt16()
|
||||
});
|
||||
}
|
||||
|
||||
PositiveAck(endPoint, kIntraSlaveId.StatusUpdate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -117,16 +140,16 @@ namespace SockScape {
|
|||
return Clients.ContainsKey(client);
|
||||
}
|
||||
|
||||
private static Packet PositiveAck(byte id) {
|
||||
return new Packet(kIntraMasterId.PositiveAck, id);
|
||||
private static void PositiveAck(IPEndPoint endPoint, kIntraSlaveId id) {
|
||||
Send(new Packet(kIntraMasterId.PositiveAck, id), endPoint);
|
||||
}
|
||||
|
||||
private static Packet NegativeAck(byte id, string message = "") {
|
||||
return new Packet(kIntraMasterId.NegativeAck, id, message);
|
||||
private static void NegativeAck(IPEndPoint endPoint, kIntraSlaveId id, string message = "An error occurred while parsing a packet.") {
|
||||
Send(new Packet(kIntraMasterId.NegativeAck, id, message), endPoint);
|
||||
}
|
||||
|
||||
private static Packet EncryptionError(string message = "A general encryption error has occurred.") {
|
||||
return new Packet(kIntraMasterId.EncryptionError, message);
|
||||
private static void EncryptionError(IPEndPoint endPoint, string message = "A general encryption error has occurred. Renegotiation required.") {
|
||||
Send(new Packet(kIntraMasterId.EncryptionError, message), endPoint);
|
||||
}
|
||||
|
||||
class Client {
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace SockScape {
|
|||
public enum kInterMasterId {
|
||||
KeyExchange = 1,
|
||||
LoginAttempt,
|
||||
RegistrationAttempt
|
||||
RegistrationAttempt,
|
||||
ServerListing
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,18 @@ namespace SockScape {
|
|||
return this;
|
||||
}
|
||||
|
||||
public bool CheckRegions(int startIndex, params int[] lengths) {
|
||||
if(startIndex + lengths.Length > RegionCount)
|
||||
return false;
|
||||
|
||||
for(int i = 0; i < lengths.Length; ++i) {
|
||||
if(this[startIndex + i].Raw.Length == lengths[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] GetBytes() {
|
||||
var header = new List<byte>();
|
||||
header.AddRange(MagicNumber);
|
||||
|
@ -117,14 +129,14 @@ namespace SockScape {
|
|||
}
|
||||
|
||||
public class Region {
|
||||
public byte[] Data { get; }
|
||||
private byte[] Data { get; }
|
||||
|
||||
public Region(byte[] data) {
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public static implicit operator byte[] (Region region) => region.Data;
|
||||
public string Bytes
|
||||
public byte[] Raw
|
||||
=> this;
|
||||
|
||||
public static implicit operator string(Region region) {
|
||||
|
|
Loading…
Reference in a new issue