// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

// <summary>
//     Socket code.
// </summary>
// <history>
//     2008 created by Truong Do (ductdo).
//     2009-... modified by Truong Do (TruongDo).
//     2023- Included in PowerToys.
// </history>
using MouseWithoutBorders.Exceptions;

[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#SendData(System.Byte[])", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#Close()", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#CreateSocket(System.Boolean)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#SendData(System.Byte[],MouseWithoutBorders.IP)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#SendData(System.Object)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#SendFile(System.Net.Sockets.Socket,System.String)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#MainTCPRoutine(System.Net.Sockets.Socket,System.String)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#TCPServerThread(System.Object)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#SendClipboardData(System.Object)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#StartNewTcpClient(MouseWithoutBorders.MachineInf)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#StartNewTcpServer(System.Net.Sockets.Socket,System.String)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#UpdateTCPClients()", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#UpdateTcpSockets(System.Net.Sockets.Socket,MouseWithoutBorders.SocketStatus)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#.ctor(System.Int32,System.Boolean)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#SendData(System.Byte[],MouseWithoutBorders.IP,System.Int32)", Justification = "Dotnet port with style preservation")]
[module: SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Scope = "type", Target = "MouseWithoutBorders.SocketStuff", Justification = "Dotnet port with style preservation")]

namespace MouseWithoutBorders.Class
{
    internal enum SocketStatus : int
    {
        NA = 0,
        Resolving = 1,
        Connecting = 2,
        Handshaking = 3,
        Error = 4,
        ForceClosed = 5,
        InvalidKey = 6,
        Timeout = 7,
        SendError = 8,
        Connected = 9,
    }

    internal class TcpSk : IDisposable
    {
        public TcpSk(bool isClient, Socket s, SocketStatus status, string machineName, IPAddress address = null)
        {
            IsClient = isClient;
            BackingSocket = s;
            Status = status;
            MachineName = machineName;
            Address = address;
            BirthTime = Common.GetTick();
        }

        public bool IsClient { get; set; }

        public Socket BackingSocket { get; set; }

        public SocketStatus Status { get; set; }

        public string MachineName { get; set; }

        public long BirthTime { get; set; }

        public uint MachineId { get; set; }

        public IPAddress Address { get; set; }

        private Stream encryptedStream;
        private Stream decryptedStream;
        private Stream socketStream;

        public Stream EncryptedStream
        {
            get
            {
                if (encryptedStream == null && BackingSocket.Connected)
                {
                    encryptedStream = Common.GetEncryptedStream(new NetworkStream(BackingSocket));
                    Common.SendOrReceiveARandomDataBlockPerInitialIV(encryptedStream);
                }

                return encryptedStream;
            }
        }

        public Stream DecryptedStream
        {
            get
            {
                if (decryptedStream == null && BackingSocket.Connected)
                {
                    decryptedStream = Common.GetDecryptedStream(new NetworkStream(BackingSocket));
                    Common.SendOrReceiveARandomDataBlockPerInitialIV(decryptedStream, false);
                }

                return decryptedStream;
            }
        }

        public Stream SocketStream
        {
            get
            {
                if (socketStream == null && BackingSocket.Connected)
                {
                    socketStream = new NetworkStream(BackingSocket);
                }

                return socketStream;
            }
        }

        public void Dispose()
        {
            encryptedStream?.Dispose();

            decryptedStream?.Dispose();

            socketStream?.Dispose();
        }
    }

    internal class SocketStuff
    {
        private readonly int bASE_PORT;
        private TcpServer skClipboardServer;
        private TcpServer skMessageServer;
        internal object TcpSocketsLock = new();
        internal static bool InvalidKeyFound;
        internal static bool InvalidKeyFoundOnClientSocket;

        internal const int CONNECT_TIMEOUT = 60000;
        private static readonly ConcurrentDictionary<string, int> FailedAttempt = new();

        internal List<TcpSk> TcpSockets
        {
            get; private set;

            // set { tcpSockets = value; }
        }

        internal int TcpPort
        {
            get;

            // set { tcpPort = value; }
        }

        private static bool firstRun;
        private readonly long connectTimeout;
        private static int restartCount;

