/* $Id: TestFirmaSat.c $ */

// Some tests using the FirmaSAT C/C++ interface.
// This uses plain-old ANSI C.
// Requires certain files to exist in the current working directory.

/******************************* 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>
*   Last updated:
*   $Date: 2020-08-05 23:43 $
*   $Version: 9.2.0 $
****************************************************************************
*/

/* Turn off MSCV warnings about fopen and sprintf */
#if defined(_MSC_VER)
#define _CRT_SECURE_NO_DEPRECATE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "diFirmaSat2.h"
#ifdef NDEBUG
	/* Make sure assertion testing is turned on */
	#undef NDEBUG
#endif
#include <assert.h>

/* Cope with case-insensitive string comparisons */
#if defined(_MSC_VER)
	#include <string.h>
	#define stricmp  _stricmp
	#define strnicmp _strnicmp
#elif defined(unix) || defined (linux) || defined(__linux) || defined(__APPLE__)
	#include <strings.h>
	#define stricmp  strcasecmp
	#define strnicmp strncasecmp
#endif

/* Link to the DLL library - MSVC ONLY */
#ifdef _MSC_VER
#if _WIN64
	#if _DEBUG
	#define MYLIBDIR "../x64/Debug/"
	#else
	#define MYLIBDIR "../x64/Release/"
	#endif
#else
	#if _DEBUG
	#define MYLIBDIR "../Debug/"
	#else
	#define MYLIBDIR "../Release/"
	#endif
#endif
#pragma comment(lib, MYLIBDIR "diFirmaSat2.lib")
#endif

/* prototype */
void display_all_errors(void);

long MIN_VERSION = 90200;

/********************/
/* HELPER UTILITIES */
/********************/
static void disp_error(long nErrCode)
{
	char msg[1024];
	long nchars;

	printf("Error code %ld", nErrCode);

	nchars = SAT_ErrorLookup(msg, sizeof(msg)-1, nErrCode);
	if (nchars > 0)
		printf(": %s", msg);

	nchars = SAT_LastError(msg, sizeof(msg)-1);
	if (nchars > 0)
		printf(" : %s", msg);
	putchar('\n');
}

int file_exists(const char *fname)
{
	FILE *fp;

	fp = fopen(fname, "rb");
	if (fp == NULL)
		return 0;

	fclose(fp);
	return 1;
}

static long file_length(const char *fname)
/* Returns the length of the file in bytes or -1 on error */
{
	long flen = -1;
	FILE *fp;
	fp = fopen(fname, "rb");
	if (fp)
	{
		int x = fseek(fp, 0, SEEK_END);
		if (0 == x)
			flen = ftell(fp);
		else
			flen = -1;
		fclose(fp);
	}
	return flen;
}

/** Returns 1 if file with name `fname` has a UTF-8 BOM, or 0 if not, or -1 if error */
static int file_has_bom(const char *fname)
{
	FILE *fp;
	int has_bom = 0;
	unsigned char buf[3];

	fp = fopen(fname, "rb");
	if (!fp) return -1;
	// Read first 3 bytes
	if (fread(buf, 1, 3, fp) == 3)
	{
		if (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF)
			has_bom = 1;
	}

	fclose(fp);
	return has_bom;
}

/** Reads a binary file into a null-terminated string. 
 * @returns Pointer to allocated buffer or NULL if error
 * @remark User must free allocated memory
 */
static char *file_read_to_string(const char *fname)
{
	FILE *fp;
	long flen;
	char *buf;
	flen = file_length(fname);
	if (flen <= 0) return NULL;

	fp = fopen(fname, "rb");
	if (!fp) return NULL;
	buf = malloc(flen+1);
	fread(buf, 1, flen, fp);
	buf[flen] = '\0';

	return buf;
}

/** Print the first `head_len` bytes and last `tail_len` bytes of a string */
static void pr_head_tail(const char *s, size_t head_len, size_t tail_len)
{
	const char *cp;
	size_t i;
	cp = s;
	for (i = 0; i < head_len; i++)
		putchar(*cp++);
	printf("\n...\n");
	cp = &s[strlen(s) - tail_len];
	while (*cp)
		putchar(*cp++);
	printf("\n");
}
	
/** Print the first `head_len` bytes and last `tail_len` bytes of a text file */
static void pr_head_tail_file(const char *fname, size_t head_len, size_t tail_len)
{
	char *buf;
	buf = file_read_to_string(fname);
	pr_head_tail(buf, head_len, tail_len);
	free(buf);
}

static int required_files_exist(void)
// Check for required files in current working directory
{
	const char *arrFiles[] = {
		"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",
		NULL
	};
	const char *fname;
	int i;
	for (i = 0, fname = arrFiles[i]; fname; fname = arrFiles[++i])
	{
		if (!file_exists(fname))
		{
			printf("**ERROR: File %s is missing.\n", fname);
			return 0;
		}
	}
	return 1;
}

