csharp/BlazorPlus/BlazorLinuxAdmin/BlazorLinuxAdmin/TcpMaps/Models.cs

Models.cs
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.IO;
using System.Security.Cryptography;
using System.Security.Policy;
using System.Net.Security;

namespace BlazorLinuxAdmin.TcpMaps
{
	public clast TcpMapBaseWorker
	{

		public bool IsStarted { get; protected set; }
		public Exception Error { get; set; }

		public void OnError(Exception x)
		{
			Error = x;
			TcpMapService.OnError(x);
		}

		public ConcurrentQueue LogMessages = new ConcurrentQueue();

		public void LogMessage(string msg)
		{
			LogMessages.Enqueue(msg);
			while (LogMessages.Count > 200)
				LogMessages.TryDequeue(out _);
			TcpMapService.LogMessage(msg);
		}
	}



	[Serializable]
	public clast TcpMapLicense
	{
		static public TcpMapLicense CreateNew(string key, string name)
		{
			TcpMapLicense lic = new TcpMapLicense() { Key = key, Name = name };
			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
			lic.RSAXmlData = rsa.ToXmlString(true);
			return lic;
		}

		public string Key { get; set; }
		public string Name { get; set; }
		public string RSAXmlData { get; set; }

		public void Validate()
		{
			if (string.IsNullOrEmpty(Key)) throw new Exception("Miss Key");
			if (string.IsNullOrEmpty(Name)) throw new Exception("Miss Name");
			if (string.IsNullOrEmpty(RSAXmlData)) throw new Exception("Miss RSAXmlData");
			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
			rsa.FromXmlString(RSAXmlData);
		}

		public TcpMapLicense Clone()
		{
			return new TcpMapLicense() { Key = Key, Name = Name, RSAXmlData = RSAXmlData };
		}


		public byte[] ComputeHash(byte[] data)
		{
			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
			rsa.FromXmlString(RSAXmlData);
			return rsa.SignHash(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
		}
		public byte[] EncryptData(byte[] data)
		{
			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
			rsa.FromXmlString(RSAXmlData);
			return rsa.Encrypt(data, false);
		}

		public void GenerateSecureKeyAndHash(out byte[] sourceKeyIV, out byte[] encryptedkeyandiv, out byte[] sourcekeyhash)
		{
			Random r = new Random(Guid.NewGuid().GetHashCode());
			byte[] keyandiv = new byte[32];//key=24,iv=8
			r.NextBytes(keyandiv);
			EncryptSourceKey(keyandiv, out encryptedkeyandiv, out sourcekeyhash);
			sourceKeyIV = keyandiv;
		}

		private void EncryptSourceKey(byte[] keyandiv, out byte[] encryptedkeyandiv, out byte[] sourcekeyhash)
		{
			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
			rsa.FromXmlString(RSAXmlData);
			encryptedkeyandiv = rsa.Encrypt(keyandiv, false);
			sourcekeyhash = rsa.SignHash(keyandiv, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
		}

		public void DescriptSourceKey(byte[] encryptedkeyandiv, byte[] sourcekeyhash, out byte[] keyandiv)
		{
			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
			rsa.FromXmlString(RSAXmlData);
			byte[] srcdata = rsa.Decrypt(encryptedkeyandiv, false);
			byte[] srchash = rsa.SignHash(srcdata, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
			if (srcdata.Length != 32)
				throw new Exception("size not match");
			if (!srchash.SequenceEqual(sourcekeyhash))
				throw new Exception("hash not match");
			keyandiv = srcdata;
		}

		public void OverrideStream(Stream stream, byte[] keyIV, out Stream sread, out Stream swrite)
		{
			Console.WriteLine("OverrideStream : " + Key + " : " + BitConverter.ToString(keyIV));

			var tdes = TripleDES.Create();
			tdes.Key = keyIV.astpan().Slice(0, 24).ToArray();
			tdes.IV = keyIV.astpan().Slice(24, 8).ToArray();
			sread = Crypto.BlockCryptoStream.CreateDecryptReader(stream, tdes);
			swrite = Crypto.BlockCryptoStream.CreateEncryptWriter(stream, tdes);

			//throw new NotSupportedException();
			//swrite = stream;
			//sread = stream;

			//TODO: not flush as expected , do it later.

			//var tdes = TripleDES.Create();
			//tdes.Key = keyIV.astpan().Slice(0, 24).ToArray();
			//tdes.IV = keyIV.astpan().Slice(24, 8).ToArray();
			//sread = new CryptoStream(stream, tdes.CreateDecryptor(), CryptoStreamMode.Read);
			//swrite = new CryptoStream(stream, tdes.CreateEncryptor(), CryptoStreamMode.Write);
		}


	}


	[Serializable]
	public clast TcpMapClient
	{
		public TcpMapLicense License { get; set; }

		public string Id { get; set; }

		public string ServerHost { get; set; }
		public int ServerPort { get; set; }    //server mapped port

		public string ClientHost { get; set; }

		public int ClientPort { get; set; }

		public int RouterClientPort { get; set; }    // the connector connect to the router mapped ClientPort directly 

		//public int RouterSecurePort { get; set; }	//laster , let client&connector connect in a secure way
		//public int SecurePort { get; set; }	// listen this port , handle requests from RouterSecurePort

		public bool IsDisabled { get; set; }

		public bool UseEncrypt { get; set; }

		public int PreSessionCount { get; set; }


		public string Comment { get; set; }

		public TcpMapClient Clone()
		{
			var newinst = (TcpMapClient)this.MemberwiseClone();
			newinst.License = License.Clone();
			return newinst;
		}
	}



	[Serializable]
	public clast TcpMapServer
	{
		public TcpMapLicense License { get; set; }

		public string Id { get; set; }

		public string Comment { get; set; }


		public string ServerBind { get; set; } = "0.0.0.0";

		public int ServerPort { get; set; }

		public bool IsDisabled { get; set; }

		public bool IsValidated { get; set; }

		public bool UseEncrypt { get; set; }

		public string IPServiceUrl { get; set; }

		public bool AllowConnector { get; set; }

		public TcpMapLicense ConnectorLicense { get; set; }

		public TcpMapServer Clone()
		{
			var newinst = (TcpMapServer)this.MemberwiseClone();
			newinst.License = License.Clone();
			newinst.ConnectorLicense = ConnectorLicense?.Clone();
			return newinst;
		}
	}


	// map local machine 0.0.0.0:LocalPort to ServerHost:ServerPort , to ClientHost:ClientPort
	// if the client Provide ProxyRoutePort/UDP , will use 0.0.0.0:LocalPort to ProxyRoutePort/UDP to ClientHost:ClientPort
	// the server will save bandwidth cost
	[Serializable]
	public clast TcpMapConnector
	{
		public TcpMapLicense License { get; set; }

		public string Id { get; set; }

		public string Comment { get; set; }

		public string LocalBind { get; set; } = "0.0.0.0";

		public int LocalPort { get; set; }      //port of localhost

		public string ServerHost { get; set; }

		public int ServerPort { get; set; }     //server mapped port

		public bool IsDisabled { get; set; }

		public bool UseEncrypt { get; set; }

		//Default is false , at most case the ProxyRoutePort/UDP shall works
		public bool UseServerBandwidth { get; set; }

		public bool UseRouterClientPort { get; set; }

		public bool UseRouterSecurePort { get; set; }

		public bool UseUDPPunching { get; set; }

		public bool UDPCachePort { get; set; }

		public TcpMapConnector Clone()
		{
			var newinst = (TcpMapConnector)this.MemberwiseClone();
			return newinst;
		}
	}

	public clast CommandMessage
	{
		public const int MAX_PACKAGE_SIZE = 1024 * 1024;

		public const string STR_H8 = "CMDMSGv1";
		public const string END_H8 = "ENDMSGv1";

		public string Name { get; set; }
		public Memory Data { get; set; }
		public string[] Args { get; set; }

		public CommandMessage() { }
		public CommandMessage(string name, params string[] args) { Name = name; Args = args; }

		public override string ToString()
		{
			if (Args == null)
				return Name;
			return Name + ":" + string.Join(",", Args);
		}



		static public async Task ReadFromSocketAsync(Socket socket)
		{
			async Task ReadFunc(byte[] buffer, int offset, int length)
			{
				ArraySegment seg = new ArraySegment(buffer, offset, length);
				return await socket.ReceiveAsync(seg, SocketFlags.None);//SocketFlags.Partial not OK in Linu
			}
			return await ReadAsync(ReadFunc);
		}
		static public async Task ReadFromStreamAsync(Stream stream)
		{
			async Task ReadFunc(byte[] buffer, int offset, int length)
			{
				return await stream.ReadAsync(buffer, offset, length);
			}
			return await ReadAsync(ReadFunc);
		}
		static public async Task ReadAsync(Func readFunc)
		{

			byte[] header = new byte[12];
			int hcount = 0;
			while (hcount < 12)
			{
				int rc = await readFunc(header, hcount, 12 - hcount);
				if (rc == 0)
				{
					if (hcount == 0)
					{
						//TcpMapService.LogMessage("ReadFromStreamAsync read 0 bytes "+Environment.StackTrace);
						return null;//client maybe quited.
					}
					throw new Exception("Unexpected END for header");
				}
				hcount += rc;
			}
			string h8 = System.Text.Encoding.ASCII.GetString(header, 0, 8);
			if (STR_H8 != h8)
				throw new Exception("Invalid header : " + h8);
			uint size = BitConverter.ToUInt32(header, 8);
			if (size > MAX_PACKAGE_SIZE)
				throw new Exception("reach MAX_PACKAGE_SIZE:" + size);
			byte[] buffer = new byte[size - 12];
			int bytecount = 0;
			while (bytecount < buffer.Length)
			{
				int rc = await readFunc(buffer, bytecount, buffer.Length - bytecount);
				if (rc == 0)
					throw new Exception("Unexpected END for message");
				bytecount += rc;
			}
			return UnpackRest(new MemoryStream(buffer));
		}

		static public CommandMessage Unpack(MemoryStream ms)
		{
			byte[] header = new byte[12];
			ms.Read(header, 0, 12);
			string h8 = System.Text.Encoding.ASCII.GetString(header, 0, 8);
			if (STR_H8 != h8)
				throw new Exception("Invalid header : " + h8);
			uint size = BitConverter.ToUInt32(header, 8);
			if (size > MAX_PACKAGE_SIZE)
				throw new Exception("reach MAX_PACKAGE_SIZE:" + size);
			return UnpackRest(ms);
		}

		static internal CommandMessage UnpackRest(MemoryStream ms)
		{
			CommandMessage msg = new CommandMessage();

			BinaryReader br = new BinaryReader(ms); //TODO:performance dont use BinaryWriter/BinaryReader
			string flag = br.ReadString();
			if (flag[0] == '1')
				msg.Name = br.ReadString();
			if (flag[1] == '1')
			{
				msg.Data = br.ReadBytes(br.ReadInt32());//TODO:..better implementation for Memory
			}
			if (flag[2] == '1')
			{
				string parts = br.ReadString();
				if (parts == "")
				{
					msg.Args = Array.Empty();
				}
				else
				{
					msg.Args = parts.Split(',');
					for (int i = 0; i < msg.Args.Length; i++)
					{
						msg.Args[i] = msg.Args[i] == "-" ? null : br.ReadString();
						//if (msg.Args[i] == END_H8) throw new Exception("unexpected " + END_H8 + " parts:" + parts);
					}
				}
			}

			string endstr = br.ReadString();
			if (endstr != END_H8)
				throw new Exception("Invalid footer : " + endstr);

			return msg;
		}

		public byte[] Pack()    //TODO:performance return Memory
		{
			int capacity = 64;
			if (Name != null) capacity += Name.Length * 2;
			if (!Data.IsEmpty) capacity += Data.Length;
			if (Args != null) capacity += Args.Length * 8 + Args.Sum(v => v?.Length * 2 ?? 0);

			MemoryStream ms = new MemoryStream(capacity);//TODO:performance dont use MemoryStream
			BinaryWriter br = new BinaryWriter(ms); //TODO:performance dont use BinaryWriter/BinaryReader
			byte[] h8 = System.Text.Encoding.ASCII.GetBytes(STR_H8);
			br.Write(h8);
			br.Write(0);//place holder
			br.Write((Name == null ? "0" : "1") + (Data.IsEmpty ? "0" : "1") + (Args == null ? "0" : "1"));
			if (Name != null) br.Write(Name);
			if (!Data.IsEmpty)
			{
				br.Write((int)Data.Length);
				br.Write(Data.Span);
			}
			if (Args != null)
			{
				br.Write(string.Join(",", Args.Select(v => v == null ? "-" : v.Length.ToString()).ToList()));//ToArray , compiler bug on my PC
				foreach (string arg in Args)
				{
					if (arg != null)
						br.Write(arg);
				}
			}
			br.Write(END_H8);

			byte[] data = ms.ToArray();
			if (data.Length > MAX_PACKAGE_SIZE)
				throw new Exception("reach MAX_PACKAGE_SIZE:" + data.Length);
			byte[] bsiv = BitConverter.GetBytes((uint)data.Length);
			Buffer.BlockCopy(bsiv, 0, data, 8, 4);

			if (data.Length > capacity)
				Console.WriteLine("capacity :" + data.Length + "/" + capacity);

			return data;
		}

	}

}