diff --git a/client/src/sock/Packet.ts b/client/src/sock/Packet.ts index 8148802..7a09214 100644 --- a/client/src/sock/Packet.ts +++ b/client/src/sock/Packet.ts @@ -5,6 +5,8 @@ const enum kPacketId { } class Packet { + private static magicNumber: Uint8Array = new Uint8Array([0xF0, 0x9F, 0xA6, 0x91]); + private _id: kPacketId; public get id(): kPacketId { return this._id; @@ -47,10 +49,14 @@ class Packet { public static fromBytes(bytes: Uint8Array): Packet { var packet = new Packet; - packet._id = bytes[0]; - var regionCount = bytes[1]; + + if(!bytes.subarray(0, 4).every((v, i) => v === Packet.magicNumber[i])) + return null; + + packet._id = bytes[4]; + var regionCount = bytes[5]; var regionLengths = []; - var ptr = 2; + var ptr = 6; for(var i = 0; i < regionCount; ++i) { if(bytes[ptr] < 0xFE) regionLengths.push(bytes[ptr]); @@ -74,7 +80,7 @@ class Packet { } public getBytes(): Uint8Array { - var headerSize = 2, bodySize = 0; + var headerSize = 6, bodySize = 0; this._regions.forEach(region => { bodySize += region.byteLength; @@ -86,9 +92,10 @@ class Packet { }); var buffer = new Uint8Array(headerSize + bodySize); - var headerPtr = 2, bodyPtr = headerSize; - buffer[0] = this._id % 256; - buffer[1] = this._regions.length; + var headerPtr = 6, bodyPtr = headerSize; + buffer.set(Packet.magicNumber, 0); + buffer[4] = this._id % 256; + buffer[5] = this._regions.length; this._regions.forEach(region => { var regionLength = region.byteLength; if(regionLength < 0xFE) diff --git a/protocol.md b/protocol.md index f8bb3e3..185852b 100644 --- a/protocol.md +++ b/protocol.md @@ -10,9 +10,10 @@ All references to the 'byte' in this document refers to individual 8-bit octets, Because the body of the packet is a sequence of many different regions of byte data that is not delimited, it is necessary for the header of the packet to determine boundaries for the regions of data. -* The first byte is the packet id, the meanings of which are defined in the [_Packet IDs_](#packet-ids) section. -* The second byte is the number of byte regions in the packet. -* The bytes following the second byte are a list of binary length segments, each of which correspond to the number of bytes in its respective region. They each follow this format: +* The first four bytes will always be 0xF0, 0x9F, 0xA6, 0x91. If this is not set properly, the endpoint must close the connection. +* The fifth byte is the packet id, the meanings of which are defined in the [_Packet IDs_](#packet-ids) section. +* The sixth byte is the number of byte regions in the packet. +* The bytes following the sixth byte are a list of binary length segments, each of which correspond to the number of bytes in its respective region. They each follow this format: * If length is less than 254, the length of the region is stored in a single byte. * If length is greater than or equal to 254 but less than 65,536, the first byte of the lenght segment should be 254 and the following two bytes is the length of the region. * If length is greater than or equal to 65,536, the first byte of the length segment should be 255 and the following four bytes is the length of the region. @@ -37,219 +38,180 @@ A packet ID may have a specific "direction" of communication, in that an endpoin #### Server to Client -
- - - - - - - - - - - - - - - - - - - - - - - - - -
- ID 0: Key Exchange
- Requester -
#RegionType
1GeneratorBig Int
2ModulusBig Int
3Server KeyBig Int
- - - - - - - - - - - - - - - - - - - - - - - - - -
- ID 1: Login Attempt
- [Encrypted] Responder -
#RegionType
1Check ConstString
2SucceededBoolean
3MessageString
- - - - - - - - - - - - - - - - - - - - - - - - - -
- ID 2: Registration Attempt
- [Encrypted] Responder -
#RegionType
1Check ConstString
2SucceededBoolean
3MessageString
-
+TODO: populate #### Client to Server -
- - - - - - - - - - - - - - -
- ID 0: Key Exchange
- Responder -
#RegionType
1Client KeyBig Int
- - - - - - - - - - - - - - - - - - - - - - - - - -
- ID 1: Login Attempt
- [Encrypted] Requester -
#RegionType
1Check ConstString
2UsernameString
3PasswordString
+TODO: populate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ID 2: Registration Attempt
- [Encrypted] Requester -
#RegionType
1Check ConstString
2UsernameString
3PasswordString
4EmailString
-
+## Master/Slave Servers + +To keep track of the status of multiple servers from a centralized point that the client may query, each server must be able to communicate with a "master" server that will record and dispense information regarding all servers to clients. All servers that report to the master server will hereby be refered to as "slave" servers. + +Communication between master and slave servers will be done over a UDP connection on a port that is defined by the master server's configuration. The protocol used for this communication is identical to the protocol defined for standard client/server communication; however, the [_Packet IDs_](#TODO) are defined differently. + +Communication between the master server and clients will be done over a WebSocket connection on a port that is defined by the master server's configuration. The protocol used for this communication is identical to the protocol defined for standard client/server communication; however, the [_Packet IDs_](#TODO) are defined differently. + +### Master/Slave Packet IDs + +#### Master to Slave + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ID 0: Key Exchange
+ Requester +
#RegionType
1GeneratorBig Int
2ModulusBig Int
3Server KeyBig Int
+ + + + + + + + + + + + + + + + + + + + +
+ ID 1: Login Attempt
+ [Encrypted] Responder +
#RegionType
1SucceededBoolean
2MessageString
+ + + + + + + + + + + + + + + + + + + + +
+ ID 2: Registration Attempt
+ [Encrypted] Responder +
#RegionType
1SucceededBoolean
2MessageString
+ +#### Slave to Master + + + + + + + + + + + + + + + +
+ ID 0: Key Exchange
+ Responder +
#RegionType
1Client KeyBig Int
+ + + + + + + + + + + + + + + + + + + + +
+ ID 1: Login Attempt
+ [Encrypted] Requester +
#RegionType
1UsernameString
2PasswordString
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ ID 2: Registration Attempt
+ [Encrypted] Requester +
#RegionType
1UsernameString
2PasswordString
3EmailString
+ +### Master/Client Packet IDs + +#### Master to Client + +#### Client to Master ## Sockstamps diff --git a/server/Entrypoint.cs b/server/Entrypoint.cs index e8e99f9..b85e394 100644 --- a/server/Entrypoint.cs +++ b/server/Entrypoint.cs @@ -23,7 +23,8 @@ namespace SockScape { InitialCount = 3, InitialSize = 3, SizeGrowth = 3, - MaxSize = 100 + MaxSize = 100, + MaxTotal = server["Max Users"] ?? Configuration.General["Max Users"] }; pools.Add(server["Id"], pool); diff --git a/server/Libraries/Kneesocks/Pool.cs b/server/Libraries/Kneesocks/Pool.cs index d3f4e93..364e50d 100644 --- a/server/Libraries/Kneesocks/Pool.cs +++ b/server/Libraries/Kneesocks/Pool.cs @@ -20,6 +20,9 @@ namespace Kneesocks { // maximum number of threads that will be spawned // 0 means no limit public int MaxCount { get; set; } = 0; + // maximum amount of total connections in the pool + // 0 means no limit + public int MaxTotal { get; set; } = 0; // maximum number of connections in a thread that exceeds the calculated // amount for the pool's thread count before the connection redistribution // function is called @@ -81,6 +84,9 @@ namespace Kneesocks { if(Disposed) return false; + if(MaxTotal != 0 && Connections.Count >= MaxTotal) + return false; + lock(Threads) { foreach(var thread in Threads) { if(thread.Stack.Count < FullThreadSize) { diff --git a/server/Libraries/Kneesocks/Server.cs b/server/Libraries/Kneesocks/Server.cs index 96b38b9..e31c208 100644 --- a/server/Libraries/Kneesocks/Server.cs +++ b/server/Libraries/Kneesocks/Server.cs @@ -49,7 +49,9 @@ namespace Kneesocks { Server = this }; templatedConnection.Initialize(Socket.AcceptTcpClient()); - ConnectionPool.AddConnection(templatedConnection); + + if(!ConnectionPool.AddConnection(templatedConnection)) + templatedConnection.Disconnect("Connection pooler rejected connection."); } Thread.Sleep(100); diff --git a/server/Socks/Packet.cs b/server/Socks/Packet.cs index 51f55f9..0ddddcc 100644 --- a/server/Socks/Packet.cs +++ b/server/Socks/Packet.cs @@ -7,27 +7,29 @@ using Glove; namespace SockScape { class Packet { + private static readonly byte[] MagicNumber = { 0xF0, 0x9F, 0xA6, 0x91 }; + public enum kId { KeyExchange = 0, LoginAttempt, RegistrationAttempt } - - private static Packet ErrorPacket { - get => new Packet { IsLegal = false }; - } public static Packet FromBytes(byte[] raw) { - if(raw.Length < 3) - return ErrorPacket; + if(raw.Length < 7) + return null; Packet packet = new Packet(); - if(!Enum.IsDefined(typeof(kId), (int)raw[0])) - return ErrorPacket; - packet.Id = (kId)raw[0]; - var regionCount = raw[1]; + if(!Enum.IsDefined(typeof(kId), (int)raw[4])) + return null; + + if(!raw.Subset(0, 4).SequenceEqual(MagicNumber)) + return null; + + packet.Id = (kId)raw[4]; + var regionCount = raw[5]; var regionLengths = new List(); - var headerPtr = 2; + var headerPtr = 6; for(var i = 0; i < regionCount; ++i) { regionLengths.Add(0); var first = raw[headerPtr]; @@ -36,22 +38,22 @@ namespace SockScape { ++headerPtr; } else if(first == 254) { if(headerPtr + 3 < raw.Length) - return ErrorPacket; + return null; regionLengths[i] = raw.Subset(headerPtr + 1, 2).UnpackUInt16(); headerPtr += 3; } else { if(headerPtr + 5 < raw.Length) - return ErrorPacket; + return null; regionLengths[i] = raw.Subset(headerPtr + 1, 4).UnpackUInt32(); headerPtr += 5; } if(headerPtr > raw.Length) - return ErrorPacket; + return null; } if(headerPtr + regionLengths.Sum(x => x) > raw.Length) - return ErrorPacket; + return null; long bodyPtr = headerPtr; foreach(var regionLength in regionLengths) { @@ -96,10 +98,11 @@ namespace SockScape { if(!IsLegal) return null; - var header = new List { - (byte)Id, - (byte)RegionCount - }; + var header = new List(); + header.AddRange(MagicNumber); + header.Add((byte)Id); + header.Add((byte)RegionCount); + IEnumerable body = new byte[0]; foreach(var region in Regions) { if(region.Length < 0xFE) diff --git a/server/Socks/PlayerConnection.cs b/server/Socks/PlayerConnection.cs index eb068bb..804fcab 100644 --- a/server/Socks/PlayerConnection.cs +++ b/server/Socks/PlayerConnection.cs @@ -32,7 +32,7 @@ namespace SockScape { Encryptor == null ? Packet.FromBytes(data) : Packet.FromBytes(Encryptor.Parse(data)); - if(!packet.IsLegal) { + if(packet == null) { Disconnect(Frame.kClosingReason.ProtocolError, "Packet received was not legal."); return; }