using System;
using System.Diagnostics;
using System.IO;
using System.Text;

using FirmaSAT;

/*
 *  $Id: TestFirmaSat.cs $
 *   Last updated:
 *   $Date: 2020-08-05 17:01 $
 *   $Version: 9.2.0 $
 */

/* Some tests using the FirmaSAT .NET interface.
 * 
 * Requires `FirmaSAT` to be installed on your system: available from <https://www.cryptosys.net/firmasat/>.
 * Add a reference to `diFirmaSatNet.dll` installed in `C:\Program Files (x86)\FirmaSAT`.
 * 
 * Test files are in `FirmaSATtestfiles.zip`. These must be in the CWD.
 *  
 * This is a Console Application written for target .NET Framework 2.0 and above.
 * Please report any bugs to <https://cryptosys.net/contact>.
 */

/******************************* LICENSE ***********************************
 * Copyright (C) 2010-20 David Ireland, DI Management Services Pty Limited.
 * All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>
 * The code in this module is licensed under the terms of the MIT license.  
 * For a copy, see <http://opensource.org/licenses/MIT>
****************************************************************************
*/

/* [2020-07] Re-organised into modular form */

/* NOTE: Requires certain files to exist in the current working directory
 * (by default, the same directory as the executable).
 * See `arrFileNames` below - these files are in `FirmaSATtestfiles.zip`
 */


namespace TestFirmaSAT
{
    /// <summary>
    /// Test examples for FirmaSAT .NET interface
    /// </summary>
    class TestFirmaSAT
    {
        private const int MIN_FSA_VERSION = 90200;

        // Test files required to exist in the current working directory:
        private static string[] arrFileNames = new string[] 
        { 
		    "A7.xml",
		    "AAA010101AAA201501BN-base.xml",
		    "AAA010101AAA201501CT-base.xml",
		    "AAA010101AAA201705CT.xml",
		    "AC4_SAT.cer",
		    "cfdv33a-base.xml",
		    "cfdv33a-base-nocertnum.xml",
		    "cfdv33a-bad-nover.xml",
		    "cfdv33a-cce11-min.xml",
		    "cfdv33a-cce11.xml",
		    "cfdv33a-detallista-min.xml",
		    "cfdv33a-detallista.xml",
		    "cfdv33a-min.xml",
		    "cfdv33a-nomina12.xml",
		    "cfdv33a-nomina12B.xml",
		    "cfdv33a-pagos10-min.xml",
		    "cfdv33a-pagos10.xml",
		    "cfdv33a-signed-tfd.xml",
		    "cfdv33a-signed.xml",
		    "contab-SelloDigitalContElec-signed.xml",
		    "ConVolE12345-base.xml",
		    "ConVolE12345-signed2015.xml",
		    "CSD_E12345CV_ACP020530MP5.cer",
		    "CSD_E12345CV_ACP020530MP5.key",
		    "Ejemplo_Retenciones-base.xml",
		    "Ejemplo_Retenciones-signed-tfd.xml",
		    "ejemplo_v32-tfd2015.xml",
		    "emisor-pem.cer",
		    "emisor-pem.key",
		    "emisor.cer",
		    "emisor.key",
		    "emisor1024.cer",
		    "emisor1024.key",
		    "pac.cer",
		    "pac.key",
		    "pac1024.cer",
		    "pac1024.key",
		    "V3_2_BadCurp.xml",
        };

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            // Make sure minimum required version of FirmaSAT is installed...
            Console.WriteLine("FirmaSAT core Version is " + General.Version());
            if (General.Version() < MIN_FSA_VERSION) {
                Console.WriteLine("FATAL ERROR: Require FirmaSAT core version " + MIN_FSA_VERSION + " or later.");
                return;
            }

            // CHECK FOR REQUIRED FILES IN CURRENT WORKING DIRECTORY
            Console.WriteLine("Current working directory is {0}", Directory.GetCurrentDirectory());
            string missingFile = "STOPPED: Required file is missing in current working directory";
            foreach (string fn in arrFileNames) {
                if (FileIsNotPresent(fn, missingFile)) return;
            }

            /* Handle command-line arguments
             * [some]      just do some tests (default = do all)
             */

            bool doSome = false;
            for (int iarg = 0; iarg < args.Length; iarg++) {
                if (args[iarg] == "some")
                    doSome = true;
            }

            //*************
            // DO THE TESTS
            //*************
            if (doSome) // Use "some" in the command line
            {   // Do some tests - comment these out as required
                Console.WriteLine("Just doing some tests...");

                //test_General();
                //test_ErrorLookup();
                //test_pipestring();
                //test_SignAndVerify();
                //test_Digest();
                //test_Validate();
                //test_ExtractAttribute();
                //test_X509Certificate();
                //test_MakeSignature();
                //test_Detallista();
                //test_CertValidity();
                //test_CheckKeyAndCert();
                //test_ReceiptVersion();
                //test_Tfd();
                //test_BOM();
                //test_ExtractConsecutive();
                //test_PrivateKey();
                //test_QueryCert();
                //test_SignInMemory();
                //test_UUID();
                //test_Retenciones();
                //test_Contabilidad();
                //test_ConVol();
                //test_InsertCert();
                //test_GetXmlAttribute_Advanced();
                test_XMLasUnicodeString();

            } 
            else {
                // Do all the test modules (default)
                Console.WriteLine("Doing all tests...");

                DoAllTests();
            }

            // ******************************************
            // FINALLY, DISPLAY QUICK INFO ABOUT THE CORE DLL
            Console.WriteLine("\nDETAILS OF CORE DLL...");
            Console.WriteLine("DLL Version={0} [{1}] Lic={2} Compiled=[{3}] ",
                General.Version(), General.Platform(), General.LicenceType(), General.CompileTime());
            Console.WriteLine("[{0}]", General.ModuleName());

            //********************************************
            Console.WriteLine("\nALL TESTS COMPLETED.");
        }

        static void DoAllTests()
        {
            test_General();
            test_ErrorLookup();
            test_pipestring();
            test_SignAndVerify();
            test_Digest();
            test_Validate();
            test_ExtractAttribute();
            test_X509Certificate();
            test_MakeSignature();
            test_Detallista();
            test_CertValidity();
            test_CheckKeyAndCert();
            test_ReceiptVersion();
            test_Tfd();
            test_BOM();
            test_ConsecutiveElements();
            test_PrivateKey();
            test_QueryCert();
            test_SignInMemory();
            test_UUID();
            test_Retenciones();
            test_Contabilidad();
            test_ConVol();
            test_InsertCert();
            test_GetXmlAttribute_Advanced();
            test_XMLasUnicodeString();
        }

        //********************
        // THE TEST MODULES...
        //********************

        static void test_General()
        {
            int n;
            char ch;
            string s;
            //****************
            // GENERAL TESTS *
            //****************
            Console.WriteLine("\nINTERROGATE THE CORE DIFIRMASAT DLL:");
            n = FirmaSAT.General.Version();
            Console.WriteLine("Version={0}", n);
            s = General.CompileTime();
            Console.WriteLine("CompileTime={0}", s);
            s = General.ModuleName();
            Console.WriteLine("ModuleName={0}", s);
            s = General.Platform();
            Console.WriteLine("Platform={0}", s);
            ch = General.LicenceType();
            Console.WriteLine("LicenceType={0}", ch);
            s = General.Comments();
            Console.WriteLine("Comments={0}", s);
        }

        static void test_ErrorLookup()
        {
            string s;
            //*****************
            // ERROR LOOKUP TESTS *
            //*********************
            Console.WriteLine("\nERROR CODES:");
            for (int i = 0; i < 10000; i++) {
                s = General.ErrorLookup(i);
                if (!s.Equals(String.Empty))
                    Console.WriteLine("{0}={1}", i, s);
            }
        }

        static void test_pipestring()
        {
            string s;
            string fname;

            Console.WriteLine("\nFORM THE PIPESTRING FROM AN XML FILE:");
            fname = "cfdv33a-base.xml";
            s = Sat.MakePipeStringFromXml(fname);
            Console.WriteLine("Sat.MakePipeStringFromXml('{0}')=\n{1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.MakePipeStringFromXml failed");
        }

