Direct connection support

main
Thomas Woischnig 2023-12-04 01:05:29 +01:00
parent a99b097a83
commit fc25760c2f
12 changed files with 397 additions and 40 deletions

View File

@ -4,6 +4,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -18,6 +19,8 @@ namespace Lobbies
AutoResetEvent waitForExternalIp = new AutoResetEvent(false); AutoResetEvent waitForExternalIp = new AutoResetEvent(false);
UdpEchoServer udpEchoServer = new UdpEchoServer();
private Dictionary<Guid, LobbyInfo> lobbyInformation = new Dictionary<Guid, LobbyInfo>(); private Dictionary<Guid, LobbyInfo> lobbyInformation = new Dictionary<Guid, LobbyInfo>();
private string? host; private string? host;
private int port; private int port;
@ -75,6 +78,8 @@ namespace Lobbies
public void HostLobby(Guid gameId, string name, int gameMode, int maxPlayerCount, string? password, string? ip, int port) public void HostLobby(Guid gameId, string name, int gameMode, int maxPlayerCount, string? password, string? ip, int port)
{ {
udpEchoServer.Start(0);
byte[]? hash = null, salt = null; byte[]? hash = null, salt = null;
if(!string.IsNullOrEmpty(password)) if(!string.IsNullOrEmpty(password))
@ -91,8 +96,9 @@ namespace Lobbies
PlayerCount = 0, PlayerCount = 0,
PasswordHash = hash, PasswordHash = hash,
PasswordSalt = salt, PasswordSalt = salt,
HostIp = ip, HostIps = GatherLocalIpAddresses().ToArray(),
HostPort = port HostPort = port,
HostTryPort = udpEchoServer.Port
}; };
byte[] messageData = bufferRental.Rent(); byte[] messageData = bufferRental.Rent();
@ -142,7 +148,7 @@ namespace Lobbies
}); });
} }
public void UpdateLobby(string name, int gameMode, int maxPlayerCount, int playerCount, string? password, string? ip, int port) public void UpdateLobby(string name, int gameMode, int maxPlayerCount, int playerCount, string? password, int port)
{ {
byte[]? hash = null, salt = null; byte[]? hash = null, salt = null;
@ -159,8 +165,9 @@ namespace Lobbies
PlayerCount = playerCount, PlayerCount = playerCount,
PasswordHash = hash, PasswordHash = hash,
PasswordSalt = salt, PasswordSalt = salt,
HostIp = ip, HostIps = GatherLocalIpAddresses().ToArray(),
HostPort = port HostPort = port,
HostTryPort = udpEchoServer.Port
}; };
byte[] messageData = bufferRental.Rent(); byte[] messageData = bufferRental.Rent();
@ -170,6 +177,8 @@ namespace Lobbies
public void CloseLobby() public void CloseLobby()
{ {
udpEchoServer.Stop();
var lobbyDelete = new LobbyDelete() var lobbyDelete = new LobbyDelete()
{ {
@ -207,6 +216,32 @@ namespace Lobbies
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); }); _ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
} }
public async Task<IPAddress?> TryDirectConnection(IPAddress[] ipAddressesToTry, int tryPort)
{
return await Task.Run(() =>
{
IPAddress? ret = null;
using (var udpEchoClient = new UdpEchoServer())
{
udpEchoClient.Reached += (ep) =>
{
ret = ep.Address;
};
udpEchoClient.Start(0);
foreach (var ip in ipAddressesToTry)
{
udpEchoClient.CheckConnectionPossible(new IPEndPoint(ip, tryPort));
}
Thread.Sleep(500);
}
return ret;
});
}
public static IPAddress[] GetIPsByName(string hostName, bool ip4Wanted, bool ip6Wanted) public static IPAddress[] GetIPsByName(string hostName, bool ip4Wanted, bool ip6Wanted)
{ {
// Check if the hostname is already an IPAddress // Check if the hostname is already an IPAddress
@ -214,7 +249,7 @@ namespace Lobbies
if (IPAddress.TryParse(hostName, out outIpAddress) == true) if (IPAddress.TryParse(hostName, out outIpAddress) == true)
return new IPAddress[] { outIpAddress }; return new IPAddress[] { outIpAddress };
//<---------- //<----------
IPAddress[] addresslist = Dns.GetHostAddresses(hostName); IPAddress[] addresslist = Dns.GetHostAddresses(hostName);
if (addresslist == null || addresslist.Length == 0) if (addresslist == null || addresslist.Length == 0)
@ -361,10 +396,24 @@ namespace Lobbies
catch { } catch { }
} }
public IEnumerable<IPAddress> GatherLocalIpAddresses()
{
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces())
{
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses)
{
yield return addr.Address;
}
}
}
public void Dispose() public void Dispose()
{ {
waitForExternalIp.Dispose(); waitForExternalIp.Dispose();
tcpClient.Dispose(); tcpClient.Dispose();
udpEchoServer.Dispose();
} }
} }