        internal SocketStuff(int port, bool byUser)
        {
            Common.LogDebug("SocketStuff started.");

            bASE_PORT = port;
            Common.Ran = new Random();

            Common.LogDebug("Validating session...");

            if (Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
            {
                if (Common.DesMachineID != Common.MachineID)
                {
                    Common.SwitchToMultipleMode(false, true);
                }

                if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
                {
                    Common.MainForm.SetTrayIconText("Not physical console session.");
                    if (byUser)
                    {
                        Common.ShowToolTip("Not physical console session.", 5000);
                    }
                }

                Common.MMSleep(1);
                Common.Log("Not physical console session.");

                throw new NotPhysicalConsoleException("Not physical console session.");
            }

            Common.LogDebug("Creating socket list and mutex...");

            try
            {
                lock (TcpSocketsLock)
                {
                    TcpSockets = new List<TcpSk>();
                }

                bool dummy1 = Setting.Values.MatrixOneRow; // Reading from reg to variable
                dummy1 = Setting.Values.MatrixCircle;

                if (Setting.Values.IsMyKeyRandom)
                {
                    Setting.Values.MyKey = Common.MyKey;
                }

                Common.MagicNumber = Common.Get24BitHash(Common.MyKey);
                Common.PackageID = Setting.Values.PackageID;

                TcpPort = bASE_PORT;

                if (Common.SocketMutex == null)
                {
                    firstRun = true;
                    Common.SocketMutex = new Mutex(false, $"Global\\{Application.ProductName}-{FrmAbout.AssemblyVersion}-FF7CDABE-1015-0904-1103-24670FA5D16E");
                }

                Common.AcquireSocketMutex();
            }
            catch (AbandonedMutexException e)
            {
                Common.TelemetryLogTrace($"{nameof(SocketStuff)}: {e.Message}", SeverityLevel.Warning);
            }

            Common.GetScreenConfig();

            if (firstRun && Common.RunOnScrSaverDesktop)
            {
                firstRun = false;
            }

            // JUST_GOT_BACK_FROM_SCREEN_SAVER: For bug: monitor does not turn off after logon screen saver exits
            else if (!Common.RunOnScrSaverDesktop)
            {
                if (Setting.Values.LastX == Common.JUST_GOT_BACK_FROM_SCREEN_SAVER)
                {
                    Common.NewDesMachineID = Common.DesMachineID = Common.MachineID;
                }
                else
                {
                    // Common.Log("Getting IP: " + Setting.Values.DesMachineID.ToString(CultureInfo.CurrentCulture));
                    Common.LastX = Setting.Values.LastX;
                    Common.LastY = Setting.Values.LastY;

                    if (Common.RunOnLogonDesktop && Setting.Values.DesMachineID == (uint)ID.ALL)
                    {
                        Common.SwitchToMultipleMode(true, false);
                    }
                    else
                    {
                        Common.SwitchToMultipleMode(false, false);
                    }
                }
            }

            connectTimeout = Common.GetTick() + (CONNECT_TIMEOUT / 2);
            Exception openSocketErr;

            /*
             * The machine might be getting a new IP address from its DHCP server
             * for ex, when a laptop with a wireless connection just wakes up, might take long time:(
             * */

            Common.GetMachineName(); // IPs might have been changed
            Common.UpdateMachineTimeAndID();

            Common.LogDebug("Creating sockets...");

            openSocketErr = CreateSocket();

            int sleepSecs = 0, errCode = 0;

            if (openSocketErr != null)
            {
                if (openSocketErr is SocketException)
                {
                    errCode = (openSocketErr as SocketException).ErrorCode;

                    switch (errCode)
                    {
                        case 0: // No error.
                            break;

                        case 10048: // WSAEADDRINUSE
                            sleepSecs = 10;

                            // It is reasonable to give a try on restarting MwB processes in other sessions.
                            if (restartCount++ < 5 && Common.IsMyDesktopActive() && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
                            {
                                Common.TelemetryLogTrace("Restarting the service dues to WSAEADDRINUSE.", SeverityLevel.Warning);
                                Program.StartService();
                                Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
                            }

                            break;

                        case 10049: // WSAEADDRNOTAVAIL
                            sleepSecs = 1;
                            break;

                        default:
                            sleepSecs = 5;
                            break;
                    }
                }
                else
                {
                    sleepSecs = 10;
                }

                if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
                {
                    if (byUser)
                    {
                        Common.ShowToolTip(errCode.ToString(CultureInfo.CurrentCulture) + ": " + openSocketErr.Message, 5000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus);
                    }
                }

                Common.MMSleep(sleepSecs);
                Common.ReleaseSocketMutex();
                throw new ExpectedSocketException(openSocketErr.Message);
            }
            else
            {
                restartCount = 0;

                if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
                {
                    IpcHelper.CreateIpcServer(false);
                }

                Common.MainForm.UpdateNotifyIcon();
            }
        }

        internal void Close(bool sentWait)
        {
            try
            {
                if (!Common.RunOnScrSaverDesktop)
                {
                    Setting.Values.LastX = Common.LastX;
                    Setting.Values.LastY = Common.LastY;
                    Setting.Values.PackageID = Common.PackageID;

                    // Common.Log("Saving IP: " + Setting.Values.DesMachineID.ToString(CultureInfo.CurrentCulture));
                    Setting.Values.DesMachineID = (uint)Common.DesMachineID;
                }

                _ = Common.ExecuteAndTrace(
                    "Closing sockets",
                    () =>
                    {
                        Common.LogDebug($"Closing socket [{skMessageServer?.Name}].");
                        skMessageServer?.Close(); // The original ones, not the socket instances produced by the accept() method.
                        skMessageServer = null;

                        Common.LogDebug($"Closing socket [{skClipboardServer?.Name}].");
                        skClipboardServer?.Close();
                        skClipboardServer = null;
                        try
                        {
                            // If these sockets are failed to be closed then the tool would not function properly, more logs are added for debugging.
                            lock (TcpSocketsLock)
                            {
                                int c = 0;

                                if (TcpSockets != null)
                                {
                                    Common.LogDebug("********** Closing Sockets: " + TcpSockets.Count.ToString(CultureInfo.InvariantCulture));

                                    List<TcpSk> notClosedSockets = new();

                                    foreach (TcpSk t in TcpSockets)
                                    {
                                        if (t != null && t.BackingSocket != null && t.Status != SocketStatus.Resolving)
                                        {
                                            try
                                            {
                                                t.MachineName = "$*NotUsed*$";
                                                t.Status = t.Status >= 0 ? 0 : t.Status - 1;

                                                if (sentWait)
                                                {
                                                    t.BackingSocket.Close(1);
                                                }
                                                else
                                                {
                                                    t.BackingSocket.Close();
                                                }

                                                c++;

                                                continue;
                                            }
                                            catch (SocketException e)
                                            {
                                                string log = $"Socket.Close error: {e.GetType()}/{e.Message}. This is expected when the socket is already closed by remote host.";
                                                Common.Log(log);
                                            }
                                            catch (ObjectDisposedException e)
                                            {
                                                string log = $"Socket.Close error: {e.GetType()}/{e.Message}. This is expected when the socket is already disposed.";
                                                Common.Log(log);
                                            }
                                            catch (Exception e)
                                            {
                                                Common.Log(e);
                                            }

                                            // If there was an error closing the socket:
                                            if ((int)t.Status > -5)
                                            {
                                                notClosedSockets.Add(t); // Try to give a few times to close the socket later on.
                                            }
                                        }
                                    }

                                    TcpSockets = notClosedSockets;
                                }

                                Common.LogDebug("********** Sockets Closed: " + c.ToString(CultureInfo.CurrentCulture));
                            }
                        }
                        catch (Exception e)
                        {
                            Common.Log(e);
                        }
                    },
                    TimeSpan.FromSeconds(3),
                    true);
            }
            catch (Exception e)
            {
                Common.Log(e);
            }
            finally
            {
                Common.ReleaseSocketMutex();
            }

            if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
            {
                try
                {
                    IpcHelper.CreateIpcServer(true);
                }
                catch (Exception e)
                {
                    Common.Log(e);
                }
            }
        }

        private Exception CreateSocket()
        {
            try
            {
                skMessageServer = new TcpServer(TcpPort + 1, new ParameterizedThreadStart(TCPServerThread));
                skClipboardServer = new TcpServer(TcpPort, new ParameterizedThreadStart(AcceptConnectionAndSendClipboardData));
            }
            catch (SocketException e)
            {
                Common.Log(e);
                return e;
            }
            catch (Exception e)
            {
                Common.Log(e);
                return e;
            }

            Common.LogDebug("==================================================");
            return null;
        }

        private static int TcpSendData(TcpSk tcp, byte[] bytes)
        {
            Stream encryptedStream = tcp.EncryptedStream;

            if (tcp.BackingSocket == null || !tcp.BackingSocket.Connected || encryptedStream == null)
            {
                string log = $"{nameof(TcpSendData)}: The socket is no longer connected, it could have been closed by the remote host.";
                Common.Log(log);
                throw new ExpectedSocketException(log);
            }

            bytes[3] = (byte)((Common.MagicNumber >> 24) & 0xFF);
            bytes[2] = (byte)((Common.MagicNumber >> 16) & 0xFF);
            bytes[1] = 0;
            for (int i = 2; i < Common.PACKAGE_SIZE; i++)
            {
                bytes[1] = (byte)(bytes[1] + bytes[i]);
            }

            try
            {
                encryptedStream.Write(bytes, 0, bytes.Length);
            }
            catch (IOException e)
            {
                string log = $"{nameof(TcpSendData)}: Exception writing to the socket: {tcp.MachineName}: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)";
                Common.Log(log);

                throw e.InnerException is SocketException se ? new ExpectedSocketException(se) : new ExpectedSocketException(log);
            }

            return bytes.Length;
        }

        private static void ProcessReceivedDataEx(byte[] buf)
        {
            int magic;
            byte checksum = 0;

            magic = (buf[3] << 24) + (buf[2] << 16);

            if (magic != (Common.MagicNumber & 0xFFFF0000))
            {
                Common.Log("Magic number invalid!");
                buf[0] = (byte)PackageType.Invalid;
            }

            for (int i = 2; i < Common.PACKAGE_SIZE; i++)
            {
                checksum = (byte)(checksum + buf[i]);
            }

            if (buf[1] != checksum)
            {
                Common.Log("Checksum invalid!");
                buf[0] = (byte)PackageType.Invalid;
            }

            buf[3] = buf[2] = buf[1] = 0;
        }

        internal static DATA TcpReceiveData(TcpSk tcp, out int bytesReceived)
        {
            byte[] buf = new byte[Common.PACKAGE_SIZE_EX];
            Stream decryptedStream = tcp.DecryptedStream;

            if (tcp.BackingSocket == null || !tcp.BackingSocket.Connected || decryptedStream == null)
            {
                string log = $"{nameof(TcpReceiveData)}: The socket is no longer connected, it could have been closed by the remote host.";
                Common.Log(log);
                throw new ExpectedSocketException(log);
            }

            DATA package;

            try
            {
                bytesReceived = decryptedStream.ReadEx(buf, 0, Common.PACKAGE_SIZE);

                if (bytesReceived != Common.PACKAGE_SIZE)
                {
                    buf[0] = bytesReceived == 0 ? (byte)PackageType.Error : (byte)PackageType.Invalid;
                }
                else
                {
                    ProcessReceivedDataEx(buf);
                }

                package = new DATA(buf);

                if (package.IsBigPackage)
                {
                    bytesReceived = decryptedStream.ReadEx(buf, Common.PACKAGE_SIZE, Common.PACKAGE_SIZE);

                    if (bytesReceived != Common.PACKAGE_SIZE)
                    {
                        buf[0] = bytesReceived == 0 ? (byte)PackageType.Error : (byte)PackageType.Invalid;
                    }
                    else
                    {
                        package.Bytes = buf;
                    }
                }
            }
            catch (IOException e)
            {
                string log = $"{nameof(TcpReceiveData)}: Exception reading from the socket: {tcp.MachineName}: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)";
                Common.Log(log);

                throw e.InnerException is SocketException se ? new ExpectedSocketException(se) : new ExpectedSocketException(log);
            }

            return package;
        }

        private static void PreProcessData(PackageType type)
        {
            switch (type)
            {
                case PackageType.Keyboard:
                    Common.PackageSent.Keyboard++;
                    break;

                case PackageType.Mouse:
                    Common.PackageSent.Mouse++;
                    break;

                case PackageType.Heartbeat:
                case PackageType.Heartbeat_ex:
                    Common.PackageSent.Heartbeat++;
                    break;

                case PackageType.Hello:
                    Common.PackageSent.Hello++;
                    break;

                case PackageType.ByeBye:
                    Common.PackageSent.ByeBye++;
                    break;

                case PackageType.Matrix:
                    Common.PackageSent.Matrix++;
                    break;

                default:
                    byte subtype = (byte)((uint)type & 0x000000FF);
                    switch (subtype)
                    {
                        case (byte)PackageType.ClipboardText:
                            Common.PackageSent.ClipboardText++;
                            break;

                        case (byte)PackageType.ClipboardImage:
                            Common.PackageSent.ClipboardImage++;
                            break;

                        default:
                            // Common.Log("Send: Other type (1-17)");
                            break;
                    }

                    break;
            }
        }

        internal int TcpSend(TcpSk tcp, DATA data)
        {
            PreProcessData(data.Type);

            if (data.Src == ID.NONE)
            {
                data.Src = Common.MachineID;
            }

            byte[] dataAsBytes = data.Bytes;
            int rv = TcpSendData(tcp, dataAsBytes);
            if (rv < dataAsBytes.Length)
            {
                Common.Log("TcpSend error! Length of sent data is unexpected.");
                UpdateTcpSockets(tcp, SocketStatus.SendError);
                throw new SocketException((int)SocketStatus.SendError);
            }

            return rv;
        }

        private void TCPServerThread(object param)
        {
            try
            {
                TcpListener server = param as TcpListener;
                do
                {
                    Common.LogDebug("TCPServerThread: Waiting for request...");
                    Socket s = server.AcceptSocket();

                    _ = Task.Run(() =>
                    {
                        try
                        {
                            AddSocket(s);
                        }
                        catch (Exception e)
                        {
                            Common.Log(e);
                        }
                    });
                }
                while (true);
            }
            catch (InvalidOperationException e)
            {
                string log = $"TCPServerThread.AcceptSocket: The server socket could have been closed. {e.Message}";
                Common.Log(log);
            }
            catch (SocketException e)
            {
                if (e.ErrorCode == (int)SocketError.Interrupted)
                {
                    Common.Log("TCPServerThread.AcceptSocket: A blocking socket call was canceled.");
                }
                else
                {
                    Common.Log(e);
                }
            }
            catch (Exception e)
            {
                Common.Log(e);
            }
        }

        private static string GetMachineNameFromSocket(Socket socket)
        {
            string stringIP = socket.RemoteEndPoint.ToString();
            string name = null;

            try
            {
                // Remote machine has IP changed, update it.
                name = Dns.GetHostEntry((socket.RemoteEndPoint as IPEndPoint).Address).HostName;
            }
            catch (SocketException e)
            {
                Common.Log($"{nameof(GetMachineNameFromSocket)}: {e.Message}");
                return stringIP;
            }

            // Remove the domain part.
            if (!string.IsNullOrEmpty(name))
            {
                int dotPos = name.IndexOf('.');

                if (dotPos > 0)
                {
                    Common.LogDebug("Removing domain part from the full machine name: {0}.", name);
                    name = name[..dotPos];
                }
            }

            return string.IsNullOrEmpty(name) ? stringIP : name;
        }

        private void AddSocket(Socket s)
        {
            string machineName = GetMachineNameFromSocket(s);
            Common.Log($"New connection from client: [{machineName}].");
            TcpSk tcp = AddTcpSocket(false, s, SocketStatus.Connecting, machineName);
            StartNewTcpServer(tcp, machineName);
        }

        private void StartNewTcpServer(TcpSk tcp, string machineName)
        {
            void ServerThread()
            {
                try
                {
                    // Receiving packages
                    MainTCPRoutine(tcp, machineName, false);
                }
                catch (Exception e)
                {
                    Common.Log(e);
                }
            }

            Thread t = new(ServerThread, "TCP Server Thread " + tcp.BackingSocket.LocalEndPoint.ToString() + " : " + machineName);

            t.SetApartmentState(ApartmentState.STA);
            t.Start();
        }

        internal void UpdateTCPClients()
        {
            if (InvalidKeyFound)
            {
                return;
            }

            Common.LogDebug("!!!!! UpdateTCPClients !!!!!");

            try
            {
                if (Common.MachineMatrix != null)
                {
                    Common.LogDebug("MachineMatrix = " + string.Join(", ", Common.MachineMatrix));

                    foreach (string st in Common.MachineMatrix)
                    {
                        string machineName = st.Trim();
                        if (!string.IsNullOrEmpty(machineName) &&
                            !machineName.Equals(Common.MachineName.Trim(), StringComparison.OrdinalIgnoreCase))
                        {
                            bool found = false;

                            found = Common.IsConnectedByAClientSocketTo(machineName);

                            if (found)
                            {
                                Common.LogDebug(machineName + " is already connected! ^^^^^^^^^^^^^^^^^^^^^");
                                continue;
                            }

                            StartNewTcpClient(machineName);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Common.Log(e);
            }
        }

        private static readonly Dictionary<string, List<IPAddress>> BadIPs = new();
        private static readonly object BadIPsLock = new();

        private static bool IsBadIP(string machineName, IPAddress ip)
        {
            lock (BadIPsLock)
            {
                return BadIPs.ContainsKey(machineName) && BadIPs.TryGetValue(machineName, out List<IPAddress> ips) && ips.Contains(ip);
            }
        }

        private static void AddBadIP(string machineName, IPAddress ip)
        {
            if (!IsBadIP(machineName, ip))
            {
                lock (BadIPsLock)
                {
                    List<IPAddress> ips;

                    if (BadIPs.ContainsKey(machineName))
                    {
                        _ = BadIPs.TryGetValue(machineName, out ips);
                    }
                    else
                    {
                        ips = new List<IPAddress>();
                        BadIPs.Add(machineName, ips);
                    }

                    ips.Add(ip);
                }
            }
        }

        internal static void ClearBadIPs()
        {
            lock (BadIPsLock)
            {
                if (BadIPs.Count > 0)
                {
                    BadIPs.Clear();
                }
            }
        }

        internal void StartNewTcpClient(string machineName)
        {
            void ClientThread(object obj)
            {
                IPHostEntry host;
                bool useName2IP = false;
                List<IPAddress> validAddresses = new();
                List<IPAddress> validatedAddresses = new();
                string validAddressesSt = string.Empty;

                // Add a dummy socket to show the status.
                Socket dummySocket = new(AddressFamily.Unspecified, SocketType.Stream, ProtocolType.Tcp);
                TcpSk dummyTcp = AddTcpSocket(true, dummySocket, SocketStatus.Resolving, machineName);

                Common.LogDebug("Connecting to: " + machineName);

                if (!string.IsNullOrEmpty(Setting.Values.Name2IP))
                {
                    string[] name2ip = Setting.Values.Name2IP.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
                    string[] nameNip;

                    if (name2ip != null)
                    {
                        foreach (string st in name2ip)
                        {
                            nameNip = st.Split(BlankSeparator, StringSplitOptions.RemoveEmptyEntries);

                            if (nameNip != null && nameNip.Length >= 2 && nameNip[0].Trim().Equals(machineName, StringComparison.OrdinalIgnoreCase)
                                && IPAddress.TryParse(nameNip[1].Trim(), out IPAddress ip) && !validAddressesSt.Contains("[" + ip.ToString() + "]")
                                )
                            {
                                validatedAddresses.Add(ip);
                                validAddressesSt += "[" + ip.ToString() + "]";
                            }
                        }
                    }

                    if (validatedAddresses.Count > 0)
                    {
                        useName2IP = true;

                        Common.LogDebug("Using both user-defined Name-to-IP mappings and DNS result for " + machineName);

                        Common.ShowToolTip("Using both user-defined Name-to-IP mappings and DNS result for " + machineName, 3000, ToolTipIcon.Info, false);

                        if (!CheckForSameSubNet(validatedAddresses, machineName))
                        {
                            return;
                        }

                        foreach (IPAddress vip in validatedAddresses)
                        {
                            StartNewTcpClientThread(machineName, vip);
                        }

                        validatedAddresses.Clear();
                    }
                }

                try
                {
                    host = Dns.GetHostEntry(machineName);
                }
                catch (SocketException e)
                {
                    host = null;

                    UpdateTcpSockets(dummyTcp, SocketStatus.Timeout);

                    Common.ShowToolTip(e.Message + ": " + machineName, 10000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus);

                    Common.Log($"{nameof(StartNewTcpClient)}.{nameof(Dns.GetHostEntry)}: {e.Message}");
                }

                UpdateTcpSockets(dummyTcp, SocketStatus.NA);

                if (!Common.InMachineMatrix(machineName))
                {
                    // While Resolving from name to IP, user may have changed the machine name and clicked on Apply.
                    return;
                }

                if (host != null)
                {
                    string ipLog = string.Empty;

                    foreach (IPAddress ip in host.AddressList)
                    {
                        ipLog += "<" + ip.ToString() + ">";

                        if ((ip.AddressFamily == AddressFamily.InterNetwork || ip.AddressFamily == AddressFamily.InterNetworkV6) && !validAddressesSt.Contains("[" + ip.ToString() + "]"))
                        {
                            validAddresses.Add(ip);
                            validAddressesSt += "[" + ip.ToString() + "]";
                        }
                    }

                    Common.LogDebug(machineName + ipLog);
                }

                if (validAddresses.Count > 0)
                {
                    if (!Setting.Values.ReverseLookup)
                    {
                        validatedAddresses = validAddresses;
                        ClearBadIPs();
                    }
                    else
                    {
                        foreach (IPAddress ip in validAddresses)
                        {
                            if (IsBadIP(machineName, ip))
                            {
                                Common.Log($"Skip bad IP address: {ip}");
                                continue;
                            }

                            try
                            {
                                // Reverse lookup to validate the IP Address.
                                string hn = Dns.GetHostEntry(ip).HostName;

                                if (hn.StartsWith(machineName, StringComparison.CurrentCultureIgnoreCase) || hn.Equals(ip.ToString(), StringComparison.OrdinalIgnoreCase))
                                {
                                    validatedAddresses.Add(ip);
                                }
                                else
                                {
                                    Common.Log($"DNS information of machine not matched: {machineName} => {ip} => {hn}.");
                                    AddBadIP(machineName, ip);
                                }
                            }
                            catch (SocketException se)
                            {
                                Common.Log($"{nameof(StartNewTcpClient)}: DNS information of machine not matched: {machineName} => {ip} => {se.Message}.");
                                AddBadIP(machineName, ip);
                            }
                            catch (ArgumentException ae)
                            {
                                Common.Log($"{nameof(StartNewTcpClient)}: DNS information of machine not matched: {machineName} => {ip} => {ae.Message}.");
                                AddBadIP(machineName, ip);
                            }
                        }
                    }
                }

                if (validatedAddresses.Count > 0)
                {
                    if (!CheckForSameSubNet(validatedAddresses, machineName))
                    {
                        return;
                    }

                    foreach (IPAddress ip in validatedAddresses)
                    {
                        StartNewTcpClientThread(machineName, ip);
                    }
                }
                else
                {
                    Common.Log("Cannot resolve IPv4 Addresses of machine: " + machineName);

                    if (!useName2IP)
                    {
                        Common.ShowToolTip($"Cannot resolve IP Address of the remote machine: {machineName}.\r\nPlease fix your DNS or use the Mapping option in the Settings form.", 10000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus);
                    }
                }
            }

            Thread t = new(
                ClientThread, "StartNewTcpClient." + machineName);

            t.SetApartmentState(ApartmentState.STA);
            t.Start();
        }

        private bool CheckForSameSubNet(List<IPAddress> validatedAddresses, string machineName)
        {
            if (!Setting.Values.SameSubNetOnly)
            {
                return true;
            }

            // Only support if IPv4 addresses found in both.
            IEnumerable<IPAddress> remoteIPv4Addresses = validatedAddresses.Where(addr => addr?.AddressFamily == AddressFamily.InterNetwork);

            if (!remoteIPv4Addresses.Any())
            {
                Common.Log($"No IPv4 resolved from the remote machine: {machineName}.");
                return true;
            }

            List<IPAddress> localIPv4Addresses = GetMyIPv4Addresses().ToList();

            if (localIPv4Addresses.Count == 0)
            {
                Common.Log($"No IPv4 resolved from the local machine: {Common.MachineName}");
                return true;
            }

            foreach (IPAddress remote in remoteIPv4Addresses)
            {
                foreach (IPAddress local in localIPv4Addresses)
                {
                    byte[] myIPAddressBytes = local.GetAddressBytes();
                    byte[] yourIPAddressBytes = remote.GetAddressBytes();

                    // Same WAN?
                    if (myIPAddressBytes[0] == yourIPAddressBytes[0] && myIPAddressBytes[1] == yourIPAddressBytes[1])
                    {
                        return true;
                    }
                }
            }

            Common.Log($"Skip machine not in the same network: {machineName}.");

            return false;
        }

        private IEnumerable<IPAddress> GetMyIPv4Addresses()
        {
            try
            {
                IEnumerable<IPAddress> ip4addresses = NetworkInterface.GetAllNetworkInterfaces()?
                    .Where(networkInterface =>
                        (networkInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
                        && networkInterface.OperationalStatus == OperationalStatus.Up)
                        .SelectMany(ni => ni?.GetIPProperties()?.UnicastAddresses.Select(uni => uni?.Address))
                        .Where(addr => addr?.AddressFamily == AddressFamily.InterNetwork);

                return ip4addresses;
            }
            catch (Exception e)
            {
                Common.Log(e);
                return Enumerable.Empty<IPAddress>();
            }
        }

        private void StartNewTcpClientThread(string machineName, IPAddress ip)
        {
            void NewTcpClient()
            {
                TcpClient tcpClient = null;

                try
                {
                    tcpClient = new TcpClient(AddressFamily.InterNetworkV6);
                    tcpClient.Client.DualMode = true;

                    if (Common.IsConnectedByAClientSocketTo(machineName))
                    {
                        Common.LogDebug(machineName + " is already connected by another client socket.");
                        return;
                    }

                    if (Common.IsConnectingByAClientSocketTo(machineName, ip))
                    {
                        Common.LogDebug($"{machineName}:{ip} is already being connected by another client socket.");
                        return;
                    }

                    TcpSk tcp = AddTcpSocket(true, tcpClient.Client, SocketStatus.Connecting, machineName, ip);

                    // Update the other server socket's machine name based on this corresponding client socket.
                    UpdateTcpSockets(tcp, SocketStatus.Connecting);

                    Common.LogDebug(string.Format(CultureInfo.CurrentCulture, "=====> Connecting to: {0}:{1}", machineName, ip.ToString()));

                    long timeoutLeft;

                    do
                    {
                        try
                        {
                            tcpClient.Connect(ip, TcpPort + 1);
                        }
                        catch (ObjectDisposedException)
                        {
                            // When user reconnects.
                            Common.LogDebug($"tcpClient.Connect: The socket has already been disposed: {machineName}:{ip}");
                            return;
                        }
                        catch (SocketException e)
                        {
                            timeoutLeft = connectTimeout - Common.GetTick();

                            if (timeoutLeft > 0)
                            {
                                Common.LogDebug($"tcpClient.Connect: {timeoutLeft}: {e.Message}");
                                Thread.Sleep(1000);
                                continue;
                            }
                            else
                            {
                                Common.Log($"tcpClient.Connect: Unable to connect after a timeout: {machineName}:{ip} : {e.Message}");

                                string message = $"Connection timed out: {machineName}:{ip}";

                                Common.ShowToolTip(message, 5000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus);

                                UpdateTcpSockets(tcp, SocketStatus.Timeout);
                                return;
                            }
                        }

                        break;
                    }
                    while (true);

                    Common.LogDebug($"=====> Connected: {tcpClient.Client.LocalEndPoint} => {machineName}: {ip}");

                    // Sending/Receiving packages
                    MainTCPRoutine(tcp, machineName, true);
                }
                catch (ObjectDisposedException e)
                {
                    Common.Log($"{nameof(StartNewTcpClientThread)}: The socket could have been closed/disposed due to machine switch: {e.Message}");
                }
                catch (SocketException e)
                {
                    // DHCP error, etc.
                    string localIP = tcpClient?.Client?.LocalEndPoint?.ToString();

                    if (localIP != null && (localIP.StartsWith("169.254", StringComparison.InvariantCulture) || localIP.ToString().StartsWith("0.0", StringComparison.InvariantCulture)))
                    {
                        Common.ShowToolTip($"Error: The machine has limited connectivity on [{localIP}].", 5000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus);
                    }
                    else
                    {
                        Common.TelemetryLogTrace($"{nameof(StartNewTcpClientThread)}: Error: {e.Message} on the IP Address: {localIP}", SeverityLevel.Error);
                    }
                }
                catch (Exception e)
                {
                    Common.Log(e);
                }
            }

            Thread t = new(NewTcpClient, "TCP Client Thread " + machineName + " " + ip.ToString());

            t.SetApartmentState(ApartmentState.STA);
            t.Start();
        }

        private void FlagReopenSocketIfNeeded(Exception e)
        {
            /* SCENARIO: MachineA has MM blocked by firewall but MachineA can connect to MachineB so the tool would work normally (MM is not blocked by firewall in MachineB).
            * 1. a connection from A to B is working. Mouse/Keyboard is connected to A.
            * 2. User moves Mouse to B and lock B by Ctrl+Alt+L.
            * 3. B closes all sockets before switches to logon desktop. The client socket in A gets reset by B (the only connection between A and B).
            * 4. B is now on the logon desktop and tries to connect to A, connection fails since it is block by firewall in A.
            * 5. When the client socket gets reset in A, it should retry to connect to B => this is the fix implemented by few lines of code below.
            * */

            // WSAECONNRESET
            if (e is ExpectedSocketException se && se.ShouldReconnect)
            {
                Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
                Common.Log($"MainTCPRoutine: {nameof(FlagReopenSocketIfNeeded)}");
            }
        }

        private long lastRemoteMachineID;
        internal static readonly string[] Separator = new string[] { "\r\n" };
        internal static readonly char[] BlankSeparator = new char[] { ' ' };

        private void MainTCPRoutine(TcpSk tcp, string machineName, bool isClient)
        {
            int packageCount = 0;
            DATA d;
            string remoteMachine = string.Empty;
            string strIP = string.Empty;
            ID remoteID = ID.NONE;

            byte[] buf = RandomNumberGenerator.GetBytes(Common.PACKAGE_SIZE_EX);
            d = new DATA(buf);

            TcpSk currentTcp = tcp;
            Socket currentSocket = currentTcp.BackingSocket;

            if (currentSocket == null)
            {
                Common.LogDebug($"{nameof(MainTCPRoutine)}: The socket could have been closed/disposed by other threads.");
                return;
            }

            try
            {
                currentSocket.SendBufferSize = Common.PACKAGE_SIZE * 10000;
                currentSocket.ReceiveBufferSize = Common.PACKAGE_SIZE * 10000;
                currentSocket.NoDelay = true; // This is very interesting to know:(
                currentSocket.SendTimeout = 500;
                d.MachineName = Common.MachineName;

                d.Type = PackageType.Handshake;

                for (int i = 0; i < 10; i++)
                {
                    _ = TcpSend(currentTcp, d);
                }

                d.Machine1 = ~d.Machine1;
                d.Machine2 = ~d.Machine2;
                d.Machine3 = ~d.Machine3;
                d.Machine4 = ~d.Machine4;

                UpdateTcpSockets(currentTcp, SocketStatus.Handshaking);

                strIP = Common.GetRemoteStringIP(currentSocket, true);
                remoteMachine = string.IsNullOrEmpty(machineName) ? GetMachineNameFromSocket(currentSocket) : machineName;

                Common.LogDebug($"MainTCPRoutine: Remote machineName/IP = {remoteMachine}/{strIP}");
            }
            catch (ObjectDisposedException e)
            {
                Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
                UpdateTcpSockets(currentTcp, SocketStatus.ForceClosed);
                currentSocket.Close();
                Common.Log($"{nameof(MainTCPRoutine)}: The socket could have been closed/disposed by other threads: {e.Message}");
            }
            catch (Exception e)
            {
                UpdateTcpSockets(currentTcp, SocketStatus.ForceClosed);
                FlagReopenSocketIfNeeded(e);
                currentSocket.Close();
                Common.Log(e);
            }

            int errCount = 0;

            while (true)
            {
                try
                {
                    DATA package = TcpReceiveData(currentTcp, out int receivedCount);
                    remoteID = package.Src;

                    if (package.Type == PackageType.Error)
                    {
                        errCount++;

                        string log = $"{nameof(MainTCPRoutine)}.TcpReceive error, invalid package from {remoteMachine}: {receivedCount}";
                        Common.Log(log);

                        if (receivedCount > 0)
                        {
                            Common.ShowToolTip($"Invalid package from {remoteMachine}. Ensure the security keys are the same in both machines.", 5000, ToolTipIcon.Warning, false);
                        }

                        if (errCount > 5)
                        {
                            Common.MMSleep(1);

                            UpdateTcpSockets(currentTcp, SocketStatus.Error);
                            currentSocket.Close();

                            /*
                             * Sometimes when the peer machine closes the connection, we do not actually get an exception.
                             * Socket status is still connected and a read on the socket stream returns 0 byte.
                             * In this case, we should give ONE try to reconnect.
                             */

                            if (Common.ReopenSocketDueToReadError)
                            {
                                Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET;
                                Common.ReopenSocketDueToReadError = false;
                            }

                            break;
                        }
                    }
                    else
                    {
                        errCount = 0;
                    }

                    if (package.Type == PackageType.Handshake)
                    {
                        // Common.Log("Got a Handshake signal!");
                        package.Type = PackageType.HandshakeAck;
                        package.Src = ID.NONE;
                        package.MachineName = Common.MachineName;

                        package.Machine1 = ~package.Machine1;
                        package.Machine2 = ~package.Machine2;
                        package.Machine3 = ~package.Machine3;
                        package.Machine4 = ~package.Machine4;

                        _ = TcpSend(currentTcp, package);
                    }
                    else
                    {
                        if (packageCount >= 0)
                        {
                            if (++packageCount >= 10)
                            {
                                // Common.ShowToolTip("Invalid Security Key from " + remoteMachine, 5000);
                                Common.Log("More than 10 invalid packages received!");

                                package.Type = PackageType.Invalid;

                                for (int i = 0; i < 10; i++)
                                {
                                    _ = TcpSend(currentTcp, package);
                                }

                                Common.MMSleep(2);

                                UpdateTcpSockets(currentTcp, SocketStatus.InvalidKey);
                                currentSocket.Close();
                                break;
                            }
                            else if (package.Type == PackageType.HandshakeAck)
                            {
                                if (package.Machine1 == d.Machine1 && package.Machine2 == d.Machine2 &&
                                   package.Machine3 == d.Machine3 && package.Machine4 == d.Machine4)
                                {
                                    string claimedMachineName = package.MachineName;

                                    if (!remoteMachine.Equals(claimedMachineName, StringComparison.Ordinal))
                                    {
                                        Common.LogDebug($"DNS.RemoteMachineName({remoteMachine}) <> Claimed.MachineName({claimedMachineName}), using the claimed machine name.");
                                        remoteMachine = claimedMachineName;
                                        currentTcp.MachineName = remoteMachine;
                                    }

                                    // Double check to avoid a redundant client socket.
                                    if (isClient && Common.IsConnectedByAClientSocketTo(remoteMachine))
                                    {
                                        Common.LogDebug("=====> Duplicate connected client socket for: " + remoteMachine + ":" + strIP + " is being removed.");
                                        UpdateTcpSockets(currentTcp, SocketStatus.ForceClosed);
                                        currentSocket.Close();
                                        return;
                                    }

                                    if (remoteMachine.Equals(Common.MachineName, StringComparison.OrdinalIgnoreCase))
                                    {
                                        Common.LogDebug("Connected to/from local socket: " + strIP + (isClient ? "-Client" : "-Server"));
                                        UpdateTcpSockets(currentTcp, SocketStatus.NA);
                                        Common.MMSleep(1);
                                        currentSocket.Close();
                                        return;
                                    }

                                    packageCount = -1; // Trusted
                                    InvalidKeyFound = false;
                                    currentTcp.MachineId = (uint)remoteID;
                                    currentTcp.Status = SocketStatus.Connected;
                                    UpdateTcpSockets(currentTcp, SocketStatus.Connected);
                                    Common.LogDebug("))))))))))))))) Machine got trusted: " + remoteMachine + ":" + strIP + ", Is client: " + isClient);

                                    if (Math.Abs(Common.GetTick() - Common.LastReconnectByHotKeyTime) < 5000)
                                    {
                                        Common.ShowToolTip("Connected to " + remoteMachine, 1000, ToolTipIcon.Info, Setting.Values.ShowClipNetStatus);
                                    }

                                    Common.SendHeartBeat(initial: true);

                                    if (Common.MachinePool.TryFindMachineByName(remoteMachine, out MachineInf machineInfo))
                                    {
                                        Common.LogDebug("Machine updated: " + remoteMachine + "/" + remoteID.ToString());

                                        if (machineInfo.Name.Equals(Common.DesMachineName, StringComparison.OrdinalIgnoreCase))
                                        {
                                            Common.LogDebug("Des ID updated: " + Common.DesMachineID.ToString() +
                                                "/" + remoteID.ToString());
                                            Common.NewDesMachineID = Common.DesMachineID = remoteID;
                                        }

                                        _ = Common.MachinePool.TryUpdateMachineID(remoteMachine, remoteID, true);
                                        Common.UpdateMachinePoolStringSetting();
                                    }
                                    else
                                    {
                                        Common.LogDebug("New machine connected: {0}.", remoteMachine);

                                        if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
                                        {
                                            Common.ShowToolTip("Connected to new machine " + remoteMachine, 1000, ToolTipIcon.Info, Setting.Values.ShowClipNetStatus);
                                        }
                                    }

                                    if (!isClient)
                                    {
                                        Common.UpdateClientSockets("MainTCPRoutine");
                                    }
                                }
                                else
                                {
                                    Common.LogDebug("Invalid ACK from " + remoteMachine);
                                    UpdateTcpSockets(currentTcp, SocketStatus.InvalidKey);

                                    string remoteEP = currentSocket.RemoteEndPoint.ToString();

                                    if (FailedAttempt.AddOrUpdate(remoteEP, 1, (key, value) => value + 1) > 10)
                                    {
                                        _ = FailedAttempt.AddOrUpdate(remoteEP, 0, (key, value) => 0);

                                        _ = MessageBox.Show($"Too many connection attempts from [{remoteEP}]!\r\nRestart the app to retry.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                                        Common.MainForm.Quit(true, false);
                                    }

                                    currentSocket.Close();
                                    break;
                                }
                            }
                            else if (package.Type == PackageType.Mouse)
                            {
                                if (packageCount > 5)
                                {
                                    packageCount--;
                                }
                            }
                            else if (package.Type is PackageType.Heartbeat or PackageType.Heartbeat_ex)
                            {
                                if (packageCount > 5)
                                {
                                    packageCount--;
                                }
                            }
                            else
                            {
                                if (packageCount > 5)
                                {
                                    UpdateTcpSockets(currentTcp, SocketStatus.InvalidKey);
                                }
                                else
                                {
                                    Common.Log(string.Format(
                                        CultureInfo.CurrentCulture,
                                        "Unexpected package, size = {0}, type = {1}",
                                        receivedCount,
                                        package.Type));
                                }
                            }
                        }
                        else if (receivedCount > 0)
                        {
                            // Add some log when remote machine switches.
                            if (lastRemoteMachineID != (long)remoteID)
                            {
                                _ = Interlocked.Exchange(ref lastRemoteMachineID, (long)remoteID);
                                Common.LogDebug($"MainTCPRoutine: Remote machine = {strIP}/{lastRemoteMachineID}");
                            }

                            if (package.Type == PackageType.HandshakeAck)
                            {
                                Common.LogDebug("Skipping the rest of the Handshake packages.");
                            }
                            else
                            {
                                Common.ProcessPackage(package, currentTcp);
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    UpdateTcpSockets(currentTcp, SocketStatus.Error);
                    FlagReopenSocketIfNeeded(e);
                    currentSocket.Close();
                    Common.Log(e);
                    break;
                }
            }

            if (remoteID != ID.NONE)
            {
                _ = Common.RemoveDeadMachines(remoteID);
            }
        }

        private static void AcceptConnectionAndSendClipboardData(object param)
        {
            TcpListener server = param as TcpListener;

            do
            {
                Common.LogDebug("SendClipboardData: Waiting for request...");
                Socket s = null;

                try
                {
                    s = server.AcceptSocket();
                }
                catch (InvalidOperationException e)
                {
                    Common.Log($"The clipboard socket could have been closed. {e.Message}");
                    break;
                }
                catch (SocketException e)
                {
                    if (e.ErrorCode == (int)SocketError.Interrupted)
                    {
                        Common.Log("server.AcceptSocket: A blocking socket call was canceled.");
                        continue;
                    }
                    else
                    {
                        Common.Log(e);
                        break;
                    }
                }
                catch (Exception e)
                {
                    Common.Log(e);
                    break;
                }

                if (s != null)
                {
                    try
                    {
                        new Task(() =>
                        {
                            System.Threading.Thread thread = Thread.CurrentThread;
                            thread.Name = $"{nameof(SendOrReceiveClipboardData)}.{thread.ManagedThreadId}";
                            Thread.UpdateThreads(thread);
                            SendOrReceiveClipboardData(s);
                        }).Start();
                    }
                    catch (Exception e)
                    {
                        Common.Log(e);
                    }
                }
            }
            while (true);
        }

        internal static void SendOrReceiveClipboardData(Socket s)
        {
            try
            {
                string remoteEndPoint = s.RemoteEndPoint.ToString();
                Common.LogDebug("SendClipboardData: Request accepted: " + s.LocalEndPoint.ToString() + "/" + remoteEndPoint);
                Common.IsDropping = false;
                Common.IsDragging = false;
                Common.DragMachine = (ID)1;

                bool clientPushData = true;
                ClipboardPostAction postAction = ClipboardPostAction.Other;
                bool handShaken = Common.ShakeHand(ref remoteEndPoint, s, out Stream enStream, out Stream deStream, ref clientPushData, ref postAction);

                if (!handShaken)
                {
                    s.Close();
                    return;
                }
                else
                {
                    Common.LogDebug($"{nameof(SendOrReceiveClipboardData)}: Clipboard connection accepted: " + remoteEndPoint);
                    Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] { Common.ICON_SMALL_CLIPBOARD, -1, -1, -1 });
                }

                if (clientPushData)
                {
                    Common.ReceiveAndProcessClipboardData(remoteEndPoint, s, enStream, deStream, $"{postAction}");
                }
                else
                {
                    SendClipboardData(s, enStream);
                }
            }
            catch (Exception e)
            {
                Common.Log(e);
            }
        }

        internal static void SendClipboardData(Socket s, Stream ecStream)
        {
            if (Common.RunWithNoAdminRight && Setting.Values.OneWayClipboardMode)
            {
                s?.Close();
                return;
            }

            const int CLOSE_TIMEOUT = 10;
            byte[] header = new byte[1024];
            string headerString = string.Empty;
            if (Common.LastDragDropFile != null)
            {
                string fileName = null;

                if (!Common.ImpersonateLoggedOnUserAndDoSomething(() =>
                {
                    if (!File.Exists(Common.LastDragDropFile))
                    {
                        headerString = Directory.Exists(Common.LastDragDropFile)
                            ? $"{0}*{Common.LastDragDropFile} - Folder is not supported, zip it first!"
                            : Common.LastDragDropFile.Contains("- File too big")
                                ? $"{0}*{Common.LastDragDropFile}"
                                : $"{0}*{Common.LastDragDropFile} not found!";
                    }
                    else
                    {
                        fileName = Common.LastDragDropFile;
                        headerString = $"{new FileInfo(fileName).Length}*{fileName}";
                    }
                }))
                {
                    s?.Close();
                    return;
                }

                Common.GetBytesU(headerString).CopyTo(header, 0);

                try
                {
                    ecStream.Write(header, 0, header.Length);

                    if (!string.IsNullOrEmpty(fileName))
                    {
                        if (SendFile(s, ecStream, fileName))
                        {
                            s.Close(CLOSE_TIMEOUT);
                        }
                    }
                    else
                    {
                        s.Close(CLOSE_TIMEOUT);
                    }
                }
                catch (IOException e)
                {
                    string log = $"{nameof(SendClipboardData)}: Exception accessing the socket: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)";
                    Common.Log(log);
                }
                catch (SocketException e)
                {
                    string log = $"{nameof(SendClipboardData)}: {e.GetType()}/{e.Message}. This is expected when the connection is closed by the remote host.";
                    Common.Log(log);
                }
                catch (ObjectDisposedException e)
                {
                    string log = $"{nameof(SendClipboardData)}: {e.GetType()}/{e.Message}. This is expected when the socket is disposed by a machine switch for ex..";
                    Common.Log(log);
                }
            }
            else if (!Common.IsClipboardDataImage && Common.LastClipboardData != null)
            {
                try
                {
                    byte[] data = Common.LastClipboardData;

                    headerString = $"{data.Length}*{"text"}";
                    Common.GetBytesU(headerString).CopyTo(header, 0);

                    if (data != null)
                    {
                        ecStream.Write(header, 0, header.Length);
                        _ = SendData(s, ecStream, data);
                        Common.LogDebug("Text sent: " + data.Length.ToString(CultureInfo.CurrentCulture));
                    }

                    s.Close(CLOSE_TIMEOUT);
                }
                catch (IOException e)
                {
                    string log = $"{nameof(SendClipboardData)}: Exception accessing the socket: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)";
                    Common.Log(log);
                }
                catch (SocketException e)
                {
                    string log = $"{nameof(SendClipboardData)}: {e.GetType()}/{e.Message}. This is expected when the connection is closed by the remote host.";
                    Common.Log(log);
                }
                catch (ObjectDisposedException e)
                {
                    string log = $"{nameof(SendClipboardData)}: {e.GetType()}/{e.Message}. This is expected when the socket is disposed by a machine switch for ex..";
                    Common.Log(log);
                }
            }
            else if (Common.LastClipboardData != null && Common.LastClipboardData.Length > 0)
            {
                byte[] data = Common.LastClipboardData;

                headerString = $"{data.Length}*{"image"}";
                Common.GetBytesU(headerString).CopyTo(header, 0);
                try
                {
                    ecStream.Write(header, 0, header.Length);
                    _ = SendData(s, ecStream, data);
                    Common.LogDebug("Image sent: " + data.Length.ToString(CultureInfo.CurrentCulture));
                    s.Close(CLOSE_TIMEOUT);
                }
                catch (IOException e)
                {
                    string log = $"{nameof(SendClipboardData)}: Exception accessing the socket: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)";
                    Common.Log(log);
                }
                catch (SocketException e)
                {
                    string log = $"{nameof(SendClipboardData)}: {e.GetType()}/{e.Message}. This is expected when the connection is closed by the remote host.";
                    Common.Log(log);
                }
                catch (ObjectDisposedException e)
                {
                    string log = $"{nameof(SendClipboardData)}: {e.GetType()}/{e.Message}. This is expected when the socket is disposed by a machine switch for ex..";
                    Common.Log(log);
                }
            }
            else
            {
                Common.Log("No data available in clipboard or LastDragDropFile!");
                s.Close();
            }
        }

        private static bool SendFileEx(Socket s, Stream ecStream, string fileName)
        {
            try
            {
                using (FileStream f = File.OpenRead(fileName))
                {
                    byte[] buf = new byte[Common.NETWORK_STREAM_BUF_SIZE];
                    int rv, sentCount = 0;

                    do
                    {
                        if ((rv = f.Read(buf, 0, Common.NETWORK_STREAM_BUF_SIZE)) > 0)
                        {
                            ecStream.Write(buf, 0, rv);
                            sentCount += rv;
                        }
                    }
                    while (rv > 0);

                    if ((rv = Common.PACKAGE_SIZE - (sentCount % Common.PACKAGE_SIZE)) > 0)
                    {
                        Array.Clear(buf, 0, buf.Length);
                        ecStream.Write(buf, 0, rv);
                    }

                    ecStream.Flush();

                    Common.LogDebug("File sent: " + fileName);
                }

                return true;
            }
            catch (Exception e)
            {
                if (e is IOException)
                {
                    string log = $"{nameof(SendFileEx)}: Exception accessing the socket: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)";
                    Common.Log(log);
                }
                else
                {
                    Common.Log(e);
                }

                Common.ShowToolTip(e.Message, 1000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus);
                s.Close();
            }

            return false;
        }

        private static bool SendFile(Socket s, Stream ecStream, string fileName)
        {
            bool r = false;

            if (Common.RunOnLogonDesktop || Common.RunOnScrSaverDesktop)
            {
                if (fileName.StartsWith(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\" + Common.BinaryName + @"\ScreenCaptures\", StringComparison.CurrentCultureIgnoreCase))
                {
                    r = SendFileEx(s, ecStream, fileName);
                }
            }
            else
            {
                _ = Common.ImpersonateLoggedOnUserAndDoSomething(() => { r = SendFileEx(s, ecStream, fileName); });
            }

            return r;
        }

        private static bool SendData(Socket s, Stream ecStream, byte[] data)
        {
            bool r = false;

            try
            {
                using MemoryStream f = new(data);
                byte[] buf = new byte[Common.NETWORK_STREAM_BUF_SIZE];
                int rv, sentCount = 0;

                do
                {
                    if ((rv = f.Read(buf, 0, Common.NETWORK_STREAM_BUF_SIZE)) > 0)
                    {
                        ecStream.Write(buf, 0, rv);
                        sentCount += rv;
                    }
                }
                while (rv > 0);

                if ((rv = sentCount % Common.PACKAGE_SIZE) > 0)
                {
                    Array.Clear(buf, 0, buf.Length);
                    ecStream.Write(buf, 0, rv);
                }

                ecStream.Flush();
                Common.LogDebug("Data sent: " + data.Length.ToString(CultureInfo.InvariantCulture));
                r = true;
            }
            catch (Exception e)
            {
                if (e is IOException)
                {
                    string log = $"{nameof(SendData)}: Exception accessing the socket: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)";
                    Common.Log(log);
                }
                else
                {
                    Common.Log(e);
                }

                Common.ShowToolTip(e.Message, 1000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus);
                ecStream.Close();
                s.Close();
            }

            return r;
        }

        private TcpSk AddTcpSocket(bool isClient, Socket s, SocketStatus status, string machineName)
        {
            Common.CloseAnUnusedSocket();
            TcpSk tcp = new(isClient, s, status, machineName);

            lock (TcpSocketsLock)
            {
#if ENABLE_LEGACY_DOS_PROTECTION
                PreventDoS(TcpSockets);
#endif
                TcpSockets.Add(tcp);
            }

            return tcp;
        }

        private TcpSk AddTcpSocket(bool isClient, Socket s, SocketStatus status, string machineName, IPAddress ip)
        {
            Common.CloseAnUnusedSocket();
            TcpSk tcp = new(isClient, s, status, machineName, ip);

            lock (TcpSocketsLock)
            {
#if ENABLE_LEGACY_DOS_PROTECTION
                PreventDoS(TcpSockets);
#endif
                TcpSockets.Add(tcp);
            }

            return tcp;
        }

        private void UpdateTcpSockets(TcpSk tcp, SocketStatus status)
        {
            if (status == SocketStatus.InvalidKey)
            {
                InvalidKeyFound = true;
            }

            InvalidKeyFoundOnClientSocket = tcp.IsClient && InvalidKeyFound;

            try
            {
                lock (TcpSocketsLock)
                {
                    if (TcpSockets != null)
                    {
                        if (status == SocketStatus.Connected && tcp.IsClient)
                        {
                            List<TcpSk> tobeRemovedSockets = TcpSockets;

                            if (tcp.MachineId == Setting.Values.MachineId)
                            {
                                tcp = null;
                                Setting.Values.MachineId = Common.Ran.Next();
                                Common.UpdateMachineTimeAndID();
                                Common.PleaseReopenSocket = Common.REOPEN_WHEN_HOTKEY;

                                Common.TelemetryLogTrace("MachineID conflict.", SeverityLevel.Information);
                            }
                            else
                            {
                                // Keep the first connected one.
                                tobeRemovedSockets = TcpSockets.Where(item => item.IsClient && !ReferenceEquals(item, tcp) && item.MachineName.Equals(tcp.MachineName, StringComparison.OrdinalIgnoreCase)).ToList();
                            }

                            foreach (TcpSk t in tobeRemovedSockets)
                            {
                                t.Status = SocketStatus.ForceClosed;
                                Common.LogDebug($"Closing duplicated socket {t.MachineName}: {t.Address}");
                            }
                        }

                        List<TcpSk> toBeRemoved = new();

                        foreach (TcpSk t in TcpSockets)
                        {
                            if (t.Status is SocketStatus.Error or
                                SocketStatus.ForceClosed or

                                // SocketStatus.InvalidKey or
                                SocketStatus.NA or
                                SocketStatus.Timeout or
                                SocketStatus.SendError)
                            {
                                try
                                {
                                    if (t.BackingSocket != null)
                                    {
                                        t.MachineName = "$*NotUsed*$";
                                        t.Status = t.Status >= 0 ? 0 : t.Status - 1; // If error closing, the socket will be closed again at SocketStuff.Close().
                                        t.BackingSocket.Close();
                                    }

                                    toBeRemoved.Add(t);
                                }
                                catch (SocketException e)
                                {
                                    string log = $"{nameof(UpdateTcpSockets)}: {e.GetType()}/{e.Message}. This is expected when the connection is closed by the remote host.";
                                    Common.Log(log);
                                }
                                catch (ObjectDisposedException e)
                                {
                                    string log = $"{nameof(UpdateTcpSockets)}: {e.GetType()}/{e.Message}. This is expected when the socket is disposed by a machine switch for ex..";
                                    Common.Log(log);
                                }
                            }
                        }

                        if (tcp != null)
                        {
                            tcp.Status = status;

                            if (status == SocketStatus.Connected)
                            {
                                // Update the socket's machine name based on its corresponding client/server socket.
                                foreach (TcpSk t in TcpSockets)
                                {
                                    if (t.MachineId == tcp.MachineId && t.IsClient != tcp.IsClient)
                                    {
                                        if ((string.IsNullOrEmpty(tcp.MachineName) || tcp.MachineName.Contains('.') || tcp.MachineName.Contains(':'))
                                            && !(string.IsNullOrEmpty(t.MachineName) || t.MachineName.Contains('.') || t.MachineName.Contains(':')))
                                        {
                                            tcp.MachineName = t.MachineName;
                                        }
                                        else if ((string.IsNullOrEmpty(t.MachineName) || t.MachineName.Contains('.') || t.MachineName.Contains(':'))
                                            && !(string.IsNullOrEmpty(tcp.MachineName) || tcp.MachineName.Contains('.') || tcp.MachineName.Contains(':')))
                                        {
                                            t.MachineName = tcp.MachineName;
                                        }
                                    }
                                }

                                if (string.IsNullOrEmpty(tcp.MachineName) || tcp.MachineName.Contains('.') || tcp.MachineName.Contains(':'))
                                {
                                    tcp.MachineName = Common.NameFromID((ID)tcp.MachineId);
                                }

                                if (string.IsNullOrEmpty(tcp.MachineName) || tcp.MachineName.Contains('.') || tcp.MachineName.Contains(':'))
                                {
                                    tcp.MachineName = Common.GetRemoteStringIP(tcp.BackingSocket);
                                }
                            }
                        }
                        else
                        {
                            Common.Log("UpdateTcpSockets.Exception: Socket not found!");
                        }

                        foreach (TcpSk t in toBeRemoved)
                        {
                            _ = TcpSockets.Remove(t);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Common.Log(e);
            }
        }

        private void PreventDoS(List<TcpSk> sockets)
        {
            if (sockets.Count > 100)
            {
                TcpSk tcp;

                try
                {
                    string msg = Application.ProductName + " has been terminated, too many connections.";

                    for (int i = 0; i < 10; i++)
                    {
                        tcp = sockets[i * 10];

                        if (tcp != null)
                        {
                            msg += $"\r\n{Common.MachineName}{(tcp.IsClient ? "=>" : "<=")}{tcp.MachineName}:{tcp.Status}";
                        }
                    }

                    _ = Common.CreateLowIntegrityProcess(
                        "\"" + Path.GetDirectoryName(Application.ExecutablePath) + "\\MouseWithoutBordersHelper.exe\"",
                        "InternalError" + " \"" + msg + "\"",
                        0,
                        false,
                        0,
                        (short)ProcessWindowStyle.Hidden);
                }
                finally
                {
                    Common.MainForm.Quit(true, false);
                }
            }
        }
    }
}