        static void test_SignAndVerify()
        {
            int n;
            string fname;
            string newname, keyfile, password, certfile;

            Console.WriteLine("\nSIGN AN XML FILE:");
            fname = "cfdv33a-base.xml";
            newname = "cfdv33a_new-signed.xml";
            keyfile = "emisor.key";
            password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
            certfile = "emisor.cer";
            n = Sat.SignXml(newname, fname, keyfile, password, certfile);
            Console.WriteLine("Sat.SignXml('{0}'-->'{1}') returns {2}", fname, newname, n);
            Debug.Assert(n == 0, "Sat.SignXml failed");
            // Did we make a valid XML file?
            n = Sat.ValidateXml(newname);
            Console.WriteLine("Sat.ValidateXml('{0}') returns {1}", newname, n);
            Debug.Assert(n == 0, "Sat.ValidateXml failed");

            Console.WriteLine("\nVERIFY A SIGNATURE IN AN XML FILE:");
            Console.WriteLine("1. One we know is good:");
            fname = "cfdv33a-signed-tfd.xml";
            n = Sat.VerifySignature(fname);
            Console.WriteLine("Sat.VerifySignature('{0}') returns {1}", fname, n);
            Debug.Assert(n == 0, "Sat.VerifySignature failed");

            Console.WriteLine("2. One we just made, so it should be good:");
            fname = newname;
            n = Sat.VerifySignature(fname);
            Console.WriteLine("Sat.VerifySignature('{0}') returns {1}", fname, n);
            Debug.Assert(n == 0, "Sat.VerifySignature failed");

        }

        static void test_Digest()
        {
            string s;
            string fname;

            Console.WriteLine("\nFORM THE DIGEST OF THE PIPESTRING IN AN XML FILE:");
            fname = "cfdv33a-signed-tfd.xml";
            s = Sat.MakeDigestFromXml(fname);
            Console.WriteLine("Sat.MakeDigestFromXml('{0}')=\n{1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.MakeDigestFromXml failed");

            Console.WriteLine("\nEXTRACT THE DIGEST FROM THE SIGNATURE IN AN XML FILE:");
            fname = "cfdv33a-signed-tfd.xml";
            s = Sat.ExtractDigestFromSignature(fname);
            Console.WriteLine("Sat.ExtractDigestFromSignature('{0}')=\n{1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.ExtractDigestFromSignature failed");

        }

        static void test_Validate()
        {
            int n;
            string s;
            string fname;

            Console.WriteLine("\nTRY VALIDATING XML FILES:");
            Console.WriteLine("1. A valid one:");
            fname = "cfdv33a-signed-tfd.xml";
            n = Sat.ValidateXml(fname);
            Console.WriteLine("Sat.ValidateXml('{0}') returns {1} (expecting 0)", fname, n);
            Debug.Assert(n == 0, "Sat.ValidateXml failed");

            Console.WriteLine("2. An invalid one (missing version):");
            fname = "cfdv33a-bad-nover.xml";
            n = Sat.ValidateXml(fname);
            Console.WriteLine("Sat.ValidateXml('{0}') returns {1}", fname, n);
            s = Sat.LastError();
            Console.WriteLine("ErrorLookup({0})={1}", n, General.ErrorLookup(n));
            Debug.Assert(n != 0, "Sat.ValidateXml should have failed");

            Console.WriteLine("\nVALIDATE XML WITH STRICT AND LOOSE OPTIONS:");
            Console.WriteLine("Default strict behaviour (Bad attribute..@CURP..is too long):");
            fname = "V3_2_BadCurp.xml";
            n = Sat.ValidateXml(fname);
            Console.WriteLine("Sat.ValidateXml('{0}') returns {1}", fname, n);
            s = Sat.LastError();
            Console.WriteLine("ErrorLookup({0})={1}", n, General.ErrorLookup(n));
            Console.WriteLine("LastError={0}", s);
            Debug.Assert(n != 0, "Sat.ValidateXml failed to fail");

            Console.WriteLine("\nUsing XmlOption.Loose:");
            n = Sat.ValidateXml(fname, XmlOption.Loose);
            Console.WriteLine("Sat.ValidateXml('{0}', Loose) returns {1}", fname, n);
            Debug.Assert(n == 0, "Sat.ValidateXml failed");


        }