View File

@ -0,0 +1,197 @@
using System.Net.Sockets;
using System.Net;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Lobbies
{
/// <summary>
/// Small udp server to receive udp packets and echo the data back to source
/// </summary>
internal class UdpEchoServer : IDisposable
{
public const int SIO_UDP_CONNRESET = -1744830452;
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private bool running = false;
private bool isDisposed = false;
private UdpClient? serverSocketV4, serverSocketV6;
public delegate void ReachableEventArgs(IPEndPoint remoteEndpoint);
/// <summary>
/// If a valid request for a ip and port query comes in call this event with the seen remote ip and port for a lobby server client id
/// </summary>
public event ReachableEventArgs? Reached;
public int Port { get; private set; }
private bool isRunningV4 = false, isRunningV6 = false;
public void CheckConnectionPossible(IPEndPoint remoteEndpoint)
{
if (!running || serverSocketV4 == null || serverSocketV6 == null)
throw new Exception("Listener not running!");
byte[] magicRequest = new byte[] { 0 };
for (int i = 0; i < 16; i++)
if(remoteEndpoint.AddressFamily == AddressFamily.InterNetwork)
serverSocketV4.Send(magicRequest, magicRequest.Length, remoteEndpoint);
else
serverSocketV6.Send(magicRequest, magicRequest.Length, remoteEndpoint);
}
/// <summary>
/// Listen to requests and fire events
/// </summary>
/// <param name="port">The port to listen on</param>
private async void ListenV4(int port)
{
try
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
serverSocketV4 = new UdpClient(port, AddressFamily.InterNetwork);
serverSocketV4.Client.IOControl(
(IOControlCode)SIO_UDP_CONNRESET,
new byte[] { 0, 0, 0, 0 },
null
);
Port = ((IPEndPoint)serverSocketV4.Client.LocalEndPoint).Port;
byte[] magicAnswer = new byte[] { 1 };
using (cancellationTokenSource.Token.Register(() => { serverSocketV4.Close(); }))
{
isRunningV4 = true;
while (running)
{
var receiveResult = await serverSocketV4.ReceiveAsync();
if (receiveResult.Buffer.Length == 1)
{
if (receiveResult.Buffer[0] == 0)
{
for (int i = 0; i < 16; i++)
serverSocketV4.Send(magicAnswer, magicAnswer.Length, receiveResult.RemoteEndPoint);
}
if (receiveResult.Buffer[0] == 1)
{
Reached?.Invoke(receiveResult.RemoteEndPoint);
}
}
}
}
}
catch when (cancellationTokenSource.IsCancellationRequested || !running) //Cancel requested
{
}
catch
{
throw;
}
finally
{
serverSocketV4?.Dispose();
serverSocketV4 = null;
running = false;
isRunningV4 = false;
}
}
/// <summary>
/// Listen to requests and fire events
/// </summary>
/// <param name="port">The port to listen on</param>
private async void ListenV6(int port)
{
try
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
serverSocketV6 = new UdpClient(port, AddressFamily.InterNetworkV6);
serverSocketV6.Client.IOControl(
(IOControlCode)SIO_UDP_CONNRESET,
new byte[] { 0, 0, 0, 0 },
null
);
byte[] magicAnswer = new byte[] { 1 };
using (cancellationTokenSource.Token.Register(() => { serverSocketV6.Close(); }))
{
isRunningV6 = true;
while (running)
{
var receiveResult = await serverSocketV6.ReceiveAsync();
if (receiveResult.Buffer.Length == 1)
{
if (receiveResult.Buffer[0] == 0)
{
Reached?.Invoke(receiveResult.RemoteEndPoint);
}
if (receiveResult.Buffer[0] == 1)
{
for (int i = 0; i < 16; i++)
serverSocketV6.Send(magicAnswer, magicAnswer.Length, receiveResult.RemoteEndPoint);
}
}
}
}
}
catch when (cancellationTokenSource.IsCancellationRequested || !running) //Cancel requested
{
}
catch
{
throw;
}
finally
{
serverSocketV6?.Dispose();
serverSocketV6 = null;
running = false;
isRunningV6 = false;
}
}
/// <summary>
/// Start udp listener
/// </summary>
/// <param name="port">The port to listen on</param>
public void Start(int port)
{
isRunningV4 = false;
isRunningV6 = false;
running = true;
_ = Task.Run(() => ListenV4(port));
while (running && !isRunningV4)
Thread.Yield();
_ = Task.Run(() => ListenV6(Port));
while (running && (!isRunningV4 || !isRunningV6))
Thread.Yield();
}
/// <summary>
/// Stop udp listener
/// </summary>
public void Stop()
{
running = false;
cancellationTokenSource.Cancel();
if (serverSocketV4 != null)
serverSocketV4?.Close();
if (serverSocketV6 != null)
serverSocketV6?.Close();
}
public void Dispose()
{
if (!isDisposed)
{
Stop();
while (isRunningV4 || isRunningV6)
Task.Yield();
cancellationTokenSource.Dispose();
isDisposed = true;
}
}
}
}

