/*  $Id: TestFirmaSat.cpp $
*   Last updated:
*   $Date: 2021-09-08 06:51:00 $
*   $Version: 10.0.0 $
*/

/******************************* LICENSE ***********************************
* Copyright (C) 2021 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>
****************************************************************************
*/
#include <iostream>
#include <fstream>
#include <clocale>
#include <string>
#include <cctype>
#include <assert.h>

#include "firmasat.hpp"

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>	// For ``SetConsoleOutputCP(CP_UTF8);``
#endif

using std::cout;
using std::endl;

/* Case-insensitive string comparison */
static bool iequals(std::string &str1, std::string &str2)
{
	return ((str1.size() == str2.size()) && std::equal(str1.begin(), str1.end(), str2.begin(), [](char & c1, char & c2){
		return (c1 == c2 || std::toupper(c1) == std::toupper(c2));
	}));
}

/* Return length of file in bytes or -1 if error */
static long long file_length(const std::string &fileName)
{
	std::ifstream file(fileName.c_str(), std::ifstream::in | std::ifstream::binary);
	if (!file.is_open()) {
		return -1;
	}
	file.seekg(0, std::ios::end);
	long long len = file.tellg();
	file.close();
	return len;
}

static std::string file_read_to_string(const std::string &fileName)
{
	std::ifstream in(fileName);
	std::string contents((std::istreambuf_iterator<char>(in)),
		std::istreambuf_iterator<char>());
	return contents;
}

int main()
{
	// Set up console and locale for UTF-8 characters
	std::setlocale(LC_ALL, "en_US.utf8");
#ifdef _WIN32
	// Set console display page to UTF-8
	SetConsoleOutputCP(CP_UTF8);
#endif
	cout << "__cplusplus=" << __cplusplus << endl;
	cout << "INTERROGATE THE CORE diFirmaSAT2 DLL:" << endl;
	cout << "Gen::Version=" << firmasat::Gen::Version() << endl;
	cout << "Gen::ModuleName=" << firmasat::Gen::ModuleName() << endl;
	cout << "Gen::Platform=" << firmasat::Gen::Platform() << endl;
	cout << "Gen::CompileTime=" << firmasat::Gen::CompileTime() << endl;
	cout << "Gen::LicenceType=" << firmasat::Gen::LicenceType() << endl;
	cout << "Gen::Comments=" << firmasat::Gen::Comments() << endl;

	std::string s, fname, newname, outfile, keyfile, password, certfile;
	std::string s1, newpassword, xmlstring, newxmlstring;
	std::string digest, digest1;
	std::string attr, attr1;
	std::string elementName, attributeName, query;
	std::string certfiledata, keyfiledata;
	int n;

	//*************
	// DO THE TESTS
	//*************
	try {	// Overall try to catch any errors at all.
		cout << endl << "FORM THE PIPESTRING FROM AN XML FILE:" << endl;
	fname = "cfdv33a-base.xml";
	s = firmasat::Sat::MakePipeStringFromXml(fname);
	cout << "Sat::MakePipeStringFromXml('" << fname << "')=" << s << endl;

	cout << endl << "SIGN AN XML FILE:" << endl;
	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 = firmasat::Sat::SignXml(newname, fname, keyfile, password, certfile);
	cout << "Sat::SignXml('" << fname << "'-->'" << newname << "') returns " << n << " (0=>success)" << endl;

	// Did we make a valid XML file?
	n = firmasat::Sat::VerifySignature(newname);
	cout << "Sat::VerifySignature('" << newname << "') returns " << n << " (0=>success)" << endl;

	cout << endl << "VERIFY A SIGNATURE IN AN XML FILE:" << endl;
	cout << "1. One we know is good:" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	s = firmasat::Sat::VerifySignature(fname);
	cout << "Sat::VerifySignature('" << fname << "') returns " << n << " (0=>success)" << endl;
	cout << "2. One we just made, so it should be good:" << endl;
	fname = newname;
	s = firmasat::Sat::VerifySignature(fname);
	cout << "Sat::VerifySignature('" << fname << "') returns " << n << " (0=>success)" << endl;

	cout << endl << "FORM THE DIGEST OF THE PIPESTRING IN AN XML FILE:" << endl;
	fname = "cfdv33a-base.xml";
	digest = firmasat::Sat::MakeDigestFromXml(fname);
	cout << "Sat::MakeDigestFromXml('" << fname << "')=" << digest << endl;

	cout << endl << "EXTRACT THE DIGEST FROM THE SIGNATURE IN AN XML FILE:" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	digest1 = firmasat::Sat::ExtractDigestFromSignature(fname);
	cout << "Sat::ExtractDigestFromSignature('" << fname << "')=" << digest1 << endl;

	/* check that the two digests match (caution: upper- vs lower-case) */
	assert(iequals(digest, digest1));

	cout << "\nEXPECTING ERRORS..." << endl;
	try {
		s = firmasat::Sat::MakeDigestFromXml("missing.file");
		cout << "MakeDigestFromXml=[" << s << "]" << endl;
	}
	catch (std::exception& e) {
		cout << e.what() << endl;
	}

	try {
		s = firmasat::Sat::MakeDigestFromXml("emisor.key");	// Not an XML file
		cout << "MakeDigestFromXml=[" << s << "]" << endl;
	}
	catch (std::exception& e) {
		cout << e.what() << endl;
	}

	// Test default Error Message after a successful method call...
	n = firmasat::Gen::Version();
	cout << "Default ErrorMessage=[" << firmasat::Err::FormatErrorMessage() << "]" << endl;

	cout << "...END ERRORS" << endl << endl;

	cout << endl << "VALIDATE THE STRUCTURE OF XML FILES:" << endl;
	cout << "1. A valid one:" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	n = firmasat::Sat::ValidateXml(fname);
	cout << "Sat::ValidateXml('" << fname << "') returns " << n << " (0=>success)" << endl;
	cout << "2. An invalid one (missing version):" << endl;
	fname = "cfdv33a-bad-nover.xml";
	n = firmasat::Sat::ValidateXml(fname);
	cout << "Sat::ValidateXml('" << fname << "') returns " << n << " (0=>success)" << endl;
	cout << "Err::LastError=[" << firmasat::Err::LastError() << "]" << endl;

	cout << "3. Not complying with SAT spec:" << endl;
	fname = "cfdv33a-badspec.xml";
	n = firmasat::Sat::ValidateXml(fname);
	cout << "Sat::ValidateXml('" << fname << "') returns " << n << " (0=>success)" << endl;
	cout << "Err::LastError=[" << firmasat::Err::LastError() << "]" << endl;

	cout << "4. Valid XML but invalid type:" << endl;
	fname = "V3_2_BadCurp.xml";
	n = firmasat::Sat::ValidateXml(fname);
	cout << "Sat::ValidateXml('" << fname << "') returns " << n << " (0=>success)" << endl;
	cout << "Err::LastError=[" << firmasat::Err::LastError() << "]" << endl;

	n = firmasat::Sat::ValidateXml(fname, firmasat::Sat::XmlOption::Loose);
	cout << "Sat::ValidateXml('" << fname << "', Loose) returns " << n << " (0=>success)" << endl;

	cout << endl << "EXTRACT AN ATTRIBUTE FROM AN XML FILE:" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	elementName = "Comprobante";
	attributeName = "Sello";	// NB Capital letter 'S'
	s = firmasat::Sat::GetXmlAttribute(fname, attributeName, elementName);
	cout << "Sat::GetXmlAttribute=" << s << endl;

	cout << "ATTRIBUTE MISSING..." << endl;
	attributeName = "missing";
	s = firmasat::Sat::GetXmlAttribute(fname, attributeName, elementName);
	cout << "Sat::GetXmlAttribute=" << s << endl;
	if (firmasat::Sat::XmlNoMatch() == s) {
		cout << "Matches XmlNoMatch()" << endl;
	}

	// XmlNoMatch tests...
	cout << "Sat::XmlNoMatch=" << firmasat::Sat::XmlNoMatch() << endl;
	if (firmasat::Sat::XmlNoMatch() == s) {
		cout << "  Match for firmasat::Sat::XmlNoMatch()" << endl;
	}
	firmasat::Sat::SetXmlNoMatch("##No coinciden##");
	cout << "Sat::XmlNoMatch=" << firmasat::Sat::XmlNoMatch() << endl;
	s = firmasat::Sat::GetXmlAttribute(fname, attributeName, elementName);
	cout << "Sat::GetXmlAttribute=" << s << endl;

	// Set as default
	firmasat::Sat::SetXmlNoMatch();
	cout << "Sat::XmlNoMatch(default)=" << firmasat::Sat::XmlNoMatch() << endl;

	cout << "EXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS:" << endl;
	fname = "cfdv33a-base.xml";
	elementName = "cfdi:Emisor";
	attributeName = "Nombre";
	s = firmasat::Sat::GetXmlAttribute(fname, attributeName, elementName);
	cout << "Sat::GetXmlAttribute=" << s << endl;

	cout << "EXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS IN ITS NAME:" << endl;
	fname = "cfdv33a-nomina12.xml";
	elementName = "nomina12:CompensacionSaldosAFavor";
	attributeName = "Año";	// Hardcoded enye: 'A' + U+00F1 LATIN SMALL LETTER N WITH TILDE + 'o'  
	s = firmasat::Sat::GetXmlAttribute(fname, attributeName, elementName);
	cout << "Sat::GetXmlAttribute=" << s << endl;

	cout << "EXTRACT AN ATTRIBUTE VALUE WHICH IS EMPTY:" << endl;
	s = firmasat::Sat::GetXmlAttribute("<a e=''></a>", "e", "a");
	cout << "Sat::GetXmlAttribute='" << s << "'" << endl;

	cout << "\nATTEMPT TO EXTRACT AN ATTRIBUTE VALUE FROM INVALID DATA..." << endl;
	try {
		s = firmasat::Sat::GetXmlAttribute("missing.file", "e", "a");
		cout << "GetXmlAttribute=[" << s << "]" << endl;
	}
	catch (std::exception& e) {
		cout << e.what() << endl;
	}
	try {
		s = firmasat::Sat::GetXmlAttribute("<a badxml>", "e", "a");
		cout << "GetXmlAttribute=[" << s << "]" << endl;
	}
	catch (std::exception& e) {
		cout << e.what() << endl;
	}

	cout << "\nQUERY X.509 CERTIFICATE:" << endl;
	cout << "1. From embedded `certificado` in XML" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	cout << "FILE: " << fname << endl;
	query = "serialNumber";
	s = firmasat::Sat::QueryCert(fname, query);
	cout << "Sat::QueryCert(" << query << ")=[" << s << "]" << endl;
	query = "rfc";
	s = firmasat::Sat::QueryCert(fname, query);
	cout << "Sat::QueryCert(" << query << ")=[" << s << "]" << endl;
	query = "organizationName";
	s = firmasat::Sat::QueryCert(fname, query);
	cout << "Sat::QueryCert(" << query << ")=[" << s << "]" << endl;

	cout << "2. From X.509 file" << endl;
	fname = "pac.cer";
	cout << "FILE: " << fname << endl;
	query = "serialNumber";
	s = firmasat::Sat::QueryCert(fname, query);
	cout << "Sat::QueryCert(" << query << ")=[" << s << "]" << endl;
	query = "rfc";
	s = firmasat::Sat::QueryCert(fname, query);
	cout << "Sat::QueryCert(" << query << ")=[" << s << "]" << endl;
	query = "organizationName";
	s = firmasat::Sat::QueryCert(fname, query);
	cout << "Sat::QueryCert(" << query << ")=[" << s << "]" << endl;

	cout << "\nGET CERTIFICATE AS A BASE64 STRING:" << endl;
	fname = "emisor.cer";
	cout << "FILE: " << fname << endl;
	s = firmasat::Sat::GetCertAsString(fname);
	cout << "Sat::GetCertAsString:" << endl << s << endl;

	// Compare against string from XML file
	fname = "cfdv33a-signed-tfd.xml";
	cout << "FILE: " << fname << endl;
	s1 = firmasat::Sat::GetCertAsString(fname);
	cout << "Sat::GetCertAsString:" << endl << s1 << endl;

	// Make sure they match
	assert(s == s1);

	cout << "\nMAKE A SIGNATURE FROM A BASE XML FILE:" << endl;
	fname = "cfdv33a-base.xml";
	keyfile = "emisor.key";
	password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	s = firmasat::Sat::MakeSignatureFromXml(fname, keyfile, password);
	cout << "Sat::MakeSignatureFromXml:" << endl << s << endl;

	cout << "\nCHECK PRIVATE KEY MATCHES PUBLIC KEY IN CERTIFICATE:" << endl;
	certfile = "emisor.cer";
	keyfile = "emisor.key";
	password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	n = firmasat::Sat::CheckKeyAndCert(keyfile, password, certfile);
	cout << "Sat::CheckKeyAndCert(" << keyfile << ", " << certfile << ") returns " << n << " (expected 0)" << endl;
	assert(0 == n);
	/* Get embedded certificate from XML doc */
	certfile = "cfdv33a-signed-tfd.xml";
	keyfile = "emisor.key";
	n = firmasat::Sat::CheckKeyAndCert(keyfile, password, certfile);
	cout << "Sat::CheckKeyAndCert(" << keyfile << ", " << certfile << ") returns " << n << " (expected 0)" << endl;
	assert(0 == n);

	cout << "Expecting errors..." << endl;
	// Now do it wrong: note no exceptions, just a nonzero return value.
	certfile = "pac.cer";
	n = firmasat::Sat::CheckKeyAndCert(keyfile, password, certfile);
	cout << "Sat::CheckKeyAndCert(" << keyfile << ", " << certfile << ") returns " << n << " (expected nonzero)" << endl;
	assert(0 != n);
	cout << "  " << firmasat::Err::FormatErrorMessage(n) << endl;
	certfile = "emisor.cer";
	keyfile = "emisor.key";
	password = "badpassword";
	n = firmasat::Sat::CheckKeyAndCert(keyfile, password, certfile);
	cout << "Sat::CheckKeyAndCert(" << keyfile << ", " << certfile << ", wrong-password) returns " << n << " (expected nonzero)" << endl;
	assert(0 != n);
	cout << "  " << firmasat::Err::FormatErrorMessage(n) << endl;

	cout << "\nGET RECEIPT (COMPROBANTE) VERSION NUMBER FROM XML FILE:" << endl;
	fname = "cfdv33a-base.xml";
	n = firmasat::Sat::XmlReceiptVersion(fname);
	cout << "Sat::XmlReceiptVersion('" << fname << "')=" << n << endl;
	assert(33 == n);

	// One-liners used in documentation
	cout << firmasat::Sat::XmlReceiptVersion("cfdv33a-base.xml") << endl;  // 33
	cout << firmasat::Sat::XmlReceiptVersion("ConVolE12345-base.xml") << endl;  // 4011
	
	cout << "\nCREATE CADENA ORIGINAL DEL TIMBRE FISCAL DIGITAL (PIPESTRING FOR TFD):" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	s = firmasat::Tfd::MakePipeStringFromXml(fname);
	cout << "Tfd::MakePipeStringFromXml('" << fname << "')=" << endl << s << endl;

	cout << "\nFORM DIGEST OF PIPESTRING FOR TFD:" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	digest = firmasat::Tfd::MakeDigestFromXml(fname);
	cout << "Tfd::MakeDigestFromXml('" << fname << "')=" << endl << digest << endl;

	cout << "\nEXTRACT DIGEST FROM TFD SELLOSAT:" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	certfile = "pac.cer";
	// NB certificate is required for TFD option
	digest1 = firmasat::Tfd::ExtractDigestFromSignature(fname, certfile);
	cout << "Tfd::ExtractDigestFromSignature('" << fname << ", " << certfile << "')=" << endl << digest1 << endl;

	assert(iequals(digest, digest1));

	cout << "\nPRETEND WE ARE A PAC WITH A KEY ALLOWED TO SIGN THE TFD:" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	keyfile = "pac.key";
	password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	s = firmasat::Tfd::MakeSignatureFromXml(fname, keyfile, password);
	cout << "Tfd::MakeSignatureFromXml('" << fname << ", " << keyfile << "')=" << endl << s << endl;

	cout << "\nVERIFY SIGNATURE IN TFD SELLOSAT:" << endl;
	fname = "cfdv33a-signed-tfd.xml";
	certfile = "pac.cer";
	n = firmasat::Tfd::VerifySignature(fname, certfile);
	cout << "Tfd::VerifySignature('" << fname << ", " << certfile << "')=" << n << " (expected 0)" << endl;
	assert(n == 0);

	cout << "\nADD A TFD ELEMENT TO A SIGNED CFDI DOCUMENT USING PAC KEY:" << endl;
	fname = "cfdv33a-signed.xml";
	newname = "cfdv33a_new-tfd.xml";
	certfile = "pac.cer";
	keyfile = "pac.key";
	password = "12345678a";
	n = firmasat::Tfd::AddSignedTfd(newname, fname, keyfile, password, certfile);
	cout << "Tfd::AddSignedTfd('" << fname << "'-->'" << newname << "') returns " << n << " (expected 0)" << endl;
	assert(n == 0);

	// Did we make a valid XML file?
	n = firmasat::Sat::ValidateXml(newname);
	cout << "Sat::ValidateXml('" << newname << "') returns " << n << endl;
	assert(n == 0);
	// Does it have a valid selloSAT?
	n = firmasat::Tfd::VerifySignature(newname, certfile);
	cout << "Tfd::VerifySignature('" << newname << ", " << certfile << "')=" << n << " (expected 0)" << endl;
	assert(n == 0);

	cout << "\nADD UTF-8 BOM TO EXISTING FILE:" << endl;
	fname = "cfdv33a-signed-nobom.xml";
	newname = "cfdv33a_new-signed-with-BOM.xml";
	n = firmasat::Sat::FixBOM(newname, fname);
	cout << "Sat::FixBOM(" << fname << "->" << newname << ")=" << n << " (expected 0)" << endl;
	assert(n == 0);

	cout << "\nEXTRACT ATTRIBUTES FROM CONSECUTIVE ELEMENTS:" << endl;
	fname = "ejemplo_v32-tfd2015.xml";
	attributeName = "descripcion";
	elementName = "cfdi:Concepto";
	cout << "For file '" << fname << "'" << endl;
	// Loop until NoMatch
	for (int i = 1; i <= 100; i++) {
		std::string eName = elementName + "[" + std::to_string(i) + "]";
		s = firmasat::Sat::GetXmlAttribute(fname, attributeName, eName);
		cout << "Sat::GetXmlAttribute(" << attributeName<< "," << eName << ")=" << s << endl;
		if (s == firmasat::Sat::XmlNoMatch()) {
			break;
		}
	}

	cout << "\nVALIDATE XML WITH STRICT AND LOOSE OPTIONS:" << endl;
	cout << "Default strict behaviour (badly formed CURP attribute):" << endl;
	fname = "V3_2_BadCurp.xml";
	n = firmasat::Sat::ValidateXml(fname);
	cout << "Sat::ValidateXml('" << fname << "') returns " << n << " (expected -ve error)" << endl;
	cout << firmasat::Err::FormatErrorMessage(n) << endl;
	assert(n != 0);

	cout << "Using XmlOption.Loose:" << endl;
	n = firmasat::Sat::ValidateXml(fname, firmasat::Sat::XmlOption::Loose);
	cout << "Sat::ValidateXml('" << fname << "', XmlOption.Loose) returns " << n << " (expected 0)" << endl;
	assert(n == 0);

	cout << "\nGET PRIVATE KEY AS BASE64:" << endl;
	fname = "emisor.key";
	password = "12345678a";
	s = firmasat::Sat::GetKeyAsString(fname, password);
	cout << "Sat::GetKeyAsString('" << fname<< "')=" << endl << s << endl;
	s = firmasat::Sat::GetKeyAsString(fname, password, firmasat::Sat::KeyOption::EncryptedPEM);
	cout << "Sat::GetKeyAsString('" << fname << "', EncryptedPEM)=" << endl << s << endl;

	cout << "\nWRITE PFX FROM PRIVATE KEY AND CERT:" << endl;
	newname = "archivo_new-pfx.txt";
	certfile = "emisor.cer";
	keyfile = "emisor.key";
	password = "12345678a";
	newpassword = "clavedesalida";
	n = firmasat::Sat::WritePfxFile(newname, newpassword, keyfile, password, certfile);
	cout << "Sat::WritePfxFile() returns " << n << " (expected 0)" << endl;
	assert(n == 0);
	cout << "  Created new file '" << newname << "' of length " << file_length(newname) << " bytes" << endl;

	cout << "\nSAVE KEYFILE WITH NEW PASSWORD..." << endl;
	keyfile = "emisor.key";
	password = "12345678a";
	newname = "emisor_new.key";
	newpassword = "password123";
	n = firmasat::Sat::NewKeyFile(newname, newpassword, keyfile, password);
	cout << "Sat::NewKeyFile() returns " << n << " (expected 0)" << endl;
	assert(0 == n);
	cout << "  Created new file '" << newname << "' of length " << file_length(newname) << " bytes" << endl;
	// And again but saving as a PEM file instead of binary
	keyfile = "emisor.key";
	password = "12345678a";
	newname = "emisor_new.pem";
	newpassword = "password123";
	n = firmasat::Sat::NewKeyFile(newname, newpassword, keyfile, password, firmasat::Sat::KeyFormat::PEM);
	cout << "Sat::NewKeyFile() returns " << n << " (expected 0)" << endl;
	assert(0 == n);
	cout << "  Created new file '" << newname << "' of length " << file_length(newname) << " bytes" << endl;

	cout << "\nASCIIFY AN XML DOC..." << endl;
	fname = "cfdv33a-base.xml";
	cout << "FILE: " << fname << endl;
	s = firmasat::Sat::Asciify(fname);
	cout << s << endl;
	// XML with non-ascii chars in the tag or element _name_ (these cannot be asciified)
	fname = "cfdv33a-nomina12B.xml";
	cout << "FILE: " << fname << endl;
	s = firmasat::Sat::Asciify(fname);
	cout << s << endl;

	cout << "\nCHECK MESSAGE DIGEST IS THE SAME FOR THE ORIGINAL FILE AND THE ASCIIFIED STRING..." << endl;
	digest = firmasat::Sat::MakeDigestFromXml(fname);
	cout << "Digest(file)  =" << digest << endl;
	digest1 = firmasat::Sat::MakeDigestFromXml(s);
	cout << "Digest(string)=" << digest << endl;
	assert(digest == digest1);

	cout << "\nGENERATE 3 UUIDs:" << endl;
	for (int i = 0; i < 3; i++) {
		s = firmasat::Sat::Uuid();
		cout << s << endl;
	}

	cout << "\nINSERT CERTIFICATE DETAILS INTO XML..." << endl;
	fname = "cfdv33a-base-nocertnum.xml";
	newname = "cfdv33a-base_new-pluscert.xml";
	certfile = "emisor.cer";
	n = firmasat::Sat::InsertCert(newname, fname, certfile);
	cout << "Sat::InsertCert() returns " << n << " (expecting 0)" << endl;
	assert(0 == n);
	// Check noCertificado attribute just inserted
	// Original should be empty
	attr = firmasat::Sat::GetXmlAttribute(fname, "NoCertificado", "cfdi:Comprobante");
	cout << "Old NoCertificado=[" << attr << "]" << endl;
	attr1 = firmasat::Sat::GetXmlAttribute(newname, "NoCertificado", "cfdi:Comprobante");
	cout << "New NoCertificado=[" << attr1 << "]" << endl;

	cout << "\nINSERT CERTIFICATE DETAILS INTO XML AS STRING..." << endl;
	s = firmasat::Sat::InsertCertToString(fname, certfile);
	cout << "Sat::InsertCertToString() returns a string of length " << s.length() << endl;
	assert(s.length() > 0);
	attr1 = firmasat::Sat::GetXmlAttribute(s, "NoCertificado", "cfdi:Comprobante");
	cout << "New NoCertificado=[" << attr1 << "]" << endl;

	cout << "\nSIGN XML TO STRING:" << endl;
	fname = "cfdv33a-base.xml";
	// Read an XML file into a string
	xmlstring = file_read_to_string(fname);
	cout << "String from file '" << fname << "' has " << xmlstring.length() << " bytes" << endl;
	/*
	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.
	*/
	certfiledata =
		"-----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-----";
	keyfiledata =
		"-----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 and certificate are matched
	n = firmasat::Sat::CheckKeyAndCert(keyfiledata, password, certfiledata);
	cout << "Sat::CheckKeyAndCert(STRINGS) returns " << n << " (expecting 0)" << endl;

	// Create a new string containing signed XML (UTF-8 encoded), all input passed in memory
	newxmlstring = firmasat::Sat::SignXmlToString(xmlstring, keyfiledata, password, certfiledata);
	cout << "Signed XML string has " << newxmlstring.length() << " bytes" << endl;

	cout << "\nPASS XML STRING TO OTHER SAT FUNCTIONS:" << endl;
	n = firmasat::Sat::ValidateXml(newxmlstring);
	cout << "Sat::ValidateXml(STRING) returns " << n << " (expecting 0)" << endl;
	assert(0 == n);
	n = firmasat::Sat::VerifySignature(newxmlstring);
	cout << "Sat::VerifySignature(STRING) returns " << n << " (expecting 0)" << endl;
	assert(0 == n);
	n = firmasat::Sat::XmlReceiptVersion(newxmlstring);
	cout << "Sat::XmlReceiptVersion(STRING) returns " << n << " (expecting 33)" << endl;
	assert(n > 0);



	// ******************************************
	// FINALLY, DISPLAY QUICK INFO ABOUT THE CORE DLL
	cout << "\nFINALLY, DETAILS OF CORE NATIVE DLL..." << endl;
	cout << "DLL Version=" << firmasat::Gen::Version() << " [" << firmasat::Gen::Platform() << "] Lic={" 
		<< firmasat::Gen::LicenceType() << "} Compiled=[" << firmasat::Gen::CompileTime() << "] " << endl;
	cout << "[" << firmasat::Gen::ModuleName() << "]" << endl;

	cout << endl << "ALL DONE." << endl;
	}
	catch (std::exception& e) {
		cout << e.what() << endl;
		cout << "TESTS FAILED TO COMPLETE!!" << endl;
	}
}