        static void test_ExtractAttribute()
        {
            string s;
            string fname;
            string attributeName, elementName;

            Console.WriteLine("\nEXTRACT AN ATTRIBUTE FROM AN XML FILE:");
            fname = "cfdv33a-signed-tfd.xml";
            elementName = "Comprobante";
            attributeName = "Sello";    // NB capital 'S' for v3.3
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})=\n{1}", fname, s, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");

            elementName = "Comprobante";
            attributeName = "Fecha";
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})=\n{1}", fname, s, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");

            Console.WriteLine("\nEXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS:");
            fname = "cfdv33a-base.xml";
            elementName = "cfdi:Emisor";
            attributeName = "Nombre";
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})={1}", fname, s, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");

            Console.WriteLine("\nEXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS IN ITS NAME:");
            fname = "cfdv33a-nomina12.xml";
            elementName = "nomina12:CompensacionSaldosAFavor";
            attributeName = "Año";
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})={1}", fname, s, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");

        }

        static void test_X509Certificate()
        {
            string s;
            string s1;
            string fname;

            Console.WriteLine("\nGET DETAILS OF X.509 CERTIFICATE:");
            Console.WriteLine("1. From embedded `certificado` in XML");
            fname = "cfdv33a-signed-tfd.xml";
            s = Sat.GetCertNumber(fname);
            Console.WriteLine("Sat.GetCertNumber('{0}')={1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.GetCertNumber failed");
            s = Sat.GetCertExpiry(fname);
            Console.WriteLine("Sat.GetCertExpiry('{0}')={1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed");

            Console.WriteLine("2. From X.509 file");
            fname = "emisor.cer";
            s = Sat.GetCertNumber(fname);
            Console.WriteLine("Sat.GetCertNumber('{0}')={1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.GetCertNumber failed");
            s = Sat.GetCertExpiry(fname);
            Console.WriteLine("Sat.GetCertExpiry('{0}')={1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed");

            Console.WriteLine("\nGET CERTIFICATE AS A BASE64 STRING:");
            fname = "emisor.cer";
            s = Sat.GetCertAsString(fname);
            Console.WriteLine("Sat.GetCertAsString('{0}')=\n{1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.GetCertAsString failed");
            Console.WriteLine("Sat.GetCertAsString('{0}').Length={1}", fname, s.Length);
            // Compare against string from XML file
            fname = "cfdv33a-signed-tfd.xml";
            s1 = Sat.GetCertAsString(fname);
            Console.WriteLine("Sat.GetCertAsString('{0}').Length={1}", fname, s1.Length);
            Debug.Assert(s1.Length > 0, "Sat.GetCertAsString failed");
            Debug.Assert(String.Compare(s, s1, true) == 0, "Sat.GetCertAsString failed");

        }

        static void test_MakeSignature()
        {
            string s;
            string fname;
            string keyfile, password;

            Console.WriteLine("\nMAKE A SIGNATURE FROM A BASE XML FILE:");
            fname = "cfdv33a-base.xml";
            keyfile = "emisor.key";
            password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
            s = Sat.MakeSignatureFromXml(fname, keyfile, password);
            Console.WriteLine("Sat.MakeSignatureFromXml('{0}')=\n{1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.MakeSignatureFromXml failed");

        }

        static void test_Detallista()
        {
            int n;
            string s;
            string fname;
            string keyfile, password;
            string newname, certfile;
            string attributeName, elementName;

            Console.WriteLine("\nSIGN A DETALLISTA XML FILE:");
            fname = "cfdv33a-detallista.xml";
            newname = "detallista_new-signed.xml";
            keyfile = "emisor.key";
            password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
            certfile = "emisor.cer";
            n = Sat.SignXml(newname, fname, keyfile, password, certfile);
            Console.WriteLine("Sat.SignXml('{0}'-->'{1}') returns {2}", fname, newname, n);
            Debug.Assert(n == 0, "Sat.SignXml failed");
            // Did we make a valid XML file?
            n = Sat.ValidateXml(newname);
            Console.WriteLine("Sat.ValidateXml('{0}') returns {1}", newname, n);
            Debug.Assert(n == 0, "Sat.ValidateXml failed");
            n = Sat.VerifySignature(newname);
            Console.WriteLine("Sat.VerifySignature('{0}') returns {1}", newname, n);
            Debug.Assert(n == 0, "Sat.VerifySignature failed");

            Console.WriteLine("\nEXTRACT AN ATTRIBUTE FROM A DETALLISTA XML FILE:");
            fname = "cfdv33a-detallista.xml";
            elementName = "detallista:detallista";
            attributeName = "documentStructureVersion";
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})={1}", fname, s, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");
            Debug.Assert(String.Compare(s, "AMC8.1") == 0, "Invalid detallista.documentStructureVersion");


        }

        static void test_CertValidity()
        {
            string s;
            string fname;

            Console.WriteLine("\nGET VALIDITY DETAILS OF X.509 CERTIFICATE:");
            Console.WriteLine("1. From embedded `certificado` in XML");
            fname = "cfdv33a-signed-tfd.xml";
            s = Sat.GetCertExpiry(fname);
            Console.WriteLine("Sat.GetCertExpiry('{0}')=\t{1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed");
            s = Sat.GetCertStart(fname);
            Console.WriteLine("Sat.GetCertStart('{0}') =\t{1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.GetCertStart failed");

            Console.WriteLine("2. From X.509 file");
            fname = "emisor.cer";
            s = Sat.GetCertExpiry(fname);
            Console.WriteLine("Sat.GetCertExpiry('{0}')=\t{1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed");
            s = Sat.GetCertStart(fname);
            Console.WriteLine("Sat.GetCertStart('{0}') =\t{1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.GetCertStart failed");

        }

        static void test_CheckKeyAndCert()
        {
            int n;
            string keyfile, password, certfile;

            Console.WriteLine("\nCHECK PRIVATE KEY MATCHES PUBLIC KEY IN CERTIFICATE:");
            certfile = "emisor.cer";
            keyfile = "emisor.key";
            password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
            n = Sat.CheckKeyAndCert(keyfile, password, certfile);
            Console.WriteLine("Sat.CheckKeyAndCert({0},{1}) = {2}", keyfile, certfile, n);
            Debug.Assert(n == 0, "Sat.CheckKeyAndCert failed");

            certfile = "pac.cer";
            keyfile = "pac.key";
            password = "12345678a";
            n = Sat.CheckKeyAndCert(keyfile, password, certfile);
            Console.WriteLine("Sat.CheckKeyAndCert({0},{1}) = {2}", keyfile, certfile, n);
            Debug.Assert(n == 0, "Sat.CheckKeyAndCert failed");

            // Get embedded certificate from XML doc
            certfile = "cfdv33a-signed-tfd.xml";
            keyfile = "emisor.key";
            password = "12345678a";
            n = Sat.CheckKeyAndCert(keyfile, password, certfile);
            Console.WriteLine("Sat.CheckKeyAndCert({0},{1}) = {2}", keyfile, certfile, n);
            Debug.Assert(n == 0, "Sat.CheckKeyAndCert failed");


        }

        static void test_ReceiptVersion()
        {
            int n;
            string fname;

            Console.WriteLine("\nFIND THE COMPROBANTE VERSION OF AN XML FILE:");
            fname = "cfdv33a-base.xml.";
            n = Sat.XmlReceiptVersion(fname);
            Console.WriteLine("Sat.XmlReceiptVersion('{0}') = {1}", fname, n);
            Debug.Assert(n == 33, "Sat.XmlReceiptVersion failed");

            // Older version... 
            Console.WriteLine("Legacy versions still work...");
            fname = "ejemplo_v32-tfd2015.xml";
            n = Sat.XmlReceiptVersion(fname);
            Console.WriteLine("Sat.XmlReceiptVersion('{0}') = {1}", fname, n);
            Debug.Assert(n == 32, "Sat.XmlReceiptVersion failed");


        }

        static void test_Tfd()
        {
            int n;
            string s;
            string fname;
            string newname, keyfile, password, certfile;
            string s1;

            Console.WriteLine("\nCREATE CADENA ORIGINAL DEL TIMBRE FISCAL DIGITAL (PIPESTRING FOR TFD):");
            fname = "cfdv33a-signed-tfd.xml";
            certfile = "pac.cer";
            s = Tfd.MakePipeStringFromXml(fname);
            Console.WriteLine("Tfd.MakePipeStringFromXml('{0}') =\n{1}", fname, s);

            Console.WriteLine("\nFORM DIGEST OF PIPESTRING FOR TFD:");
            s = Tfd.MakeDigestFromXml(fname);
            Console.WriteLine("Tfd.MakeDigestFromXml('{0}')=\n{1}", fname, s);

            Console.WriteLine("\nEXTRACT DIGEST FROM TFD SELLOSAT:");
            // NB certFile is required for Tfd
            s1 = Tfd.ExtractDigestFromSignature(fname, certfile);
            Console.WriteLine("Tfd.ExtractDigestFromSignature('{0}')=\n{1}", fname, s1);

            // Check the two digests match
            Debug.Assert(String.Compare(s, s1, true) == 0, "Digests do not match");

            Console.WriteLine("\nPRETEND WE ARE A PAC WITH A KEY ALLOWED TO SIGN THE TFD:");
            // Pretend we are a PAC with a key allowed to sign the TFD
            // so create a TFD signature string we could paste into the `selloSAT' node
            fname = "cfdv33a-signed-tfd.xml";
            certfile = "pac.cer";
            keyfile = "pac.key";
            password = "12345678a";
            s = Tfd.MakeSignatureFromXml(fname, keyfile, password);
            Console.WriteLine("Tfd.MakeSignatureFromXml('{0}')=\n{1}", fname, s);
            s1 = Sat.GetXmlAttribute(fname, "SelloSAT", "TimbreFiscalDigital"); // NB Capital 'S' in Sello for TFD v1.1
            Console.WriteLine("Correct=\n{0}", s1);
            Debug.Assert(String.Compare(s, s1, true) == 0, "selloSAT values do not match");

            Console.WriteLine("\nADD A TFD ELEMENT TO A SIGNED CFDI DOCUMENT USING PAC KEY:");
            fname = "cfdv33a-signed.xml";
            newname = "cfdv33a_new-tfd.xml";
            certfile = "pac.cer";
            keyfile = "pac.key";
            password = "12345678a";
            n = Tfd.AddSignedTfd(newname, fname, keyfile, password, certfile);
            Console.WriteLine("Tfd.AddSignedTfd('{0}') returns {1} (expected 0)\n", newname, n);
            Debug.Assert(0 == n, "Tfd.AddSignedTfd failed");

            // Did we make a valid XML file?
            n = Sat.ValidateXml(newname);
            Console.WriteLine("Sat.ValidateXml('{0}') returns {1} (expected 0)\n", newname, n);
            Debug.Assert(0 == n, "Sat.ValidateXml failed");

            Console.WriteLine("\nVERIFY SIGNATURE IN TFD SELLOSAT:");
            n = Tfd.VerifySignature(newname, certfile);
            Console.WriteLine("Tfd.VerifySignature({0},{1})={2} (expected 0)", newname, certfile, n);
            Debug.Assert(n == 0, "Tfd.VerifySignature failed");


        }

        static void test_BOM()
        {
            int n;
            string fname;
            string newname;

            Console.WriteLine("\nADD UTF-8 BOM TO EXISTING FILE:");
            fname = "cfdv33a-signed-nobom.xml";
            Console.WriteLine("FileHasBom('{0}')={1} ", fname, FileHasBom(fname));
            newname = "cfdv33a_new-signed-with-BOM.xml";
            n = Sat.FixBom(newname, fname);
            Console.WriteLine("Sat.FixBom({0}->{1})={2} (expected 0)", fname, newname, n);
            Debug.Assert(n == 0, "Sat.FixBom failed");
            Console.WriteLine("FileHasBom('{0}')={1} ", newname, FileHasBom(newname));

        }

        static void test_ConsecutiveElements()
        {
            string s;
            string fname;
            string attributeName, elementName;

            Console.WriteLine("\nEXTRACT ATTRIBUTES FROM CONSECUTIVE ELEMENTS:");
            fname = "ejemplo_v32-tfd2015.xml";
            attributeName = "descripcion";
            elementName = "cfdi:Concepto";
            Console.WriteLine("For file '{0}'", fname);
            string eName = null;
            for (int i = 1; i <= 10; i++) {
                eName = elementName + string.Format("[{0:d}]", i);
                Console.Write("Sat.GetXmlAttribute({0},{1})=", attributeName, eName);
                s = Sat.GetXmlAttribute(fname, attributeName, eName);
                if (Sat.XmlNoMatch() == s) {    // [2020-07-06] Changed from empty string
                    break;
                }
                Console.WriteLine("{0}", s);
            }
            Console.WriteLine();


        }

        static void test_PrivateKey()
        {
            int n;
            string s;
            string fname;
            string keyfile, password, certfile;
            string newname;
            string newpassword;

            Console.WriteLine("\nGET PRIVATE KEY AS BASE64:");
            fname = "emisor.key";
            password = "12345678a";
            s = Sat.GetKeyAsString(fname, password);
            Console.WriteLine("Sat.GetKeyAsString({0})=\n{1}\n", fname, s);
            Console.WriteLine("Sat.GetKeyAsString('{0}').Length={1}", fname, s.Length);
            Debug.Assert(s.Length > 0, "Sat.GetKeyAsString failed");

            Console.WriteLine("\nWRITE PFX FROM PRIVATE KEY AND CERT:");
            certfile = "emisor.cer";
            keyfile = "emisor.key";
            password = "12345678a";
            fname = "archivo_new-pfx.txt";
            newpassword = "clavedesalida";
            n = Sat.WritePfxFile(fname, newpassword, keyfile, password, certfile);
            Console.WriteLine("Sat.WritePfxFile()->{0} returns {1}", fname, n);
            Debug.Assert(n == 0, "Sat.WritePfxFile failed");
            Console.WriteLine("New PFX file is {0} bytes long.", FileLength(fname));

            Console.WriteLine("\nSAVE KEYFILE WITH NEW PASSWORD...");
            keyfile = "emisor.key";
            password = "12345678a";
            newname = "emisor_new.key";
            newpassword = "password123";
            n = Sat.NewKeyFile(newname, newpassword, keyfile, password, 0);
            Console.WriteLine("Sat.NewKeyFile() returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Sat.NewKeyFile failed");
            Console.WriteLine("Created new key file '{0}' of length {1} bytes with password '{2}'.", newname, FileLength(newname), newpassword);
            Console.WriteLine("Save again in PEM format...");
            newname = "emisor_new.pem";
            newpassword = "password456";
            n = Sat.NewKeyFile(newname, newpassword, keyfile, password, KeyFormat.PEM);
            Console.WriteLine("Sat.NewKeyFile(KeyFormat.PEM) returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Sat.NewKeyFile failed");
            Console.WriteLine("Created new key file '{0}' of length {1} bytes with password '{2}'.", newname, FileLength(newname), newpassword);


        }

        static void test_QueryCert()
        {
            string s;
            string fname;

            Console.WriteLine("\nGET RFC AND ORG NAME FROM CERT:");
            fname = "emisor.cer";
            Console.WriteLine("FILE: {0}", fname);
            s = Sat.QueryCert(fname, Query.rfc);
            Console.WriteLine("Sat.QueryCert(rfc)={0}", s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(rfc) failed");
            s = Sat.QueryCert(fname, Query.organizationName);
            Console.WriteLine("Sat.QueryCert(organizationName)='{0}'", s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(organizationName) failed");
            fname = "cfdv33a-signed-tfd.xml";
            Console.WriteLine("FILE: {0}", fname);
            s = Sat.QueryCert(fname, Query.rfc);
            Console.WriteLine("Sat.QueryCert(rfc)={0}", s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(rfc) failed");
            s = Sat.QueryCert(fname, Query.organizationName);
            Console.WriteLine("Sat.QueryCert(organizationName)='{0}'", s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(organizationName) failed");

            Console.WriteLine("\nTEST OTHER QUERIES FOR CERT:");
            fname = "emisor.cer";
            Console.WriteLine("FILE: {0}", fname);
            s = Sat.QueryCert(fname, Query.notBefore);
            Console.WriteLine("Sat.QueryCert(notBefore)={0}", s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(notBefore) failed");
            s = Sat.QueryCert(fname, Query.notAfter);
            Console.WriteLine("Sat.QueryCert(notAfter)={0}", s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(notAfter) failed");
            s = Sat.QueryCert(fname, Query.serialNumber);
            Console.WriteLine("Sat.QueryCert(serialNumber)={0}", s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(serialNumber) failed");

            Console.WriteLine("\nQUERY KEY SIZE OF CERTIFICATES...");
            fname = "emisor1024.cer";
            s = Sat.QueryCert(fname, Query.keySize);
            Console.WriteLine("Sat.QueryCert('{0}',keySize)={1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(keySize) failed");
            fname = "emisor.cer";
            s = Sat.QueryCert(fname, Query.keySize);
            Console.WriteLine("Sat.QueryCert('{0}',keySize)={1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(keySize) failed");
            fname = "AC4_SAT.cer";
            s = Sat.QueryCert(fname, Query.keySize);
            Console.WriteLine("Sat.QueryCert('{0}',keySize)={1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(keySize) failed");

            Console.WriteLine("\nQUERY SIGNATURE ALGORITHM IN CERTIFICATES...");
            fname = "emisor1024.cer";
            s = Sat.QueryCert(fname, Query.sigAlg);
            Console.WriteLine("Sat.QueryCert('{0}',sigAlg)={1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(sigAlg) failed");
            fname = "emisor.cer";
            s = Sat.QueryCert(fname, Query.sigAlg);
            Console.WriteLine("Sat.QueryCert('{0}',sigAlg)={1}", fname, s);
            Debug.Assert(s.Length > 0, "Sat.QueryCert(sigAlg) failed");


        }

        static void test_SignInMemory()
        {
            int n;
            string fname;
            string keyfile, password, certfile;
            byte[] xmlArr;
            byte[] b;

            Console.WriteLine("\nSIGN XML FROM BYTES TO BYTES:");
            // We can pass key file and certificate as "PEM" strings.
            // The "BEGIN/END" encapsulation is optional for a certificate,
            // but is required for the encrypted private key.
            // These strings are from `emisor-pem.cer` and `emisor-pem.key`, respectively
            certfile =
                "-----BEGIN CERTIFICATE-----" +
                "MIIF+TCCA+GgAwIBAgIUMzAwMDEwMDAwMDAzMDAwMjM3MDgwDQYJKoZIhvcNAQELBQAwggFmMSAwHgY" +
                "DVQQDDBdBLkMuIDIgZGUgcHJ1ZWJhcyg0MDk2KTEvMC0GA1UECgwmU2VydmljaW8gZGUgQWRtaW5pc3" +
                "RyYWNpw7NuIFRyaWJ1dGFyaWExODA2BgNVBAsML0FkbWluaXN0cmFjacOzbiBkZSBTZWd1cmlkYWQgZ" +
                "GUgbGEgSW5mb3JtYWNpw7NuMSkwJwYJKoZIhvcNAQkBFhphc2lzbmV0QHBydWViYXMuc2F0LmdvYi5t" +
                "eDEmMCQGA1UECQwdQXYuIEhpZGFsZ28gNzcsIENvbC4gR3VlcnJlcm8xDjAMBgNVBBEMBTA2MzAwMQs" +
                "wCQYDVQQGEwJNWDEZMBcGA1UECAwQRGlzdHJpdG8gRmVkZXJhbDESMBAGA1UEBwwJQ295b2Fjw6FuMR" +
                "UwEwYDVQQtEwxTQVQ5NzA3MDFOTjMxITAfBgkqhkiG9w0BCQIMElJlc3BvbnNhYmxlOiBBQ0RNQTAeF" +
                "w0xNzA1MTgwMzU0NTZaFw0yMTA1MTgwMzU0NTZaMIHlMSkwJwYDVQQDEyBBQ0NFTSBTRVJWSUNJT1Mg" +
                "RU1QUkVTQVJJQUxFUyBTQzEpMCcGA1UEKRMgQUNDRU0gU0VSVklDSU9TIEVNUFJFU0FSSUFMRVMgU0M" +
                "xKTAnBgNVBAoTIEFDQ0VNIFNFUlZJQ0lPUyBFTVBSRVNBUklBTEVTIFNDMSUwIwYDVQQtExxBQUEwMT" +
                "AxMDFBQUEgLyBIRUdUNzYxMDAzNFMyMR4wHAYDVQQFExUgLyBIRUdUNzYxMDAzTURGUk5OMDkxGzAZB" +
                "gNVBAsUEkNTRDAxX0FBQTAxMDEwMUFBQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdU" +
                "csHIEIgwivvAantGnYVIO3+7yTdD1tkKopbL+tKSjRFo1ErPdGJxP3gxT5O+ACIDQXN+HS9uMWDYnaU" +
                "RalSIF9COFCdh/OH2Pn+UmkN4culr2DanKztVIO8idXM6c9aHn5hOo7hDxXMC3uOuGV3FS4ObkxTV+9" +
                "NsvOAV2lMe27SHrSB0DhuLurUbZwXm+/r4dtz3b2uLgBc+Diy95PG+MIu7oNKM89aBNGcjTJw+9k+Wz" +
                "JiPd3ZpQgIedYBD+8QWxlYCgxhnta3k9ylgXKYXCYk0k0qauvBJ1jSRVf5BjjIUbOstaQp59nkgHh45" +
                "c9gnwJRV618NW0fMeDzuKR0CAwEAAaMdMBswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwDQYJKoZ" +
                "IhvcNAQELBQADggIBABKj0DCNL1lh44y+OcWFrT2icnKF7WySOVihx0oR+HPrWKBMXxo9KtrodnB1tg" +
                "Ix8f+Xjqyphhbw+juDSeDrb99PhC4+E6JeXOkdQcJt50Kyodl9URpCVWNWjUb3F/ypa8oTcff/eMftQ" +
                "ZT7MQ1Lqht+xm3QhVoxTIASce0jjsnBTGD2JQ4uT3oCem8bmoMXV/fk9aJ3v0+ZIL42MpY4POGUa/iT" +
                "aawklKRAL1Xj9IdIR06RK68RS6xrGk6jwbDTEKxJpmZ3SPLtlsmPUTO1kraTPIo9FCmU/zZkWGpd8ZE" +
                "AAFw+ZfI+bdXBfvdDwaM2iMGTQZTTEgU5KKTIvkAnHo9O45SqSJwqV9NLfPAxCo5eRR2OGibd9jhHe8" +
                "1zUsp5GdE1mZiSqJU82H3cu6BiE+D3YbZeZnjrNSxBgKTIf8w+KNYPM4aWnuUMl0mLgtOxTUXi9MKnU" +
                "ccq3GZLA7bx7Zn211yPRqEjSAqybUMVIOho6aqzkfc3WLZ6LnGU+hyHuZUfPwbnClb7oFFz1PlvGOpN" +
                "DsUb0qP42QCGBiTUseGugAzqOP6EYpVPC73gFourmdBQgfayaEvi3xjNanFkPlW1XEYNrYJB4yNjphF" +
                "rvWwTY86vL2o8gZN0Utmc5fnoBTfM9r2zVKmEi6FUeJ1iaDaVNv47te9iS1ai4V4vBY8r" +
                "-----END CERTIFICATE-----";
            keyfile =
                "-----BEGIN ENCRYPTED PRIVATE KEY-----" +
                "MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI5qDMtGWYa2wCAggA" +
                "MBQGCCqGSIb3DQMHBAhFAqj+c0f8JASCBMhNUpNUp57vMu8L3LHBKRBTFl0VE3oq" +
                "BIEKBHFYYz063iiS0Y3tPW3cplLTSqG25MdbIQcHCxwmPVYNdetHUjqjeR+TklWg" +
                "tnMbLqvdMmmRxAFuHXznHFIa4U+YNedhFm7sdR2DsGFijm3vIpUbvpILtpTrhog/" +
                "EHAvZXV6+F86cYc9+LUg3d0DRwJc+sWmk+2xOoXvOvvpnnQqfhQxkSknfITmc+HA" +
                "WgHbKLK2q6e2RixjpWn0sA9LslYD0ZDn5uhrce+QEfK97asraFfiteqXf2Ll8B54" +
                "Ku/er+O2JEu62vVDFumwMtZOuHKH4NbjOmMzKIwRTKp/1jp6OTGYSKIRiTDXnTET" +
                "JwgItHahf7UAoM/qnkJa17Ood4hiCYopMyCXdhyMDJoFhWRanQODaiocb7XpMm1S" +
                "EpTtHZeKgEVWSc/obYgSgs4iY497UR2MUVZQSCBdRXCgs5g1c31cCwAZ6r41KMoL" +
                "OBVLtRXoT0mc0D6ovlwYuJhqYvuwjdNkWJS7qwXuy8b2ux4t027NGUXmgtb9XQDm" +
                "8yJrdTtm0CktWPKe7i2tQtBC2tAjduGAlBrzY+whySRN8KUJQbYKhOBaLXgEPI93" +
                "wi/SKHJO13WvfqqjKqrqJwB3tvhjz5E1uDKmDFoivdS76uq+k/xpmF5OWBmypWNV" +
                "iw7kgvmH1OeTBKYkUHIL85skL6pdycGnTk3g0AmG9xtPYu6pdSqUv+N8QmTdmmdu" +
                "85fDEN0fk2t2BRPANsbIqxopVfj5qIwm+8TbZDdNj8OssxrC5sRy5yDBjV4J+x25" +
                "3yaILn7wgUR6Yj6GaHUUF4GISmFZ/PTbnVPDd424w6hGV8NKtUHXq5ms2kJXo6XG" +
                "iGqjbdePM53QhdSrxTM5Dt76RcAInky6w5s/7gvT/w7tdbVA/SPhp4xgaT8Crmjb" +
                "k3upcSqNI0HuROBxOs0gRRAWXScUZJ0Vd1V0F+C5cG2R1CtGTYeRmIAwLwcWf6Dj" +
                "Y1Q+TOe/W3eTatOo+gIozjYDCk5ZNfeQzq4p1ApN6+gzS8kNxtvKOYJogjV74RK/" +
                "Xl7u7oLv4SZT7Nl1YRpScW1ouIcNNTP0AC+j2OFZ3YueN8CcmvXbgSW8pYRooTxn" +
                "Ffo9sdOL624uwRyb2DwwLO0Vo3aBIEIf8sm9sqocXmwh9sxFPEbTXPCuMSao8Qjy" +
                "BOlsCem2589NVZs0h0ipGwdbatcjkgf+hzRoYBdlvHtKHJ8gL/A/Ap8z0+TK5NaV" +
                "WUA+zXOZRZ66NYfs18DEbJKjwOcnnsLcfAMYoSn697148sL4JBv8IOmM6QXfxCl/" +
                "0yU0d5/876L5jOL56lfH0eBk8s2nioAl3yRBl2wlihWi39sA0bsdHFKYEX+LqPBB" +
                "CAdxZAvXCCJcdEdxOXSgEiFAmW9+IXFT/WJeGcZ4OmCd3Qf0fxGqFXA/9hIUumWd" +
                "e6s0wN8LjXuFZQaMDaaVIGXKguP3OijsfBF0PYzI+L6CfUi2BLaYNJTlbQxbncmW" +
                "2PKeDiypgt3ZY1PKV66o5OAJEAkV3vf9cRwXE5T8GwZHA+wx2rWC98hkH15xfI9q" +
                "EsYulVdcXWzCF58HFQjUoDon0e/QMukS0eNgq9ipmoKAWKyy7+TQw7Xx3MmqkGlL" +
                "HGM=" +
                "-----END ENCRYPTED PRIVATE KEY-----";
            password = "12345678a";
            // Check key matches certificate
            n = Sat.CheckKeyAndCert(keyfile, password, certfile);
            Console.WriteLine("Sat.CheckKeyAndCert(STRINGS) = {0}", n);
            Debug.Assert(n == 0, "Sat.CheckKeyAndCert failed");

            // Read in a UTF-8-encoded XML file into a byte array
            fname = "cfdv33a-base.xml";
            xmlArr = File.ReadAllBytes(fname);
            Console.WriteLine("File '{0}'-->{1} UTF-8 bytes", fname, xmlArr.Length);
            string s1 = "";
            string s2 = "";
            // Sign it and receive result back as a byte array...
            b = Sat.SignXmlToBytes(xmlArr, keyfile, password, certfile, 0);
            if (b.Length == 0) {
                Console.WriteLine("ERROR={0}", General.LastError());
            } else {
                // Convert this to a string
                s1 = System.Text.Encoding.UTF8.GetString(b);
                Console.WriteLine("XML-OUT=\n---\n{0}\n---\n", s1);
                Console.WriteLine("From bytes: XML-OUT: {0} bytes --> {1} chars", b.Length, s1.Length);
            }

            // Sign the file instead and receive result back as a byte array...
            b = Sat.SignXmlToBytes(fname, keyfile, password, certfile, 0);
            if (b.Length == 0)
                Console.WriteLine("ERROR={0}", General.LastError());
            else {
                // Convert this to a string
                s2 = System.Text.Encoding.UTF8.GetString(b);
                Console.WriteLine("From file:  XML-OUT: {0} bytes --> {1} chars", b.Length, s2.Length);
                Debug.Assert(s1.Equals(s2));
            }


        }

        static void test_UUID()
        {
            string s;

            Console.WriteLine("\nGENERATE 3 UUIDs:");
            s = Sat.Uuid();
            Console.WriteLine("UUID={0}", s);
            s = Sat.Uuid();
            Console.WriteLine("UUID={0}", s);
            s = Sat.Uuid();
            Console.WriteLine("UUID={0}", s);

        }

        static void test_Retenciones()
        {
            int n;
            string s;
            string fname;

            Console.WriteLine("\nWORK WITH A `RETENCIONES` DOCUMENT:");
            fname = "Ejemplo_Retenciones-base.xml";
            Console.WriteLine("FILE={0}", fname);
            n = Sat.XmlReceiptVersion(fname);
            Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 1010)", n);
            Debug.Assert(1010 == n, "Sat.XmlReceiptVersion failed");
            s = Sat.MakeDigestFromXml(fname);
            Console.WriteLine("Sat.MakeDigestFromXml() -> {0}", s);
            Debug.Assert(s.Length > 0, "Sat.MakeDigestFromXml failed");
            // Use new [v6.0] option to find name of root element
            s = Sat.GetXmlAttribute(fname, "", "");
            Console.WriteLine("File root element is '{0}'", s);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute('','') failed");


        }

        static void test_Contabilidad()
        {
            int n;
            string s;
            string fname;
            string newname, keyfile, password, certfile;

            Console.WriteLine("\nWORK WITH `CONTABILIDAD` DOCUMENTS:");
            fname = "AAA010101AAA201501CT-base.xml";
            Console.WriteLine("CATALOGOCUENTAS FILE={0}", fname);
            n = Sat.XmlReceiptVersion(fname);
            Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 2011)", n);
            Debug.Assert(2011 == n, "Sat.XmlReceiptVersion failed");
            Console.WriteLine("SIGN A CATALOGOCUENTAS DOCUMENT...");
            newname = "AAA010101AAA201501CT_new-signed.xml";
            keyfile = "emisor.key";
            certfile = "emisor.cer";
            password = "12345678a";
            n = Sat.SignXml(newname, fname, keyfile, password, certfile);
            Console.WriteLine("Sat.SignXml() returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Sat.SignXml failed");
            n = Sat.VerifySignature(newname);
            Console.WriteLine("Sat.VerifySignature() returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Sat.VerifySignature failed");

            fname = "AAA010101AAA201501BN-base.xml";
            Console.WriteLine("BALANZA FILE={0}", fname);
            n = Sat.XmlReceiptVersion(fname);
            Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 2111)", n);
            Debug.Assert(2111 == n, "Sat.XmlReceiptVersion failed");
            Console.WriteLine("MAKE THE SIGNATURE STRING FOR BALANZA...");
            s = Sat.MakeSignatureFromXml(fname, keyfile, password);
            Console.WriteLine("Sat.MakeSignatureFromXml() ->\n{0}", s);
            Debug.Assert(s.Length > 0, "Sat.MakeSignatureFromXml() failed");

            fname = "contab-SelloDigitalContElec-signed.xml";
            Console.WriteLine("SELLODIGITALCONTELEC FILE={0}", fname);
            n = Sat.XmlReceiptVersion(fname);
            Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 2511)", n);
            Debug.Assert(2511 == n, "Sat.XmlReceiptVersion failed");
            Console.WriteLine("VERIFY SIGNATURE FOR SELLODIGITALCONTELEC USING PAC CERTIFICATE...");
            n = Sat.VerifySignature(fname, "pac1024.cer");
            Console.WriteLine("Sat.VerifySignature() returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Sat.VerifySignature failed");

            // NEW IN [v8.1]

            Console.WriteLine("\nSUPPORT FOR CONTABILIDAD V1.3...");
            fname = "AAA010101AAA201705CT.xml";
            Console.WriteLine("FILE: {0}", fname);
            s = Sat.GetXmlAttribute(fname, "", "");
            Console.WriteLine("Doc type is '{1}'", fname, s);
            n = Sat.ValidateXml(fname);
            Console.WriteLine("Sat.ValidateXml() returns {0} (0 => OK)", n);
            Debug.Assert(0 == n, "Sat.ValidateXml failed");
            n = Sat.VerifySignature(fname);
            Console.WriteLine("Sat.VerifySignature() returns {0} (0 => OK)", n);
            Debug.Assert(0 == n, "Sat.VerifySignature failed");
            n = Sat.XmlReceiptVersion(fname);
            Console.WriteLine("Sat.XmlReceiptVersion() returns {0} (expecting 2013)", n);
            s = Sat.DefaultDigestAlg(fname);
            Console.WriteLine("Default digest algorithm is '{0}'", s);

        }

        static void test_ConVol()
        {
            int n;
            string fname;
            string newname, keyfile, password, certfile;

            Console.WriteLine("\nWORK WITH `CONTROLESVOLUMETRICOS` DOCUMENT:");
            fname = "ConVolE12345-base.xml";
            Console.WriteLine("FILE={0}", fname);
            n = Sat.XmlReceiptVersion(fname);
            Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 4011)", n);
            Debug.Assert(4011 == n, "Sat.XmlReceiptVersion failed");

            Console.WriteLine("SIGN A CONVOL DOCUMENT WITH BIGFILE FLAG...");
            newname = "ConVolE12345_new-signed.xml";
            // Use key and cert provided for ConVol tests
            keyfile = "CSD_E12345CV_ACP020530MP5.key";
            certfile = "CSD_E12345CV_ACP020530MP5.cer";
            password = "12345678a";
            n = Sat.SignXmlEx(newname, fname, keyfile, password, certfile, SignOptions.BigFile);
            Console.WriteLine("Sat.SignXmlEx(BigFile) returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Sat.SignXmlEx(BigFile) failed");
            n = Sat.VerifySignature(newname);
            Console.WriteLine("Sat.VerifySignature() returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Sat.VerifySignature failed");


        }

        static void test_InsertCert()
        {
            int n;
            string s;
            string fname;
            string newname, password, certfile;
            string cerStr;
            string xmlStr;
            string keyStr;
            string newStr;
            byte[] b;

            Console.WriteLine("\nINSERT CERTIFICATE INTO XML...");
            // Take an XML file without a Nocertificado...
            fname = "cfdv33a-base-nocertnum.xml";
            Console.WriteLine("Start file '{0}'.NoCertificado=[{1}]", fname, Sat.GetXmlAttribute(fname, "NoCertificado", "cfdi:Comprobante"));
            Console.WriteLine("Start file '{0}'.Certificado={1} bytes", fname, Sat.GetXmlAttribute(fname, "Certificado", "cfdi:Comprobante").Length);
            // Insert certificate details...
            certfile = "emisor.cer";
            newname = "cfdv33a_new-base-pluscert.xml";
            n = Sat.InsertCert(newname, fname, certfile);
            Console.WriteLine("Sat.InsertCert() returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Sat.InsertCert failed");
            fname = newname;
            Console.WriteLine("Inter file '{0}'.NoCertificado=[{1}]", fname, Sat.GetXmlAttribute(fname, "NoCertificado", "cfdi:Comprobante"));
            Console.WriteLine("Inter file '{0}'.Certificado={1} bytes", fname, Sat.GetXmlAttribute(fname, "Certificado", "cfdi:Comprobante").Length);

            // Sign it - no need to specify the certificate
            newname = "cfdv33a_new-signed-pluscert.xml";
            n = Sat.SignXml(newname, fname, "emisor.key", "12345678a", null);
            Console.WriteLine("Sat.SignXml() returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Sat.SignXml failed");
            // Verify that it worked
            n = Sat.VerifySignature(newname);
            Console.WriteLine("Sat.VerifySignature() returns {0} (0 => OK)", n);
            Debug.Assert(0 == n, "Sat.VerifySignature failed");

            Console.WriteLine("\nINSERT CERTIFICATE INTO XML USING STRINGS ONLY...");
            // Read in data to strings
            password = "12345678a";
            xmlStr = File.ReadAllText("cfdv33a-base-nocertnum.xml");
            cerStr = Sat.GetCertAsString("emisor.cer");
            keyStr = Sat.GetKeyAsString("emisor.key", password, KeyOption.EncryptedPEM);
            Console.WriteLine("String lengths: XML={0}, CER={1}, KEY={2}", xmlStr.Length, cerStr.Length, keyStr.Length);
            // Show we can use just strings
            n = Sat.CheckKeyAndCert(keyStr, password, cerStr);
            Console.WriteLine("Sat.CheckKeyAndCert() returns {0} (0 => OK)", n);
            s = Sat.GetXmlAttribute(xmlStr, "Moneda", "cfdi:Comprobante");
            Console.WriteLine("Moneda in XML=[{0}]", s);

            // Insert certificate details. Note (string) -> (bytes) -> (string)
            b = Sat.InsertCertToBytes(xmlStr, cerStr);
            Console.WriteLine("Sat.InsertCertToBytes() returns byte array of length {0}", b.Length);
            if (b.Length == 0) DisplayError();
            Debug.Assert(b.Length > 0);

            newStr = System.Text.Encoding.UTF8.GetString(b);
            Console.WriteLine("XML string has length {0}", newStr.Length);

            // Check we have a NoCertificado attribute...
            s = Sat.GetXmlAttribute(newStr, "NoCertificado", "cfdi:Comprobante");
            Console.WriteLine("NoCertificado=[{0}]", s);
            if (s.Length == 0) DisplayError();

            xmlStr = newStr;
            // Sign it - all strings, no cert required
            newStr = System.Text.Encoding.UTF8.GetString(Sat.SignXmlToBytes(xmlStr, keyStr, password, null, 0));

            // Verify that it worked
            n = Sat.VerifySignature(newStr);
            Console.WriteLine("Sat.VerifySignature() returns {0} (0 => OK)", n);
            if (n != 0) DisplayError(n);
            Debug.Assert(0 == n, "Sat.VerifySignature failed");

        }

        static void test_GetXmlAttribute_Advanced()
        {
            string s;
            string fname;
            string attributeName, elementName;
            string xbase, xpath, val;
            int i, j;

            Console.WriteLine("\nXPATH EXPRESSIONS FOR XMLGETATTRIBUTE...");
            fname = "A7.xml";
            Console.WriteLine("FILE: {0}", fname);
            elementName = "/Comprobante";
            attributeName = "Version";
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");
            Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s);

            elementName = "/Comprobante/Conceptos/Concepto[2]/Impuestos/Traslados/Traslado[1]";
            attributeName = "Importe";
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");
            Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s);

            elementName = "/Comprobante/Conceptos/Concepto[1]/Impuestos/Retenciones/Retencion[2]";
            attributeName = "Importe";
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");
            Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s);

            // Same as above but shorter
            elementName = "//Conceptos/Concepto[1]//Retencion[2]";
            attributeName = "Importe";
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");
            Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s);

            // Test for existence
            Console.WriteLine("Check for existence...");
            elementName = "/Comprobante/Conceptos/Concepto[3]";
            attributeName = "Importe";
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");
            Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s);

            Console.WriteLine("Loop through all Concepto elements until no match...");
            for (i = 1; true; i++) {
                elementName = "//Conceptos/Concepto" + "[" + i + "]";
                s = Sat.GetXmlAttribute(fname, attributeName, elementName);
                Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s);
                if (Sat.XmlNoMatch() == s)
                    break;
            }
            Console.WriteLine("Invalid path expression...");
            elementName = "/";
            attributeName = "Version";
            Console.WriteLine("elemname='{0}' attributeName='{1}'", elementName, attributeName);
            s = Sat.GetXmlAttribute(fname, attributeName, elementName);
            if (s.Length == 0) {
                Console.WriteLine("ERROR={0}", General.LastError());
            }
            Debug.Assert(s.Length == 0, "Sat.GetXmlAttribute failed");

            Console.WriteLine("\nADVANCED XMLGETATTRIBUTE 'NOMATCH' FEATURES...");
            s = Sat.XmlNoMatch();
            Console.WriteLine("Sat.XmlNoMatch() returns '{0}'", s);
            Console.WriteLine("Set a new value to be returned...");
            Sat.SetXmlNoMatch("##No coinciden##");
            s = Sat.XmlNoMatch();
            Console.WriteLine("Sat.XmlNoMatch() returns '{0}'", s);
            fname = "cfdv33a-signed-tfd.xml";
            Console.WriteLine("FILE: {0}", fname);
            elementName = "/Comprobante/notthere";
            s = Sat.GetXmlAttribute(fname, "", elementName);
            Console.WriteLine("Sat.GetXmlAttribute({0}) returns '{1}'", elementName, s);
            elementName = "/Comprobante/Impuestos";
            s = Sat.GetXmlAttribute(fname, "", elementName);
            Console.WriteLine("Sat.GetXmlAttribute({0}) returns '{1}'", elementName, s);

            Console.WriteLine("\nUSE XPATH TO FIND ALL ATTRIBUTES NAMED 'IMPORTE'...");
            fname = "A7.xml";
            Console.WriteLine("FILE: {0}", fname);
            // Output all attributes named "Importe" in a <Traslado> or <Retencion> element.
            attributeName = "Importe";

            // First look at each <Concepto> in the <Conceptos> element.
            // (We can use either "/Comprobante/Conceptos" or "//Conceptos")
            xbase = "//Conceptos/Concepto";
            for (i = 1; true; i++) {
                // FOREACH //Conceptos/Concepto[i] element output the value of Importe
                xpath = xbase + "[" + i + "]";
                val = Sat.GetXmlAttribute(fname, attributeName, xpath);
                if (Sat.XmlNoMatch() == val) break;
                Console.WriteLine("{0}/@{1}='{2}'", xpath, attributeName, val);

                // FOREACH //Conceptos/Concepto[i]//Traslado[j] element output the value of Importe
                // Long xpath is /Comprobante/Conceptos/Concepto[i]/Impuestos/Traslados/Traslado[j]
                for (j = 1; true; j++) {
                    string xpath1 = xpath + "//Traslado[" + j + "]";
                    val = Sat.GetXmlAttribute(fname, attributeName, xpath1);
                    if (Sat.XmlNoMatch() == val) break;
                    Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val);
                }

                // FOREACH //Conceptos/Concepto[i]//Retencion[j] element output the value of Importe
                for (j = 1; true; j++) {
                    string xpath1 = xpath + "//Retencion[" + j + "]";
                    val = Sat.GetXmlAttribute(fname, attributeName, xpath1);
                    if (Sat.XmlNoMatch() == val) break;
                    Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val);
                }
            }
            // Now look in the /Comprobante/Impuestos element.
            // NB we cannot use "//Impuestos" here
            xpath = "/Comprobante/Impuestos";

            // FOREACH /Comprobante/Impuestos//Retencion[j] element output the value of Importe
            // Long xpath is /Comprobante/Impuestos/Retenciones/Retencion[j]
            for (j = 1; true; j++) {
                string xpath1 = xpath + "//Retencion[" + j + "]";
                val = Sat.GetXmlAttribute(fname, attributeName, xpath1);
                if (Sat.XmlNoMatch() == val) break;
                Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val);
            }

            // FOREACH /Comprobante/Impuestos//Traslado[j] element output the value of Importe
            for (j = 1; true; j++) {
                string xpath1 = xpath + "//Traslado[" + j + "]";
                val = Sat.GetXmlAttribute(fname, attributeName, xpath1);
                if (Sat.XmlNoMatch() == val) break;
                Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val);
            }

        }

        /// <summary>
        /// Tests passing xmlDoc parameter as a Unicode UTF-16 string. Improved in [v9.2].
        /// </summary>
        static void test_XMLasUnicodeString()
        {
            int n;
            string s;
            string fname;
            string newname, password;
            string xmlStr;
            string elemName, attrName;
            string digest1, digest2;


            Console.WriteLine("\nREAD IN A FILE TO A UNICODE UTF-16 STRING THEN PASS THE STRING AS XML DOC...");
            fname = "cfdv33a-nomina12B.xml";
            Console.WriteLine("FILE: {0}", fname);

            // Read in file to a Unicode text string
            xmlStr = File.ReadAllText(fname, Encoding.UTF8);

            Console.WriteLine("xmlStr is {0} characters", xmlStr.Length);

            Console.WriteLine("\nGET ATTRIBUTE VALUE USING UTF-16 STRING...");

            // Attribute name contains non-ASCII character 'ü', Antigüedad="P3Y2M23D"
            elemName = "nomina12:Receptor";
            attrName = "Antigüedad";
            s = Sat.GetXmlAttribute(xmlStr, attrName, elemName);
            Console.WriteLine("{0}/@{1}='{2}'", elemName, attrName, s);
            if (s.Length == 0) DisplayError();
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");

            // Attribute name contains non-ASCII character 'ñ', Año="2016"
            elemName = "nomina12:CompensacionSaldosAFavor";
            attrName = "Año";
            s = Sat.GetXmlAttribute(xmlStr, attrName, elemName);
            Console.WriteLine("{0}/@{1}='{2}'", elemName, attrName, s);
            if (s.Length == 0) DisplayError();
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");

            // Attribute value contains non-ASCII character 'í', Sindicalizado="Sí"
            elemName = "nomina12:Receptor";
            attrName = "Sindicalizado";
            s = Sat.GetXmlAttribute(xmlStr, attrName, elemName);
            Console.WriteLine("{0}/@{1}='{2}'", elemName, attrName, s);
            if (s.Length == 0) DisplayError();
            Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed");

            Console.WriteLine("\nCHECK XML IS OK USING UTF-16 STRING...");
            n = Sat.ValidateXml(xmlStr);
            Console.WriteLine("Sat.ValidateXml returns {0} (expecting 0)", n);
            if (n != 0) DisplayError(n);
            Debug.Assert(0 == n, "Sat.ValidateXml failed");

            Console.WriteLine("\nMAKE PIPE STRING USING UTF-16 STRING...");
            s = Sat.MakePipeStringFromXml(xmlStr);
            Console.WriteLine("PIPESTRING=\n{0}", s);
            if (s.Length == 0) DisplayError();
            Debug.Assert(s.Length > 0, "Sat.MakePipeStringFromXml failed");

            Console.WriteLine("\nFORM MESSAGE DIGEST OF PIPE STRING USING UTF-16 STRING...");
            s = Sat.MakeDigestFromXml(xmlStr);
            Console.WriteLine("DIGEST={0}", s);
            if (s.Length == 0) DisplayError();
            Debug.Assert(s.Length > 0, "Sat.MakeDigestFromXml failed");

            Console.WriteLine("\nINSERT CERTIFICATE INTO XML USING UTF-16 STRING AS INPUT...");
            newname = "cfdv33a-nomina12B-plus-cert.xml";
            n = Sat.InsertCert(newname, xmlStr, "emisor.cer");
            Console.WriteLine("Sat.InsertCert returns {0} (expecting 0)", n);
            if (n != 0) DisplayError(n);
            Debug.Assert(0 == n, "Sat.InsertCert failed");
            Console.WriteLine("Created new file '{0}' length = {1}", newname, FileLength(newname));
            // Query this new file for certificate details
            s = Sat.QueryCert(newname, Query.organizationName);
            Console.WriteLine("organizationName('{0}')='{1}'", newname, s);

            Console.WriteLine("\nSIGN AN XML FILE WORKING ENTIRELY IN MEMORY...:");

            Console.WriteLine("Read in private key and matching certificate to memory...");
            password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
            // Read in the private key
            string keyStr = Sat.GetKeyAsString("emisor.key", password, KeyOption.EncryptedPEM);
            Console.WriteLine("Key string is {0} characters", keyStr.Length);
            if (keyStr.Length == 0) DisplayError();
            // And the matching cert
            string cerStr = Sat.GetCertAsString("emisor.cer");
            Console.WriteLine("Cert string is {0} characters", cerStr.Length);
            if (cerStr.Length == 0) DisplayError();

            Console.WriteLine("Check that the key and certificate are matched...");
            n = Sat.CheckKeyAndCert(keyStr, password, cerStr);
            Console.WriteLine("Sat.CheckKeyAndCert() returns {0} (expecting 0)", n);
            if (n != 0) DisplayError(n);
            Debug.Assert(0 == n, "Sat.CheckKeyAndCert failed");


            // xmlStr already contains the unsigned XML doc, but we need XML data in bytes.
            // As a test, we will force encoding of *input* string to be Latin-1
            // but note the *output* will still be UTF-8.
            byte[] xmlArr = System.Text.Encoding.GetEncoding("iso-8859-1").GetBytes(xmlStr);
            Console.WriteLine("Unsigned xmlArr is {0} bytes", xmlArr.Length);

            // Sign XML document with all parameters passed in memory
            byte[] xmlArrSigned = Sat.SignXmlToBytes(xmlArr, keyStr, password, cerStr, 0);

            Console.WriteLine("Sat.SignXmlToBytes(xmlArr) returns {0} bytes", xmlArrSigned.Length);

            // Convert bytes back to Unicode string
            // NB xmlStr now contains the *signed* document

            xmlStr = System.Text.Encoding.UTF8.GetString(xmlArrSigned);
            Console.WriteLine("xmlStr-signed is {0} characters", xmlStr.Length);

            Console.WriteLine("\nCHECK SIGNED XML IS OK USING UTF-16 STRING...");
            n = Sat.ValidateXml(xmlStr);
            Console.WriteLine("Sat.ValidateXml(xmlStr-signed) returns {0} (expecting 0)", n);
            if (n != 0) DisplayError(n);
            Debug.Assert(0 == n, "Sat.ValidateXml failed");

            Console.WriteLine("\nVALIDATE THE SIGNATURE USING UTF-16 STRING...");
            n = Sat.VerifySignature(xmlStr);
            Console.WriteLine("Sat.VerifySignature(xmlStr-signed) returns {0} (expecting 0)", n);
            if (n != 0) DisplayError(n);
            Debug.Assert(0 == n, "Sat.VerifySignature failed");

            Console.WriteLine("\nGET CERTIFICATE ORGNAME USING UTF-16 STRING...");
            // This time we have a result...
            s = Sat.QueryCert(xmlStr, Query.organizationName);
            Console.WriteLine("organizationName={0}", s);
            if (s.Length == 0) DisplayError();
            Debug.Assert(s.Length > 0, "Sat.QueryCert failed");

            Console.WriteLine("\nMAKE SIGNATURE USING UTF-16 STRING...");
            s = Sat.MakeSignatureFromXml(xmlStr, keyStr, password);
            Console.WriteLine("Sello={0}", s);
            if (s.Length == 0) DisplayError();
            Debug.Assert(s.Length > 0, "Sat.MakeSignatureFromXml failed");

            Console.WriteLine("\nEXTRACT SIGNATURE DIGEST USING UTF-16 STRING...");
            s = Sat.ExtractDigestFromSignature(xmlStr);
            Console.WriteLine("digest1={0}", s);
            if (s.Length == 0) DisplayError();
            Debug.Assert(s.Length > 0, "Sat.ExtractDigestFromSignature failed");
            digest1 = s;

            Console.WriteLine("\nMAKE MESSAGE DIGEST OF PIPE STRING USING UTF-16 STRING...");
            s = Sat.MakeDigestFromXml(xmlStr);
            Console.WriteLine("digest2={0}", s);
            if (s.Length == 0) DisplayError();
            Debug.Assert(s.Length > 0, "Sat.MakeDigestFromXml failed");
            digest2 = s;

            // The last two should match (ignore case)
            n = string.Compare(digest1, digest2, true);
            Console.WriteLine("String.Compare(digest1, digest2, ignoreCase) returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Digest values do not match");




        }

        
        //*************************************
        // LOCAL UTILITIES...
        //*************************************

        static bool FileIsNotPresent(string filePath, string message)
        {
            FileInfo fi = new FileInfo(filePath);
            if (!fi.Exists)
            {
                Console.WriteLine("\n{0}: {1}", message, filePath);
                return true;
            }
            return false;
        }
        static long FileLength(string filePath)
        {
            FileInfo fi = new FileInfo(filePath);
            if (!fi.Exists)
            {
                return -1;
            }
            return fi.Length;
        }

        static bool FileHasBom(string filePath)
        {
            const int kNBYTES = 3;
            byte[] buf = new byte[kNBYTES];
            FileInfo finfo = new FileInfo(filePath);
            Debug.Assert(finfo.Exists, "File '" + filePath + "' does not exist.");
            if (finfo.Exists)
            {
                FileStream fsi = finfo.OpenRead();
                BinaryReader br = new BinaryReader(fsi);
                buf = br.ReadBytes(kNBYTES);
                br.Close();
                fsi.Close();
            }
            /* BOM consists of three bytes (0xEF, 0xBB, 0xBF) */
            return (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF);
        }

        /** Display error - overloaded */
        static void DisplayError()
        {
            Console.WriteLine("ERROR: {0}", General.LastError());
        }
        static void DisplayError(int errCode)
        {
            string s = General.LastError();
            Console.WriteLine("ERROR {0}: {1}: {2}", errCode, General.ErrorLookup(errCode), s);
        }
    }
}