once as a kid i cried over a mario flash animation
my mom looked very disappointed
This commit is contained in:
parent
07e50a3301
commit
4d281dac5a
7 changed files with 105 additions and 37 deletions
10
protocol.md
10
protocol.md
|
@ -287,24 +287,18 @@ Communication between the master server and clients will be done over a WebSocke
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="center">2</td>
|
<td class="center">2</td>
|
||||||
<td>Session Id</td>
|
|
||||||
<td>Packed Unsigned Long</td>
|
|
||||||
<td>R<sub>1</sub></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="center">3</td>
|
|
||||||
<td>Secret</td>
|
<td>Secret</td>
|
||||||
<td>Bytes (16)</td>
|
<td>Bytes (16)</td>
|
||||||
<td>R<sub>1</sub></td>
|
<td>R<sub>1</sub></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="center">4</td>
|
<td class="center">3</td>
|
||||||
<td>Server Address</td>
|
<td>Server Address</td>
|
||||||
<td>IPv4 String</td>
|
<td>IPv4 String</td>
|
||||||
<td>R<sub>1</sub></td>
|
<td>R<sub>1</sub></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="center">5</td>
|
<td class="center">4</td>
|
||||||
<td>Server Port</td>
|
<td>Server Port</td>
|
||||||
<td>Packed Unsigned Short</td>
|
<td>Packed Unsigned Short</td>
|
||||||
<td>R<sub>1</sub></td>
|
<td>R<sub>1</sub></td>
|
||||||
|
|
|
@ -26,6 +26,16 @@ namespace SockScape {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
new SectionRules {
|
||||||
|
Name = "Mail",
|
||||||
|
Required = true,
|
||||||
|
RequiredFields = new[] {
|
||||||
|
"Host",
|
||||||
|
"UseTLS",
|
||||||
|
"Auth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
new SectionRules {
|
new SectionRules {
|
||||||
Name = "Database",
|
Name = "Database",
|
||||||
Required = true,
|
Required = true,
|
||||||
|
|
|
@ -8,7 +8,6 @@ using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace Glove {
|
namespace Glove {
|
||||||
public static class RNG {
|
public static class RNG {
|
||||||
// TODO add cryptographically secure rng
|
|
||||||
private static readonly Random RandCtx = new Random();
|
private static readonly Random RandCtx = new Random();
|
||||||
private static readonly RNGCryptoServiceProvider CsRandCtx
|
private static readonly RNGCryptoServiceProvider CsRandCtx
|
||||||
= new RNGCryptoServiceProvider();
|
= new RNGCryptoServiceProvider();
|
||||||
|
|
|
@ -21,6 +21,12 @@ namespace SockScape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Server Get(ushort id) {
|
||||||
|
lock(_Servers) {
|
||||||
|
return _Servers[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void Write(Server server) {
|
public static void Write(Server server) {
|
||||||
lock(_Servers) {
|
lock(_Servers) {
|
||||||
if(HasId(server.Id) && !_Servers[server.Id].Address.Equals(server.Address))
|
if(HasId(server.Id) && !_Servers[server.Id].Address.Equals(server.Address))
|
||||||
|
|
|
@ -13,8 +13,8 @@ using SockScape.Encryption;
|
||||||
|
|
||||||
namespace SockScape {
|
namespace SockScape {
|
||||||
class MasterConnection : Connection {
|
class MasterConnection : Connection {
|
||||||
private Regex UsernameRegex = new Regex("[A-Z0-9_]", RegexOptions.IgnoreCase);
|
private readonly Regex UsernameRegex = new Regex("[A-Z0-9_]", RegexOptions.IgnoreCase);
|
||||||
private Regex EmailRegex = new Regex("\\B[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\B", RegexOptions.IgnoreCase);
|
private readonly Regex EmailRegex = new Regex(@"\B[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\B", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private Key Key;
|
private Key Key;
|
||||||
public StreamCipher Encryptor { get; private set; }
|
public StreamCipher Encryptor { get; private set; }
|
||||||
|
@ -55,9 +55,10 @@ namespace SockScape {
|
||||||
Encryptor = new StreamCipher(Key.PrivateKey);
|
Encryptor = new StreamCipher(Key.PrivateKey);
|
||||||
break;
|
break;
|
||||||
case kInterMasterId.LoginAttempt:
|
case kInterMasterId.LoginAttempt:
|
||||||
if(packet.RegionCount != 3 || !packet.CheckRegions(2, 2))
|
if(packet.RegionCount != 3 || !packet.CheckRegionsMaxLength(0, 16, 255) || !packet.CheckRegionLengths(2, 2))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
Session session;
|
||||||
using(var db = new ScapeDb()) {
|
using(var db = new ScapeDb()) {
|
||||||
User user;
|
User user;
|
||||||
if((user = db.Users.FirstOrDefault(x => x.Username == packet[0])) == null) {
|
if((user = db.Users.FirstOrDefault(x => x.Username == packet[0])) == null) {
|
||||||
|
@ -75,8 +76,8 @@ namespace SockScape {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort server = packet[2].Raw.UnpackUInt16();
|
ushort serverId = packet[2].Raw.UnpackUInt16();
|
||||||
if(!MasterServerList.HasId(server)) {
|
if(!MasterServerList.HasId(serverId)) {
|
||||||
SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(false), "The world you have specified is offline."));
|
SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(false), "The world you have specified is offline."));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -86,32 +87,65 @@ namespace SockScape {
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Sessions.Add(new Session {
|
db.Sessions.Add(session = new Session {
|
||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
Secret = RNG.NextBytes(16),
|
Secret = RNG.NextBytes(16),
|
||||||
ServerId = server,
|
ServerId = serverId,
|
||||||
LastPing = DateTime.UtcNow
|
LastPing = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var server = MasterServerList.Get((ushort)session.ServerId);
|
||||||
|
SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(true), session.Secret, server.Address.ToString(), server.Port.Pack()));
|
||||||
break;
|
break;
|
||||||
case kInterMasterId.RegistrationAttempt:
|
case kInterMasterId.RegistrationAttempt:
|
||||||
if(packet.RegionCount != 3)
|
if(packet.RegionCount != 3 || !packet.CheckAllMaxLength(0xFF))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
using(var db = new ScapeDb()) {
|
if(!packet[0].Raw.IsAsciiString()) {
|
||||||
if(!packet[0].Raw.IsAsciiString()) {
|
SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "Your username cannot contain unicode characters."));
|
||||||
SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "Your username cannot contain unicode characters."));
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(packet[0].Raw.Length > 16 || !UsernameRegex.IsMatch(packet[0].Str)) {
|
|
||||||
SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "The username is max 16 characters and can only be letters, numbers, and underscores."));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string username = packet[0].Str.Trim(),
|
||||||
|
password = packet[1].Str.Trim(),
|
||||||
|
email = packet[2].Str.Trim();
|
||||||
|
|
||||||
|
if(username.Length > 16 || !UsernameRegex.IsMatch(username)) {
|
||||||
|
SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "The username is max 16 characters and can only be letters, numbers, and underscores."));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!EmailRegex.IsMatch(email)) {
|
||||||
|
SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "The email address is malformed."));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
using(var db = new ScapeDb()) {
|
||||||
|
if(db.Users.FirstOrDefault(x => x.Username == username) != null) {
|
||||||
|
SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "This username is already in use."));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(db.Users.FirstOrDefault(x => x.Email == email) != null) {
|
||||||
|
SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "This email address is already in use."));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO email activation
|
||||||
|
db.Users.Add(new User {
|
||||||
|
Username = username,
|
||||||
|
Password = password.HashPassword(),
|
||||||
|
Email = email,
|
||||||
|
Joined = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(true), "Registration was successful."));
|
||||||
break;
|
break;
|
||||||
case kInterMasterId.ServerListing:
|
case kInterMasterId.ServerListing:
|
||||||
SendEncrypted(MasterServerList.ReportPacket);
|
SendEncrypted(MasterServerList.ReportPacket);
|
||||||
|
|
|
@ -102,7 +102,7 @@ namespace SockScape {
|
||||||
if(!IsClientConnected(client) || packet.RegionCount < 1)
|
if(!IsClientConnected(client) || packet.RegionCount < 1)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if(!packet.CheckRegions(0, 1)) {
|
if(!packet.CheckRegionLengths(0, 1)) {
|
||||||
NegativeAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate, "Server count is malformed.");
|
NegativeAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate, "Server count is malformed.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ namespace SockScape {
|
||||||
}
|
}
|
||||||
|
|
||||||
for(byte i = 0; i < serverCount; ++i) {
|
for(byte i = 0; i < serverCount; ++i) {
|
||||||
if(!packet.CheckRegions(1 + 3 * i, 2, 2, 2))
|
if(!packet.CheckRegionLengths(1 + 3 * i, 2, 2, 2))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
MasterServerList.Write(new Server {
|
MasterServerList.Write(new Server {
|
||||||
|
@ -132,7 +132,7 @@ namespace SockScape {
|
||||||
}
|
}
|
||||||
|
|
||||||
Prospects = Prospects.Where(x => x.Value.ReceiveDelta.TotalSeconds < 10)
|
Prospects = Prospects.Where(x => x.Value.ReceiveDelta.TotalSeconds < 10)
|
||||||
.ToDictionary(x => x.Key, x => x.Value);
|
.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
var expiredClients = Clients.Where(x => x.Value.ReceiveDelta.TotalSeconds > 60).Select(x => x.Value).ToList();
|
var expiredClients = Clients.Where(x => x.Value.ReceiveDelta.TotalSeconds > 60).Select(x => x.Value).ToList();
|
||||||
if(expiredClients.Count > 0)
|
if(expiredClients.Count > 0)
|
||||||
|
|
|
@ -50,7 +50,7 @@ namespace SockScape {
|
||||||
|
|
||||||
long bodyPtr = headerPtr;
|
long bodyPtr = headerPtr;
|
||||||
foreach(var regionLength in regionLengths) {
|
foreach(var regionLength in regionLengths) {
|
||||||
// FLAG this could fail if one region exceeds 2^31-1 in size, check later
|
// FLAG TODO this could fail if one region exceeds 2^31-1 in size, check later
|
||||||
packet.Regions.Add(raw.Subset((int)bodyPtr, (int)regionLength));
|
packet.Regions.Add(raw.Subset((int)bodyPtr, (int)regionLength));
|
||||||
bodyPtr += regionLength;
|
bodyPtr += regionLength;
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ namespace SockScape {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckRegions(int startIndex, params int[] lengths) {
|
public bool CheckRegionLengths(int startIndex, params int[] lengths) {
|
||||||
if(startIndex + lengths.Length > RegionCount)
|
if(startIndex + lengths.Length > RegionCount)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -115,6 +115,30 @@ namespace SockScape {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CheckRegionsMaxLength(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 bool CheckAllMaxLength(int length, int startIndex = 0) {
|
||||||
|
if(startIndex > RegionCount)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for(int i = startIndex; i < RegionCount; ++i) {
|
||||||
|
if(this[i].Raw.Length > length)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] GetBytes() {
|
public byte[] GetBytes() {
|
||||||
var header = new List<byte>();
|
var header = new List<byte>();
|
||||||
header.AddRange(MagicNumber);
|
header.AddRange(MagicNumber);
|
||||||
|
@ -162,8 +186,9 @@ namespace SockScape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FLAG TODO determine if you still need this thing, probably not
|
||||||
public class PacketBuffer {
|
public class PacketBuffer {
|
||||||
private short MaxSize;
|
private readonly short MaxSize;
|
||||||
private Dictionary<uint, byte[]> Buffer
|
private Dictionary<uint, byte[]> Buffer
|
||||||
= new Dictionary<uint, byte[]>();
|
= new Dictionary<uint, byte[]>();
|
||||||
|
|
||||||
|
@ -174,9 +199,9 @@ namespace SockScape {
|
||||||
public void Add(uint id, byte[] packet) {
|
public void Add(uint id, byte[] packet) {
|
||||||
Buffer[id] = packet;
|
Buffer[id] = packet;
|
||||||
|
|
||||||
if(Buffer.Count > 10)
|
if(Buffer.Count > MaxSize)
|
||||||
Buffer =
|
Buffer =
|
||||||
Buffer.Where(x => x.Key >= id - 10)
|
Buffer.Where(x => x.Key >= id - MaxSize)
|
||||||
.ToDictionary(x => x.Key, x => x.Value);
|
.ToDictionary(x => x.Key, x => x.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue