CheckCircuit – Rev 2

Subversion Repositories:
Rev:
using CommandLine.Text;
using CommandLine;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Intrinsics.Arm;
using System.Text;
using System.Threading.Tasks;
using CheckCircuit.CommandLine;
using System.Threading;

namespace CheckCircuit
{
    internal class Program
    {

        public static TcpClient TcpClient { get; set; }

        public static ConcurrentDictionary<IntPtr, TcpClient> tcpClients { get; set; }
        public static bool Verbose { get; private set; }

        static int Main(string[] args)
        {
            var CancellationTokenSource = new CancellationTokenSource();
            var CancellationToken = CancellationTokenSource.Token;

            tcpClients = new ConcurrentDictionary<IntPtr, TcpClient>();

            return Parser.Default.ParseArguments<ConnectOptions>(args)
                .MapResult(opts => RunConnectAndReturnExitCode(opts, CancellationToken), errs => 1);
        }

        private static int RunConnectAndReturnExitCode(ConnectOptions opts, CancellationToken cancellationToken)
        {
            Verbose = opts.Verbose;

            if(!IPEndPoint.TryParse(opts.Listen, out var listenEndPoint))
            {
                Console.WriteLine("Bad listen HOST:PORT format.");
                return 1;
            }
            var server = StartServer(listenEndPoint, cancellationToken);

            if(!IPEndPoint.TryParse(opts.Connect, out var connectEndPoint))
            {
                Console.WriteLine("Bad connect HOST:PORT format.");
                return 1;
            }

            if(string.IsNullOrEmpty(opts.Password))
            {
                Console.WriteLine("Bad password string.");
                return 1;
            }

            var client = StartClient(connectEndPoint, opts.Password, cancellationToken);

            Task.WhenAll(new[] { server, client }).Wait(cancellationToken);

            return 0;
        }

        private static async Task StartServer(IPEndPoint listen, CancellationToken cancellationToken)
        {
            var tcpServer = new TcpListener(listen);
            tcpServer.Start();

            do
            {
                var tcpClient = await tcpServer.AcceptTcpClientAsync();
                var handle = tcpClient.Client.Handle;
                if (!tcpClients.ContainsKey(handle))
                {
                    tcpClients.TryAdd(handle, tcpClient);
                }

            } while (!cancellationToken.IsCancellationRequested);
        }

        private static async Task StartClient(IPEndPoint connect, string password, CancellationToken cancellationToken)
        {
            try
            {
                using (var tcpClient = new TcpClient())
                {
                    await tcpClient.ConnectAsync(connect.Address, connect.Port, cancellationToken);
                    using (var tcpClientStream = tcpClient.GetStream())
                    {
                        using (var streamReader = new StreamReader(tcpClientStream))
                        {
                            using (var streamWriter = new StreamWriter(tcpClientStream))
                            {
                                do
                                {
                                    if(!await Authenticate(streamReader, streamWriter, password))
                                    {
                                        if (Verbose)
                                        {
                                            Console.WriteLine("Authentication failed, please check OR port and password.");
                                        }
                                        await SendPayload(tcpClients, "0", cancellationToken);
                                        await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
                                        continue;
                                    }

                                    do
                                    {
                                        try
                                        {
                                            if (await IsCircuitFormed(streamReader, streamWriter))
                                            {
                                                await SendPayload(tcpClients, "1", cancellationToken);
                                                await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
                                                continue;
                                            }

                                            await SendPayload(tcpClients, "0", cancellationToken);
                                            await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
                                        } 
                                        catch(ArgumentException)
                                        {
                                            await SendPayload(tcpClients, "0", cancellationToken);
                                            await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
                                        }

                                    } while (tcpClient.Connected && !cancellationToken.IsCancellationRequested);
                                } while (tcpClient.Connected && !cancellationToken.IsCancellationRequested);
                            }
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                if (Verbose)
                {
                    Console.WriteLine(exception);
                }
            }
        }

        public static async Task<bool> Authenticate(StreamReader sr, StreamWriter sw, string password)
        {
            var readLineTask = sr.ReadLineAsync();
            await sw.WriteLineAsync($"AUTHENTICATE \"{password}\"");
            await sw.FlushAsync();
            var readLine = await readLineTask;

            return string.Equals(readLine, @"250 OK", StringComparison.Ordinal);
        }

        public static async Task<bool> IsCircuitFormed(StreamReader sr, StreamWriter sw)
        {
            var readLineTask = sr.ReadLineAsync();
            await sw.WriteLineAsync(@"GETINFO status/circuit-established");
            await sw.FlushAsync();
            var readLine = await readLineTask;

            var success = string.Equals(readLine, @"250-status/circuit-established=1", StringComparison.Ordinal);
            readLine = await sr.ReadLineAsync();
            if(string.Equals(readLine, @"250 OK", StringComparison.Ordinal))
            {
                return success;
            }

            throw new ArgumentException("Unable to read response from control port.");
        }

        public static async Task SendPayload(ConcurrentDictionary<IntPtr, TcpClient> clients, string payload, CancellationToken CancellationToken)
        {
            foreach (var (handle, client) in clients)
            {
                if(CancellationToken.IsCancellationRequested)
                {
                    throw new TaskCanceledException();
                }

                try
                {
                    if (!client.Connected)
                    {
                        continue;
                    }

                    using (var networkStream = client.GetStream())
                    {
                        using (var sw = new StreamWriter(networkStream))
                        {
                            await sw.WriteLineAsync(payload);
                            
                        }
                    }
                }
                catch(Exception exception)
                {
                    if (Verbose)
                    {
                        Console.WriteLine(exception);
                    }
                }
                finally
                {
                    clients.TryRemove(handle, out _);
                    try
                    {
                        client.Close();
                    }
                    catch(ObjectDisposedException exception)
                    {
                        if (Verbose)
                        {
                            Console.WriteLine(exception);
                        }
                    }
                }
            }
        }
    }


}