/**********************/
/* DO THE BUSINESS... */
/**********************/
int main(void)
{
	long ret, nchars, n;
	char ch;
	char *buf = "", *buf1 = "";
	char *fname;
	char *newname, *keyfile, *password, *certfile;
	char *attributeName, *elementName;
	char digest[SAT_MAX_HASH_CHARS+1];	/* Digest hex length plus one. */
	char digest1[SAT_MAX_HASH_CHARS+1];
	char numstr[21];
	char cdate[64];
	int i, j;
	char eName[64];
	char qrybuf[256];
	char *query, *newpassword;
	int has_bom;
	char *keyfiledata, *certfiledata;
	char *xmlstring, *newxmlstring;
	char uuid[37];
	char attrbuf[128];
	char *xbase;
	char xpath[256], xpath1[256];

	/* Check if all required files exist in the CWD */
	if (!required_files_exist()) {
		printf("Required file cannot be found in current working directory.\n");
		exit(1);
	}

	printf("INTERROGATE THE CORE diFirmaSAT2 DLL:\n");
	ret = SAT_Version();
	printf("Version=%ld\n", ret);
	if (ret < MIN_VERSION) {
		printf("ERROR: STOP. Require FirmaSAT version %ld or higher.\n", MIN_VERSION);
		exit(1);
	}

	ch = (char)SAT_LicenceType();
	printf("LicenceType=%c\n", ch);

	nchars = SAT_ModuleName(NULL, 0, 0);
	if (nchars > 0) {
		buf = malloc(nchars+1);
		nchars = SAT_ModuleName(buf, nchars, 0);
		printf("Module=%s\n", buf);
		free(buf);
	}
	assert(nchars > 0);
	nchars = SAT_ModuleName(NULL, 0, SAT_GEN_PLATFORM);
	if (nchars > 0) {
		buf = malloc(nchars+1);
		nchars = SAT_ModuleName(buf, nchars, 64);
		printf("Platform=%s\n", buf);
		free(buf);
	}
	assert(nchars > 0);

	nchars = SAT_CompileTime(NULL, 0);
	if (nchars > 0) {
		buf = malloc(nchars+1);
		nchars = SAT_CompileTime(buf, nchars);
		printf("Compiled=%s\n", buf);
		free(buf);
	}
	assert(nchars > 0);

	printf("\nFORM THE PIPESTRING FROM AN XML FILE: \n");
	fname = "cfdv33a-base.xml";
	nchars = SAT_MakePipeStringFromXml(NULL, 0, fname, SAT_ENCODE_UTF8);
	printf("SAT_MakePipeStringFromXml(UTF8) returns %ld\n", nchars);
	if (nchars >= 0) {
		buf = malloc(nchars+1);
		nchars = SAT_MakePipeStringFromXml(buf, nchars, fname, SAT_ENCODE_UTF8);
		printf("%s\n", buf);
		printf("Actual length=%ld\n", nchars);
		free(buf);
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);

	printf("\nSIGN AN XML FILE: \n");
	fname = "cfdv33a-base.xml";
	newname = "cfdv33a_new-signed.xml";
	keyfile = "emisor.key";
	password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	certfile = "emisor.cer";
	ret = SAT_SignXml(newname, fname, keyfile, password, certfile, 0);
	printf("Sat.SignXml('%s'-->'%s') returns %ld\n", fname, newname, ret);
	assert (ret == 0);
	// Did we make a valid XML file?
	ret = SAT_ValidateXml(newname, 0);
	printf("SAT_ValidateXml(%s) returns %ld (0=>success)\n", newname, ret);
	assert (ret == 0);

	printf("\nVERIFY A SIGNATURE IN AN XML FILE: \n");
	printf("1. One we know is good:\n");
	fname = "cfdv33a-signed-tfd.xml";
	ret = SAT_VerifySignature(fname, NULL, 0);
	printf("Sat.VerifySignature('%s') returns %ld\n", fname, ret);
	assert (ret == 0);

	printf("2. One we just made, so it should be good:\n");
	fname = newname;
	ret = SAT_VerifySignature(fname, NULL, 0);
	printf("Sat.VerifySignature('%s') returns %ld\n", fname, ret);
	assert (ret == 0);

	printf("\nFORM THE DIGEST OF THE PIPESTRING IN AN XML FILE: \n");
	fname = "cfdv33a-base.xml";
	ret = SAT_MakeDigestFromXml(digest, sizeof(digest)-1, fname, 0);
	printf("SAT_MakeDigestFromXml returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("Digest=%s\n", digest);
	assert (ret > 0);

	printf("\nEXTRACT THE DIGEST FROM THE SIGNATURE IN AN XML FILE: \n");
	fname = "cfdv33a-signed-tfd.xml";
	ret = SAT_ExtractDigestFromSignature(digest1, sizeof(digest1)-1, fname, NULL, 0);
	printf("SAT_ExtractDigestFromSignature returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("Digest=%s\n", digest1);
	assert (ret > 0);
	/* check that the two digests match (caution: upper- vs lower-case) */
	assert (stricmp(digest, digest1) == 0);

	printf("\nVALIDATE THE STRUCTURE OF XML FILES:\n");
	printf("1. A valid one:\n");
	fname = "cfdv33a-signed-tfd.xml";
	ret = SAT_ValidateXml(fname, 0);
	printf("SAT_ValidateXml(%s) returns %ld (0=>success)\n", fname, ret);
	assert (ret == 0);

	printf("2. An invalid one (missing version):\n");
	fname = "cfdv33a-bad-nover.xml";
	ret = SAT_ValidateXml(fname, 0);
	printf("SAT_ValidateXml(%s) returns %ld (expected error)\n", fname, ret);
	assert (ret < 0);
	disp_error(ret);

	printf("\nEXTRACT AN ATTRIBUTE FROM AN XML FILE: \n");
	fname = "cfdv33a-signed-tfd.xml";
	elementName = "Comprobante";
	attributeName = "Sello";	// NB Capital letter 'S'
	nchars = SAT_GetXmlAttribute(NULL, 0, fname, attributeName, elementName);
	if (nchars >= 0) {
		buf = malloc(nchars+1);
		nchars = SAT_GetXmlAttribute(buf, nchars, fname, attributeName, elementName);
		printf("SAT_GetXmlAttribute('%s','%s','%s')=\n%s\n", fname, attributeName, elementName, buf);
		free(buf);
	}
	else {
		disp_error(nchars);
	}
	assert(nchars >= 0);

	printf("\nGET DETAILS OF X.509 CERTIFICATE:\n");
	printf("1. From embedded `certificado` in XML\n");
	fname = "cfdv33a-signed-tfd.xml.xml";
	printf("Input file: %s\n", fname);
	ret = SAT_GetCertNumber(numstr, sizeof(numstr)-1, fname, 0);
	printf("SAT_GetCertNumber returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("serialNumber=%s\n", numstr);
	ret = SAT_GetCertExpiry(numstr, sizeof(numstr)-1, fname, 0);
	printf("SAT_GetCertExpiry returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("notAfter=%s\n", numstr);

	printf("2. From X.509 certificate file\n");
	fname = "emisor.cer";
	printf("Input file: %s\n", fname);
	ret = SAT_GetCertNumber(numstr, sizeof(numstr)-1, fname, 0);
	printf("SAT_GetCertNumber returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("serialNumber=%s\n", numstr);
	ret = SAT_GetCertExpiry(numstr, sizeof(numstr)-1, fname, 0);
	printf("SAT_GetCertExpiry returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("notAfter=%s\n", numstr);

	printf("\nGET CERTIFICATE AS A BASE64 STRING:\n");
	fname = "emisor.cer";
	printf("For file %s...\n", fname);
	nchars = SAT_GetCertAsString(NULL, 0, fname, 0);
	printf("SAT_GetCertAsString(%s) returns %ld\n", fname, nchars);
	if (nchars < 0) disp_error(ret);
	assert(nchars > 0);
	if (nchars >= 0) {
		buf = malloc(nchars+1);
		nchars = SAT_GetCertAsString(buf, nchars, fname, 0);
		printf("%s\n", buf);
		printf("Actual length=%ld\n", nchars);
		//free(buf); // Keep string for later...
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);

	// Compare against string from XML file
	fname = "cfdv33a-signed-tfd.xml";
	printf("For file %s...\n", fname);
	nchars = SAT_GetCertAsString(NULL, 0, fname, 0);
	if (nchars < 0) disp_error(ret);
	assert(nchars > 0);
	if (nchars >= 0) {
		buf1 = malloc(nchars+1);
		nchars = SAT_GetCertAsString(buf1, nchars, fname, 0);
		printf("Length of cert string=%ld\n", nchars);
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);
	// Compare strings
	if (strcmp(buf, buf1) == 0)
		printf("OK, cert strings are equal.\n");
	else
		printf("ERROR: cert strings do not match!\n");
	assert(strcmp(buf, buf1) == 0);
	free(buf);
	free(buf1);

	printf("\nMAKE A SIGNATURE FROM A BASE XML FILE: \n");
	fname = "cfdv33a-base.xml";
	keyfile = "emisor.key";
	password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	nchars = SAT_MakeSignatureFromXml(NULL, 0, fname, keyfile, password);
	if (nchars >= 0) {
		buf = malloc(nchars+1);
		nchars = SAT_MakeSignatureFromXml(buf, nchars, fname, keyfile, password);
		printf("SAT_MakeSignatureFromXml('%s','%s')=\n%s\n", fname, keyfile, buf);
		free(buf);
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);

	printf("\nSIGN A DETALLISTA XML FILE: \n");
	fname = "cfdv33a-detallista.xml";
	newname = "detallista_new-signed.xml";
	keyfile = "emisor.key";
	password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	certfile = "emisor.cer";
	ret = SAT_SignXml(newname, fname, keyfile, password, certfile, 0);
	printf("SAT_SignXml(%s) returns %ld (0=>success)\n", fname, ret);
	if (ret != 0) disp_error(ret);
	assert(0 == ret);

	// Did we make a valid XML file?
	fname = newname;
	ret = SAT_ValidateXml(fname, 0);
	printf("SAT_ValidateXml(%s) returns %ld (0=>success)\n", fname, ret);
	if (ret != 0) disp_error(ret);
	assert(0 == ret);
	// Is the signature valid?
	ret = SAT_VerifySignature(fname, NULL, 0);
	printf("SAT_VerifySignature(%s) returns %ld (0=>success)\n", fname, ret);
	if (ret != 0) disp_error(ret);
	assert(0 == ret);

	printf("\nEXTRACT AN ATTRIBUTE FROM A DETALLISTA XML FILE: \n");
	fname = "cfdv33a-detallista.xml";
	elementName = "detallista:detallista";
	attributeName = "documentStructureVersion";
	nchars = SAT_GetXmlAttribute(NULL, 0, fname, attributeName, elementName);
	if (nchars >= 0) {
		buf = malloc(nchars+1);
		nchars = SAT_GetXmlAttribute(buf, nchars, fname, attributeName, elementName);
		printf("SAT_GetXmlAttribute('%s','%s','%s')='%s'\n", fname, attributeName, elementName, buf);
		//free(buf); // Save string for later
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);
	// Check against the correct answer
	assert(strcmp(buf, "AMC8.1") == 0);
	free(buf);

	printf("\nEXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS: \n");
	fname = "cfdv33a-base.xml";
	elementName = "cfdi:Emisor";
	attributeName = "Nombre";
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	if (nchars >= 0)
		printf("SAT_GetXmlAttribute('%s','%s','%s')='%s'\n", fname, attributeName, elementName, attrbuf);
	else
		disp_error(nchars);

	printf("\nEXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS IN ITS NAME: \n");
	printf("(May look 'funny' in Windows console)\n");
	fname = "cfdv33a-nomina12.xml";
	elementName = "nomina12:CompensacionSaldosAFavor";
	attributeName = "Año";
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	if (nchars >= 0)
		printf("SAT_GetXmlAttribute('%s','%s','%s')='%s'\n", fname, attributeName, elementName, attrbuf);
	else
		disp_error(nchars);

	printf("\nCHECK PRIVATE KEY MATCHES PUBLIC KEY IN CERTIFICATE: \n");
	certfile = "emisor.cer";
	keyfile = "emisor.key";
	password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	ret = SAT_CheckKeyAndCert(keyfile, password, certfile, 0);
	printf("SAT_CheckKeyAndCert(%s,%s) returns %ld (expected 0)\n", keyfile, certfile, ret);
	if (ret != 0) disp_error(ret);
	assert(ret == 0);

	certfile = "pac.cer";
	keyfile = "pac.key";
	password = "12345678a";
	ret = SAT_CheckKeyAndCert(keyfile, password, certfile, 0);
	printf("SAT_CheckKeyAndCert(%s,%s) returns %ld (expected 0)\n", keyfile, certfile, ret);
	if (ret != 0) disp_error(ret);
	assert(ret == 0);

	/* Get embedded certificate from XML doc */
	certfile = "cfdv33a-signed-tfd.xml";
	keyfile = "emisor.key";
	password = "12345678a";
	ret = SAT_CheckKeyAndCert(keyfile, password, certfile, 0);
	printf("SAT_CheckKeyAndCert(%s,%s) returns %ld (expected 0)\n", keyfile, certfile, ret);
	if (ret != 0) disp_error(ret);
	assert(ret == 0);

	printf("\nGET RECEIPT (COMPROBANTE) VERSION NUMBER FROM XML FILE: \n");
	fname = "cfdv33a-base.xml";
	ret = SAT_XmlReceiptVersion(fname, 0);
	printf("SAT_XmlReceiptVersion(%s)=%ld\n", fname, ret);
	if (ret <= 0) disp_error(ret);
	assert(33 == ret);

	// Older version...
	fname = "ejemplo_v32-tfd2015.xml";
	ret = SAT_XmlReceiptVersion(fname, 0);
	printf("SAT_XmlReceiptVersion(%s)=%ld\n", fname, ret);
	if (ret <= 0) disp_error(ret);
	assert (32 == ret);


	printf("\nCREATE CADENA ORIGINAL DEL TIMBRE FISCAL DIGITAL (PIPESTRING FOR TFD): \n");
	fname = "cfdv33a-signed-tfd.xml";
	nchars = SAT_MakePipeStringFromXml(NULL, 0, fname, SAT_TFD);
	printf("SAT_MakePipeStringFromXml(%s, SAT_TFD) returns %ld\n", fname, nchars);
	if (nchars >= 0) {
		buf = malloc(nchars+1);
		nchars = SAT_MakePipeStringFromXml(buf, nchars, fname, SAT_TFD);
		printf("%s\n", buf);
		free(buf);
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);

	printf("\nFORM DIGEST OF PIPESTRING FOR TFD: \n");
	fname = "cfdv33a-signed-tfd.xml";
	ret = SAT_MakeDigestFromXml(digest, sizeof(digest)-1, fname, SAT_TFD);
	printf("SAT_MakeDigestFromXml returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("SAT_MakeDigestFromXml(%s, SAT_TFD)=\n%s\n", fname, digest);
	assert (ret > 0);

	printf("\nEXTRACT DIGEST FROM TFD SELLOSAT: \n");
	fname = "cfdv33a-signed-tfd.xml";
	certfile = "pac.cer";
	// NB certificate is required for TFD option
	ret = SAT_ExtractDigestFromSignature(digest1, sizeof(digest1)-1, fname, certfile, SAT_TFD);
	printf("SAT_ExtractDigestFromSignature returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("SAT_ExtractDigestFromSignature(%s, SAT_TFD)=\n%s\n", fname, digest1);
	assert (ret > 0);
	/* check that the two digests match (CAUTION: upper- vs lower-case) */
	assert (stricmp(digest, digest1) == 0);

	printf("\nPRETEND WE ARE A PAC WITH A KEY ALLOWED TO SIGN THE TFD: \n");
	fname = "cfdv33a-signed-tfd.xml";
	keyfile = "pac.key";
	password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	nchars = SAT_MakeSignatureFromXmlEx(NULL, 0, fname, keyfile, password, SAT_TFD);
	if (nchars >= 0) {
		buf = malloc(nchars+1);
		nchars = SAT_MakeSignatureFromXmlEx(buf, nchars, fname, keyfile, password, SAT_TFD);
		printf("SAT_MakeSignatureFromXmlEx(%s,%s,SAT_TFD)=\n%s\n", fname, keyfile, buf);
		// free(buf); // Save for later...
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);

	// Extract the correct signature field from the XML...
	fname = "cfdv33a-signed-tfd.xml";
	elementName = "tfd:TimbreFiscalDigital";
	attributeName = "SelloSAT";	// NB Capital 'S'
	nchars = SAT_GetXmlAttribute(NULL, 0, fname, attributeName, elementName);
	if (nchars >= 0) {
		buf1 = malloc(nchars+1);
		nchars = SAT_GetXmlAttribute(buf1, nchars, fname, attributeName, elementName);
		printf("SAT_GetXmlAttribute(%s,%s,%s)=\n%s\n", fname, attributeName, elementName, buf1);
		// free(buf); // Save for later...
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);
	// Check against the correct answer
	assert(strcmp(buf, buf1) == 0);
	free(buf);
	free(buf1);

	printf("\nVERIFY SIGNATURE IN TFD SELLOSAT: \n");
	fname = "cfdv33a-signed-tfd.xml";
	certfile = "pac.cer";
	ret = SAT_VerifySignature(fname, certfile, SAT_TFD);
	printf("SAT_VerifySignature(%s,%s,SAT_TFD')=%ld \n(expected 0)\n", fname, certfile, ret);
	assert (ret == 0);

	printf("\nADD A TFD ELEMENT TO A SIGNED CFDI DOCUMENT USING PAC KEY: \n");
	fname = "cfdv33a-signed.xml";
	newname = "cfdv33a_new-tfd.xml";
	certfile = "pac.cer";
	keyfile = "pac.key";
	password = "12345678a";
	n = SAT_SignXml(newname, fname, keyfile, password, certfile, SAT_TFD);
	printf("SAT_SignXml(SAT_TFD, '%s'-->'%s') returns %ld\n", fname, newname, n);
	assert(n == 0);
	// Did we make a valid XML file?
	n = SAT_ValidateXml(newname, 0);
	printf("SAT_ValidateXml('%s') returns %ld\n", newname, n);
	assert(n == 0);
	// Does it have a valid selloSAT?
	n = SAT_VerifySignature(newname, certfile, SAT_TFD);
	printf("SAT_VerifySignature('%s', SAT_TFD) returns %ld\n", newname, n);
	assert(n == 0);
	// Extract digests from the two signatures: `sello` and `selloSAT`
	n = SAT_ExtractDigestFromSignature(digest, sizeof(digest) - 1, newname, NULL, 0);
	assert(n > 0);
	printf("DIGEST(sello, '%s')=\n%s\n", newname, digest);
	// Note that the TFD selloSAT will be different each time it is created
	n = SAT_ExtractDigestFromSignature(digest, sizeof(digest) - 1, newname, certfile, SAT_TFD);
	assert(n > 0);
	printf("DIGEST(selloSAT, '%s')=\n%s\n", newname, digest);

	printf("\nADD UTF-8 BOM TO EXISTING FILE: \n");
	fname = "cfdv33a-signed-nobom.xml";
	newname = "cfdv33a_new-signed-with-BOM.xml";
	n = SAT_FixBOM(newname, fname, 0);
	printf("SAT_FixBOM(%s->%s})=%ld (expected 0)\n", fname, newname, n);
	assert(n == 0);

	printf("\nEXTRACT ATTRIBUTES FROM CONSECUTIVE ELEMENTS:\n");
	fname = "ejemplo_v32-tfd2015.xml";
	attributeName = "descripcion";
	elementName = "cfdi:Concepto";
	printf("For file '%s'\n", fname);
	for (i = 1; i <= 100; i++) {
		char s[64];
		sprintf(eName, "%s[%d]", elementName, i);
		SAT_GetXmlAttribute(s, sizeof(s)-1, fname, attributeName, eName);
		printf("SAT_GetXmlAttribute(%s,%s)=%s\n", attributeName, eName, s);
		if (strlen(s) == 0) {
			break;
		}
	}

	printf("\nVALIDATE XML WITH STRICT AND LOOSE OPTIONS:\n");
	printf("Default strict behaviour (badly formed CURP attribute):\n");
	fname = "V3_2_BadCurp.xml";
	n = SAT_ValidateXml(fname, 0);
	printf("SAT_ValidateXml('%s') returns %ld\n", fname, n);
	disp_error(n);
	assert(n != 0);

	printf("\nUsing XmlOption.Loose:\n");
	n = SAT_ValidateXml(fname, SAT_XML_LOOSE);
	printf("SAT_ValidateXml('%s', LOOSE) returns %ld\n", fname, n);
	assert(n == 0);

	printf("\nGET PRIVATE KEY AS BASE64:\n");
	fname = "emisor.key";
	password = "12345678a";
	nchars = SAT_GetKeyAsString(NULL, 0, fname, password, 0);
	assert(nchars > 0);
	buf = malloc(nchars+1);
	nchars = SAT_GetKeyAsString(buf, nchars, fname, password, 0);
	printf("SAT_GetKeyAsString(%s)=\n%s\n", fname, buf);
	printf("SAT_GetKeyAsString(%s) length=%ld\n", fname, nchars);

	printf("\nWRITE PFX FROM PRIVATE KEY AND CERT:\n");
	fname = "archivo_new-pfx.txt";
	certfile = "emisor.cer";
	keyfile = "emisor.key";
	password = "12345678a";
	newpassword = "clavedesalida";
	ret = SAT_WritePfxFile(fname, newpassword, keyfile, password, certfile, 0);
	printf("SAT_WritePfxFile('%s') returns %ld\n", fname, n);
	assert(ret == 0);
	printf("New PFX file is %ld bytes\n", file_length(fname));

	printf("\nQUERY X.509 CERTIFICATE:\n");
	printf("1. From embedded `certificado` in XML\n");
	fname = "cfdv33a-signed-tfd.xml";
	printf("Input file: %s\n", fname);
	query = "notAfter";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);
	query = "notBefore";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);
	query = "serialNumber";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);
	query = "rfc";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);
	query = "organizationName";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);


	printf("2. From X.509 file\n");
	fname = "pac.cer";
	printf("Input file: %s\n", fname);
	query = "notAfter";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);
	query = "notBefore";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);
	query = "serialNumber";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);
	query = "rfc";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);
	query = "organizationName";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, query, 0);
	printf("SAT_QueryCert('%s')=[%s]\n", query, qrybuf);
	if (ret < 0) disp_error(ret);


	printf("\nSIGN FILE WITHOUT BOM: \n");
	fname = "cfdv33a-base.xml";
	newname = "cfdv33a_new-signed-nobom.xml";
	certfile = "emisor.cer";
	keyfile = "emisor.key";
	password = "12345678a";
	n = SAT_SignXml(newname, fname, keyfile, password, certfile, SAT_FILE_NO_BOM);
	printf("SAT_SignXml('%s'-->'%s') returns %ld\n", fname, newname, n);
	assert(n == 0);
	// Did we make a valid XML file?
	n = SAT_ValidateXml(newname, 0);
	printf("SAT_ValidateXml('%s') returns %ld\n", newname, n);
	assert(n == 0);
	// Check new file does NOT have a BOM
	has_bom = file_has_bom(newname);
	printf("File '%s' %s a BOM\n", newname, (1 == has_bom ? "HAS" : "DOES NOT have"));
	assert(has_bom == 0);


	printf("\nREAD ENCRYPTED PRIVATE KEY FILE AS PEM STRING:\n");
	fname = "emisor.key";
	password = "12345678a";
	nchars = SAT_GetKeyAsString(NULL, 0, fname, password, SAT_KEY_ENCRYPTED);
	assert(nchars > 0);
	buf = malloc(nchars+1);
	nchars = SAT_GetKeyAsString(buf, nchars, fname, password, SAT_KEY_ENCRYPTED);
	printf("SAT_GetKeyAsString(%s,SAT_KEY_ENCRYPTED)=\n%s\n", fname, buf);
	printf("SAT_GetKeyAsString(%s,SAT_KEY_ENCRYPTED) length=%ld\n", fname, nchars);

	printf("\nSIGN XML TO STRING:\n");
	fname = "cfdv33a-base.xml";
	// Read an XML file into a string
	xmlstring = file_read_to_string(fname);
	assert(xmlstring);
	printf("String from file '%s' has %ld bytes\n", fname, strlen(xmlstring));
	/*
	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
	ret = SAT_CheckKeyAndCert(keyfiledata, password, certfiledata, 0);
	printf("SAT_CheckKeyAndCert(STRINGS) returns %ld\n", ret);
	if (ret != 0) disp_error(ret);
	assert(ret == 0);

	// Create a new string containing signed XML (UTF-8 encoded)
	nchars = SAT_SignXmlToString(NULL, 0, xmlstring, keyfiledata, password, certfiledata, 0);
	printf("SAT_SignXmlToString returns %ld\n", nchars);
	assert(nchars >= 0);
	newxmlstring = malloc(nchars+1);
	nchars = SAT_SignXmlToString(newxmlstring, nchars, xmlstring, keyfiledata, password, certfiledata, 0);
	printf("Signed XML string has %ld bytes\n", strlen(newxmlstring));
	printf("------------\n");
	pr_head_tail(newxmlstring, 120, 320);
	printf("------------\n");

	printf("\nPASS XML STRING TO OTHER SAT FUNCTIONS:\n");
	n = SAT_ValidateXml(newxmlstring, 0);
	printf("SAT_ValidateXml(STRING) returns %ld (0=>OK)\n", n);
	assert(n == 0);
	n = SAT_XmlReceiptVersion(newxmlstring, 0);
	printf("SAT_XmlReceiptVersion(STRING) returns %ld (expecting 32)\n", n);
	assert(n > 0);
	free(newxmlstring);

	printf("\nSIGN XML USING EMPTY-ELEMENT TAGS:\n");
	nchars = SAT_SignXmlToString(NULL, 0, xmlstring, keyfiledata, password, certfiledata, SAT_XML_EMPTYELEMTAG);
	printf("SAT_SignXmlToString returns %ld\n", nchars);
	assert(nchars >= 0);
	newxmlstring = malloc(nchars+1);
	nchars = SAT_SignXmlToString(newxmlstring, nchars, xmlstring, keyfiledata, password, certfiledata, SAT_XML_EMPTYELEMTAG);
	printf("Signed XML string has %ld bytes\n", strlen(newxmlstring));
	printf("------------\n");
	pr_head_tail(newxmlstring, 120, 262);
	printf("------------\n");
	n = SAT_ValidateXml(newxmlstring, 0);
	printf("SAT_ValidateXml(STRING) returns %ld (0=>OK)\n", n);
	assert(n == 0);
	free(newxmlstring);

	// Free memory allocated for XML strings
	free(xmlstring);

	printf("\nGENERATE THREE UUIDs: \n");
	ret = SAT_Uuid(uuid, sizeof(uuid)-1, 0);
	printf("UUID=%s\n", uuid);
	ret = SAT_Uuid(uuid, sizeof(uuid)-1, 0);
	printf("UUID=%s\n", uuid);
	ret = SAT_Uuid(uuid, sizeof(uuid)-1, 0);
	printf("UUID=%s\n", uuid);

	// NEW IN [v6.0]...
	printf("\nWORK WITH A `RETENCIONES` DOCUMENT: \n");
	fname = "Ejemplo_Retenciones-base.xml";
	printf("FILE=%s\n", fname);
	n = SAT_XmlReceiptVersion(fname, 0);
	printf("SAT_XmlReceiptVersion() returns ID=%ld (expecting 1010)\n", n);
	assert(1010 == n);
	n = SAT_MakeDigestFromXml(digest, sizeof(digest)-1, fname, 0);
	printf("SAT_MakeDigestFromXml() -> '%s')=\n", digest);
	assert(n > 0);
	// Use new [v6.0] arguments to find name of root element
	nchars = SAT_GetXmlAttribute(NULL, 0, fname, "", "");
	printf("SAT_GetXmlAttribute('','') returns %ld\n", nchars);
	if (nchars >= 0) {
		buf = malloc(nchars+1);
		nchars = SAT_GetXmlAttribute(buf, nchars, fname, "", "");
		printf("File root element is '%s'\n", buf);
		free(buf);
	} else {
		disp_error(nchars);
	}
	assert(nchars > 0);

	// NEW IN [v7.0]...
	printf("\nWORK WITH `CONTABILIDAD` DOCUMENTS: \n");
	fname = "AAA010101AAA201501CT-base.xml";
	printf("CATALOGOCUENTAS FILE=%s\n", fname);
	n = SAT_XmlReceiptVersion(fname, 0);
	printf("SAT_XmlReceiptVersion() returns ID=%ld (expecting 2011)\n", n);
	assert(2011 == n);

	printf("SIGN A CATALOGOCUENTAS DOCUMENT...\n");
	newname = "AAA010101AAA201501CT_new-signed.xml";
	keyfile = "emisor.key";
	certfile = "emisor.cer";
	password = "12345678a";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	n = SAT_SignXml(newname, fname, keyfile, password, certfile, 0);
	printf("SAT_SignXml() returns %ld (expecting 0)\n", n);
	assert(0 == n);
	n = SAT_VerifySignature(newname, NULL, 0);
	printf("SAT_VerifySignature() returns %ld (expecting 0)\n", n);
	assert(0 == n);

	fname = "AAA010101AAA201501BN-base.xml";
	printf("BALANZA FILE=%s\n", fname);
	n = SAT_XmlReceiptVersion(fname, 0);
	printf("SAT_XmlReceiptVersion() returns ID=%ld (expecting 2111)\n", n);
	assert(2111 == n);

	printf("MAKE THE SIGNATURE STRING FOR BALANZA...\n");
	nchars = SAT_MakeSignatureFromXml(NULL, 0, fname, keyfile, password);
	if (nchars >= 0) {
		buf = malloc(nchars+1);
		nchars = SAT_MakeSignatureFromXml(buf, nchars, fname, keyfile, password);
		printf("SAT_MakeSignatureFromXml('%s','%s')=\n%s\n", fname, keyfile, buf);
		free(buf);
	}
	else
		disp_error(nchars);
	assert(nchars > 0);

	fname = "contab-SelloDigitalContElec-signed.xml";
	printf("SELLODIGITALCONTELEC FILE=%s\n", fname);
	n = SAT_XmlReceiptVersion(fname, 0);
	printf("SAT_XmlReceiptVersion() returns ID=%ld (expecting 2511)\n", n);
	assert(2511 == n);
	printf("VERIFY SIGNATURE FOR SELLODIGITALCONTELEC USING PAC CERTIFICATE...\n");
	n = SAT_VerifySignature(fname, "pac1024.cer", 0);
	printf("SAT_VerifySignature() returns %ld (expecting 0)\n", n);
	assert(0 == n);

	printf("\nWORK WITH `CONTROLESVOLUMETRICOS` DOCUMENT: \n");
	fname = "ConVolE12345-base.xml";
	printf("FILE=%s\n", fname);
	n = SAT_XmlReceiptVersion(fname, 0);
	printf("SAT_XmlReceiptVersion() returns ID=%ld (expecting 4011)\n", n);
	assert(4011 == n);

	printf("SIGN A CONVOL DOCUMENT WITH BIGFILE FLAG...\n");
	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";   /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */
	n = SAT_SignXml(newname, fname, keyfile, password, certfile, SAT_FILE_BIGFILE);
	printf("SAT_SignXml(BIGFILE) returns %ld (expecting 0)\n", n);
	assert(0 == n);
	n = SAT_VerifySignature(newname, NULL, 0);
	printf("SAT_VerifySignature() returns %ld (expecting 0)\n", n);
	assert(0 == n);

	printf("\nQUERY KEY SIZE OF CERTIFICATES...\n");
	fname = "emisor1024.cer";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, "keySize", 0);
	assert(ret > 0);
	printf("SAT_QueryCert('%s',keySize)=%s\n", fname, qrybuf);
	fname = "emisor.cer";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, "keySize", 0);
	assert(ret > 0);
	printf("SAT_QueryCert('%s',keySize)=%s\n", fname, qrybuf);
	fname = "AC4_SAT.cer";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, "keySize", 0);
	assert(ret > 0);
	printf("SAT_QueryCert('%s',keySize)=%s\n", fname, qrybuf);

	printf("\nQUERY SIGNATURE ALGORITHM IN CERTIFICATES...\n");
	fname = "emisor1024.cer";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, "sigAlg", 0);
	assert(ret > 0);
	printf("SAT_QueryCert('%s',sigAlg)=%s\n", fname, qrybuf);
	fname = "emisor.cer";
	ret = SAT_QueryCert(qrybuf, sizeof(qrybuf)-1, fname, "sigAlg", 0);
	assert(ret > 0);
	printf("SAT_QueryCert('%s',sigAlg)=%s\n", fname, qrybuf);

	// NEW IN [v8.0]

	printf("\nREAD IN XML DOC AS 'ASCIIFIED' STRING...\n");
	// "Esta es una demostración" -> "Esta es una demostraci&#xF3;n"
	fname = "cfdv33a-base.xml";
	nchars = SAT_Asciify(NULL, 0, fname, 0);
	printf("SAT_Asciify() returns %ld\n", nchars);
	if (nchars >= 0) {
		xmlstring = malloc(nchars + 1);
		nchars = SAT_Asciify(xmlstring, nchars, fname, 0);
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);
	printf("ASCIIFIED XML:\n");
	pr_head_tail(xmlstring, 1020, 44);

	printf("Compute digests from XML string and original file...\n");
	ret = SAT_MakeDigestFromXml(digest, sizeof(digest) - 1, xmlstring, 0);
	if (ret < 0) disp_error(ret);
	printf("Digest(str) =%s\n", digest);
	assert(ret > 0);

	// -- this should match the digest of the original UTF-8-encoded file
	ret = SAT_MakeDigestFromXml(digest1, sizeof(digest1) - 1, fname, 0);
	if (ret < 0) disp_error(ret);
	printf("Digest(file)=%s\n", digest1);
	assert(ret > 0);
	assert(0 == strcmp(digest, digest1));

	free(xmlstring);


	printf("\nINSERT CERTIFICATE DETAILS INTO XML...\n");
	fname = "cfdv33a-base-nocertnum.xml";
	newname = "cfdv33a-base_new-pluscert.xml";
	certfile = "emisor.cer";
	n = SAT_InsertCert(newname, fname, certfile, 0);
	printf("SAT_InsertCert() returns %ld (expecting 0)\n", n);
	assert(0 == n);
	// Check noCertificado just inserted
	// Original should be empty
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, "NoCertificado", "cfdi:Comprobante");
	printf("Old NoCertificado=[%s]\n", attrbuf);
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, newname, "NoCertificado", "cfdi:Comprobante");
	printf("New NoCertificado=[%s]\n", attrbuf);
	assert(strlen(attrbuf) > 0);

	printf("\nINSERT CERTIFICATE DETAILS INTO XML AS STRING...\n");
	nchars = SAT_InsertCertToString(NULL, 0, fname, certfile, 0);
	printf("SAT_InsertCertToString() returns %ld\n", nchars);
	if (nchars >= 0) {
		newxmlstring = malloc(nchars + 1);
		nchars = SAT_InsertCertToString(newxmlstring, nchars, fname, certfile, 0);
	}
	else {
		disp_error(nchars);
	}
	assert(nchars > 0);
	// Extract new attribute from XML as string
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, newxmlstring, "NoCertificado", "cfdi:Comprobante");
	printf("New NoCertificado=[%s]\n", attrbuf);
	assert(nchars > 0);

	// NEW IN [v8.1]

	printf("\nSUPPORT FOR CONTABILIDAD V1.3...\n");
	fname = "AAA010101AAA201705CT.xml";
	printf("FILE: %s\n", fname);
	SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, "", "");
	printf("Doc type is '%s'\n", attrbuf);
	n = SAT_ValidateXml(fname, 0);
	printf("SAT_ValidateXml() returns %ld (0 => OK)\n", n);
	assert(0 == n);
	n = SAT_VerifySignature(fname, NULL, 0);
	printf("SAT_VerifySignature() returns %ld (0 => OK)\n", n);
	assert(0 == n);
	n = SAT_XmlReceiptVersion(fname, 0);
	printf("SAT_XmlReceiptVersion() returns %ld (expecting 2013)\n", n);
	assert(n > 0);
	// Get default digest algorithm for this document type (a hack!)
	n = SAT_XmlReceiptVersion(fname, SAT_GEN_DIGALG);
	printf("Default digest algorithm is SHA-%ld\n", n);

	// NEW IN [v8.2]

	printf("\nSAVE KEYFILE WITH NEW PASSWORD...\n");
	certfile = "emisor.cer";
	keyfile = "emisor.key";
	password = "12345678a";
	newname = "emisor_new.key";
	newpassword = "password123";
	n = SAT_NewKeyFile(newname, newpassword, keyfile, password, "", 0);
	printf("Sat.NewKeyFile() returns %ld (expecting 0)\n", n);
	assert(0 == n);
	printf("Created new key file '%s' of length %d bytes with password '%s'.\n", newname, file_length(newname), newpassword);
	printf("Save again in PEM format...\n");
	newname = "emisor_new-key.pem";
	newpassword = "password456";
	n = SAT_NewKeyFile(newname, newpassword, keyfile, password, "", SAT_FORMAT_PEM);
	printf("Sat.NewKeyFile() returns %ld (expecting 0)\n", n);
	assert(0 == n);
	printf("Created new key file '%s' of length %d bytes with password '%s'.\n", newname, file_length(newname), newpassword);
	pr_head_tail_file(newname, 103, 109);
	printf("Check new key still matches old certificate...\n");
	n = SAT_CheckKeyAndCert(newname, newpassword, certfile, 0);
	printf("Sat.NewKeyFile() returns %ld (expecting 0)\n", n);
	assert(0 == n);

	printf("\nXPATH EXPRESSIONS FOR XML-GET-ATTRIBUTE...\n");
	fname = "A7.xml";
	printf("FILE: %s\n", fname);
	elementName = "/Comprobante";
	attributeName = "Version";
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	assert(nchars > 0);
	// NOTE: we display here using the XPath '@' operator, but we don't accept it in an xpath expression.
	// Put the element path in `szElementName` and the attribute name in `szAttributeName`.
	printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);

	elementName = "/Comprobante/Conceptos/Concepto[2]/Impuestos/Traslados/Traslado[1]";
	attributeName = "Importe";
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	assert(nchars > 0);
	printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);

	elementName = "/Comprobante/Conceptos/Concepto[1]/Impuestos/Retenciones/Retencion[2]";
	attributeName = "Importe";
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	assert(nchars > 0);
	printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);

	// Same as above but shorter
	elementName = "//Conceptos/Concepto[1]//Retencion[2]";
	attributeName = "Importe";
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	assert(nchars > 0);
	printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);

	// Test for existence
	printf("Check for existence (and fail)...\n");
	elementName = "/Comprobante/Conceptos/Concepto[3]";
	attributeName = "Importe";
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	printf("elemName='%s' attrName='%s' returned=%d\n", elementName, attributeName, nchars);
	// A missing element/attribute returns a negative error
	assert(nchars < 0);
	disp_error(nchars);

	printf("Invalid path expression...\n");
	elementName = "/";
	attributeName = "Version";
	nchars = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	printf("elemName='%s' attrName='%s' returned=%d\n", elementName, attributeName, nchars);
	// A missing element/attribute returns a negative error
	assert(nchars < 0);
	disp_error(nchars);


	printf("\nUSE XPATH TO FIND ALL ATTRIBUTES NAMED 'IMPORTE'...\n");
	fname = "A7.xml";
	printf("FILE: %s\n", fname);
	attributeName = "Importe";
	// First look at each <Concepto> in the <Conceptos> element.
	xbase = "//Conceptos/Concepto";
	for (i = 1;; i++) {
		// FOREACH //Conceptos/Concepto[i] element output the value of Importe
		sprintf(xpath, "%s[%d]", xbase, i);
		elementName = xpath;
		n = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
		if (n < 0) break;
		printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);
		// 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;; j++) {
			sprintf(xpath1, "%s//Traslado[%d]", xpath, j);
			elementName = xpath1;
			n = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
			if (n < 0) break;
			printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);
		}
		// FOREACH //Conceptos/Concepto[i]//Retencion[j] element output the value of Importe
		for (j = 1;; j++) {
			sprintf(xpath1, "%s//Retencion[%d]", xpath, j);
			elementName = xpath1;
			n = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
			if (n < 0) break;
			printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);
		}
	}
	// Now look in the /Comprobante/Impuestos element.
	xbase = "/Comprobante/Impuestos";

	// FOREACH /Comprobante/Impuestos//Retencion[j] element output the value of Importe
	// Long xpath is /Comprobante/Impuestos/Retenciones/Retencion[j]
	for (j = 1;; j++) {
		sprintf(xpath1, "%s//Retencion[%d]", xbase, j);
		elementName = xpath1;
		n = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
		if (n < 0) break;
		printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);
	}
	// FOREACH /Comprobante/Impuestos//Traslado[j] element output the value of Importe
	for (j = 1;; j++) {
		sprintf(xpath1, "%s//Traslado[%d]", xbase, j);
		elementName = xpath1;
		n = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
		if (n < 0) break;
		printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);
	}

	// New in [v9.2]

	printf("\nREAD IN A FILE TO A VBA UNICODE STRING THEN PASS THE STRING AS XML DOC...\n");
	fname = "cfdv33a-nomina12B.xml";
	printf("FILE: %s\n", fname);
	// Use SAT_Asciify to solve issues when reading UTF-8-encoded file into a string
	nchars = SAT_Asciify(NULL, 0, fname, 0);
	printf("SAT_Asciify() returns %ld\n", nchars);
	assert(nchars > 0);
	xmlstring = malloc(nchars + 1);
	nchars = SAT_Asciify(xmlstring, nchars, fname, 0);
	printf("xmlstring is %d bytes long\n", strlen(xmlstring));

	printf("\nCHECK XML IS OK USING XML STRING AS INPUT...\n");
	n = SAT_ValidateXml(xmlstring, 0);
	printf("SAT_ValidateXml() returns %ld (expecting 0)\n", n);
	assert(0 == n);

	printf("\nGET ATTRIBUTE VALUE USING STRING INPUT...\n");
	// Attribute name contains non-ASCII character 'ü', Antigüedad="P3Y2M23D"
	elementName = "nomina12:Receptor";
	attributeName = "Antigüedad";
	n = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	assert(n > 0);
	printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);

	// Attribute value contains non-ASCII character 'í', Sindicalizado="Sí"
	elementName = "nomina12:Receptor";
	attributeName = "Sindicalizado";
	n = SAT_GetXmlAttribute(attrbuf, sizeof(attrbuf) - 1, fname, attributeName, elementName);
	assert(n > 0);
	printf("'%s/@%s'='%s'\n", elementName, attributeName, attrbuf);

	printf("\nFORM MESSAGE DIGEST OF PIPE STRING USING XML STRING AS INPUT...\n");
	ret = SAT_MakeDigestFromXml(digest, sizeof(digest) - 1, xmlstring, 0);
	printf("SAT_MakeDigestFromXml(string) returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("Digest=%s\n", digest);
	assert(ret > 0);

	printf("\nCHECK MESSAGE DIGEST IS THE SAME WHEN USING THE FILE AS INPUT...\n");
	printf("FILE: %s\n", fname);
	ret = SAT_MakeDigestFromXml(digest1, sizeof(digest1) - 1, fname, 0);
	printf("SAT_MakeDigestFromXml(file) returns %ld\n", ret);
	if (ret < 0) disp_error(ret);
	printf("Digest=%s\n", digest1);
	assert(ret > 0);
	assert(strcmp(digest, digest1) == 0);



	// ******************************************************
	// FINALLY, DISPLAY CURRENT DLL VERSION AND OTHER DETAILS
	printf("\n");
	SAT_ModuleName(cdate, sizeof(cdate)-1, SAT_GEN_PLATFORM);
	printf("\nCore DLL Version=%03ld [%s] Lic=%c\n", SAT_Version(), cdate, (char)SAT_LicenceType());
	SAT_ModuleName(qrybuf, sizeof(qrybuf)-1, 0);
	printf("[%s]\n", qrybuf);
	SAT_CompileTime(cdate, sizeof(cdate)-1);
	printf("Compiled [%s]\n", cdate);

// Change "#if 0" to "#if 1" to activate this
#if 0
	printf("\nDISPLAY ALL POSSIBLE ERROR MESSAGES:\n");
	display_all_errors();
#endif

	printf("\nALL DONE.\n");

	return 0;
}

void display_all_errors(void)
{
	long i, nchars;
	char errbuf[256];
	for (i = 0; i < 10000; i++)
	{
		nchars = SAT_ErrorLookup(errbuf, sizeof(errbuf)-1, i);
		if (nchars > 0)
			printf("%ld=>%s\n", i, errbuf);
	}
}