View File

@ -3,6 +3,7 @@ using Lobbies;
using LobbyClientTest; using LobbyClientTest;
using LobbyServerDto; using LobbyServerDto;
using System.Net; using System.Net;
using System.Net.WebSockets;
Console.WriteLine("Starting lobby client v0.7!"); Console.WriteLine("Starting lobby client v0.7!");
var lobbyClient = new LobbyClient(); var lobbyClient = new LobbyClient();
@ -16,12 +17,11 @@ FakeGameHost fakeGameHost = new FakeGameHost();
int myPort = fakeGameHost.Server(0); int myPort = fakeGameHost.Server(0);
string? myExternalIp = null; string? myExternalIp = null;
int myExternalPort = -1; int myExternalPort = -1;
IPEndPoint? hostInfo = null;
bool running = true; bool running = true;
bool connected = false; bool connected = false;
_ = Task.Run(() => _ = Task.Run(async () =>
{ {
while (running) while (running)
{ {
@ -78,8 +78,23 @@ _ = Task.Run(() =>
var lobbyHostInfo = lobbyEvent.EventData as LobbyHostInfo; var lobbyHostInfo = lobbyEvent.EventData as LobbyHostInfo;
var p = Console.GetCursorPosition(); var p = Console.GetCursorPosition();
Console.SetCursorPosition(0, p.Top); Console.SetCursorPosition(0, p.Top);
Console.WriteLine($"Host info for lobby {lobbyHostInfo!.LobbyId} is {lobbyHostInfo.HostIp}:{lobbyHostInfo.HostPort}!"); Console.WriteLine($"Host info for lobby {lobbyHostInfo!.LobbyId} is {(lobbyHostInfo.HostIps != null && lobbyHostInfo.HostIps.Length > 0 ? lobbyHostInfo.HostIps[0].ToString() : "")}:{lobbyHostInfo.HostPort}!");
hostInfo = new IPEndPoint(IPAddress.Parse(lobbyHostInfo.HostIp!), lobbyHostInfo.HostPort);
//Try direct connection
if (lobbyHostInfo.HostIps != null && lobbyHostInfo.HostIps.Length > 0)
{
Console.WriteLine($"Trying direct connection to {string.Join<IPAddress>(",", lobbyHostInfo.HostIps)} on port {lobbyHostInfo.HostTryPort}!");
var reachableIp = await lobbyClient.TryDirectConnection(lobbyHostInfo.HostIps, lobbyHostInfo.HostTryPort);
if(reachableIp != null)
{
Console.WriteLine($"Direct connection to {reachableIp.ToString()} possible, using direct connection!");
Console.WriteLine($"Connecting game client!");
fakeGameHost.Send(new IPEndPoint(reachableIp, lobbyHostInfo.HostPort), "Hello from Game Client!");
Console.Write(">");
continue;
}
}
Console.WriteLine($"Requesting nat punch to me!"); Console.WriteLine($"Requesting nat punch to me!");
lobbyClient.RequestLobbyNatPunch(lobbyHostInfo.LobbyId, null, (remoteEndpoint, messageBuffer, messageLength) => { lobbyClient.RequestLobbyNatPunch(lobbyHostInfo.LobbyId, null, (remoteEndpoint, messageBuffer, messageLength) => {
fakeGameHost.Send(remoteEndpoint, messageBuffer, messageLength); fakeGameHost.Send(remoteEndpoint, messageBuffer, messageLength);
@ -111,7 +126,7 @@ _ = Task.Run(() =>
Console.SetCursorPosition(0, p.Top); Console.SetCursorPosition(0, p.Top);
Console.WriteLine($"Nat punch requested to {lobbyRequestNatPunch!.ClientIp}:{lobbyRequestNatPunch.ClientPort}!"); Console.WriteLine($"Nat punch requested to {lobbyRequestNatPunch!.ClientIp}:{lobbyRequestNatPunch.ClientPort}!");
Task.Run(() => _ = Task.Run(() =>
{ {
lobbyClient.QueryExternalIpAndPort((remoteEndpoint, messageData, messageLength) => { lobbyClient.QueryExternalIpAndPort((remoteEndpoint, messageData, messageLength) => {
fakeGameHost.Send(remoteEndpoint, messageData, messageLength); fakeGameHost.Send(remoteEndpoint, messageData, messageLength);
@ -140,7 +155,6 @@ _ = Task.Run(() =>
Console.SetCursorPosition(0, p.Top); Console.SetCursorPosition(0, p.Top);
Console.WriteLine($"Nat punch request done!"); Console.WriteLine($"Nat punch request done!");
Console.WriteLine($"Connecting game client!"); Console.WriteLine($"Connecting game client!");
fakeGameHost.Send(new IPEndPoint(IPAddress.Parse(lobbyNatPunchDone!.ExternalIp!), lobbyNatPunchDone.ExternalPort), "Hello from Game Client!"); fakeGameHost.Send(new IPEndPoint(IPAddress.Parse(lobbyNatPunchDone!.ExternalIp!), lobbyNatPunchDone.ExternalPort), "Hello from Game Client!");
Console.Write(">"); Console.Write(">");
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -24,7 +25,8 @@ namespace LobbyServer
public int MaxPlayerCount { get; set; } public int MaxPlayerCount { get; set; }
public byte[]? PasswordHash { get; set; } public byte[]? PasswordHash { get; set; }
public byte[]? PasswordSalt { get; set; } public byte[]? PasswordSalt { get; set; }
public required string HostIp { get; set; } public required IPAddress[] HostIps { get; set; }
public int HostPort { get; set; } public int HostPort { get; set; }
public int HostTryPort { get; set; }
} }
} }

View File

@ -1,7 +1,7 @@
using LobbyServer; using LobbyServer;
using LobbyServerDto; using LobbyServerDto;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Net;
using var closing = new AutoResetEvent(false); using var closing = new AutoResetEvent(false);
using var tcpServer = new TcpServer(); using var tcpServer = new TcpServer();
@ -42,6 +42,11 @@ tcpServer.DataReceived += (clientId, dataLength, data) =>
if (!GameGuids.ValidGuids.Contains(lobbyCreate.GameId)) if (!GameGuids.ValidGuids.Contains(lobbyCreate.GameId))
throw new Exception("Invalid game guid!"); throw new Exception("Invalid game guid!");
List<IPAddress> hostIpAddresses = new List<IPAddress>();
hostIpAddresses.Add(IPAddress.Parse(tcpServer.GetClientIp(clientId)!));
if(lobbyCreate.HostIps != null)
hostIpAddresses.AddRange(lobbyCreate.HostIps);
var lobby = new Lobby var lobby = new Lobby
{ {
Name = lobbyCreate.Name, Name = lobbyCreate.Name,
@ -52,8 +57,9 @@ tcpServer.DataReceived += (clientId, dataLength, data) =>
PasswordHash = lobbyCreate.PasswordHash, PasswordHash = lobbyCreate.PasswordHash,
PasswordSalt = lobbyCreate.PasswordSalt, PasswordSalt = lobbyCreate.PasswordSalt,
HostClientId = clientId, HostClientId = clientId,
HostIp = lobbyCreate.HostIp == null ? tcpServer.GetClientIp(clientId)! : lobbyCreate.HostIp, HostIps = hostIpAddresses.ToArray(),
HostPort = lobbyCreate.HostPort, HostPort = lobbyCreate.HostPort,
HostTryPort = lobbyCreate.HostTryPort
}; };
if(lobbiesByClientId.TryGetValue(clientId, out var existingLobby)) if(lobbiesByClientId.TryGetValue(clientId, out var existingLobby))
@ -113,10 +119,17 @@ tcpServer.DataReceived += (clientId, dataLength, data) =>
existingLobby.PasswordHash = lobbyUpdate.PasswordHash; existingLobby.PasswordHash = lobbyUpdate.PasswordHash;
existingLobby.PasswordSalt = lobbyUpdate.PasswordSalt; existingLobby.PasswordSalt = lobbyUpdate.PasswordSalt;
if (lobbyUpdate.HostIp != null) List<IPAddress> hostIpAddresses = new List<IPAddress>();
existingLobby.HostIp = lobbyUpdate.HostIp; hostIpAddresses.Add(IPAddress.Parse(tcpServer.GetClientIp(clientId)!));
if (lobbyUpdate.HostIps != null)
hostIpAddresses.AddRange(lobbyUpdate.HostIps);
if (!Enumerable.SequenceEqual(existingLobby.HostIps, hostIpAddresses))
existingLobby.HostIps = hostIpAddresses.ToArray();
existingLobby.HostPort = lobbyUpdate.HostPort; existingLobby.HostPort = lobbyUpdate.HostPort;
existingLobby.HostTryPort = lobbyUpdate.HostTryPort;
_ = Task.Run(() => SendLobbyUpdate(Lobby.LobbyUpdateType.Update, existingLobby)); _ = Task.Run(() => SendLobbyUpdate(Lobby.LobbyUpdateType.Update, existingLobby));
} }
} }
@ -221,7 +234,7 @@ tcpServer.DataReceived += (clientId, dataLength, data) =>
{ {
var messageData = bufferRental.Rent(); var messageData = bufferRental.Rent();
var lobbyHostInfo = new LobbyHostInfo() { LobbyId = lobby.Id, HostIp = lobby.HostIp, HostPort = lobby.HostPort }; var lobbyHostInfo = new LobbyHostInfo() { LobbyId = lobby.Id, HostIps = lobby.HostIps, HostPort = lobby.HostPort, HostTryPort = lobby.HostTryPort };
var messageDataLength = lobbyHostInfo.Serialize(messageData); var messageDataLength = lobbyHostInfo.Serialize(messageData);
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
@ -426,7 +439,7 @@ Console.CancelKeyPress += (sender, args) =>
args.Cancel = true; args.Cancel = true;
}; };
Console.WriteLine($"{DateTime.Now}: Application started v0.7"); Console.WriteLine($"{DateTime.Now}: Application started v0.8");
udpServer.Start(8088); udpServer.Start(8088);
tcpServer.Start(8088); tcpServer.Start(8088);

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Net;
namespace LobbyServerDto namespace LobbyServerDto
{ {
@ -41,13 +42,17 @@ namespace LobbyServerDto
[MaxLength(16)] [MaxLength(16)]
public byte[]? PasswordSalt { get; set; } public byte[]? PasswordSalt { get; set; }
/// <summary> /// <summary>
/// The hosts ip. Used the the host information send to clients on their request. /// The hosts ip addresses locally detected. Used the the host information send to clients on their request.
/// </summary> /// </summary>
[MaxLength(32)] [MaxLength(32)]
public string? HostIp { get; set; } public IPAddress[]? HostIps { get; set; }
/// <summary> /// <summary>
/// The hosts port. Used the the host information send to clients on their request. /// The hosts port. Used the the host information send to clients on their request.
/// </summary> /// </summary>
public int HostPort { get; set; } public int HostPort { get; set; }
/// <summary>
/// The hosts echo port to try if a connection is possible.
/// </summary>
public int HostTryPort { get; set; }
} }
} }

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Net;
namespace LobbyServerDto namespace LobbyServerDto
{ {
@ -13,13 +14,17 @@ namespace LobbyServerDto
/// </summary> /// </summary>
public Guid LobbyId { get; set; } public Guid LobbyId { get; set; }
/// <summary> /// <summary>
/// The hosts ip, this could be an internal address /// The hosts ip addresses locally detected. Used the the host information send to clients on their request.
/// </summary> /// </summary>
[MaxLength(32)] [MaxLength(32)]
public string? HostIp { get; set; } public IPAddress[]? HostIps { get; set; }
/// <summary> /// <summary>
/// The hosts port /// The hosts port
/// </summary> /// </summary>
public int HostPort { get; set; } public int HostPort { get; set; }
/// <summary>
/// The hosts echo port to try if a connection is possible.
/// </summary>
public int HostTryPort { get; set; }
} }
} }

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Net;
namespace LobbyServerDto namespace LobbyServerDto
{ {
@ -7,7 +8,7 @@ namespace LobbyServerDto
/// </summary> /// </summary>
[LobbyMessage] [LobbyMessage]
public partial class LobbyUpdate public partial class LobbyUpdate
{ {
/// <summary> /// <summary>
/// The displayname of the lobby /// The displayname of the lobby
/// </summary> /// </summary>
@ -34,13 +35,17 @@ namespace LobbyServerDto
[MaxLength(16)] [MaxLength(16)]
public byte[]? PasswordSalt { get; set; } public byte[]? PasswordSalt { get; set; }
/// <summary> /// <summary>
/// The hosts ip /// The hosts ip addresses locally detected. Used the the host information send to clients on their request.
/// </summary> /// </summary>
[MaxLength(32)] [MaxLength(32)]
public string? HostIp { get; set; } public IPAddress[]? HostIps { get; set; }
/// <summary> /// <summary>
/// The hosts port /// The hosts port. Used the the host information send to clients on their request.
/// </summary> /// </summary>
public int HostPort { get; set; } public int HostPort { get; set; }
/// <summary>
/// The hosts echo port to try if a connection is possible.
/// </summary>
public int HostTryPort { get; set; }
} }
} }

View File

@ -74,6 +74,7 @@ namespace LobbyServerDto
s.Append(@$"// <auto-generated /> s.Append(@$"// <auto-generated />
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Net;
{(foundClass.Value.nameSpace is null ? null : $@"namespace {foundClass.Value.nameSpace} {(foundClass.Value.nameSpace is null ? null : $@"namespace {foundClass.Value.nameSpace}
{{")} {{")}
@ -113,6 +114,34 @@ using System.Text;
var name = p.Identifier.ToString(); var name = p.Identifier.ToString();
switch(p.Type.ToString()) switch(p.Type.ToString())
{ {
case "IPAddress[]":
case "IPAddress[]?":
s.Append($@"
if ({name} != null)
{{
int maxLength = Math.Min({name}.Length, {maxLength});
uint v = (uint)maxLength;
while (v >= 0x80)
{{
buffer[offset++] = (byte)(v | 0x80);
v >>= 7;
}}
buffer[offset++] = (byte)v;
for(int i = 0; i < maxLength; i++)
{{
var ipBuffer = {name}[i].GetAddressBytes();
buffer[offset++] = (byte)ipBuffer.Length;
Buffer.BlockCopy(ipBuffer, 0, buffer, offset, ipBuffer.Length);
offset += ipBuffer.Length;
}}
}}
else
{{
buffer[offset++] = 0;
}}
");
break;
case "bool": case "bool":
s.Append($@" s.Append($@"
buffer[offset++] = (byte)({name} == true ? 1 : 0);"); buffer[offset++] = (byte)({name} == true ? 1 : 0);");
@ -226,6 +255,42 @@ using System.Text;
var name = p.Identifier.ToString(); var name = p.Identifier.ToString();
switch (p.Type.ToString()) switch (p.Type.ToString())
{ {
case "IPAddress[]":
case "IPAddress[]?":
s.Append($@"
{{
int arrayLen = 0;
int shift = 0;
byte b;
do {{
// Check for a corrupted stream. Read a max of 5 bytes.
// In a future version, add a DataFormatException.
if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7
throw new FormatException(""Format_Bad7BitInt32"");
// ReadByte handles end of stream cases for us.
b = buffer[offset++];
arrayLen |= (b & 0x7F) << shift;
shift += 7;
}} while ((b & 0x80) != 0);
if(arrayLen > {maxLength})
throw new FormatException(""Format_IPAddressArrayToLong"");
if(arrayLen > 0)
{{
ret.{name} = new IPAddress[arrayLen];
for(int i=0; i < arrayLen; i++)
{{
var itemLen = buffer[offset++];
if(itemLen > 16)
throw new FormatException(""Format_IPAddressBytesArrayToLong"");
ret.{name}[i] = new IPAddress(buffer.Slice(offset, itemLen).ToArray());
offset += itemLen;
}}
}}
}}");
break;
case "bool": case "bool":
s.Append($@" s.Append($@"
ret.{name} = buffer[offset++] == 0 ? false : true;"); ret.{name} = buffer[offset++] == 0 ? false : true;");
@ -236,10 +301,10 @@ using System.Text;
break; break;
case "Guid": case "Guid":
s.Append($@" s.Append($@"
{{ {{
ret.{name} = new Guid(buffer.Slice(offset, 16).ToArray()); ret.{name} = new Guid(buffer.Slice(offset, 16).ToArray());
offset+=16; offset+=16;
}}"); }}");
break; break;
case "string": case "string":
case "string?": case "string?":
@ -260,6 +325,9 @@ using System.Text;
shift += 7; shift += 7;
}} while ((b & 0x80) != 0); }} while ((b & 0x80) != 0);
if(strLen > {maxLength})
throw new FormatException(""Format_StringToLong"");
if(strLen > 0) if(strLen > 0)
{{ {{
ret.{name} = Encoding.UTF8.GetString(buffer.Slice(offset, strLen).ToArray()); ret.{name} = Encoding.UTF8.GetString(buffer.Slice(offset, strLen).ToArray());
@ -271,7 +339,7 @@ using System.Text;
case "byte[]?": case "byte[]?":
s.Append($@" s.Append($@"
{{ {{
int strLen = 0; int byteLen = 0;
int shift = 0; int shift = 0;
byte b; byte b;
do {{ do {{
@ -282,14 +350,17 @@ using System.Text;
// ReadByte handles end of stream cases for us. // ReadByte handles end of stream cases for us.
b = buffer[offset++]; b = buffer[offset++];
strLen |= (b & 0x7F) << shift; byteLen |= (b & 0x7F) << shift;
shift += 7; shift += 7;
}} while ((b & 0x80) != 0); }} while ((b & 0x80) != 0);
if(strLen > 0) if(byteLen > {maxLength})
throw new FormatException(""Format_ByteArrayToLong"");
if(byteLen > 0)
{{ {{
ret.{name} = buffer.Slice(offset, strLen).ToArray(); ret.{name} = buffer.Slice(offset, byteLen).ToArray();
offset += strLen; offset += byteLen;
}} }}
}}"); }}");
break; break;

View File

@ -15,10 +15,6 @@
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="bin\Release\netstandard2.0\\LobbyServerSourceGenerator.dll" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />